Code

Merged branch 'master' of git://git.tokkee.org/sysdb.
authorSebastian Harl <sh@tokkee.org>
Thu, 7 Nov 2013 14:47:07 +0000 (15:47 +0100)
committerSebastian Harl <sh@tokkee.org>
Thu, 7 Nov 2013 14:47:07 +0000 (15:47 +0100)
22 files changed:
configure.ac
src/Makefile.am
src/core/plugin.c
src/core/store.c
src/daemon/config.c
src/daemon/sysdbd.c
src/frontend/sock.c [new file with mode: 0644]
src/include/core/plugin.h
src/include/daemon/config.h
src/include/frontend/sock.h [new file with mode: 0644]
src/include/utils/channel.h [new file with mode: 0644]
src/include/utils/llist.h
src/include/utils/strbuf.h
src/utils/channel.c [new file with mode: 0644]
src/utils/llist.c
src/utils/strbuf.c
t/Makefile.am
t/libsysdb_test.c
t/libsysdb_test.h
t/utils/channel_test.c [new file with mode: 0644]
t/utils/llist_test.c
t/utils/strbuf_test.c

index a4f21853dc22b506164a328c5c1f5d1452c26ff9..dde772705dc6a913524116a4e4399dd044f0b823 100644 (file)
@@ -185,8 +185,8 @@ fi
 AC_SUBST([STRICT_CFLAGS])
 
 dnl Testing.
-build_testing="no"
-PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [build_testing="yes"])
+PKG_CHECK_MODULES([CHECK], [check >= 0.9.4],
+               [build_testing="yes"], [build_testing="no"])
 
 AC_CHECK_HEADERS(libgen.h)
 
index 3cdb58e906edbf36483dfb5c7beb81b05e6d7a30..de2f5cf642ef30c1eba8434256ea96bb89a66b5f 100644 (file)
@@ -3,6 +3,7 @@ SUBDIRS = liboconfig
 AM_CFLAGS = @STRICT_CFLAGS@
 AM_CPPFLAGS  = -Iinclude
 AM_CPPFLAGS += -DSYSCONFDIR='"${sysconfdir}"'
+AM_CPPFLAGS += -DLOCALSTATEDIR='"${localstatedir}"'
 AM_CPPFLAGS += -DPKGLIBDIR='"${pkglibdir}"'
 
 BUILT_SOURCES = include/sysdb.h
@@ -25,6 +26,8 @@ libsysdb_la_SOURCES = \
                core/store.c include/core/store.h \
                include/core/data.h \
                core/error.c include/core/error.h \
+               frontend/sock.c include/frontend/sock.h \
+               utils/channel.c include/utils/channel.h \
                utils/llist.c include/utils/llist.h \
                utils/strbuf.c include/utils/strbuf.h \
                core/time.c include/core/time.h \
index ca79284fe60a41b5d17b3f129db277755587e9f7..09d5ccea3ebdcd4fbcc01465ee827e2e313cfd35 100644 (file)
@@ -103,7 +103,9 @@ typedef struct {
 } sdb_plugin_collector_cb_t;
 
 #define SDB_PLUGIN_CB(obj) ((sdb_plugin_cb_t *)(obj))
+#define SDB_CONST_PLUGIN_CB(obj) ((const sdb_plugin_cb_t *)(obj))
 #define SDB_PLUGIN_CCB(obj) ((sdb_plugin_collector_cb_t *)(obj))
+#define SDB_CONST_PLUGIN_CCB(obj) ((const sdb_plugin_collector_cb_t *)(obj))
 
 /*
  * private variables
@@ -175,6 +177,55 @@ plugin_cmp_next_update(const sdb_object_t *a, const sdb_object_t *b)
                ? -1 : 0;
 } /* plugin_cmp_next_update */
 
+static int
+plugin_lookup_by_name(const sdb_object_t *obj, const void *id)
+{
+       const sdb_plugin_cb_t *cb = SDB_CONST_PLUGIN_CB(obj);
+       const char *name = id;
+
+       assert(cb && id && cb->cb_ctx);
+       if (!strcasecmp(cb->cb_ctx->info.plugin_name, name))
+               return 0;
+       return 1;
+} /* plugin_lookup_by_name */
+
+static void
+plugin_unregister_by_name(const char *plugin_name)
+{
+       size_t i;
+
+       struct {
+               const char  *type;
+               sdb_llist_t *list;
+       } all_lists[] = {
+               { "config",    config_list },
+               { "init",      init_list },
+               { "collector", collector_list },
+               { "cname",     cname_list },
+               { "shutdown",  shutdown_list },
+               { "log",       log_list },
+       };
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(all_lists); ++i) {
+               const char  *type = all_lists[i].type;
+               sdb_llist_t *list = all_lists[i].list;
+
+               while (1) {
+                       sdb_object_t *obj = sdb_llist_remove(list,
+                                       plugin_lookup_by_name, plugin_name);
+                       sdb_plugin_cb_t *cb = SDB_PLUGIN_CB(obj);
+
+                       if (! obj)
+                               break;
+
+                       sdb_log(SDB_LOG_INFO, "core: Unregistering "
+                                       "%s callback '%s' (module %s)", type, obj->name,
+                                       cb->cb_ctx->info.plugin_name);
+                       sdb_object_deref(obj);
+               }
+       }
+} /* plugin_unregister_by_name */
+
 /*
  * private types
  */
