Logo Search packages:      
Sourcecode: ladish version File versions  Download package

conf.c

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
 * LADI Session Handler (ladish)
 *
 * Copyright (C) 2010 Nedko Arnaudov <nedko@arnaudov.name>
 *
 **************************************************************************
 * This file contains implementation of the settings storage
 **************************************************************************
 *
 * LADI Session Handler is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * LADI Session Handler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "common.h"

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "dbus/helpers.h"
#include "dbus/error.h"
#include "dbus_constants.h"
#include "common/catdup.h"
#include "common/dirhelpers.h"

#define STORAGE_BASE_DIR "/.ladish/conf/"

extern const struct dbus_interface_descriptor g_interface;

static const char * g_dbus_unique_name;
static dbus_object_path g_object;
static bool g_quit;

00050 struct pair
{
  struct list_head siblings;
  uint64_t version;
  char * key;
  char * value;
  bool stored;
};

struct list_head g_pairs;

static bool connect_dbus(void)
{
  int ret;

  dbus_error_init(&g_dbus_error);

  g_dbus_connection = dbus_bus_get(DBUS_BUS_SESSION, &g_dbus_error);
  if (dbus_error_is_set(&g_dbus_error))
  {
    log_error("Failed to get bus: %s", g_dbus_error.message);
    dbus_error_free(&g_dbus_error);
    goto fail;
  }

  g_dbus_unique_name = dbus_bus_get_unique_name(g_dbus_connection);
  if (g_dbus_unique_name == NULL)
  {
    log_error("Failed to read unique bus name");
    goto unref_connection;
  }

  log_info("Connected to local session bus, unique name is \"%s\"", g_dbus_unique_name);

  ret = dbus_bus_request_name(g_dbus_connection, CONF_SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &g_dbus_error);
  if (ret == -1)
  {
    log_error("Failed to acquire bus name: %s", g_dbus_error.message);
    dbus_error_free(&g_dbus_error);
    goto unref_connection;
  }

  if (ret == DBUS_REQUEST_NAME_REPLY_EXISTS)
  {
    log_error("Requested connection name already exists");
    goto unref_connection;
  }

  g_object = dbus_object_path_new(CONF_OBJECT_PATH, &g_interface, NULL, NULL);
  if (g_object == NULL)
  {
    goto unref_connection;
  }

  if (!dbus_object_path_register(g_dbus_connection, g_object))
  {
    goto destroy_control_object;
  }

  return true;

destroy_control_object:
  dbus_object_path_destroy(g_dbus_connection, g_object);
unref_connection:
  dbus_connection_unref(g_dbus_connection);

fail:
  return false;
}

static void disconnect_dbus(void)
{
  dbus_object_path_destroy(g_dbus_connection, g_object);
  dbus_connection_unref(g_dbus_connection);
}

void term_signal_handler(int signum)
{
  log_info("Caught signal %d (%s), terminating", signum, strsignal(signum));
  g_quit = true;
}

bool install_term_signal_handler(int signum, bool ignore_if_already_ignored)
{
  sig_t sigh;

  sigh = signal(signum, term_signal_handler);
  if (sigh == SIG_ERR)
  {
    log_error("signal() failed to install handler function for signal %d.", signum);
    return false;
  }

  if (sigh == SIG_IGN && ignore_if_already_ignored)
  {
    signal(SIGTERM, SIG_IGN);
  }

  return true;
}

int main(int argc, char ** argv)
{
  int ret;

  if (getenv("HOME") == NULL)
  {
    log_error("Environment variable HOME not set");
    return 1;
  }

  INIT_LIST_HEAD(&g_pairs);

  install_term_signal_handler(SIGTERM, false);
  install_term_signal_handler(SIGINT, true);

  if (!connect_dbus())
  {
    log_error("Failed to connect to D-Bus");
    return 1;
  }

  while (!g_quit)
  {
    dbus_connection_read_write_dispatch(g_dbus_connection, 50);
  }

  ret = 0;

  disconnect_dbus();
  return 0;
}

static struct pair * create_pair(const char * key, const char * value)
{
  struct pair * pair_ptr;

  pair_ptr = malloc(sizeof(struct pair));
  if (pair_ptr == NULL)
  {
    log_error("malloc() failed to allocate memory for pair struct");
    return NULL;
  }

  pair_ptr->key = strdup(key);
  if (pair_ptr->key == NULL)
  {
    log_error("strdup(\"%s\") failed for key", key);
    free(pair_ptr);
    return NULL;
  }

  if (value != NULL)
  {
    pair_ptr->value = strdup(value);
    if (pair_ptr->value == NULL)
    {
      log_error("strdup(\"%s\") failed for value", value);
      free(pair_ptr->key);
      free(pair_ptr);
      return NULL;
    }
  }
  else
  {
    /* Caller will fill this shortly */
    pair_ptr->value = NULL;
  }

