From: Sebastian Harl Date: Sun, 1 Dec 2013 16:34:48 +0000 (+0100) Subject: Merged branch 'master' of git://git.tokkee.org/sysdb. X-Git-Tag: sysdb-0.1.0~336 X-Git-Url: https://git.tokkee.org/?p=sysdb.git;a=commitdiff_plain;h=2e2e989712ec5991526b3aaeec011440adb5bf7e;hp=102de67cdf77fa5819f61ba43111429d8d9a6c5b Merged branch 'master' of git://git.tokkee.org/sysdb. Conflicts: t/Makefile.am t/libsysdb_test.c t/libsysdb_test.h --- diff --git a/configure.ac b/configure.ac index a4f2185..dde7727 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/src/Makefile.am b/src/Makefile.am index 3cdb58e..5a15f9b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 @@ -10,11 +11,23 @@ BUILT_SOURCES = include/sysdb.h pkginclude_HEADERS = include/sysdb.h pkgcoreincludedir = $(pkgincludedir)/core pkgcoreinclude_HEADERS = \ + include/core/data.h \ + include/core/error.h \ include/core/object.h \ - include/core/plugin.h + include/core/plugin.h \ + include/core/store.h \ + include/core/time.h +pkgfeincludedir = $(pkgincludedir)/frontend +pkgfeinclude_HEADERS = \ + include/frontend/connection.h \ + include/frontend/sock.h pkgutilsincludedir = $(pkgincludedir)/utils pkgutilsinclude_HEADERS = \ - include/utils/llist.h + include/utils/channel.h \ + include/utils/dbi.h \ + include/utils/llist.h \ + include/utils/strbuf.h \ + include/utils/unixsock.h lib_LTLIBRARIES = libsysdb.la @@ -25,6 +38,12 @@ libsysdb_la_SOURCES = \ core/store.c include/core/store.h \ include/core/data.h \ core/error.c include/core/error.h \ + frontend/connection.c include/frontend/connection.h \ + include/frontend/connection-private.h \ + frontend/sock.c include/frontend/sock.h \ + frontend/session.c \ + frontend/query.c \ + 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 \ diff --git a/src/core/plugin.c b/src/core/plugin.c index ca79284..09d5cce 100644 --- a/src/core/plugin.c +++ b/src/core/plugin.c @@ -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); diff --git a/src/core/store.c b/src/core/store.c index 43a7a6b..587c527 100644 --- a/src/core/store.c +++ b/src/core/store.c @@ -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)); @@ -443,11 +446,11 @@ sdb_store_service(const char *hostname, const char *name, /* TODO: actually support hierarchical data */ int -sdb_store_dump(FILE *fh) +sdb_store_tojson(sdb_strbuf_t *buf) { sdb_llist_iter_t *host_iter; - if (! fh) + if (! buf) return -1; pthread_rwlock_rdlock(&obj_lock); @@ -458,6 +461,8 @@ sdb_store_dump(FILE *fh) return -1; } + sdb_strbuf_append(buf, "{\"hosts\":["); + while (sdb_llist_iter_has_next(host_iter)) { sdb_store_obj_t *host = SDB_STORE_OBJ(sdb_llist_iter_get_next(host_iter)); sdb_llist_iter_t *svc_iter; @@ -472,17 +477,21 @@ sdb_store_dump(FILE *fh) snprintf(time_str, sizeof(time_str), ""); time_str[sizeof(time_str) - 1] = '\0'; - fprintf(fh, "Host '%s' (last updated: %s):\n", + sdb_strbuf_append(buf, "{\"name\": \"%s\", " + "\"last_update\": \"%s\", " + "\"attributes\": [", SDB_OBJ(host)->name, time_str); attr_iter = sdb_llist_get_iter(host->attributes); if (! attr_iter) { char errbuf[1024]; - fprintf(fh, "Failed to retrieve attributes: %s\n", + sdb_log(SDB_LOG_ERR, "store: Failed to retrieve attributes: %s\n", sdb_strerror(errno, errbuf, sizeof(errbuf))); - continue; + sdb_strbuf_append(buf, "{\"error\": \"failed to retrieve " + "attributes: %s\"}", errbuf); } + /* has_next returns false if the iterator is NULL */ while (sdb_llist_iter_has_next(attr_iter)) { sdb_attribute_t *attr = SDB_ATTR(sdb_llist_iter_get_next(attr_iter)); assert(attr); @@ -492,18 +501,21 @@ sdb_store_dump(FILE *fh) snprintf(time_str, sizeof(time_str), ""); time_str[sizeof(time_str) - 1] = '\0'; - fprintf(fh, "\tAttribute '%s' -> '%s' (last updated: %s)\n", + sdb_strbuf_append(buf, "{\"name\": \"%s\", " + "\"value\": \"%s\", \"last_update\": \"%s\"},", SDB_OBJ(attr)->name, attr->value, time_str); } sdb_llist_iter_destroy(attr_iter); + sdb_strbuf_append(buf, "], \"services\": ["); svc_iter = sdb_llist_get_iter(host->children); if (! svc_iter) { char errbuf[1024]; - fprintf(fh, "Failed to retrieve services: %s\n", + sdb_log(SDB_LOG_ERR, "store: Failed to retrieve services: %s\n", sdb_strerror(errno, errbuf, sizeof(errbuf))); - continue; + sdb_strbuf_append(buf, "{\"error\": \"failed to retrieve " + "services: %s\"}", errbuf); } while (sdb_llist_iter_has_next(svc_iter)) { @@ -515,17 +527,21 @@ sdb_store_dump(FILE *fh) snprintf(time_str, sizeof(time_str), ""); time_str[sizeof(time_str) - 1] = '\0'; - fprintf(fh, "\tService '%s' (last updated: %s)\n", + sdb_strbuf_append(buf, "{\"name\": \"%s\", " + "\"last_update\": \"%s\"},", SDB_OBJ(svc)->name, time_str); } sdb_llist_iter_destroy(svc_iter); + sdb_strbuf_append(buf, "]}"); } + sdb_strbuf_append(buf, "]}"); + sdb_llist_iter_destroy(host_iter); pthread_rwlock_unlock(&obj_lock); return 0; -} /* sdb_store_dump */ +} /* sdb_store_tojson */ /* vim: set tw=78 sw=4 ts=4 noexpandtab : */ diff --git a/src/daemon/config.c b/src/daemon/config.c index d15081f..beb0217 100644 --- a/src/daemon/config.c +++ b/src/daemon/config.c @@ -36,8 +36,26 @@ #include "liboconfig/utils.h" #include +#include + +#include +#include #include +/* + * 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; diff --git a/src/daemon/sysdbd.c b/src/daemon/sysdbd.c index f199261..123314d 100644 --- a/src/daemon/sysdbd.c +++ b/src/daemon/sysdbd.c @@ -34,6 +34,8 @@ #include "core/store.h" #include "core/error.h" +#include "frontend/sock.h" + #include "daemon/config.h" #if HAVE_LIBGEN_H @@ -55,16 +57,27 @@ #include +#include + #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,13 +257,36 @@ 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()); - - fprintf(stderr, "Store dump:\n"); - sdb_store_dump(stderr); return 0; } /* main */ diff --git a/src/frontend/connection.c b/src/frontend/connection.c new file mode 100644 index 0000000..962cd9c --- /dev/null +++ b/src/frontend/connection.c @@ -0,0 +1,362 @@ +/* + * SysDB - src/frontend/connection.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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/connection-private.h" +#include "utils/strbuf.h" + +#include +#include + +#include +#include + +#include + +/* + * private data types + */ + +/* name of connection objects */ +#define CONN_FD_PREFIX "conn#" +#define CONN_FD_PLACEHOLDER "XXXXXXX" + +static int +connection_init(sdb_object_t *obj, va_list ap) +{ + sdb_conn_t *conn; + int sock_fd; + int sock_fl; + + assert(obj); + conn = CONN(obj); + + sock_fd = va_arg(ap, int); + + conn->buf = sdb_strbuf_create(/* size = */ 128); + if (! conn->buf) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate a read buffer " + "for a new connection"); + return -1; + } + conn->errbuf = sdb_strbuf_create(0); + if (! conn->errbuf) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate an error buffer " + "for a new 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); + + conn->cmd = CONNECTION_IDLE; + conn->cmd_len = 0; + + /* 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) +{ + sdb_conn_t *conn; + size_t len; + + assert(obj); + conn = CONN(obj); + + if (conn->buf) { + len = sdb_strbuf_len(conn->buf); + if (len) + sdb_log(SDB_LOG_INFO, "frontend: Discarding incomplete command " + "(%zu byte%s left in buffer)", len, len == 1 ? "" : "s"); + } + + sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection on fd=%i", + conn->fd); + close(conn->fd); + conn->fd = -1; + + sdb_strbuf_destroy(conn->buf); + conn->buf = NULL; + sdb_strbuf_destroy(conn->errbuf); + conn->errbuf = NULL; +} /* connection_destroy */ + +static sdb_type_t connection_type = { + /* size = */ sizeof(sdb_conn_t), + /* init = */ connection_init, + /* destroy = */ connection_destroy, +}; + +/* + * connection handler functions + */ + +static uint32_t +connection_get_int32(sdb_conn_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(sdb_conn_t *conn) +{ + int status = -1; + + assert(conn && (conn->cmd != CONNECTION_IDLE)); + + sdb_log(SDB_LOG_DEBUG, "frontend: Handling command %u (len: %u)", + conn->cmd, conn->cmd_len); + + /* reset */ + sdb_strbuf_sprintf(conn->errbuf, ""); + + switch (conn->cmd) { + case CONNECTION_PING: + status = sdb_connection_ping(conn); + break; + case CONNECTION_STARTUP: + status = sdb_fe_session_start(conn); + break; + + case CONNECTION_LIST: + status = sdb_fe_list(conn); + break; + + default: + { + sdb_log(SDB_LOG_WARNING, "frontend: Ignoring invalid command"); + sdb_strbuf_sprintf(conn->errbuf, "Invalid command %#x", conn->cmd); + status = -1; + break; + } + } + + if (status) + sdb_connection_send(conn, CONNECTION_ERROR, + (uint32_t)sdb_strbuf_len(conn->errbuf), + sdb_strbuf_string(conn->errbuf)); + + /* remove the command from the buffer */ + if (conn->cmd_len) + sdb_strbuf_skip(conn->buf, conn->cmd_len); + conn->cmd = CONNECTION_IDLE; + conn->cmd_len = 0; + return status; +} /* command_handle */ + +/* initialize the connection state information */ +static int +command_init(sdb_conn_t *conn) +{ + size_t len; + + assert(conn && (conn->cmd == CONNECTION_IDLE) && (! conn->cmd_len)); + + conn->cmd = connection_get_int32(conn, 0); + conn->cmd_len = connection_get_int32(conn, sizeof(uint32_t)); + + len = 2 * sizeof(uint32_t); + if (conn->cmd == CONNECTION_IDLE) + len += conn->cmd_len; + sdb_strbuf_skip(conn->buf, len); + return 0; +} /* command_init */ + +/* returns negative value on error, 0 on EOF, number of octets else */ +static ssize_t +connection_read(sdb_conn_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)) + break; + return (int)status; + } + else if (! status) /* EOF */ + break; + + n += status; + } + + return n; +} /* connection_read */ + +/* + * public API + */ + +sdb_conn_t * +sdb_connection_accept(int fd) +{ + if (fd < 0) + return NULL; + + /* the placeholder will be replaced with the accepted file + * descriptor when initializing the object */ + return CONN(sdb_object_create(CONN_FD_PREFIX CONN_FD_PLACEHOLDER, + connection_type, fd)); +} /* sdb_connection_create */ + +void +sdb_connection_close(sdb_conn_t *conn) +{ + sdb_object_deref(SDB_OBJ(conn)); +} /* sdb_connection_close */ + +ssize_t +sdb_connection_read(sdb_conn_t *conn) +{ + ssize_t n = 0; + + while (42) { + ssize_t status = connection_read(conn); + + if ((conn->cmd == CONNECTION_IDLE) && (! conn->cmd_len) + && (sdb_strbuf_len(conn->buf) >= 2 * sizeof(int32_t))) + command_init(conn); + if ((conn->cmd != CONNECTION_IDLE) + && (sdb_strbuf_len(conn->buf) >= conn->cmd_len)) + command_handle(conn); + + if (status <= 0) + break; + + n += status; + } + return n; +} /* sdb_connection_read */ + +ssize_t +sdb_connection_send(sdb_conn_t *conn, uint32_t code, + uint32_t msg_len, const char *msg) +{ + size_t len = 2 * sizeof(uint32_t) + msg_len; + char buffer[len]; + char *buf; + + uint32_t tmp; + + if ((! conn) || (conn->fd < 0)) + return -1; + + tmp = htonl(code); + memcpy(buffer, &tmp, sizeof(uint32_t)); + + tmp = htonl(msg_len); + memcpy(buffer + sizeof(uint32_t), &tmp, sizeof(uint32_t)); + + if (msg_len) + memcpy(buffer + 2 * sizeof(uint32_t), msg, msg_len); + + buf = buffer; + while (len > 0) { + ssize_t status; + + /* XXX: use select() */ + + errno = 0; + status = write(conn->fd, buf, len); + if (status < 0) { + char errbuf[1024]; + + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + continue; + if (errno == EINTR) + continue; + + sdb_log(SDB_LOG_ERR, "frontend: Failed to send msg " + "(code: %u, len: %u) to client: %s", code, msg_len, + sdb_strerror(errno, errbuf, sizeof(errbuf))); + return status; + } + + len -= (size_t)status; + buf += status; + } + return (ssize_t)len; +} /* sdb_connection_send */ + +int +sdb_connection_ping(sdb_conn_t *conn) +{ + if ((! conn) || (conn->cmd != CONNECTION_PING)) + return -1; + + /* we're alive */ + sdb_connection_send(conn, CONNECTION_OK, 0, NULL); + return 0; +} /* sdb_connection_ping */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/frontend/query.c b/src/frontend/query.c new file mode 100644 index 0000000..fa493a6 --- /dev/null +++ b/src/frontend/query.c @@ -0,0 +1,73 @@ +/* + * SysDB - src/frontend/query.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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/store.h" +#include "frontend/connection-private.h" +#include "utils/strbuf.h" + +#include + +/* + * public API + */ + +int +sdb_fe_list(sdb_conn_t *conn) +{ + sdb_strbuf_t *buf; + + buf = sdb_strbuf_create(1024); + if (! buf) { + char errbuf[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to create " + "buffer to handle LIST command: %s", + sdb_strerror(errno, errbuf, sizeof(errbuf))); + + sdb_strbuf_sprintf(conn->errbuf, "Out of memory"); + sdb_strbuf_destroy(buf); + return -1; + } + + if (sdb_store_tojson(buf)) { + sdb_log(SDB_LOG_ERR, "frontend: Failed to serialize " + "store to JSON"); + sdb_strbuf_sprintf(conn->errbuf, "Out of memory"); + sdb_strbuf_destroy(buf); + return -1; + } + + sdb_connection_send(conn, CONNECTION_OK, + (uint32_t)sdb_strbuf_len(buf), sdb_strbuf_string(buf)); + sdb_strbuf_destroy(buf); + return 0; +} /* session_start */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/frontend/session.c b/src/frontend/session.c new file mode 100644 index 0000000..6403469 --- /dev/null +++ b/src/frontend/session.c @@ -0,0 +1,51 @@ +/* + * SysDB - src/frontend/session.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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 "frontend/connection-private.h" + +/* + * public API + */ + +int +sdb_fe_session_start(sdb_conn_t *conn) +{ + if ((! conn) || (conn->username)) + return -1; + + if (conn->cmd != CONNECTION_STARTUP) + return -1; + + /* XXX: for now, simply accept all connections */ + sdb_connection_send(conn, CONNECTION_OK, 0, NULL); + return 0; +} /* sdb_fe_session_start */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/frontend/sock.c b/src/frontend/sock.c new file mode 100644 index 0000000..cac49e1 --- /dev/null +++ b/src/frontend/sock.c @@ -0,0 +1,531 @@ +/* + * SysDB - src/frontend/sock.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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/connection-private.h" +#include "frontend/sock.h" + +#include "utils/channel.h" +#include "utils/llist.h" +#include "utils/strbuf.h" + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +/* + * private data types + */ + +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 */ + +/* + * connection handler functions + */ + +static void * +connection_handler(void *data) +{ + sdb_fe_socket_t *sock = data; + + assert(sock); + + while (42) { + struct timespec timeout = { 0, 500000000 }; /* .5 seconds */ + sdb_conn_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)sdb_connection_read(conn); + if (status <= 0) { + /* error or EOF -> close connection */ + sdb_object_deref(SDB_OBJ(conn)); + continue; + } + + /* 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; + int status; + + obj = SDB_OBJ(sdb_connection_accept(listener->sock_fd)); + if (! obj) + return -1; + + status = sdb_llist_append(sock->open_connections, obj); + if (status) + sdb_log(SDB_LOG_ERR, "frontend: Failed to append " + "connection %s to list of open connections", + obj->name); + + /* hand ownership over to the list; or destroy in case of an error */ + sdb_object_deref(obj); + return status; +} /* 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(sdb_conn_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 : */ + diff --git a/src/include/core/plugin.h b/src/include/core/plugin.h index 6a679ce..a4217ac 100644 --- a/src/include/core/plugin.h +++ b/src/include/core/plugin.h @@ -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); diff --git a/src/include/core/store.h b/src/include/core/store.h index 538390a..bc4de10 100644 --- a/src/include/core/store.h +++ b/src/include/core/store.h @@ -32,6 +32,7 @@ #include "core/object.h" #include "core/time.h" #include "utils/llist.h" +#include "utils/strbuf.h" #include @@ -98,8 +99,17 @@ int sdb_store_service(const char *hostname, const char *name, sdb_time_t last_update); +/* + * sdb_store_tojson: + * Serialize the entire store to JSON and append the result to the specified + * buffer. + * + * Returns: + * - 0 on success + * - a negative value on error + */ int -sdb_store_dump(FILE *fh); +sdb_store_tojson(sdb_strbuf_t *buf); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/include/daemon/config.h b/src/include/daemon/config.h index 6d4b0d3..0fe278d 100644 --- a/src/include/daemon/config.h +++ b/src/include/daemon/config.h @@ -28,6 +28,23 @@ #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/connection-private.h b/src/include/frontend/connection-private.h new file mode 100644 index 0000000..6cf7395 --- /dev/null +++ b/src/include/frontend/connection-private.h @@ -0,0 +1,77 @@ +/* + * SysDB - src/include/frontend/connection-private.h + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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. + */ + +/* + * private data structures used by frontend modules + */ + +#ifndef SDB_FRONTEND_CONNECTION_PRIVATE_H +#define SDB_FRONTEND_CONNECTION_PRIVATE_H 1 + +#include "core/object.h" +#include "utils/strbuf.h" +#include "frontend/connection.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct sdb_conn { + sdb_object_t super; + + /* file-descriptor of the open connection */ + int fd; + + /* connection and client information */ + struct sockaddr_storage client_addr; + socklen_t client_addr_len; + + /* read buffer */ + sdb_strbuf_t *buf; + + /* connection / protocol state information */ + uint32_t cmd; + uint32_t cmd_len; + + sdb_strbuf_t *errbuf; + + /* user information */ + char *username; /* NULL if the user has not been authenticated */ +}; +#define CONN(obj) ((sdb_conn_t *)(obj)) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ! SDB_FRONTEND_CONNECTION_H */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/include/frontend/connection.h b/src/include/frontend/connection.h new file mode 100644 index 0000000..955180f --- /dev/null +++ b/src/include/frontend/connection.h @@ -0,0 +1,149 @@ +/* + * SysDB - src/include/frontend/connection.h + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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_CONNECTION_H +#define SDB_FRONTEND_CONNECTION_H 1 + +#include "utils/strbuf.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* status codes returned to a client */ +typedef enum { + CONNECTION_OK = 0, + CONNECTION_ERROR +} sdb_conn_status_t; + +/* accepted commands / state of the connection */ +typedef enum { + /* connection handling */ + CONNECTION_IDLE = 0, + CONNECTION_PING, + CONNECTION_STARTUP, + + /* querying */ + CONNECTION_LIST, +} sdb_conn_state_t; + +typedef struct sdb_conn sdb_conn_t; + +/* + * sdb_connection_accpet: + * Accept a new connection on the specified file-descriptor 'fd' and return a + * newly allocated connection object. + * + * Returns: + * - 0 on success + * - a negative value else + */ +sdb_conn_t * +sdb_connection_accept(int fd); + +/* + * sdb_connection_close: + * Close a open connection and deallocate any memory. The connection object is + * no longer valid after calling this function. + */ +void +sdb_connection_close(sdb_conn_t *conn); + +/* + * sdb_connection_read: + * Read from an open connection until reading would block. + * + * Returns: + * - the number of bytes read (0 on EOF) + * - a negative value on error + */ +ssize_t +sdb_connection_read(sdb_conn_t *conn); + +/* + * sdb_connection_send: + * Send to an open connection. + * + * Returns: + * - the number of bytes written + * - a negative value on error + */ +ssize_t +sdb_connection_send(sdb_conn_t *conn, uint32_t code, + uint32_t msg_len, const char *msg); + +/* + * sdb_connection_ping: + * Send back a backend status indicator to the connected client. + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sdb_connection_ping(sdb_conn_t *conn); + +/* + * session handling + */ + +/* + * sdb_fe_session_start: + * Start a new user session on the specified connection. + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sdb_fe_session_start(sdb_conn_t *conn); + +/* + * store access + */ + +/* + * sdb_fe_list: + * Send a complete listing of the store, serialized as JSON, to the client. + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sdb_fe_list(sdb_conn_t *conn); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ! SDB_FRONTEND_CONNECTION_H */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/include/frontend/sock.h b/src/include/frontend/sock.h new file mode 100644 index 0000000..6ab8cca --- /dev/null +++ b/src/include/frontend/sock.h @@ -0,0 +1,100 @@ +/* + * SysDB - src/include/frontend/sock.h + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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 :
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 index 0000000..49d37fd --- /dev/null +++ b/src/include/utils/channel.h @@ -0,0 +1,130 @@ +/* + * SysDB - src/include/utils/channel.h + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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 + +#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 : */ + diff --git a/src/include/utils/llist.h b/src/include/utils/llist.h index 4bf9d60..2742139 100644 --- a/src/include/utils/llist.h +++ b/src/include/utils/llist.h @@ -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 diff --git a/src/include/utils/strbuf.h b/src/include/utils/strbuf.h index 302cfdd..9577a64 100644 --- a/src/include/utils/strbuf.h +++ b/src/include/utils/strbuf.h @@ -37,6 +37,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -93,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 @@ -105,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 index 0000000..a45247a --- /dev/null +++ b/src/utils/channel.c @@ -0,0 +1,279 @@ +/* + * SysDB - src/utils/channel.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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 + +#include + +#include +#include + +#include + +#include + +/* + * 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 : */ + diff --git a/src/utils/llist.c b/src/utils/llist.c index dfc8fb9..0a4665d 100644 --- a/src/utils/llist.c +++ b/src/utils/llist.c @@ -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 : */ diff --git a/src/utils/strbuf.c b/src/utils/strbuf.c index 7c24b3a..77d19f0 100644 --- a/src/utils/strbuf.c +++ b/src/utils/strbuf.c @@ -32,6 +32,9 @@ #include #include #include +#include + +#include /* * private data structures @@ -116,9 +119,14 @@ sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap) assert((strbuf->size == 0) || (strbuf->string[strbuf->pos] == '\0')); - if (strbuf->pos >= strbuf->size) + if (! strbuf->size) { /* use some arbitrary but somewhat reasonable default */ - if (strbuf_resize(strbuf, strbuf->size ? 2 * strbuf->size : 64)) + if (strbuf_resize(strbuf, 64)) + return -1; + } + /* make sure to reserve space for the nul-byte */ + else if (strbuf->pos >= strbuf->size - 1) + if (strbuf_resize(strbuf, 2 * strbuf->size)) return -1; assert(strbuf->size && strbuf->string); @@ -134,8 +142,12 @@ sdb_strbuf_vappend(sdb_strbuf_t *strbuf, const char *fmt, va_list ap) return status; } - if ((size_t)status >= strbuf->size - strbuf->pos) { - strbuf_resize(strbuf, (size_t)status + 1); + /* 'status' does not include nul-byte */ + if ((size_t)status >= strbuf->size - strbuf->pos - 1) { + if (strbuf_resize(strbuf, strbuf->size + (size_t)status)) { + va_end(aq); + return -1; + } /* reset string and try again */ strbuf->string[strbuf->pos] = '\0'; @@ -188,6 +200,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 +282,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) { diff --git a/t/Makefile.am b/t/Makefile.am index 7d249e4..c31ac50 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -7,6 +7,7 @@ check_PROGRAMS = libsysdb_test libsysdb_net_test libsysdb_test_SOURCES = \ libsysdb_test.c libsysdb_test.h \ core/object_test.c \ + utils/channel_test.c \ utils/dbi_test.c \ utils/llist_test.c \ utils/strbuf_test.c diff --git a/t/libsysdb_test.c b/t/libsysdb_test.c index 2693a8d..91909b8 100644 --- a/t/libsysdb_test.c +++ b/t/libsysdb_test.c @@ -38,8 +38,9 @@ main(void) suite_creator_t creators[] = { { core_object_suite, NULL }, - { util_llist_suite, NULL }, + { util_channel_suite, NULL }, { util_dbi_suite, NULL }, + { util_llist_suite, NULL }, { util_strbuf_suite, NULL }, }; diff --git a/t/libsysdb_test.h b/t/libsysdb_test.h index 9d83dc6..eadeca5 100644 --- a/t/libsysdb_test.h +++ b/t/libsysdb_test.h @@ -63,6 +63,10 @@ typedef struct { Suite * core_object_suite(void); +/* 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 index 0000000..02ed172 --- /dev/null +++ b/t/utils/channel_test.c @@ -0,0 +1,370 @@ +/* + * SysDB - t/utils/channel_test.c + * Copyright (C) 2013 Sebastian 'tokkee' Harl + * 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 +#include +#include + +#include + +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 : */ + diff --git a/t/utils/llist_test.c b/t/utils/llist_test.c index 638d2fb..ede8200 100644 --- a/t/utils/llist_test.c +++ b/t/utils/llist_test.c @@ -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; diff --git a/t/utils/strbuf_test.c b/t/utils/strbuf_test.c index f007cbe..d76c0f2 100644 --- a/t/utils/strbuf_test.c +++ b/t/utils/strbuf_test.c @@ -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,125 @@ START_TEST(test_sdb_strbuf_sprintf) } END_TEST +START_TEST(test_incremental) +{ + ssize_t n; + size_t i; + + sdb_strbuf_destroy(buf); + buf = sdb_strbuf_create(1024); + + /* fill buffer one by one; leave room for nul-byte */ + for (i = 0; i < 1023; ++i) { + n = sdb_strbuf_append(buf, "."); + fail_unless(n == 1, "sdb_strbuf_append() = %zi; expected: 1", n); + } + + /* write another byte; this has to trigger a resize */ + n = sdb_strbuf_append(buf, "."); + fail_unless(n == 1, "sdb_strbuf_append() = %zi; expected: 1", n); + + /* write more bytes; this should trigger at least one more resize but + * that's an implementation detail */ + for (i = 0; i < 1024; ++i) { + n = sdb_strbuf_append(buf, "."); + fail_unless(n == 1, "sdb_strbuf_append() = %zi; expected: 1", n); + } + + n = (ssize_t)sdb_strbuf_len(buf); + fail_unless(n == 2048, "sdb_strbuf_len() = %zi; expectd: 2048", n); +} +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 +364,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 +469,20 @@ 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_incremental); + 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);