@@ -254,7 +305,7 @@ plugin_cb_init(sdb_object_t *obj, va_list ap)
        assert(obj);
 
        if (sdb_llist_search_by_name(*list, obj->name)) {
-               sdb_log(SDB_LOG_WARNING, "plugin: %s callback '%s' "
+               sdb_log(SDB_LOG_WARNING, "core: %s callback '%s' "
                                "has already been registered. Ignoring newly "
                                "registered version.", type, obj->name);
                return -1;
@@ -320,7 +371,7 @@ plugin_add_callback(sdb_llist_t **list, const char *type,
        /* pass control to the list */
        sdb_object_deref(obj);
 
-       sdb_log(SDB_LOG_INFO, "plugin: Registered %s callback '%s'.",
+       sdb_log(SDB_LOG_INFO, "core: Registered %s callback '%s'.",
                        type, name);
        return 0;
 } /* plugin_add_callback */
@@ -363,7 +414,7 @@ sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
 
        if (access(filename, R_OK)) {
                char errbuf[1024];
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s' (%s): %s",
+               sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s' (%s): %s",
                                name, filename, sdb_strerror(errno, errbuf, sizeof(errbuf)));
                return -1;
        }
@@ -373,18 +424,18 @@ sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
 
        lh = lt_dlopen(filename);
        if (! lh) {
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': %s"
+               sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': %s"
                                "The most common cause for this problem are missing "
                                "dependencies.\n", name, lt_dlerror());
                return -1;
        }
 
        if (ctx_get())
-               sdb_log(SDB_LOG_WARNING, "plugin: Discarding old plugin context");
+               sdb_log(SDB_LOG_WARNING, "core: Discarding old plugin context");
 
        ctx = ctx_create();
        if (! ctx) {
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize plugin context");
+               sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin context");
                return -1;
        }
 
@@ -396,7 +447,7 @@ sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
 
        mod_init = (int (*)(sdb_plugin_info_t *))lt_dlsym(lh, "sdb_module_init");
        if (! mod_init) {
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to load plugin '%s': "
+               sdb_log(SDB_LOG_ERR, "core: Failed to load plugin '%s': "
                                "could not find symbol 'sdb_module_init'", name);
                sdb_object_deref(SDB_OBJ(ctx));
                return -1;
@@ -404,8 +455,9 @@ sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
 
        status = mod_init(&ctx->info);
        if (status) {
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to initialize "
-                               "plugin '%s'", name);
+               sdb_log(SDB_LOG_ERR, "core: Failed to initialize "
+                               "module '%s'", name);
+               plugin_unregister_by_name(ctx->info.plugin_name);
                sdb_object_deref(SDB_OBJ(ctx));
                return -1;
        }
@@ -413,13 +465,13 @@ sdb_plugin_load(const char *name, const sdb_plugin_ctx_t *plugin_ctx)
        /* compare minor version */
        if ((ctx->info.version < 0)
                        || ((int)(ctx->info.version / 100) != (int)(SDB_VERSION / 100)))
-               sdb_log(SDB_LOG_WARNING, "plugin: WARNING: version of "
+               sdb_log(SDB_LOG_WARNING, "core: WARNING: version of "
                                "plugin '%s' (%i.%i.%i) does not match our version "
                                "(%i.%i.%i); this might cause problems",
                                name, SDB_VERSION_DECODE(ctx->info.version),
                                SDB_VERSION_DECODE(SDB_VERSION));
 
-       sdb_log(SDB_LOG_INFO, "plugin: Successfully loaded "
+       sdb_log(SDB_LOG_INFO, "core: Successfully loaded "
                        "plugin '%s' v%i (%s)\n\t%s\n\tLicense: %s",
                        INFO_GET(&ctx->info, name), ctx->info.plugin_version,
                        INFO_GET(&ctx->info, description),
@@ -574,7 +626,7 @@ sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback
 
        if (! (SDB_PLUGIN_CCB(obj)->ccb_next_update = sdb_gettime())) {
                char errbuf[1024];
-               sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
+               sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
                                "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
                sdb_object_deref(obj);
                return -1;
@@ -589,7 +641,7 @@ sdb_plugin_register_collector(const char *name, sdb_plugin_collector_cb callback
        /* pass control to the list */
        sdb_object_deref(obj);
 
-       sdb_log(SDB_LOG_INFO, "plugin: Registered collector callback '%s' "
+       sdb_log(SDB_LOG_INFO, "core: Registered collector callback '%s' "
                        "(interval = %.3fs).", name,
                        SDB_TIME_TO_DOUBLE(SDB_PLUGIN_CCB(obj)->ccb_interval));
        return 0;
@@ -602,7 +654,7 @@ sdb_plugin_get_ctx(void)
 
        c = ctx_get();
        if (! c) {
-               sdb_plugin_log(SDB_LOG_ERR, "plugin: Invalid read access to plugin "
+               sdb_plugin_log(SDB_LOG_ERR, "core: Invalid read access to plugin "
                                "context outside a plugin");
                return plugin_default_ctx;
        }
@@ -616,7 +668,7 @@ sdb_plugin_set_ctx(sdb_plugin_ctx_t ctx, sdb_plugin_ctx_t *old)
 
        c = ctx_get();
        if (! c) {
-               sdb_plugin_log(SDB_LOG_ERR, "plugin: Invalid write access to plugin "
+               sdb_plugin_log(SDB_LOG_ERR, "core: Invalid write access to plugin "
                                "context outside a plugin");
                return -1;
        }
@@ -643,7 +695,7 @@ sdb_plugin_configure(const char *name, oconfig_item_t *ci)
        plugin = SDB_PLUGIN_CB(sdb_llist_search_by_name(config_list, name));
        if (! plugin) {
                /* XXX: check if any such plugin has been loaded */
-               sdb_log(SDB_LOG_ERR, "plugin: Plugin '%s' did not register "
+               sdb_log(SDB_LOG_ERR, "core: Plugin '%s' did not register "
                                "a config callback.", name);
                errno = ENOENT;
                return -1;
@@ -660,32 +712,38 @@ int
 sdb_plugin_init_all(void)
 {
        sdb_llist_iter_t *iter;
+       int ret = 0;
 
        iter = sdb_llist_get_iter(init_list);
        while (sdb_llist_iter_has_next(iter)) {
+               sdb_plugin_cb_t *cb;
                sdb_plugin_init_cb callback;
                ctx_t *old_ctx;
 
                sdb_object_t *obj = sdb_llist_iter_get_next(iter);
                assert(obj);
+               cb = SDB_PLUGIN_CB(obj);
 
-               callback = SDB_PLUGIN_CB(obj)->cb_callback;
+               callback = cb->cb_callback;
 
-               old_ctx = ctx_set(SDB_PLUGIN_CB(obj)->cb_ctx);
-               if (callback(SDB_PLUGIN_CB(obj)->cb_user_data)) {
-                       /* XXX: unload plugin */
+               old_ctx = ctx_set(cb->cb_ctx);
+               if (callback(cb->cb_user_data)) {
+                       sdb_log(SDB_LOG_ERR, "core: Failed to initialize plugin "
+                                       "'%s'. Unregistering all callbacks.", obj->name);
+                       plugin_unregister_by_name(cb->cb_ctx->info.plugin_name);
+                       ++ret;
                }
                ctx_set(old_ctx);
        }
        sdb_llist_iter_destroy(iter);
-       return 0;
+       return ret;
 } /* sdb_plugin_init_all */
 
 int
 sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
 {
        if (! collector_list) {
-               sdb_log(SDB_LOG_WARNING, "plugin: No collectors registered. "
+               sdb_log(SDB_LOG_WARNING, "core: No collectors registered. "
                                "Quiting main loop.");
                return -1;
        }
@@ -707,8 +765,9 @@ sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
 
                if (! (now = sdb_gettime())) {
                        char errbuf[1024];
-                       sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
-                                       "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
+                       sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
+                                       "time in collector main loop: %s",
+                                       sdb_strerror(errno, errbuf, sizeof(errbuf)));
                        now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
                }
 
@@ -719,7 +778,8 @@ sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
                        while (loop->do_loop && sdb_sleep(interval, &interval)) {
                                if (errno != EINTR) {
                                        char errbuf[1024];
-                                       sdb_log(SDB_LOG_ERR, "plugin: Failed to sleep: %s",
+                                       sdb_log(SDB_LOG_ERR, "core: Failed to sleep "
+                                                       "in collector main loop: %s",
                                                        sdb_strerror(errno, errbuf, sizeof(errbuf)));
                                        return -1;
                                }
@@ -740,7 +800,7 @@ sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
                if (! interval)
                        interval = loop->default_interval;
                if (! interval) {
-                       sdb_log(SDB_LOG_WARNING, "plugin: No interval configured "
+                       sdb_log(SDB_LOG_WARNING, "core: No interval configured "
                                        "for plugin '%s'; skipping any further "
                                        "iterations.", obj->name);
                        sdb_object_deref(obj);
@@ -751,13 +811,14 @@ sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
 
                if (! (now = sdb_gettime())) {
                        char errbuf[1024];
-                       sdb_log(SDB_LOG_ERR, "plugin: Failed to determine current "
-                                       "time: %s", sdb_strerror(errno, errbuf, sizeof(errbuf)));
+                       sdb_log(SDB_LOG_ERR, "core: Failed to determine current "
+                                       "time in collector main loop: %s",
+                                       sdb_strerror(errno, errbuf, sizeof(errbuf)));
                        now = SDB_PLUGIN_CCB(obj)->ccb_next_update;
                }
 
                if (now > SDB_PLUGIN_CCB(obj)->ccb_next_update) {
-                       sdb_log(SDB_LOG_WARNING, "plugin: Plugin '%s' took too "
+                       sdb_log(SDB_LOG_WARNING, "core: Plugin '%s' took too "
                                        "long; skipping iterations to keep up.",
                                        obj->name);
                        SDB_PLUGIN_CCB(obj)->ccb_next_update = now;
@@ -765,7 +826,7 @@ sdb_plugin_collector_loop(sdb_plugin_loop_t *loop)
 
                if (sdb_llist_insert_sorted(collector_list, obj,
                                        plugin_cmp_next_update)) {
-                       sdb_log(SDB_LOG_ERR, "plugin: Failed to re-insert "
+                       sdb_log(SDB_LOG_ERR, "core: Failed to re-insert "
                                        "plugin '%s' into collector list. Unable to further "
                                        "use the plugin.",
                                        obj->name);
index 43a7a6bda20d1228f8fa6b389a5c9710d92936a2..19faee43994bdde816a6a84f4c09061bfb25bb4f 100644 (file)
@@ -309,7 +309,10 @@ store_obj(int parent_type, const char *parent_name,
        }
 
        /* TODO: only look into direct children? */
-       if (type == SDB_ATTRIBUTE)
+       if (type == SDB_HOST)
+               /* make sure that each host is unique */
+               old = STORE_OBJ(sdb_store_lookup_in_list(obj_list, type, name));
+       else if (type == SDB_ATTRIBUTE)
                old = STORE_OBJ(sdb_llist_search_by_name(parent_list, name));
        else
                old = STORE_OBJ(sdb_store_lookup_in_list(parent_list, type, name));
index d15081f469446ee1f3b3214b6dbe8efcdf6e7253..beb0217baa323d5bbd17aff02891aeaed73a5ea7 100644 (file)
 #include "liboconfig/utils.h"
 
 #include <assert.h>
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
 #include <strings.h>
 
+/*
+ * Parser error values:
+ *  - Values less than zero indicate an error in the daemon or libsysdb.
+ *  - Zero indicates success.
+ *  - Any other values indicate parsing errors.
+ */
+
+enum {
+       ERR_UNKNOWN_OPTION = 1,
+       ERR_UNKNOWN_ARG    = 2,
+       ERR_INVALID_ARG    = 3,
+       ERR_PARSE_FAILED   = 4,
+};
+
 /*
  * private variables
  */
@@ -59,20 +77,27 @@ config_get_interval(oconfig_item_t *ci, sdb_time_t *interval)
                sdb_log(SDB_LOG_ERR, "config: Interval requires "
                                "a single numeric argument\n"
                                "\tUsage: Interval SECONDS");
-               return -1;
+               return ERR_INVALID_ARG;
        }
 
        if (interval_dbl <= 0.0) {
                sdb_log(SDB_LOG_ERR, "config: Invalid interval: %f\n"
                                "\tInterval may not be less than or equal to zero.",
                                interval_dbl);
-               return -1;
+               return ERR_INVALID_ARG;
        }
 
        *interval = DOUBLE_TO_SDB_TIME(interval_dbl);
        return 0;
 } /* config_get_interval */
 
+/*
+ * public parse results
+ */
+
+char **listen_addresses = NULL;
+size_t listen_addresses_num = 0;
+
 /*
  * token parser
  */
@@ -82,6 +107,41 @@ typedef struct {
        int (*dispatcher)(oconfig_item_t *);
 } token_parser_t;
 
+static int
+daemon_add_listener(oconfig_item_t *ci)
+{
+       char **tmp;
+       char *address;
+
+       if (oconfig_get_string(ci, &address)) {
+               sdb_log(SDB_LOG_ERR, "config: Listen requires a single "
+                               "string argument\n"
+                               "\tUsage: Listen ADDRESS");
+               return ERR_INVALID_ARG;
+       }
+
+       tmp = realloc(listen_addresses,
+                       (listen_addresses_num + 1) * sizeof(*listen_addresses));
+       if (! tmp) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "config: Failed to allocate memory: %s",
+                               sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+
+       tmp[listen_addresses_num] = strdup(address);
+       if (! tmp[listen_addresses_num]) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "config: Failed to allocate memory: %s",
+                               sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+
+       listen_addresses = tmp;
+       ++listen_addresses_num;
+       return 0;
+} /* daemon_add_listener */
+
 static int
 daemon_set_interval(oconfig_item_t *ci)
 {
@@ -98,7 +158,7 @@ daemon_load_plugin(oconfig_item_t *ci)
                sdb_log(SDB_LOG_ERR, "config: LoadPlugin requires a single "
                                "string argument\n"
                                "\tUsage: LoadPlugin PLUGIN");
-               return -1;
+               return ERR_INVALID_ARG;
        }
 
        for (i = 0; i < ci->children_num; ++i) {
@@ -111,6 +171,7 @@ daemon_load_plugin(oconfig_item_t *ci)
                continue;
        }
 
+       /* returns a negative value on error */
        return sdb_plugin_load(name, NULL);
 } /* daemon_load_plugin */
 
@@ -130,7 +191,7 @@ daemon_load_backend(oconfig_item_t *ci)
                sdb_log(SDB_LOG_ERR, "config: LoadBackend requires a single "
                                "string argument\n"
                                "\tUsage: LoadBackend BACKEND");
-               return -1;
+               return ERR_INVALID_ARG;
        }
 
        snprintf(plugin_name, sizeof(plugin_name), "backend::%s", name);
@@ -140,7 +201,7 @@ daemon_load_backend(oconfig_item_t *ci)
 
                if (! strcasecmp(child->key, "Interval")) {
                        if (config_get_interval(child, &ctx.interval))
-                               return -1;
+                               return ERR_INVALID_ARG;
                }
                else {
                        sdb_log(SDB_LOG_WARNING, "config: Unknown option '%s' "
@@ -165,13 +226,14 @@ daemon_configure_plugin(oconfig_item_t *ci)
                                "string argument\n"
                                "\tUsage: LoadBackend BACKEND",
                                ci->key);
-               return -1;
+               return ERR_INVALID_ARG;
        }
 
        return sdb_plugin_configure(name, ci);
 } /* daemon_configure_backend */
 
 static token_parser_t token_parser_list[] = {
+       { "Listen", daemon_add_listener },
        { "Interval", daemon_set_interval },
        { "LoadPlugin", daemon_load_plugin },
        { "LoadBackend", daemon_load_backend },
@@ -192,11 +254,11 @@ daemon_parse_config(const char *filename)
 
        ci = oconfig_parse_file(filename);
        if (! ci)
-               return -1;
+               return ERR_PARSE_FAILED;
 
        for (i = 0; i < ci->children_num; ++i) {
                oconfig_item_t *child = ci->children + i;
-               int status = 1, j;
+               int status = ERR_UNKNOWN_OPTION, j;
 
                for (j = 0; token_parser_list[j].name; ++j) {
                        if (! strcasecmp(token_parser_list[j].name, child->key))
@@ -206,13 +268,13 @@ daemon_parse_config(const char *filename)
                if (status) {
                        sdb_error_set("config: Failed to parse option '%s'\n",
                                        child->key);
-                       if (status > 0)
+                       if (status == ERR_UNKNOWN_OPTION)
                                sdb_error_append("\tUnknown option '%s' -- "
                                                "see the documentation for details\n",
                                                child->key);
                        sdb_error_chomp();
                        sdb_error_log(SDB_LOG_ERR);
-                       retval = -1;
+                       retval = status;
                }
        }
        return retval;
index f1992610f2a2164af9d189c8f1324ea6a3265daf..e2309224d57243bd9dd7d7bc19f7fde1841c8ed9 100644 (file)
@@ -34,6 +34,8 @@
 #include "core/store.h"
 #include "core/error.h"
 
+#include "frontend/sock.h"
+
 #include "daemon/config.h"
 
 #if HAVE_LIBGEN_H
 
 #include <unistd.h>
 
+#include <pthread.h>
+
 #ifndef CONFIGFILE
 #      define CONFIGFILE SYSCONFDIR"/sysdb/sysdbd.conf"
 #endif
 
+#ifndef DEFAULT_SOCKET
+#      define DEFAULT_SOCKET "unix:"LOCALSTATEDIR"/run/sysdbd.sock"
+#endif
+
 static sdb_plugin_loop_t plugin_main_loop = SDB_PLUGIN_LOOP_INIT;
+static sdb_fe_loop_t frontend_main_loop = SDB_FE_LOOP_INIT;
+
+static char *default_listen_addresses[] = {
+       DEFAULT_SOCKET,
+};
 
 static void
 sigintterm_handler(int __attribute__((unused)) signo)
 {
-       plugin_main_loop.do_loop = 0;
+       frontend_main_loop.do_loop = 0;
 } /* sigintterm_handler */
 
 static void
@@ -154,13 +167,23 @@ daemonize(void)
        return 0;
 } /* daemonize */
 
+static void *
+backend_handler(void __attribute__((unused)) *data)
+{
+       sdb_plugin_collector_loop(&plugin_main_loop);
+       return NULL;
+} /* backend_handler */
+
 int
 main(int argc, char **argv)
 {
        char *config_filename = NULL;
        _Bool do_daemonize = 0;
 
+       pthread_t backend_thread;
+
        struct sigaction sa_intterm;
+       int status;
 
        while (42) {
                int opt = getopt(argc, argv, "C:dhV");
@@ -193,11 +216,20 @@ main(int argc, char **argv)
        if (! config_filename)
                config_filename = CONFIGFILE;
 
-       if (daemon_parse_config(config_filename)) {
-               sdb_log(SDB_LOG_ERR, "Failed to parse configuration file.");
+       if ((status = daemon_parse_config(config_filename))) {
+               if (status > 0)
+                       sdb_log(SDB_LOG_ERR, "Failed to parse configuration file.");
+               else
+                       sdb_log(SDB_LOG_ERR, "Failed to load configuration file.\n"
+                                       "\tCheck other error messages for details.");
                exit(1);
        }
 
+       if (! listen_addresses) {
+               listen_addresses = default_listen_addresses;
+               listen_addresses_num = SDB_STATIC_ARRAY_LEN(default_listen_addresses);
+       }
+
        memset(&sa_intterm, 0, sizeof(sa_intterm));
        sa_intterm.sa_handler = sigintterm_handler;
        sa_intterm.sa_flags = 0;
@@ -225,7 +257,33 @@ main(int argc, char **argv)
 
        sdb_plugin_init_all();
        plugin_main_loop.default_interval = SECS_TO_SDB_TIME(60);
-       sdb_plugin_collector_loop(&plugin_main_loop);
+
+       memset(&backend_thread, 0, sizeof(backend_thread));
+       if (pthread_create(&backend_thread, /* attr = */ NULL,
+                               backend_handler, /* arg = */ NULL)) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "Failed to create backend handler thread: %s",
+                               sdb_strerror(errno, buf, sizeof(buf)));
+
+               plugin_main_loop.do_loop = 0;
+       }
+       else {
+               size_t i;
+
+               sdb_fe_socket_t *sock = sdb_fe_sock_create();
+               for (i = 0; i < listen_addresses_num; ++i)
+                       if (sdb_fe_sock_add_listener(sock, listen_addresses[i]))
+                               break;
+
+               /* break on error */
+               if (i >= listen_addresses_num)
+                       sdb_fe_sock_listen_and_serve(sock, &frontend_main_loop);
+
+               sdb_log(SDB_LOG_INFO, "Waiting for backend thread to terminate");
+               plugin_main_loop.do_loop = 0;
+               pthread_join(backend_thread, NULL);
+               sdb_fe_sock_destroy(sock);
+       }
 
        sdb_log(SDB_LOG_INFO, "Shutting down SysDB daemon "SDB_VERSION_STRING
                        SDB_VERSION_EXTRA" (pid %i)", (int)getpid());
diff --git a/src/frontend/sock.c b/src/frontend/sock.c
new file mode 100644 (file)
index 0000000..d7b2ee9
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * SysDB - src/frontend/sock.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sysdb.h"
+#include "core/error.h"
+#include "core/object.h"
+#include "frontend/sock.h"
+
+#include "utils/channel.h"
+#include "utils/llist.h"
+#include "utils/strbuf.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <pthread.h>
+
+/* name of connection objects */
+#define CONN_FD_PREFIX "conn#"
+#define CONN_FD_PLACEHOLDER "XXXXXXX"
+
+/*
+ * private data types
+ */
+
+typedef struct {
+       sdb_object_t super;
+
+       /* connection and client information */
+       int fd;
+       struct sockaddr_storage client_addr;
+       socklen_t client_addr_len;
+
+       /* read buffer */
+       sdb_strbuf_t *buf;
+
+       /* state information for the currently executed command */
+       uint32_t cmd;
+       uint32_t cmd_len;
+} connection_obj_t;
+#define CONN(obj) ((connection_obj_t *)(obj))
+
+typedef struct {
+       char *address;
+       int   type;
+
+       int sock_fd;
+} listener_t;
+
+typedef struct {
+       int type;
+       const char *prefix;
+
+       int (*opener)(listener_t *);
+} fe_listener_impl_t;
+
+struct sdb_fe_socket {
+       listener_t *listeners;
+       size_t listeners_num;
+
+       sdb_llist_t *open_connections;
+
+       /* channel used for communication between main
+        * and connection handler threads */
+       sdb_channel_t *chan;
+};
+
+/*
+ * connection management functions
+ */
+
+static int
+open_unix_sock(listener_t *listener)
+{
+       struct sockaddr_un sa;
+       int status;
+
+       listener->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (listener->sock_fd < 0) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to open UNIX socket %s: %s",
+                               listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sun_family = AF_UNIX;
+       strncpy(sa.sun_path, listener->address + strlen("unix:"),
+                       sizeof(sa.sun_path));
+
+       status = bind(listener->sock_fd, (struct sockaddr *)&sa, sizeof(sa));
+       if (status) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to UNIX socket %s: %s",
+                               listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+       return 0;
+} /* open_unix_sock */
+
+/*
+ * private variables
+ */
+
+/* the enum has to be sorted the same as the implementations array
+ * to ensure that the type may be used as index into the array */
+enum {
+       LISTENER_UNIXSOCK = 0,
+};
+static fe_listener_impl_t listener_impls[] = {
+       { LISTENER_UNIXSOCK, "unix", open_unix_sock },
+};
+
+/*
+ * private helper functions
+ */
+
+static int
+get_type(const char *address)
+{
+       char *sep;
+       size_t len;
+       size_t i;
+
+       sep = strchr(address, (int)':');
+       if (! sep)
+               return -1;
+
+       assert(sep > address);
+       len = (size_t)(sep - address);
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(listener_impls); ++i) {
+               fe_listener_impl_t *impl = listener_impls + i;
+
+               if (!strncmp(address, impl->prefix, len)) {
+                       assert(impl->type == (int)i);
+                       return impl->type;
+               }
+       }
+       return -1;
+} /* get_type */
+
+static void
+listener_destroy(listener_t *listener)
+{
+       if (! listener)
+               return;
+
+       if (listener->sock_fd >= 0)
+               close(listener->sock_fd);
+       listener->sock_fd = -1;
+
+       if (listener->address)
+               free(listener->address);
+} /* listener_destroy */
+
+static listener_t *
+listener_create(sdb_fe_socket_t *sock, const char *address)
+{
+       listener_t *listener;
+       int type;
+
+       type = get_type(address);
+       if (type < 0) {
+               sdb_log(SDB_LOG_ERR, "frontend: Unsupported address type specified "
+                               "in listen address '%s'", address);
+               return NULL;
+       }
+
+       listener = realloc(sock->listeners,
+                       sock->listeners_num * sizeof(*sock->listeners));
+       if (! listener) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
+                               sdb_strerror(errno, buf, sizeof(buf)));
+               return NULL;
+       }
+
+       sock->listeners = listener;
+       listener = sock->listeners + sock->listeners_num;
+
+       listener->sock_fd = -1;
+       listener->address = strdup(address);
+       if (! listener->address) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
+                               sdb_strerror(errno, buf, sizeof(buf)));
+               listener_destroy(listener);
+               return NULL;
+       }
+       listener->type = type;
+
+       if (listener_impls[type].opener(listener)) {
+               /* prints error */
+               listener_destroy(listener);
+               return NULL;
+       }
+
+       ++sock->listeners_num;
+       return listener;
+} /* listener_create */
+
+static int
+listener_listen(listener_t *listener)
+{
+       assert(listener);
+
+       /* try to reopen */
+       if (listener->sock_fd < 0)
+               if (listener_impls[listener->type].opener(listener))
+                       return -1;
+       assert(listener->sock_fd >= 0);
+
+       if (listen(listener->sock_fd, /* backlog = */ 32)) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to listen on socket %s: %s",
+                               listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+       return 0;
+} /* listener_listen */
+
+static void
+listener_close(listener_t *listener)
+{
+       assert(listener);
+
+       if (listener->sock_fd < 0)
+               return;
+
+       close(listener->sock_fd);
+       listener->sock_fd = -1;
+} /* listener_close */
+
+static void
+socket_close(sdb_fe_socket_t *sock)
+{
+       size_t i;
+
+       assert(sock);
+       for (i = 0; i < sock->listeners_num; ++i)
+               listener_close(sock->listeners + i);
+} /* socket_close */
+
+/*
+ * private data types
+ */
+
+static int
+connection_init(sdb_object_t *obj, va_list ap)
+{
+       connection_obj_t *conn;
+       int sock_fd;
+       int sock_fl;
+
+       assert(obj);
+       conn = CONN(obj);
+
+       sock_fd = va_arg(ap, int);
+
+       CONN(obj)->buf = sdb_strbuf_create(/* size = */ 128);
+       if (! CONN(obj)->buf) {
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate a read buffer "
+                               "for a new remote connection");
+               return -1;
+       }
+
+       conn->client_addr_len = sizeof(conn->client_addr);
+       conn->fd = accept(sock_fd, (struct sockaddr *)&conn->client_addr,
+                       &conn->client_addr_len);
+
+       if (conn->fd < 0) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to accept remote "
+                               "connection: %s", sdb_strerror(errno,
+                                       buf, sizeof(buf)));
+               return -1;
+       }
+
+       if (conn->client_addr.ss_family != AF_UNIX) {
+               sdb_log(SDB_LOG_ERR, "frontend: Accepted connection using "
+                               "unexpected family type %d", conn->client_addr.ss_family);
+               return -1;
+       }
+
+       sock_fl = fcntl(conn->fd, F_GETFL);
+       if (fcntl(conn->fd, F_SETFL, sock_fl | O_NONBLOCK)) {
+               char buf[1024];
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to switch connection conn#%i "
+                               "to non-blocking mode: %s", conn->fd,
+                               sdb_strerror(errno, buf, sizeof(buf)));
+               return -1;
+       }
+
+       sdb_log(SDB_LOG_DEBUG, "frontend: Accepted connection on fd=%i",
+                       conn->fd);
+
+       /* update the object name */
+       snprintf(obj->name + strlen(CONN_FD_PREFIX),
+                       strlen(CONN_FD_PLACEHOLDER), "%i", conn->fd);
+       return 0;
+} /* connection_init */
+
+static void
+connection_destroy(sdb_object_t *obj)
+{
+       connection_obj_t *conn;
+       size_t len;
+
+       assert(obj);
+       conn = CONN(obj);
+
+       len = sdb_strbuf_len(conn->buf);
+       if (len)
+               sdb_log(SDB_LOG_INFO, "frontend: Discarding incomplete command "
+                               "(%zu bytes left in buffer)", len);
+
+       sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection on fd=%i", conn->fd);
+       close(conn->fd);
+       conn->fd = -1;
+
+       sdb_strbuf_destroy(CONN(obj)->buf);
+} /* connection_destroy */
+
+static sdb_type_t connection_type = {
+       /* size = */ sizeof(connection_obj_t),
+       /* init = */ connection_init,
+       /* destroy = */ connection_destroy,
+};
+
+/*
+ * connection handler functions
+ */
+
+static uint32_t
+connection_get_int32(connection_obj_t *conn, size_t offset)
+{
+       const char *data;
+       uint32_t n;
+
+       assert(conn && (sdb_strbuf_len(conn->buf) >= offset + sizeof(uint32_t)));
+
+       data = sdb_strbuf_string(conn->buf);
+       memcpy(&n, data + offset, sizeof(n));
+       n = ntohl(n);
+       return n;
+} /* connection_get_int32 */
+
+static int
+command_handle(connection_obj_t *conn)
+{
+       assert(conn && conn->cmd && conn->cmd_len);
+       /* XXX */
+       sdb_strbuf_skip(conn->buf, conn->cmd_len);
+       return 0;
+} /* command_handle */
+
+/* initialize the connection state information */
+static int
+command_init(connection_obj_t *conn)
+{
+       assert(conn && (! conn->cmd) && (! conn->cmd_len));
+
+       conn->cmd = connection_get_int32(conn, 0);
+       conn->cmd_len = connection_get_int32(conn, sizeof(uint32_t));
+       sdb_strbuf_skip(conn->buf, 2 * sizeof(uint32_t));
+       return 0;
+} /* command_init */
+
+/* returns negative value on error, 0 on EOF, number of octets else */
+static ssize_t
+connection_read(connection_obj_t *conn)
+{
+       ssize_t n = 0;
+
+       while (42) {
+               ssize_t status;
+
+               errno = 0;
+               status = sdb_strbuf_read(conn->buf, conn->fd, 1024);
+               if (status < 0) {
+                       if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+                               return n;
+                       return (int)status;
+               }
+               else if (! status) /* EOF */
+                       return n;
+
+               n += status;
+       }
+
+       return n;
+} /* connection_read */
+
+static void *
+connection_handler(void *data)
+{
+       sdb_fe_socket_t *sock = data;
+
+       assert(sock);
+
+       while (42) {
+               struct timespec timeout = { 0, 500000000 }; /* .5 seconds */
+               connection_obj_t *conn;
+               int status;
+
+               errno = 0;
+               status = sdb_channel_select(sock->chan, /* read */ NULL, &conn,
+                               /* write */ NULL, NULL, &timeout);
+               if (status) {
+                       char buf[1024];
+
+                       if (errno == ETIMEDOUT)
+                               continue;
+                       if (errno == EBADF) /* channel shut down */
+                               break;
+
+                       sdb_log(SDB_LOG_ERR, "frontend: Failed to read from channel: %s",
+                                       sdb_strerror(errno, buf, sizeof(buf)));
+                       continue;
+               }
+
+               status = (int)connection_read(conn);
+               if (status <= 0) {
+                       /* error or EOF -> close connection */
+                       sdb_object_deref(SDB_OBJ(conn));
+                       continue;
+               }
+
+               if (conn->cmd_len && (sdb_strbuf_len(conn->buf) >= conn->cmd_len))
+                       command_handle(conn);
+               else if (sdb_strbuf_len(conn->buf) >= 2 * sizeof(int32_t))
+                       command_init(conn);
+
+               /* return the connection to the main loop */
+               if (sdb_llist_append(sock->open_connections, SDB_OBJ(conn))) {
+                       sdb_log(SDB_LOG_ERR, "frontend: Failed to re-append "
+                                       "connection %s to list of open connections",
+                                       SDB_OBJ(conn)->name);
+               }
+
+               /* pass ownership back to list; or destroy in case of an error */
+               sdb_object_deref(SDB_OBJ(conn));
+       }
+       return NULL;
+} /* connection_handler */
+
+static int
+connection_accept(sdb_fe_socket_t *sock, listener_t *listener)
+{
+       sdb_object_t *obj;
+
+       /* the placeholder will be replaced with the accepted file
+        * descriptor when initializing the object */
+       obj = sdb_object_create(CONN_FD_PREFIX CONN_FD_PLACEHOLDER,
+                       connection_type, listener->sock_fd);
+       if (! obj)
+               return -1;
+
+       if (sdb_llist_append(sock->open_connections, obj)) {
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to append "
+                               "connection %s to list of open connections",
+                               obj->name);
+               sdb_object_deref(obj);
+               return -1;
+       }
+
+       /* hand ownership over to the list */
+       sdb_object_deref(obj);
+       return 0;
+} /* connection_accept */
+
+static int
+socket_handle_incoming(sdb_fe_socket_t *sock,
+               fd_set *ready, fd_set *exceptions)
+{
+       sdb_llist_iter_t *iter;
+       size_t i;
+
+       for (i = 0; i < sock->listeners_num; ++i) {
+               listener_t *listener = sock->listeners + i;
+               if (FD_ISSET(listener->sock_fd, ready))
+                       if (connection_accept(sock, listener))
+                               continue;
+       }
+
+       iter = sdb_llist_get_iter(sock->open_connections);
+       if (! iter) {
+               sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
+                               "for open connections");
+               return -1;
+       }
+
+       while (sdb_llist_iter_has_next(iter)) {
+               sdb_object_t *obj = sdb_llist_iter_get_next(iter);
+
+               if (FD_ISSET(CONN(obj)->fd, exceptions))
+                       sdb_log(SDB_LOG_INFO, "Exception on fd %d",
+                                       CONN(obj)->fd);
+
+               if (FD_ISSET(CONN(obj)->fd, ready)) {
+                       sdb_llist_iter_remove_current(iter);
+                       sdb_channel_write(sock->chan, &obj);
+               }
+       }
+       sdb_llist_iter_destroy(iter);
+       return 0;
+} /* socket_handle_incoming */
+
+/*
+ * public API
+ */
+
+sdb_fe_socket_t *
+sdb_fe_sock_create(void)
+{
+       sdb_fe_socket_t *sock;
+
+       sock = calloc(1, sizeof(*sock));
+       if (! sock)
+               return NULL;
+
+       sock->open_connections = sdb_llist_create();
+       if (! sock->open_connections) {
+               sdb_fe_sock_destroy(sock);
+               return NULL;
+       }
+       return sock;
+} /* sdb_fe_sock_create */
+
+void
+sdb_fe_sock_destroy(sdb_fe_socket_t *sock)
+{
+       size_t i;
+
+       if (! sock)
+               return;
+
+       for (i = 0; i < sock->listeners_num; ++i) {
+               listener_destroy(sock->listeners + i);
+       }
+       if (sock->listeners)
+               free(sock->listeners);
+       sock->listeners = NULL;
+
+       sdb_llist_destroy(sock->open_connections);
+       sock->open_connections = NULL;
+       free(sock);
+} /* sdb_fe_sock_destroy */
+
+int
+sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address)
+{
+       listener_t *listener;
+
+       if ((! sock) || (! address))
+               return -1;
+
+       listener = listener_create(sock, address);
+       if (! listener)
+               return -1;
+       return 0;
+} /* sdb_fe_sock_add_listener */
+
+int
+sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop)
+{
+       fd_set sockets;
+       int max_listen_fd = 0;
+       size_t i;
+
+       /* XXX: make the number of threads configurable */
+       pthread_t handler_threads[5];
+
+       if ((! sock) || (! sock->listeners_num) || (! loop) || sock->chan)
+               return -1;
+
+       FD_ZERO(&sockets);
+       for (i = 0; i < sock->listeners_num; ++i) {
+               listener_t *listener = sock->listeners + i;
+
+               if (listener_listen(listener)) {
+                       socket_close(sock);
+                       return -1;
+               }
+
+               FD_SET(listener->sock_fd, &sockets);
+               if (listener->sock_fd > max_listen_fd)
+                       max_listen_fd = listener->sock_fd;
+       }
+
+       sock->chan = sdb_channel_create(1024, sizeof(connection_obj_t *));
+       if (! sock->chan) {
+               socket_close(sock);
+               return -1;
+       }
+
+       memset(&handler_threads, 0, sizeof(handler_threads));
+       /* XXX: error handling */
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(handler_threads); ++i)
+               pthread_create(&handler_threads[i], /* attr = */ NULL,
+                               connection_handler, /* arg = */ sock);
+
+       while (loop->do_loop) {
+               struct timeval timeout = { 1, 0 }; /* one second */
+               sdb_llist_iter_t *iter;
+
+               int max_fd = max_listen_fd;
+               fd_set ready;
+               fd_set exceptions;
+               int n;
+
+               FD_ZERO(&ready);
+               FD_ZERO(&exceptions);
+
+               ready = sockets;
+
+               iter = sdb_llist_get_iter(sock->open_connections);
+               if (! iter) {
+                       sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
+                                       "for open connections");
+                       break;
+               }
+
+               while (sdb_llist_iter_has_next(iter)) {
+                       sdb_object_t *obj = sdb_llist_iter_get_next(iter);
+                       FD_SET(CONN(obj)->fd, &ready);
+                       FD_SET(CONN(obj)->fd, &exceptions);
+
+                       if (CONN(obj)->fd > max_fd)
+                               max_fd = CONN(obj)->fd;
+               }
+               sdb_llist_iter_destroy(iter);
+
+               errno = 0;
+               n = select(max_fd + 1, &ready, NULL, &exceptions, &timeout);
+               if (n < 0) {
+                       char buf[1024];
+
+                       if (errno == EINTR)
+                               continue;
+
+                       sdb_log(SDB_LOG_ERR, "frontend: Failed to monitor sockets: %s",
+                                       sdb_strerror(errno, buf, sizeof(buf)));
+                       break;
+               }
+               else if (! n)
+                       continue;
+
+               /* handle new and open connections */
+               if (socket_handle_incoming(sock, &ready, &exceptions))
+                       break;
+       }
+
+       socket_close(sock);
+
+       sdb_log(SDB_LOG_INFO, "frontend: Waiting for connection handler threads "
+                       "to terminate");
+       if (! sdb_channel_shutdown(sock->chan))
+               for (i = 0; i < SDB_STATIC_ARRAY_LEN(handler_threads); ++i)
+                       pthread_join(handler_threads[i], NULL);
+       /* else: we tried our best; let the operating system clean up */
+
+       sdb_channel_destroy(sock->chan);
+       sock->chan = NULL;
+       return 0;
+} /* sdb_fe_sock_listen_and_server */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index 6a679ce8a17baa5900573ee38cf0deb3e22882cb..a4217ac814aa35c632f334aaae8d22e13f79a46d 100644 (file)
@@ -245,6 +245,9 @@ sdb_plugin_configure(const char *name, oconfig_item_t *ci);
 /*
  * sdb_plugin_init_all:
  * Initialize all plugins using their registered "init" function.
+ *
+ * Returns:
+ * The number of failed initializations.
  */
 int
 sdb_plugin_init_all(void);
index 6d4b0d3bac074ffd1bc748216dcfbc3a56e0080e..0fe278d731ccd3e639f294506e02eafeb0743a7d 100644 (file)
 #ifndef DAEMON_CONFIG_H
 #define DAEMON_CONFIG_H 1
 
+/*
+ * parse result values
+ */
+
+extern char **listen_addresses;
+extern size_t listen_addresses_num;
+
+/*
+ * daemon_parse_config:
+ * Parse the specified configuration file.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value when loading the configuration failed because of errors
+ *    in the daemon or libsysdb
+ *  - a positive value on parser errors
+ */
 int
 daemon_parse_config(const char *filename);
 
diff --git a/src/include/frontend/sock.h b/src/include/frontend/sock.h
new file mode 100644 (file)
index 0000000..6ab8cca
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SysDB - src/include/frontend/sock.h
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDB_FRONTEND_SOCK_H
+#define SDB_FRONTEND_SOCK_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* manage a front-end listener loop */
+typedef struct {
+       _Bool do_loop;
+} sdb_fe_loop_t;
+#define SDB_FE_LOOP_INIT { 1 }
+
+/*
+ * sdb_fe_socket_t:
+ * A front-end socket accepting connections from clients.
+ */
+typedef struct sdb_fe_socket sdb_fe_socket_t;
+
+/*
+ * sdb_fe_sock_create:
+ * Create a socket object.
+ *
+ * Returns:
+ *  - a socket object on success
+ *  - NULL else
+ */
+sdb_fe_socket_t *
+sdb_fe_sock_create(void);
+
+/*
+ * sdb_fe_sock_destroy:
+ * Shut down all listeners and destroy a socket object.
+ */
+void
+sdb_fe_sock_destroy(sdb_fe_socket_t *sock);
+
+/*
+ * sdb_fe_sock_add_listener:
+ * Tell the specified socket to listen on the specified 'address'.
+ * The address has to be specified as <type>:<address> where the following
+ * types are currently supported:
+ *
+ *  - unix: listen on a UNIX socket
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address);
+
+/*
+ * sdb_fe_sock_listen_and_serve:
+ * Listen on the specified socket and serve client requests. The loop
+ * terminates on error or when the loop condition turns to false. All
+ * listening sockets will be closed at that time.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! SDB_FRONTEND_SOCK_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/include/utils/channel.h b/src/include/utils/channel.h
new file mode 100644 (file)
index 0000000..49d37fd
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SysDB - src/include/utils/channel.h
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDB_UTILS_CHANNEL_H
+#define SDB_UTILS_CHANNEL_H 1
+
+#include "core/object.h"
+
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A channel is an asynchronous I/O multiplexer supporting multiple parallel
+ * readers and writers. A channel may be buffered (depending on its 'size'
+ * attribute). Writing fails unless buffer space is available and reading
+ * fails if no data is available.
+ */
+
+struct sdb_channel;
+typedef struct sdb_channel sdb_channel_t;
+
+/*
+ * sdb_channel_create:
+ * Create a new channel for elements of size 'elem_size'. At most, 'size'
+ * elements can be buffered in the channel (default: 1).
+ *
+ * Returns:
+ *  - a channel object on success
+ *  - a negative value else
+ */
+sdb_channel_t *
+sdb_channel_create(size_t size, size_t elem_size);
+
+/*
+ * sdb_channel_destroy:
+ * Removing all pending data and destroy the specified channel freeing its
+ * memory.
+ */
+void
+sdb_channel_destroy(sdb_channel_t *chan);
+
+/*
+ * sdb_channel_write:
+ * Write an element to a channel. The memory pointed to by 'data' is copied to
+ * the buffer based on the channel's element size.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_channel_write(sdb_channel_t *chan, const void *data);
+
+/*
+ * sdb_channel_read:
+ * Read an element from a channel. The element is copied to the location
+ * pointed to by 'data' which needs to be large enough to store a whole
+ * element based on the channel's element size.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_channel_read(sdb_channel_t *chan, void *data);
+
+/*
+ * sdb_channel_select:
+ * Wait for a channel to become "ready" for I/O. A channel is considered ready
+ * if it is possible to perform the corresponding I/O operation successfully
+ * *in some thread*. In case 'wantread' or 'read_data' is non-NULL, wait for
+ * data to be available in the channel for reading. In case 'wantwrite' or
+ * 'write_data' is non-NULL, wait for buffer space to be available for writing
+ * to the channel. If non-NULL, the value pointed to by the 'want...'
+ * arguments will be "true" iff the respective operation is ready. If the
+ * '..._data' arguments are non-NULL, the respective operation is executed
+ * atomically once the channel is ready for it. If 'timeout' is specified, the
+ * operation will time out with an error after the specified time has passed.
+ */
+int
+sdb_channel_select(sdb_channel_t *chan, int *wantread, void *read_data,
+               int *wantwrite, void *write_data, const struct timespec *timeout);
+
+/* sdb_channel_shutdown:
+ * Initiate a shutdown of the channel. Any subsequent writes will fail. Read
+ * operations will still be possible until the channel buffer is empty and
+ * then fail as well. Failing operations set errno to EBADF.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_channel_shutdown(sdb_channel_t *chan);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! SDB_UTILS_CHANNEL_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index 4bf9d605ad74e8d46e28c262a8c92470acf6dc47..2742139f17db051d12b36c279889ff0c0bbfb48f 100644 (file)
@@ -41,6 +41,7 @@ struct sdb_llist_iter;
 typedef struct sdb_llist_iter sdb_llist_iter_t;
 
 typedef int (*sdb_llist_cmp_cb)(const sdb_object_t *, const sdb_object_t *);
+typedef int (*sdb_llist_lookup_cb)(const sdb_object_t *, const void *user_data);
 
 /*
  * sdb_llist_create, sdb_llist_destroy:
@@ -120,9 +121,9 @@ sdb_llist_insert_sorted(sdb_llist_t *list,
 
 /*
  * sdb_llist_search:
- * Search for a 'key' in the given 'list'. The function will return the first
- * entry that matches the specified 'key'. For that purpose, the 'compare'
- * function is used. It should return 0 iff the two arguments compare equal.
+ * Search for a object in the given 'list'. The function will return the first
+ * entry for which the 'lookup' callback returns 0. The 'user_data' is passed
+ * on to the lookup function on each invocation.
  *
  * Returns:
  *  - a pointer to the first matching object
@@ -130,7 +131,7 @@ sdb_llist_insert_sorted(sdb_llist_t *list,
  */
 sdb_object_t *
 sdb_llist_search(sdb_llist_t *list,
-               const sdb_object_t *key, sdb_llist_cmp_cb);
+               sdb_llist_lookup_cb lookup, const void *user_data);
 
 /*
  * sdb_llist_search_by_name:
@@ -145,6 +146,20 @@ sdb_llist_search(sdb_llist_t *list,
 sdb_object_t *
 sdb_llist_search_by_name(sdb_llist_t *list, const char *key);
 
+/*
+ * sdb_llist_remove:
+ * Removes and returns the first matchin element of the list. The ref-count of
+ * the item will not be changed, that is, if the element will not be used any
+ * further, it should be de-referenced by the caller.
+ *
+ * Returns:
+ *  - a pointer to the first matching object
+ *  - NULL else
+ */
+sdb_object_t *
+sdb_llist_remove(sdb_llist_t *list,
+               sdb_llist_lookup_cb lookup, const void *user_data);
+
 /*
  * sdb_llist_shift:
  * Removes and returns the first element of the list. The ref-count of the
@@ -158,7 +173,8 @@ sdb_llist_search_by_name(sdb_llist_t *list, const char *key);
 sdb_object_t *
 sdb_llist_shift(sdb_llist_t *list);
 
-/* sdb_llist_get_iter, sdb_llist_iter_has_next, sdb_llist_iter_get_next:
+/*
+ * sdb_llist_get_iter, sdb_llist_iter_has_next, sdb_llist_iter_get_next:
  * Iterate through the list, element by element.
  *
  * sdb_llist_iter_get_next returns NULL if there is no next element.
@@ -173,6 +189,20 @@ sdb_llist_iter_has_next(sdb_llist_iter_t *iter);
 sdb_object_t *
 sdb_llist_iter_get_next(sdb_llist_iter_t *iter);
 
+/*
+ * sdb_llist_iter_remove_current:
+ * Remove the current object from the list, that is, the object which was
+ * returned by the last call to sdb_llist_iter_get_next().
+ *
+ * This operation is not safe if another iterator is in use at the same time.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_llist_iter_remove_current(sdb_llist_iter_t *iter);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
index 31435497f6d9b0a77673b76b74fd46d9966f44f6..9577a64f2956ce0a600ea9498adc485cf892f2c8 100644 (file)
@@ -94,6 +94,37 @@ sdb_strbuf_vsprintf(sdb_strbuf_t *strbuf, const char *fmt, va_list ap);
 ssize_t
 sdb_strbuf_sprintf(sdb_strbuf_t *strbuf, const char *fmt, ...);
 
+/*
+ * sdb_strbuf_memcpy, sdb_strbuf_memappend:
+ * Copy or a append a memory area to the buffer. These functions do not
+ * interpret any information in the data pointer (including \0 bytes).
+ *
+ * These functions may be used to handle arbitrary byte arrays. Mixing these
+ * function calls with any of the printf-style function works but will usually
+ * lead to arbitrary behavior.
+ *
+ * Returns:
+ *  - the number of bytes written
+ *  - a negative value on error
+ */
+ssize_t
+sdb_strbuf_memcpy(sdb_strbuf_t *strbuf, const void *data, size_t n);
+ssize_t
+sdb_strbuf_memappend(sdb_strbuf_t *strbuf, const void *data, size_t n);
+
+/*
+ * sdb_strbuf_read:
+ * Read from an open file-descriptor and append the data to the buffer. The
+ * function does not handle *any* read errors. This allows for more
+ * flexibility for the caller regarding the handling of EAGAIN or EWOULDBLOCK.
+ *
+ * Returns:
+ *  - the number of bytes read (zero on EOF)
+ *  - a negative value on error
+ */
+ssize_t
+sdb_strbuf_read(sdb_strbuf_t *strbuf, int fd, size_t n);
+
 /*
  * sdb_strbuf_chomp:
  * Remove all consecutive newline characters from the end of the string buffer
@@ -106,6 +137,13 @@ sdb_strbuf_sprintf(sdb_strbuf_t *strbuf, const char *fmt, ...);
 ssize_t
 sdb_strbuf_chomp(sdb_strbuf_t *strbuf);
 
+/*
+ * sdb_strbuf_skip:
+ * Removes the first 'n' bytes from the buffer.
+ */
+void
+sdb_strbuf_skip(sdb_strbuf_t *strbuf, size_t n);
+
 /*
  * sdb_strbuf_string:
  * Returns the content of the string buffer. The caller may not modify the
diff --git a/src/utils/channel.c b/src/utils/channel.c
new file mode 100644 (file)
index 0000000..a45247a
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * SysDB - src/utils/channel.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils/channel.h"
+
+#include <assert.h>
+
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <time.h>
+
+#include <pthread.h>
+
+/*
+ * private data types
+ */
+
+struct sdb_channel {
+       pthread_mutex_t lock;
+
+       /* signaling for select() operation */
+       pthread_cond_t cond;
+
+       /* maybe TODO: add support for 'nil' values using a boolean area */
+
+       void  *data;
+       size_t data_len;
+       size_t elem_size;
+
+       size_t head;
+       size_t tail;
+       _Bool full;
+
+       _Bool shutdown;
+};
+
+/*
+ * private helper functions
+ */
+
+#define NEXT_WRITE(chan) (((chan)->tail + 1) % (chan)->data_len)
+#define NEXT_READ(chan) (((chan)->head + 1) % (chan)->data_len)
+
+#define ELEM(chan, i) \
+       (void *)((char *)(chan)->data + (i) * (chan)->elem_size)
+#define TAIL(chan) ELEM(chan, (chan)->tail)
+#define HEAD(chan) ELEM(chan, (chan)->head)
+
+/* Insert a new element at the end; chan->lock must be held.
+ * Returns 0 if data has been written or if data may be written
+ * if 'data' is NULL. */
+static int
+channel_write(sdb_channel_t *chan, const void *data)
+{
+       assert(chan);
+
+       if (chan->full || chan->shutdown)
+               return -1;
+       else if (! data)
+               return 0;
+
+       memcpy(TAIL(chan), data, chan->elem_size);
+       chan->tail = NEXT_WRITE(chan);
+
+       chan->full = chan->tail == chan->head;
+       pthread_cond_broadcast(&chan->cond);
+       return 0;
+} /* channel_write */
+
+/* Retrieve the first element; chan->lock must be held.
+ * Returns 0 if data has been read or if data is available
+ * if 'data' is NULL. */
+static int
+channel_read(sdb_channel_t *chan, void *data)
+{
+       assert(chan);
+
+       if ((chan->head == chan->tail) && (! chan->full))
+               return -1;
+       else if (! data)
+               return 0;
+
+       memcpy(data, HEAD(chan), chan->elem_size);
+       chan->head = NEXT_READ(chan);
+
+       chan->full = 0;
+       pthread_cond_broadcast(&chan->cond);
+       return 0;
+} /* channel_read */
+
+/*
+ * public API
+ */
+
+sdb_channel_t *
+sdb_channel_create(size_t size, size_t elem_size)
+{
+       sdb_channel_t *chan;
+
+       if (! elem_size)
+               return NULL;
+       if (! size)
+               size = 1;
+
+       chan = calloc(1, sizeof(*chan));
+       if (! chan)
+               return NULL;
+
+       chan->data = calloc(size, elem_size);
+       if (! chan->data) {
+               sdb_channel_destroy(chan);
+               return NULL;
+       }
+
+       chan->data_len = size;
+       chan->elem_size = elem_size;
+
+       pthread_mutex_init(&chan->lock, /* attr = */ NULL);
+       pthread_cond_init(&chan->cond, /* attr = */ NULL);
+
+       chan->head = chan->tail = 0;
+       return chan;
+} /* sdb_channel_create */
+
+void
+sdb_channel_destroy(sdb_channel_t *chan)
+{
+       if (! chan)
+               return;
+
+       pthread_mutex_lock(&chan->lock);
+       free(chan->data);
+       chan->data = NULL;
+       chan->data_len = 0;
+
+       pthread_cond_destroy(&chan->cond);
+
+       pthread_mutex_unlock(&chan->lock);
+       pthread_mutex_destroy(&chan->lock);
+       free(chan);
+} /* sdb_channel_destroy */
+
+int
+sdb_channel_select(sdb_channel_t *chan, int *wantread, void *read_data,
+               int *wantwrite, void *write_data, const struct timespec *timeout)
+{
+       int status = 0;
+
+       if (! chan) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if ((! wantread) && (! read_data) && (! wantwrite) && (! write_data)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       pthread_mutex_lock(&chan->lock);
+       while (! status) {
+               int read_status, write_status;
+
+               read_status = channel_read(chan, read_data);
+               write_status = channel_write(chan, write_data);
+
+               if ((! read_status) || (! write_status)) {
+                       if (wantread)
+                               *wantread = read_status == 0;
+                       if (wantwrite)
+                               *wantwrite = write_status == 0;
+
+                       if (((wantread || read_data) && (! read_status))
+                                       || ((wantwrite || write_data) && (! write_status)))
+                               break;
+               }
+
+               if (chan->shutdown) {
+                       if (read_status)
+                               status = EBADF;
+                       break;
+               }
+
+               if (timeout) {
+                       struct timespec abstime;
+
+                       if (clock_gettime(CLOCK_REALTIME, &abstime)) {
+                               pthread_mutex_unlock(&chan->lock);
+                               return -1;
+                       }
+
+                       abstime.tv_sec += timeout->tv_sec;
+                       abstime.tv_nsec += timeout->tv_nsec;
+
+                       if (abstime.tv_nsec > 1000000000) {
+                               abstime.tv_nsec -= 1000000000;
+                               abstime.tv_sec += 1;
+                       }
+
+                       status = pthread_cond_timedwait(&chan->cond, &chan->lock,
+                                       &abstime);
+               }
+               else
+                       status = pthread_cond_wait(&chan->cond, &chan->lock);
+       }
+       pthread_mutex_unlock(&chan->lock);
+
+       if (status) {
+               errno = status;
+               return -1;
+       }
+       return 0;
+} /* sdb_channel_select */
+
+int
+sdb_channel_write(sdb_channel_t *chan, const void *data)
+{
+       int status;
+
+       if ((! chan) || (! data))
+               return -1;
+
+       pthread_mutex_lock(&chan->lock);
+       status = channel_write(chan, data);
+       pthread_mutex_unlock(&chan->lock);
+       return status;
+} /* sdb_channel_write */
+
+int
+sdb_channel_read(sdb_channel_t *chan, void *data)
+{
+       int status;
+
+       if ((! chan) || (! data))
+               return -1;
+
+       pthread_mutex_lock(&chan->lock);
+       status = channel_read(chan, data);
+       pthread_mutex_unlock(&chan->lock);
+       return status;
+} /* sdb_channel_read */
+
+int
+sdb_channel_shutdown(sdb_channel_t *chan)
+{
+       if (! chan)
+               return -1;
+       chan->shutdown = 1;
+       return 0;
+} /* sdb_channel_shutdown */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index dfc8fb99d18ecfeb6e38cc0a7339426854691704..0a4665d09296c2387bcf1f369f81f5e751c20fa4 100644 (file)
@@ -115,6 +115,20 @@ sdb_llist_insert_after(sdb_llist_t *list, sdb_llist_elem_t *elem,
        return 0;
 } /* sdb_llist_insert_after */
 
+static sdb_llist_elem_t *
+llist_search(sdb_llist_t *list,
+               sdb_llist_lookup_cb lookup, const void *user_data)
+{
+       sdb_llist_elem_t *elem;
+
+       assert(list && lookup);
+
+       for (elem = list->head; elem; elem = elem->next)
+               if (! lookup(elem->obj, user_data))
+                       break;
+       return elem;
+} /* llist_search */
+
 static sdb_object_t *
 sdb_llist_remove_elem(sdb_llist_t *list, sdb_llist_elem_t *elem)
 {
@@ -292,19 +306,15 @@ sdb_llist_insert_sorted(sdb_llist_t *list,
 
 sdb_object_t *
 sdb_llist_search(sdb_llist_t *list,
-               const sdb_object_t *key, sdb_llist_cmp_cb compare)
+               sdb_llist_lookup_cb lookup, const void *user_data)
 {
        sdb_llist_elem_t *elem;
 
-       if ((! list) || (! compare))
+       if ((! list) || (! lookup))
                return NULL;
 
        pthread_rwlock_rdlock(&list->lock);
-
-       for (elem = list->head; elem; elem = elem->next)
-               if (! compare(elem->obj, key))
-                       break;
-
+       elem = llist_search(list, lookup, user_data);
        pthread_rwlock_unlock(&list->lock);
 
        if (elem)
@@ -333,6 +343,25 @@ sdb_llist_search_by_name(sdb_llist_t *list, const char *key)
        return NULL;
 } /* sdb_llist_search_by_name */
 
+sdb_object_t *
+sdb_llist_remove(sdb_llist_t *list,
+               sdb_llist_lookup_cb lookup, const void *user_data)
+{
+       sdb_llist_elem_t *elem;
+       sdb_object_t *obj = NULL;
+
+       if ((! list) || (! lookup))
+               return NULL;
+
+       pthread_rwlock_wrlock(&list->lock);
+       elem = llist_search(list, lookup, user_data);
+       if (elem)
+               obj = sdb_llist_remove_elem(list, elem);
+       pthread_rwlock_unlock(&list->lock);
+
+       return obj;
+} /* sdb_llist_remove */
+
 sdb_object_t *
 sdb_llist_shift(sdb_llist_t *list)
 {
@@ -405,5 +434,29 @@ sdb_llist_iter_get_next(sdb_llist_iter_t *iter)
        return obj;
 } /* sdb_llist_iter_get_next */
 
+int
+sdb_llist_iter_remove_current(sdb_llist_iter_t *iter)
+{
+       sdb_llist_elem_t *elem;
+
+       if ((! iter) || (! iter->list))
+               return -1;
+
+       pthread_rwlock_wrlock(&iter->list->lock);
+
+       if (! iter->elem) /* reached end of list */
+               elem = iter->list->tail;
+       else
+               elem = iter->elem->prev;
+       if (elem)
+               sdb_llist_remove_elem(iter->list, elem);
+
+       pthread_rwlock_unlock(&iter->list->lock);
+
+       if (! elem)
+               return -1;
+       return 0;
+} /* sdb_llist_iter_remove */
+
 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */
 
index 7c24b3a8a75733382274d3f34f4ea46a46283f80..4d8fc8b91ba234df10edd8d15d552169bcbf9930 100644 (file)
@@ -32,6 +32,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <string.h>
+
+#include <unistd.h>
 
 /*
  * private data structures
@@ -188,6 +191,67 @@ sdb_strbuf_sprintf(sdb_strbuf_t *strbuf, const char *fmt, ...)
        return status;
 } /* sdb_strbuf_sprintf */
 
+ssize_t
+sdb_strbuf_memappend(sdb_strbuf_t *strbuf, const void *data, size_t n)
+{
+       if ((! strbuf) || (! data))
+               return -1;
+
+       assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0'));
+
+       if (strbuf->pos + n + 1 >= strbuf->size) {
+               size_t newsize = strbuf->size * 2;
+
+               if (! newsize)
+                       newsize = 64;
+               while (strbuf->pos + n + 1 >= newsize)
+                       newsize *= 2;
+
+               if (strbuf_resize(strbuf, newsize))
+                       return -1;
+       }
+
+       assert(strbuf->size && strbuf->string);
+       assert(strbuf->pos < strbuf->size);
+
+       memcpy((void *)(strbuf->string + strbuf->pos), data, n);
+       strbuf->pos += n;
+       strbuf->string[strbuf->pos] = '\0';
+
+       return (ssize_t)n;
+} /* sdb_strbuf_memappend */
+
+ssize_t
+sdb_strbuf_memcpy(sdb_strbuf_t *strbuf, const void *data, size_t n)
+{
+       if ((! strbuf) || (! data))
+               return -1;
+
+       if (strbuf->size) {
+               strbuf->string[0] = '\0';
+               strbuf->pos = 0;
+       }
+
+       return sdb_strbuf_memappend(strbuf, data, n);
+} /* sdb_strbuf_memcpy */
+
+ssize_t
+sdb_strbuf_read(sdb_strbuf_t *strbuf, int fd, size_t n)
+{
+       ssize_t ret;
+
+       if (! strbuf)
+               return -1;
+
+       if (strbuf_resize(strbuf, strbuf->pos + n + 1))
+               return -1;
+
+       ret = read(fd, strbuf->string + strbuf->pos, n);
+       if (ret > 0)
+               strbuf->pos += (size_t)ret;
+       return ret;
+} /* sdb_strbuf_read */
+
 ssize_t
 sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
 {
@@ -209,6 +273,24 @@ sdb_strbuf_chomp(sdb_strbuf_t *strbuf)
        return ret;
 } /* sdb_strbuf_chomp */
 
+void
+sdb_strbuf_skip(sdb_strbuf_t *strbuf, size_t n)
+{
+       if ((! strbuf) || (! n))
+               return;
+
+       if (n >= strbuf->pos) {
+               strbuf->string[0] = '\0';
+               strbuf->pos = 0;
+               return;
+       }
+
+       assert(n < strbuf->pos);
+       memmove(strbuf->string, strbuf->string + n, strbuf->pos - n);
+       strbuf->pos -= n;
+       strbuf->string[strbuf->pos] = '\0';
+} /* sdb_strbuf_skip */
+
 const char *
 sdb_strbuf_string(sdb_strbuf_t *strbuf)
 {
index e4cc8036641c6ac221dd2e80318812077f0681b7..1e4419dd7fcf053fae573e60278ce82520397ece 100644 (file)
@@ -6,6 +6,7 @@ check_PROGRAMS = libsysdb_test libsysdb_net_test
 
 libsysdb_test_SOURCES = \
                libsysdb_test.c libsysdb_test.h \
+               utils/channel_test.c \
                utils/dbi_test.c \
                utils/llist_test.c \
                utils/strbuf_test.c
index 3241aba495796d1b24261f317d8dbf271e25bcf4..7ce41247c679a8b9a2f636d5e9def467d8c1e311 100644 (file)
@@ -37,8 +37,9 @@ main(void)
        size_t i;
 
        suite_creator_t creators[] = {
-               { util_llist_suite, NULL },
+               { util_channel_suite, NULL },
                { util_dbi_suite, NULL },
+               { util_llist_suite, NULL },
                { util_strbuf_suite, NULL },
        };
 
index 9e4be80d0a22b84e2210891cf9cfe517dfdfda40..da57246fe8f7e5fad93db503dea004b26fc5baf0 100644 (file)
@@ -59,6 +59,10 @@ typedef struct {
  * test suites
  */
 
+/* t/utils/channel_test */
+Suite *
+util_channel_suite(void);
+
 /* t/utils/dbi_test */
 Suite *
 util_dbi_suite(void);
diff --git a/t/utils/channel_test.c b/t/utils/channel_test.c
new file mode 100644 (file)
index 0000000..02ed172
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * SysDB - t/utils/channel_test.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils/channel.h"
+#include "libsysdb_test.h"
+
+#include <check.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+static struct {
+       int data;
+       int expected_write;
+       int expected_read;
+} golden_data_int[] = {
+       { 5,       0, 0 },
+       { 15,      0, 0 },
+       { -3,      0, 0 },
+       { INT_MAX, 0, 0 },
+       { 27,      0, 0 },
+       { 42,      0, 0 },
+       { 6,       0, 0 },
+       { 2854,    0, 0 },
+       { 10562,   0, 0 },
+       { 0,       0, 0 },
+
+       /* exceeding buffer size */
+       { 20, -1, -1 },
+       { 42, -1, -1 },
+};
+
+static struct {
+       char *data;
+       int   expected_write;
+       int   expected_read;
+} golden_data_string[] = {
+       { "c",      0, 0 },
+       { "",       0, 0 },
+       { "abc",    0, 0 },
+       { "foobar", 0, 0 },
+       { "qux",    0, 0 },
+       { "a b c",  0, 0 },
+       { "123",    0, 0 },
+       { "xyz",    0, 0 },
+       { "b",      0, 0 },
+       { "a",      0, 0 },
+
+       /* exceeding buffer size */
+       { "err1", -1, -1 },
+       { "err2", -1, -1 },
+};
+
+static sdb_channel_t *chan;
+
+static void
+setup_int(void)
+{
+       chan = sdb_channel_create(10, sizeof(int));
+       fail_unless(chan != NULL,
+                       "sdb_channel_create(10, sizeof(int)) = NULL; "
+                       "expected list object");
+} /* setup_int */
+
+static void
+setup_string(void)
+{
+       chan = sdb_channel_create(10, sizeof(char *));
+       fail_unless(chan != NULL,
+                       "sdb_chan_create(10, sizeof(char *))) = NULL; "
+                       "expected channel object");
+} /* setup_string */
+
+static void
+teardown(void)
+{
+       sdb_channel_destroy(chan);
+       chan = NULL;
+} /* teardown */
+
+START_TEST(test_create)
+{
+       chan = sdb_channel_create(0, 0);
+       fail_unless(chan == NULL,
+                       "sdb_channel_create(0, 0) = %p; expected: NULL", chan);
+
+       chan = sdb_channel_create(0, 1);
+       fail_unless(chan != NULL,
+                       "sdb_channel_create(0, 1) = NULL; expected: channel object");
+       sdb_channel_destroy(chan);
+
+       chan = sdb_channel_create(42, 23);
+       fail_unless(chan != NULL,
+                       "sdb_channel_create(32, 23) = NULL; expected: channel object");
+       sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_write_read)
+{
+       uint32_t data;
+       int check;
+
+       chan = sdb_channel_create(0, 1);
+       fail_unless(chan != NULL,
+                       "sdb_channel_create(0, 0) = NULL; expected: channel object");
+
+       data = 0x00ffff00;
+       check = sdb_channel_write(chan, &data);
+       fail_unless(!check, "sdb_channel_write() = %i; expected: 0", check);
+       check = sdb_channel_write(chan, &data);
+       fail_unless(check, "sdb_channel_write() = 0; expected: <0");
+
+       data = 0xffffffff;
+       check = sdb_channel_read(chan, &data);
+       /* result depends on endianess */
+       fail_unless((data == 0xffffff00) || (data == 0x00ffffff),
+                       "sdb_channel_read() returned data %x; "
+                       "expected: 0xffffff00 || 0x00ffffff", data);
+
+       sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_select)
+{
+       struct timespec ts = { 0, 10 };
+       int check;
+       int data;
+
+       chan = sdb_channel_create(0, 1);
+
+       errno = 0;
+       check = sdb_channel_select(chan, &data, NULL, NULL, NULL, &ts);
+       fail_unless(check < ETIMEDOUT,
+                       "sdb_channel_select() = %i; expected: <0", check);
+       fail_unless(errno == ETIMEDOUT,
+                       "sdb_channel_select() set errno to %i; expected: %i (ETIMEDOUT)",
+                       errno, ETIMEDOUT);
+
+       check = sdb_channel_select(chan, NULL, NULL, &data, NULL, NULL);
+       fail_unless(! check, "sdb_channel_select() = %i; expected: 0", check);
+       check = sdb_channel_select(chan, NULL, NULL, &data, NULL, &ts);
+       fail_unless(! check, "sdb_channel_select() = %i; expected: 0", check);
+
+       sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_write_int)
+{
+       size_t i;
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+               int data = golden_data_int[i].data;
+               int expected = golden_data_int[i].expected_write;
+
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_write(chan, %i) = %i; expected: %i",
+                               data, check, expected);
+       }
+}
+END_TEST
+
+START_TEST(test_read_int)
+{
+       size_t i;
+
+       /* populate */
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+               int data = golden_data_int[i].data;
+               int expected = golden_data_int[i].expected_write;
+
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_write(chan, %i) = %i; expected: %i",
+                               data, check, expected);
+       }
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+               int data = (int)i;
+               int expected = golden_data_int[i].expected_read;
+
+               int check = sdb_channel_read(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_read(chan, %i) = %i; expected: %i",
+                               data, check, expected);
+               if (check) {
+                       fail_unless(data == (int)i,
+                                       "sdb_channel_read() modified data to '%i'; "
+                                       "expected: no modification", data);
+               }
+               else {
+                       fail_unless(data == golden_data_int[i].data,
+                                       "sdb_channel_read() returned data %i; expected: %i",
+                                       data, golden_data_int[i].data);
+               }
+       }
+}
+END_TEST
+
+START_TEST(test_write_read_int)
+{
+       size_t i;
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+               int data = golden_data_int[i].data;
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == 0,
+                               "sdb_channel_write(chan, %i) = %i; expected: 0",
+                               data, check);
+
+               data = (int)i;
+               check = sdb_channel_read(chan, &data);
+               fail_unless(check == 0,
+                               "sdb_channel_read(chan, %i) = %i; expected: 0",
+                               data, check);
+               if (check) {
+                       fail_unless(data == (int)i,
+                                       "sdb_channel_read() modified data to '%i'; "
+                                       "expected: no modification", data);
+               }
+               else {
+                       fail_unless(data == golden_data_int[i].data,
+                                       "sdb_channel_read() returned data %i; expected: %i",
+                                       data, golden_data_int[i].data);
+               }
+       }
+}
+END_TEST
+
+START_TEST(test_write_string)
+{
+       size_t i;
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+               char *data = golden_data_string[i].data;
+               int expected = golden_data_string[i].expected_write;
+
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_write(chan, '%s') = %i; expected: %i",
+                               data, check, expected);
+       }
+}
+END_TEST
+
+START_TEST(test_read_string)
+{
+       size_t i;
+
+       /* populate */
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+               char *data = golden_data_string[i].data;
+               int expected = golden_data_string[i].expected_write;
+
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_write(chan, '%s') = %i; expected: %i",
+                               data, check, expected);
+       }
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+               char *data = NULL;
+               int expected = golden_data_string[i].expected_read;
+
+               int check = sdb_channel_read(chan, &data);
+               fail_unless(check == expected,
+                               "sdb_channel_read(chan, '') = %i; expected: %i",
+                               check, expected);
+               if (check) {
+                       fail_unless(data == NULL,
+                                       "sdb_channel_read() modified data to '%s'; "
+                                       "expected: no modification", data);
+               }
+               else {
+                       fail_unless(data != NULL,
+                                       "sdb_channel_read() did not return any data");
+                       fail_unless(!strcmp(data, golden_data_string[i].data),
+                                       "sdb_channel_read() returned data '%s'; expected: '%s'",
+                                       data, golden_data_string[i].data);
+               }
+       }
+}
+END_TEST
+
+START_TEST(test_write_read_string)
+{
+       size_t i;
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+               char *data = golden_data_string[i].data;
+               int check = sdb_channel_write(chan, &data);
+               fail_unless(check == 0,
+                               "sdb_channel_write(chan, '%s') = %i; expected: 0",
+                               data, check);
+
+               data = NULL;
+               check = sdb_channel_read(chan, &data);
+               fail_unless(check == 0,
+                               "sdb_channel_read(chan, '') = %i; expected: 0", check);
+               if (check) {
+                       fail_unless(data == NULL,
+                                       "sdb_channel_read() modified data to '%s'; "
+                                       "expected: no modifications", data);
+               }
+               else {
+                       fail_unless(data != NULL,
+                                       "sdb_channel_read() did not return any data");
+                       fail_unless(!strcmp(data, golden_data_string[i].data),
+                                       "sdb_channel_read() returned data '%s'; expected: '%s'",
+                                       data, golden_data_string[i].data);
+               }
+       }
+}
+END_TEST
+
+Suite *
+util_channel_suite(void)
+{
+       Suite *s = suite_create("utils::channel");
+       TCase *tc;
+
+       tc = tcase_create("core");
+       tcase_add_test(tc, test_create);
+       tcase_add_test(tc, test_write_read);
+       tcase_add_test(tc, test_select);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("integer");
+       tcase_add_checked_fixture(tc, setup_int, teardown);
+       tcase_add_test(tc, test_write_int);
+       tcase_add_test(tc, test_read_int);
+       tcase_add_test(tc, test_write_read_int);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("string");
+       tcase_add_checked_fixture(tc, setup_string, teardown);
+       tcase_add_test(tc, test_write_string);
+       tcase_add_test(tc, test_read_string);
+       tcase_add_test(tc, test_write_read_string);
+       suite_add_tcase(s, tc);
+
+       return s;
+} /* util_llist_suite */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index 638d2fbdbeef1affe64e15753a7e6ca08cd7633e..ede8200a42a325d3f22707eb001febdc8ac9b51e 100644 (file)
@@ -233,6 +233,41 @@ START_TEST(test_sdb_llist_iter)
 }
 END_TEST
 