  pair_ptr->version = 1;
  pair_ptr->stored = false;

  list_add_tail(&pair_ptr->siblings, &g_pairs);

  return pair_ptr;
}

static bool store_pair(struct pair * pair_ptr)
{
  char * dirpath;
  char * filepath;
  int fd;
  size_t len;
  ssize_t written;

  dirpath = catdupv(getenv("HOME"), STORAGE_BASE_DIR, pair_ptr->key, NULL);
  if (dirpath == NULL)
  {
    return false;
  }

  if (!ensure_dir_exist(dirpath, 0700))
  {
    free(dirpath);
    return false;
  }

  filepath = catdup(dirpath, "/value");
  free(dirpath);
  if (filepath == NULL)
  {
    return false;
  }

  fd = creat(filepath, 0700);
  if (fd == -1)
  {
    log_error("Failed to create \"%s\": %d (%s)", filepath, errno, strerror(errno));
    free(filepath);
    return false;
  }

  len = strlen(pair_ptr->value);

  written = write(fd, pair_ptr->value, len);
  if (written < 0)
  {
    log_error("Failed to write() to \"%s\": %d (%s)", filepath, errno, strerror(errno));
    free(filepath);
    return false;
  }

  if ((size_t)written != len)
  {
    log_error("write() to \"%s\" returned %zd instead of %zu", filepath, written, len);
    free(filepath);
    return false;
  }

  close(fd);
  free(filepath);

  pair_ptr->stored = true;
  
  return true;
}

static struct pair * load_pair(const char * key)
{
  struct pair * pair_ptr;
  char * path;
  struct stat st;
  int fd;
  char * buffer;
  ssize_t bytes_read;

  path = catdupv(getenv("HOME"), STORAGE_BASE_DIR, key, "/value", NULL);
  if (path == NULL)
  {
    return false;
  }

  if (stat(path, &st) != 0)
  {
    log_error("Failed to stat \"%s\": %d (%s)", path, errno, strerror(errno));
    free(path);
    return false;
  }

  if (!S_ISREG(st.st_mode))
  {
    log_error("\"%s\" is not a regular file.", path);
    free(path);
    return false;
  }

  fd = open(path, O_RDONLY);
  if (fd == -1)
  {
    log_error("Failed to open \"%s\": %d (%s)", path, errno, strerror(errno));
    free(path);
    return false;
  }

  buffer = malloc((size_t)st.st_size + 1);
  if (buffer == NULL)
  {
    log_error("malloc() failed to allocate %zu bytes of memory for value", (size_t)st.st_size + 1);
    close(fd);
    free(path);
    return false;
  }

  bytes_read = read(fd, buffer, st.st_size);
  if (bytes_read < 0)
  {
    log_error("Failed to read() from \"%s\": %d (%s)", path, errno, strerror(errno));
    free(buffer);
    close(fd);
    free(path);
    return false;
  }

  if (bytes_read != st.st_size)
  {
    log_error("read() from \"%s\" returned %zd instead of %llu", path, bytes_read, (unsigned long long)st.st_size);
    free(buffer);
    close(fd);
    free(path);
    return false;
  }

  buffer[st.st_size] = 0;

  pair_ptr = create_pair(key, NULL);
  if (pair_ptr == NULL)
  {
    free(buffer);
    close(fd);
    free(path);
    return false;
  }

  pair_ptr->value = buffer;

  close(fd);
  free(path);

  return pair_ptr;
}

static struct pair * find_pair(const char * key)
{
  struct list_head * node_ptr;
  struct pair * pair_ptr;

  list_for_each(node_ptr, &g_pairs)
  {
    pair_ptr = list_entry(node_ptr, struct pair, siblings);
    if (strcmp(pair_ptr->key, key) == 0)
    {
      return pair_ptr;
    }
  }

  return NULL;
}

static void emit_changed(struct pair * pair_ptr)
{
  dbus_signal_emit(
    g_dbus_connection,
    CONF_OBJECT_PATH,
    CONF_IFACE,
    "changed",
    "sst",
    &pair_ptr->key,
    &pair_ptr->value,
    &pair_ptr->version);
}

/***************************************************************************/
/* D-Bus interface implementation */

