From 85f8601728878667da9390c7d687a4b81063628b Mon Sep 17 00:00:00 2001 From: Nedko Arnaudov Date: Mon, 20 Sep 2010 00:59:06 +0300 Subject: [PATCH] settings storage daemon --- conf.c | 537 +++++++++++++++++++++++++++++++++++++++++++++++ dbus_constants.h | 4 + wscript | 58 +++-- 3 files changed, 583 insertions(+), 16 deletions(-) create mode 100644 conf.c diff --git a/conf.c b/conf.c new file mode 100644 index 00000000..5f4e753f --- /dev/null +++ b/conf.c @@ -0,0 +1,537 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + * LADI Session Handler (ladish) + * + * Copyright (C) 2010 Nedko Arnaudov + * + ************************************************************************** + * 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 + * or write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "common.h" + +#include +#include +#include +#include +#include +#include + +#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; + +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; +} + +/***************************************************************************/ +/* 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; + } + + 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 */ + } + 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 diff --git a/dbus_constants.h b/dbus_constants.h index 03d6597e..d24278dc 100644 --- a/dbus_constants.h +++ b/dbus_constants.h @@ -48,6 +48,10 @@ #define JMCORE_IFACE JMCORE_SERVICE_NAME #define JMCORE_OBJECT_PATH DBUS_BASE_PATH "/jmcore" +#define CONF_SERVICE_NAME DBUS_NAME_BASE ".conf" +#define CONF_IFACE CONF_SERVICE_NAME +#define CONF_OBJECT_PATH DBUS_BASE_PATH "/conf" + #define JACKDBUS_PORT_FLAG_INPUT 0x00000001 #define JACKDBUS_PORT_FLAG_OUTPUT 0x00000002 #define JACKDBUS_PORT_FLAG_PHYSICAL 0x00000004 diff --git a/wscript b/wscript index 7da4e05c..3557bddc 100644 --- a/wscript +++ b/wscript @@ -6,6 +6,7 @@ import Options import Utils import shutil import re +import misc APPNAME='ladish' VERSION='0.3-rc' @@ -63,6 +64,15 @@ def check_gcc_optimizations_enabled(flags): gcc_optimizations_enabled = flag[2] != '0'; return gcc_optimizations_enabled +def create_service_taskgen(bld, target, opath, binary): + obj = bld.new_task_gen('subst') + obj.source = os.path.join('daemon', 'dbus.service.in') + obj.target = target + obj.dict = {'dbus_object_path': opath, + 'daemon_bin_path': os.path.join(bld.env['PREFIX'], 'bin', binary)} + obj.install_path = bld.env['DBUS_SERVICES_DIR'] + os.path.sep + obj.fun = misc.subst_func + def configure(conf): conf.check_tool('compiler_cc') conf.check_tool('compiler_cxx') @@ -314,14 +324,7 @@ def build(bld): daemon.source.append(os.path.join("common", source)) # process dbus.service.in -> ladish.service - import misc - obj = bld.new_task_gen('subst') - obj.source = os.path.join('daemon', 'dbus.service.in') - obj.target = DBUS_NAME_BASE + '.service' - obj.dict = {'dbus_object_path': DBUS_NAME_BASE, - 'daemon_bin_path': os.path.join(bld.env['PREFIX'], 'bin', daemon.target)} - obj.install_path = bld.env['DBUS_SERVICES_DIR'] + os.path.sep - obj.fun = misc.subst_func + create_service_taskgen(bld, DBUS_NAME_BASE + '.service', DBUS_NAME_BASE, daemon.target) ##################################################### # jmcore @@ -342,6 +345,37 @@ def build(bld): ]: jmcore.source.append(os.path.join("dbus", source)) + create_service_taskgen(bld, DBUS_NAME_BASE + '.jmcore.service', DBUS_NAME_BASE + ".jmcore", jmcore.target) + + ##################################################### + # conf + ladiconfd = bld.new_task_gen('cc', 'program') + ladiconfd.target = 'ladiconfd' + ladiconfd.includes = "build/default" # XXX config.h version.h and other generated files + ladiconfd.uselib = 'DBUS-1' + ladiconfd.defines = ['LOG_OUTPUT_STDOUT'] + ladiconfd.source = ['conf.c'] + + for source in [ + 'dirhelpers.c', + 'catdup.c', + ]: + ladiconfd.source.append(os.path.join("common", source)) + + for source in [ + 'signal.c', + 'method.c', + 'error.c', + 'object_path.c', + 'interface.c', + 'helpers.c', + ]: + ladiconfd.source.append(os.path.join("dbus", source)) + + create_service_taskgen(bld, DBUS_NAME_BASE + '.conf.service', DBUS_NAME_BASE + ".ladiconfd", ladiconfd.target) + + ##################################################### + # liblash if bld.env['BUILD_LIBLASH']: liblash = bld.new_task_gen('cc', 'shlib') liblash.includes = "build/default" # XXX config.h version.h and other generated files @@ -365,14 +399,6 @@ def build(bld): obj.install_path = '${LIBDIR}/pkgconfig/' obj.fun = misc.subst_func - obj = bld.new_task_gen('subst') - obj.source = os.path.join('daemon', 'dbus.service.in') - obj.target = DBUS_NAME_BASE + '.jmcore.service' - obj.dict = {'dbus_object_path': DBUS_NAME_BASE + ".jmcore", - 'daemon_bin_path': os.path.join(bld.env['PREFIX'], 'bin', jmcore.target)} - obj.install_path = bld.env['DBUS_SERVICES_DIR'] + os.path.sep - obj.fun = misc.subst_func - ##################################################### # pylash