+START_TEST(test_sdb_llist_iter_remove)
+{
+       sdb_llist_iter_t *iter;
+       sdb_object_t *check;
+       size_t i;
+
+       populate();
+
+       iter = sdb_llist_get_iter(list);
+       fail_unless(iter != NULL,
+                       "sdb_llist_get_iter() did not return an iterator");
+
+       i = 0;
+       while (sdb_llist_iter_has_next(iter)) {
+               check = sdb_llist_iter_get_next(iter);
+               fail_unless(check == &golden_data[i],
+                               "sdb_llist_iter_get_next() = %p; expected: %p",
+                               check, &golden_data[i]);
+
+               sdb_llist_iter_remove_current(iter);
+               ++i;
+       }
+       sdb_llist_iter_destroy(iter);
+
+       fail_unless(i == (size_t)SDB_STATIC_ARRAY_LEN(golden_data),
+                       "iterated for %zu steps; expected: %i",
+                       i, SDB_STATIC_ARRAY_LEN(golden_data));
+
+       /* all elements should be removed */
+       check = sdb_llist_shift(list);
+       fail_unless(check == NULL,
+                       "sdb_llist_shift() = %p; expected: NULL", check);
+}
+END_TEST
+
 Suite *
 util_llist_suite(void)
 {
@@ -249,6 +284,7 @@ util_llist_suite(void)
        tcase_add_test(tc, test_sdb_llist_search);
        tcase_add_test(tc, test_sdb_llist_shift);
        tcase_add_test(tc, test_sdb_llist_iter);
+       tcase_add_test(tc, test_sdb_llist_iter_remove);
        suite_add_tcase(s, tc);
 
        return s;
index f007cbe20425bf1562522a4e2e64fbc1dba7258e..9e96b984a2a764d5d322fc60e1065e2ee9cb2b6b 100644 (file)
@@ -55,6 +55,36 @@ teardown(void)
  * tests
  */
 
+START_TEST(test_empty)
+{
+       sdb_strbuf_t *b = NULL;
+       va_list ap;
+
+       /* check that methods don't crash */
+       sdb_strbuf_destroy(b);
+       sdb_strbuf_skip(b, 0);
+       sdb_strbuf_skip(b, 10);
+
+       /* check that methods return an error */
+       fail_unless(sdb_strbuf_vappend(b, "test", ap) < 0,
+                       "sdb_strbuf_vappend(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_append(b, "test") < 0,
+                       "sdb_strbuf_append(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_vsprintf(b, "test", ap) < 0,
+                       "sdb_strbuf_vsprintf(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_sprintf(b, "test") < 0,
+                       "sdb_strbuf_sprintf(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_memcpy(b, "test", 4) < 0,
+                       "sdb_strbuf_memcpy(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_memappend(b, "test", 4) < 0,
+                       "sdb_strbuf_memappend(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_read(b, 0, 32) < 0,
+                       "sdb_strbuf_read(NULL) didn't report failure");
+       fail_unless(sdb_strbuf_chomp(b) < 0,
+                       "sdb_strbuf_chomp(NULL) didn't report failure");
+}
+END_TEST
+
 START_TEST(test_sdb_strbuf_create)
 {
        sdb_strbuf_t *s;
@@ -177,6 +207,95 @@ START_TEST(test_sdb_strbuf_sprintf)
 }
 END_TEST
 
+static struct {
+       const char *input;
+       size_t size;
+} mem_golden_data[] = {
+       { "abc\0\x10\x42", 6 },
+       { "\0\1\2\3\4", 5 },
+       { "\n\n\0\n\n", 5 },
+       { "", 0 },
+};
+
+START_TEST(test_sdb_strbuf_memcpy)
+{
+       size_t i;
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(mem_golden_data); ++i) {
+               ssize_t n;
+               const char *check;
+
+               n = sdb_strbuf_memcpy(buf, mem_golden_data[i].input,
+                               mem_golden_data[i].size);
+               fail_unless(n >= 0,
+                               "sdb_strbuf_memcpy() = %zi; expected: >=0", n);
+               fail_unless((size_t)n == mem_golden_data[i].size,
+                               "sdb_strbuf_memcpy() = %zi; expected: %zu",
+                               n, mem_golden_data[i].size);
+
+               n = (ssize_t)sdb_strbuf_len(buf);
+               fail_unless((size_t)n == mem_golden_data[i].size,
+                               "sdb_strbuf_len() = %zu (after memcpy); expected: %zu",
+                               n, mem_golden_data[i].size);
+
+               check = sdb_strbuf_string(buf);
+               fail_unless(check != NULL,
+                               "sdb_strbuf_string() = NULL (after memcpy); expected: data");
+               fail_unless(check[mem_golden_data[i].size] == '\0',
+                               "sdb_strbuf_memcpy() did not nil-terminate the data");
+               fail_unless(!memcmp(check, mem_golden_data[i].input,
+                                       mem_golden_data[i].size),
+                               "sdb_strbuf_memcpy() did not set the buffer correctly");
+       }
+}
+END_TEST
+
+START_TEST(test_sdb_strbuf_memappend)
+{
+       size_t i;
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(mem_golden_data); ++i) {
+               ssize_t n;
+               const char *check;
+
+               size_t total, j;
+
+               n = sdb_strbuf_memappend(buf, mem_golden_data[i].input,
+                               mem_golden_data[i].size);
+               fail_unless(n >= 0,
+                               "sdb_strbuf_memappend() = %zi; expected: >=0", n);
+               fail_unless((size_t)n == mem_golden_data[i].size,
+                               "sdb_strbuf_memappend() = %zi; expected: %zu",
+                               n, mem_golden_data[i].size);
+
+               check = sdb_strbuf_string(buf);
+               fail_unless(check != NULL,
+                               "sdb_strbuf_string() = NULL (after memappend); "
+                               "expected: data");
+
+               n = (ssize_t)sdb_strbuf_len(buf);
+               total = 0;
+               for (j = 0; j <= i; ++j) {
+                       fail_unless(total + mem_golden_data[j].size <= (size_t)n,
+                                       "sdb_strbuf_len() = %zu (after memappend); "
+                                       "expected: >=%zu", n, total + mem_golden_data[j].size);
+
+                       fail_unless(!memcmp(check + total, mem_golden_data[j].input,
+                                               mem_golden_data[j].size),
+                                       "sdb_strbuf_memappend() did not "
+                                       "set the buffer correctly");
+                       total += mem_golden_data[j].size;
+               }
+               fail_unless((size_t)n == total,
+                               "sdb_strbuf_len() = %zu (after memappend); expected: %zu",
+                               n, total);
+
+               fail_unless(check[total] == '\0',
+                               "sdb_strbuf_memappend() did not nil-terminate the data");
+       }
+}
+END_TEST
+
 static struct {
        const char *input;
        ssize_t expected;
@@ -215,6 +334,53 @@ START_TEST(test_sdb_strbuf_chomp)
 }
 END_TEST
 