static void conf_set(struct dbus_method_call * call_ptr)
{
  const char * key;
  const char * value;
  struct pair * pair_ptr;
  char * buffer;
  bool store;

  if (!dbus_message_get_args(
        call_ptr->message,
        &g_dbus_error,
        DBUS_TYPE_STRING, &key,
        DBUS_TYPE_STRING, &value,
        DBUS_TYPE_INVALID))
  {
    lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "Invalid arguments to method \"%s\": %s",  call_ptr->method_name, g_dbus_error.message);
    dbus_error_free(&g_dbus_error);
    return;
  }

  log_info("set '%s' <- '%s'", key, value);

  pair_ptr = find_pair(key);
  if (pair_ptr == NULL)
  {
    pair_ptr = create_pair(key, value);
    if (pair_ptr == NULL)
    {
      lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Memory allocation failed");
      return;
    }

    emit_changed(pair_ptr);

    store = true;
  }
  else
  {
    store = strcmp(pair_ptr->value, value) != 0;
    if (store)
    {
      buffer = strdup(value);
      if (buffer == NULL)
      {
        lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Memory allocation failed. strdup(\"%s\") failed for value", value);
        return;
      }
      free(pair_ptr->value);
      pair_ptr->value = buffer;
      pair_ptr->version++;
      pair_ptr->stored = false; /* mark that new value was not stored on disk yet */

      emit_changed(pair_ptr);
    }
    else if (!pair_ptr->stored) /* if store to disk failed last time, retry */
    {
      store = true;
    }
  }

  if (store)
  {
    if (!store_pair(pair_ptr))
    {
      lash_dbus_error(call_ptr, LASH_DBUS_ERROR_GENERIC, "Storing the value of key '%s' to disk failed", pair_ptr->key);
      return;
    }
  }

  method_return_new_single(call_ptr, DBUS_TYPE_UINT64, &pair_ptr->version);
}

static void conf_get(struct dbus_method_call * call_ptr)
{
  const char * key;
  struct pair * pair_ptr;

  if (!dbus_message_get_args(
        call_ptr->message,
        &g_dbus_error,
        DBUS_TYPE_STRING, &key,
        DBUS_TYPE_INVALID))
  {
    lash_dbus_error(call_ptr, LASH_DBUS_ERROR_INVALID_ARGS, "Invalid arguments to method \"%s\": %s",  call_ptr->method_name, g_dbus_error.message);
    dbus_error_free(&g_dbus_error);
    return;
  }

  pair_ptr = find_pair(key);
  if (pair_ptr == NULL)
  {
    pair_ptr = load_pair(key);
    if (pair_ptr == NULL)
    {
      lash_dbus_error(call_ptr, LASH_DBUS_ERROR_KEY_NOT_FOUND, "Key '%s' not found", key);
      return;
    }
  }

  log_info("get '%s' -> '%s'", key, pair_ptr->value);

  method_return_new_valist(
    call_ptr,
    DBUS_TYPE_STRING, &pair_ptr->value,
    DBUS_TYPE_UINT64, &pair_ptr->version,
    DBUS_TYPE_INVALID);
}

static void conf_exit(struct dbus_method_call * call_ptr)
{
  log_info("Exit command received through D-Bus");
  g_quit = true;
  method_return_new_void(call_ptr);
}

METHOD_ARGS_BEGIN(set, "Set conf value")
  METHOD_ARG_DESCRIBE_IN("key", DBUS_TYPE_STRING_AS_STRING, "")
  METHOD_ARG_DESCRIBE_IN("value", DBUS_TYPE_STRING_AS_STRING, "")
  METHOD_ARG_DESCRIBE_OUT("version", DBUS_TYPE_UINT64_AS_STRING, "")
METHOD_ARGS_END

METHOD_ARGS_BEGIN(get, "Get conf value")
  METHOD_ARG_DESCRIBE_IN("key", DBUS_TYPE_STRING_AS_STRING, "")
  METHOD_ARG_DESCRIBE_OUT("value", DBUS_TYPE_STRING_AS_STRING, "")
  METHOD_ARG_DESCRIBE_OUT("version", DBUS_TYPE_UINT64_AS_STRING, "")
METHOD_ARGS_END

METHOD_ARGS_BEGIN(exit, "Tell conf D-Bus service to exit")
METHOD_ARGS_END

METHODS_BEGIN
  METHOD_DESCRIBE(set, conf_set)
  METHOD_DESCRIBE(get, conf_get)
  METHOD_DESCRIBE(exit, conf_exit)
METHODS_END

SIGNAL_ARGS_BEGIN(changed, "")
  SIGNAL_ARG_DESCRIBE("key", DBUS_TYPE_STRING_AS_STRING, "")
  SIGNAL_ARG_DESCRIBE("value", DBUS_TYPE_STRING_AS_STRING, "")
  SIGNAL_ARG_DESCRIBE("version", DBUS_TYPE_UINT64_AS_STRING, "")
SIGNAL_ARGS_END

SIGNALS_BEGIN
  SIGNAL_DESCRIBE(changed)
SIGNALS_END

INTERFACE_BEGIN(g_interface, CONF_IFACE)
  INTERFACE_DEFAULT_HANDLER
  INTERFACE_EXPOSE_METHODS
  INTERFACE_EXPOSE_SIGNALS
INTERFACE_END

Generated by  Doxygen 1.6.0   Back to index