+/* input is "1234567890" */
+static struct {
+       size_t n;
+       const char *expected;
+       size_t expected_len;
+} skip_golden_data[] = {
+       { 0, "1234567890", 10 },
+       { 1, "234567890", 9 },
+       { 2, "34567890", 8 },
+       { 9, "0", 1 },
+       { 10, "", 0 },
+       { 11, "", 0 },
+       { 100, "", 0 },
+};
+
+START_TEST(test_sdb_strbuf_skip)
+{
+       const char *input = "1234567890";
+       size_t i;
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(skip_golden_data); ++i) {
+               const char *check;
+               size_t n;
+
+               sdb_strbuf_sprintf(buf, input);
+               sdb_strbuf_skip(buf, skip_golden_data[i].n);
+
+               n = sdb_strbuf_len(buf);
+               fail_unless(n == skip_golden_data[i].expected_len,
+                               "sdb_strbuf_len() = %zu (after skip); expected: %zu",
+                               n, skip_golden_data[i].expected_len);
+
+               check = sdb_strbuf_string(buf);
+               fail_unless(!!check,
+                               "sdb_strbuf_string() = NULL (after skip); expected: string");
+
+               fail_unless(check[n] == '\0',
+                               "sdb_strbuf_skip() did not nil-terminate the string");
+
+               fail_unless(! strcmp(skip_golden_data[i].expected, check),
+                               "sdb_strbuf_skip('%s', %zu) did not skip correctly; "
+                               "got string '%s'; expected: '%s'", input,
+                               skip_golden_data[i].n, check, skip_golden_data[i].expected);
+       }
+}
+END_TEST
+
 static struct {
        const char *input;
        const char *expected;
@@ -273,12 +439,19 @@ util_strbuf_suite(void)
        Suite *s = suite_create("utils::strbuf");
        TCase *tc;
 
+       tc = tcase_create("empty");
+       tcase_add_test(tc, test_empty);
+       suite_add_tcase(s, tc);
+
        tc = tcase_create("core");
        tcase_add_checked_fixture(tc, setup, teardown);
        tcase_add_test(tc, test_sdb_strbuf_create);
        tcase_add_test(tc, test_sdb_strbuf_append);
        tcase_add_test(tc, test_sdb_strbuf_sprintf);
+       tcase_add_test(tc, test_sdb_strbuf_memcpy);
+       tcase_add_test(tc, test_sdb_strbuf_memappend);
        tcase_add_test(tc, test_sdb_strbuf_chomp);
+       tcase_add_test(tc, test_sdb_strbuf_skip);
        tcase_add_test(tc, test_sdb_strbuf_string);
        tcase_add_test(tc, test_sdb_strbuf_len);
        suite_add_tcase(s, tc);