author | Sebastian Harl <sh@tokkee.org> | |
Thu, 7 Nov 2013 14:47:07 +0000 (15:47 +0100) | ||
committer | Sebastian Harl <sh@tokkee.org> | |
Thu, 7 Nov 2013 14:47:07 +0000 (15:47 +0100) |
22 files changed:
diff --git a/configure.ac b/configure.ac
index a4f21853dc22b506164a328c5c1f5d1452c26ff9..dde772705dc6a913524116a4e4399dd044f0b823 100644 (file)
--- a/configure.ac
+++ b/configure.ac
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 3cdb58e906edbf36483dfb5c7beb81b05e6d7a30..de2f5cf642ef30c1eba8434256ea96bb89a66b5f 100644 (file)
--- a/src/Makefile.am
+++ b/src/Makefile.am
AM_CFLAGS = @STRICT_CFLAGS@
AM_CPPFLAGS = -Iinclude
AM_CPPFLAGS += -DSYSCONFDIR='"${sysconfdir}"'
+AM_CPPFLAGS += -DLOCALSTATEDIR='"${localstatedir}"'
AM_CPPFLAGS += -DPKGLIBDIR='"${pkglibdir}"'
BUILT_SOURCES = include/sysdb.h
core/store.c include/core/store.h \
include/core/data.h \
core/error.c include/core/error.h \
+ frontend/sock.c include/frontend/sock.h \
+ utils/channel.c include/utils/channel.h \
utils/llist.c include/utils/llist.h \
utils/strbuf.c include/utils/strbuf.h \
core/time.c include/core/time.h \
diff --git a/src/core/plugin.c b/src/core/plugin.c
index ca79284fe60a41b5d17b3f129db277755587e9f7..09d5ccea3ebdcd4fbcc01465ee827e2e313cfd35 100644 (file)
--- a/src/core/plugin.c
+++ b/src/core/plugin.c
} 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
? -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
*/
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;
/* 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 */
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;
}
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;
}
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;
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;
}
/* 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;
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;
}
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;
}
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;
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;
}
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;
}
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;
}
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);
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;
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 43a7a6bda20d1228f8fa6b389a5c9710d92936a2..19faee43994bdde816a6a84f4c09061bfb25bb4f 100644 (file)
--- a/src/core/store.c
+++ b/src/core/store.c
}
/* 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));
diff --git a/src/daemon/config.c b/src/daemon/config.c
index d15081f469446ee1f3b3214b6dbe8efcdf6e7253..beb0217baa323d5bbd17aff02891aeaed73a5ea7 100644 (file)
--- a/src/daemon/config.c
+++ b/src/daemon/config.c
#include "liboconfig/utils.h"
#include <assert.h>
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
#include <strings.h>
+/*
+ * Parser error values:
+ * - Values less than zero indicate an error in the daemon or libsysdb.
+ * - Zero indicates success.
+ * - Any other values indicate parsing errors.
+ */
+
+enum {
+ ERR_UNKNOWN_OPTION = 1,
+ ERR_UNKNOWN_ARG = 2,
+ ERR_INVALID_ARG = 3,
+ ERR_PARSE_FAILED = 4,
+};
+
/*
* private variables
*/
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
*/
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)
{
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) {
continue;
}
+ /* returns a negative value on error */
return sdb_plugin_load(name, NULL);
} /* daemon_load_plugin */
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);
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' "
"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 },
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))
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 f1992610f2a2164af9d189c8f1324ea6a3265daf..e2309224d57243bd9dd7d7bc19f7fde1841c8ed9 100644 (file)
--- a/src/daemon/sysdbd.c
+++ b/src/daemon/sysdbd.c
#include "core/store.h"
#include "core/error.h"
+#include "frontend/sock.h"
+
#include "daemon/config.h"
#if HAVE_LIBGEN_H
#include <unistd.h>
+#include <pthread.h>
+
#ifndef CONFIGFILE
# define CONFIGFILE SYSCONFDIR"/sysdb/sysdbd.conf"
#endif
+#ifndef DEFAULT_SOCKET
+# define DEFAULT_SOCKET "unix:"LOCALSTATEDIR"/run/sysdbd.sock"
+#endif
+
static sdb_plugin_loop_t plugin_main_loop = SDB_PLUGIN_LOOP_INIT;
+static sdb_fe_loop_t frontend_main_loop = SDB_FE_LOOP_INIT;
+
+static char *default_listen_addresses[] = {
+ DEFAULT_SOCKET,
+};
static void
sigintterm_handler(int __attribute__((unused)) signo)
{
- plugin_main_loop.do_loop = 0;
+ frontend_main_loop.do_loop = 0;
} /* sigintterm_handler */
static void
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");
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;
sdb_plugin_init_all();
plugin_main_loop.default_interval = SECS_TO_SDB_TIME(60);
- sdb_plugin_collector_loop(&plugin_main_loop);
+
+ memset(&backend_thread, 0, sizeof(backend_thread));
+ if (pthread_create(&backend_thread, /* attr = */ NULL,
+ backend_handler, /* arg = */ NULL)) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "Failed to create backend handler thread: %s",
+ sdb_strerror(errno, buf, sizeof(buf)));
+
+ plugin_main_loop.do_loop = 0;
+ }
+ else {
+ size_t i;
+
+ sdb_fe_socket_t *sock = sdb_fe_sock_create();
+ for (i = 0; i < listen_addresses_num; ++i)
+ if (sdb_fe_sock_add_listener(sock, listen_addresses[i]))
+ break;
+
+ /* break on error */
+ if (i >= listen_addresses_num)
+ sdb_fe_sock_listen_and_serve(sock, &frontend_main_loop);
+
+ sdb_log(SDB_LOG_INFO, "Waiting for backend thread to terminate");
+ plugin_main_loop.do_loop = 0;
+ pthread_join(backend_thread, NULL);
+ sdb_fe_sock_destroy(sock);
+ }
sdb_log(SDB_LOG_INFO, "Shutting down SysDB daemon "SDB_VERSION_STRING
SDB_VERSION_EXTRA" (pid %i)", (int)getpid());
diff --git a/src/frontend/sock.c b/src/frontend/sock.c
--- /dev/null
+++ b/src/frontend/sock.c
@@ -0,0 +1,710 @@
+/*
+ * SysDB - src/frontend/sock.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "sysdb.h"
+#include "core/error.h"
+#include "core/object.h"
+#include "frontend/sock.h"
+
+#include "utils/channel.h"
+#include "utils/llist.h"
+#include "utils/strbuf.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <pthread.h>
+
+/* name of connection objects */
+#define CONN_FD_PREFIX "conn#"
+#define CONN_FD_PLACEHOLDER "XXXXXXX"
+
+/*
+ * private data types
+ */
+
+typedef struct {
+ sdb_object_t super;
+
+ /* connection and client information */
+ int fd;
+ struct sockaddr_storage client_addr;
+ socklen_t client_addr_len;
+
+ /* read buffer */
+ sdb_strbuf_t *buf;
+
+ /* state information for the currently executed command */
+ uint32_t cmd;
+ uint32_t cmd_len;
+} connection_obj_t;
+#define CONN(obj) ((connection_obj_t *)(obj))
+
+typedef struct {
+ char *address;
+ int type;
+
+ int sock_fd;
+} listener_t;
+
+typedef struct {
+ int type;
+ const char *prefix;
+
+ int (*opener)(listener_t *);
+} fe_listener_impl_t;
+
+struct sdb_fe_socket {
+ listener_t *listeners;
+ size_t listeners_num;
+
+ sdb_llist_t *open_connections;
+
+ /* channel used for communication between main
+ * and connection handler threads */
+ sdb_channel_t *chan;
+};
+
+/*
+ * connection management functions
+ */
+
+static int
+open_unix_sock(listener_t *listener)
+{
+ struct sockaddr_un sa;
+ int status;
+
+ listener->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listener->sock_fd < 0) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to open UNIX socket %s: %s",
+ listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+ return -1;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, listener->address + strlen("unix:"),
+ sizeof(sa.sun_path));
+
+ status = bind(listener->sock_fd, (struct sockaddr *)&sa, sizeof(sa));
+ if (status) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to bind to UNIX socket %s: %s",
+ listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+ return -1;
+ }
+ return 0;
+} /* open_unix_sock */
+
+/*
+ * private variables
+ */
+
+/* the enum has to be sorted the same as the implementations array
+ * to ensure that the type may be used as index into the array */
+enum {
+ LISTENER_UNIXSOCK = 0,
+};
+static fe_listener_impl_t listener_impls[] = {
+ { LISTENER_UNIXSOCK, "unix", open_unix_sock },
+};
+
+/*
+ * private helper functions
+ */
+
+static int
+get_type(const char *address)
+{
+ char *sep;
+ size_t len;
+ size_t i;
+
+ sep = strchr(address, (int)':');
+ if (! sep)
+ return -1;
+
+ assert(sep > address);
+ len = (size_t)(sep - address);
+
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(listener_impls); ++i) {
+ fe_listener_impl_t *impl = listener_impls + i;
+
+ if (!strncmp(address, impl->prefix, len)) {
+ assert(impl->type == (int)i);
+ return impl->type;
+ }
+ }
+ return -1;
+} /* get_type */
+
+static void
+listener_destroy(listener_t *listener)
+{
+ if (! listener)
+ return;
+
+ if (listener->sock_fd >= 0)
+ close(listener->sock_fd);
+ listener->sock_fd = -1;
+
+ if (listener->address)
+ free(listener->address);
+} /* listener_destroy */
+
+static listener_t *
+listener_create(sdb_fe_socket_t *sock, const char *address)
+{
+ listener_t *listener;
+ int type;
+
+ type = get_type(address);
+ if (type < 0) {
+ sdb_log(SDB_LOG_ERR, "frontend: Unsupported address type specified "
+ "in listen address '%s'", address);
+ return NULL;
+ }
+
+ listener = realloc(sock->listeners,
+ sock->listeners_num * sizeof(*sock->listeners));
+ if (! listener) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
+ sdb_strerror(errno, buf, sizeof(buf)));
+ return NULL;
+ }
+
+ sock->listeners = listener;
+ listener = sock->listeners + sock->listeners_num;
+
+ listener->sock_fd = -1;
+ listener->address = strdup(address);
+ if (! listener->address) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate memory: %s",
+ sdb_strerror(errno, buf, sizeof(buf)));
+ listener_destroy(listener);
+ return NULL;
+ }
+ listener->type = type;
+
+ if (listener_impls[type].opener(listener)) {
+ /* prints error */
+ listener_destroy(listener);
+ return NULL;
+ }
+
+ ++sock->listeners_num;
+ return listener;
+} /* listener_create */
+
+static int
+listener_listen(listener_t *listener)
+{
+ assert(listener);
+
+ /* try to reopen */
+ if (listener->sock_fd < 0)
+ if (listener_impls[listener->type].opener(listener))
+ return -1;
+ assert(listener->sock_fd >= 0);
+
+ if (listen(listener->sock_fd, /* backlog = */ 32)) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to listen on socket %s: %s",
+ listener->address, sdb_strerror(errno, buf, sizeof(buf)));
+ return -1;
+ }
+ return 0;
+} /* listener_listen */
+
+static void
+listener_close(listener_t *listener)
+{
+ assert(listener);
+
+ if (listener->sock_fd < 0)
+ return;
+
+ close(listener->sock_fd);
+ listener->sock_fd = -1;
+} /* listener_close */
+
+static void
+socket_close(sdb_fe_socket_t *sock)
+{
+ size_t i;
+
+ assert(sock);
+ for (i = 0; i < sock->listeners_num; ++i)
+ listener_close(sock->listeners + i);
+} /* socket_close */
+
+/*
+ * private data types
+ */
+
+static int
+connection_init(sdb_object_t *obj, va_list ap)
+{
+ connection_obj_t *conn;
+ int sock_fd;
+ int sock_fl;
+
+ assert(obj);
+ conn = CONN(obj);
+
+ sock_fd = va_arg(ap, int);
+
+ CONN(obj)->buf = sdb_strbuf_create(/* size = */ 128);
+ if (! CONN(obj)->buf) {
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate a read buffer "
+ "for a new remote connection");
+ return -1;
+ }
+
+ conn->client_addr_len = sizeof(conn->client_addr);
+ conn->fd = accept(sock_fd, (struct sockaddr *)&conn->client_addr,
+ &conn->client_addr_len);
+
+ if (conn->fd < 0) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to accept remote "
+ "connection: %s", sdb_strerror(errno,
+ buf, sizeof(buf)));
+ return -1;
+ }
+
+ if (conn->client_addr.ss_family != AF_UNIX) {
+ sdb_log(SDB_LOG_ERR, "frontend: Accepted connection using "
+ "unexpected family type %d", conn->client_addr.ss_family);
+ return -1;
+ }
+
+ sock_fl = fcntl(conn->fd, F_GETFL);
+ if (fcntl(conn->fd, F_SETFL, sock_fl | O_NONBLOCK)) {
+ char buf[1024];
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to switch connection conn#%i "
+ "to non-blocking mode: %s", conn->fd,
+ sdb_strerror(errno, buf, sizeof(buf)));
+ return -1;
+ }
+
+ sdb_log(SDB_LOG_DEBUG, "frontend: Accepted connection on fd=%i",
+ conn->fd);
+
+ /* update the object name */
+ snprintf(obj->name + strlen(CONN_FD_PREFIX),
+ strlen(CONN_FD_PLACEHOLDER), "%i", conn->fd);
+ return 0;
+} /* connection_init */
+
+static void
+connection_destroy(sdb_object_t *obj)
+{
+ connection_obj_t *conn;
+ size_t len;
+
+ assert(obj);
+ conn = CONN(obj);
+
+ len = sdb_strbuf_len(conn->buf);
+ if (len)
+ sdb_log(SDB_LOG_INFO, "frontend: Discarding incomplete command "
+ "(%zu bytes left in buffer)", len);
+
+ sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection on fd=%i", conn->fd);
+ close(conn->fd);
+ conn->fd = -1;
+
+ sdb_strbuf_destroy(CONN(obj)->buf);
+} /* connection_destroy */
+
+static sdb_type_t connection_type = {
+ /* size = */ sizeof(connection_obj_t),
+ /* init = */ connection_init,
+ /* destroy = */ connection_destroy,
+};
+
+/*
+ * connection handler functions
+ */
+
+static uint32_t
+connection_get_int32(connection_obj_t *conn, size_t offset)
+{
+ const char *data;
+ uint32_t n;
+
+ assert(conn && (sdb_strbuf_len(conn->buf) >= offset + sizeof(uint32_t)));
+
+ data = sdb_strbuf_string(conn->buf);
+ memcpy(&n, data + offset, sizeof(n));
+ n = ntohl(n);
+ return n;
+} /* connection_get_int32 */
+
+static int
+command_handle(connection_obj_t *conn)
+{
+ assert(conn && conn->cmd && conn->cmd_len);
+ /* XXX */
+ sdb_strbuf_skip(conn->buf, conn->cmd_len);
+ return 0;
+} /* command_handle */
+
+/* initialize the connection state information */
+static int
+command_init(connection_obj_t *conn)
+{
+ assert(conn && (! conn->cmd) && (! conn->cmd_len));
+
+ conn->cmd = connection_get_int32(conn, 0);
+ conn->cmd_len = connection_get_int32(conn, sizeof(uint32_t));
+ sdb_strbuf_skip(conn->buf, 2 * sizeof(uint32_t));
+ return 0;
+} /* command_init */
+
+/* returns negative value on error, 0 on EOF, number of octets else */
+static ssize_t
+connection_read(connection_obj_t *conn)
+{
+ ssize_t n = 0;
+
+ while (42) {
+ ssize_t status;
+
+ errno = 0;
+ status = sdb_strbuf_read(conn->buf, conn->fd, 1024);
+ if (status < 0) {
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+ return n;
+ return (int)status;
+ }
+ else if (! status) /* EOF */
+ return n;
+
+ n += status;
+ }
+
+ return n;
+} /* connection_read */
+
+static void *
+connection_handler(void *data)
+{
+ sdb_fe_socket_t *sock = data;
+
+ assert(sock);
+
+ while (42) {
+ struct timespec timeout = { 0, 500000000 }; /* .5 seconds */
+ connection_obj_t *conn;
+ int status;
+
+ errno = 0;
+ status = sdb_channel_select(sock->chan, /* read */ NULL, &conn,
+ /* write */ NULL, NULL, &timeout);
+ if (status) {
+ char buf[1024];
+
+ if (errno == ETIMEDOUT)
+ continue;
+ if (errno == EBADF) /* channel shut down */
+ break;
+
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to read from channel: %s",
+ sdb_strerror(errno, buf, sizeof(buf)));
+ continue;
+ }
+
+ status = (int)connection_read(conn);
+ if (status <= 0) {
+ /* error or EOF -> close connection */
+ sdb_object_deref(SDB_OBJ(conn));
+ continue;
+ }
+
+ if (conn->cmd_len && (sdb_strbuf_len(conn->buf) >= conn->cmd_len))
+ command_handle(conn);
+ else if (sdb_strbuf_len(conn->buf) >= 2 * sizeof(int32_t))
+ command_init(conn);
+
+ /* return the connection to the main loop */
+ if (sdb_llist_append(sock->open_connections, SDB_OBJ(conn))) {
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to re-append "
+ "connection %s to list of open connections",
+ SDB_OBJ(conn)->name);
+ }
+
+ /* pass ownership back to list; or destroy in case of an error */
+ sdb_object_deref(SDB_OBJ(conn));
+ }
+ return NULL;
+} /* connection_handler */
+
+static int
+connection_accept(sdb_fe_socket_t *sock, listener_t *listener)
+{
+ sdb_object_t *obj;
+
+ /* the placeholder will be replaced with the accepted file
+ * descriptor when initializing the object */
+ obj = sdb_object_create(CONN_FD_PREFIX CONN_FD_PLACEHOLDER,
+ connection_type, listener->sock_fd);
+ if (! obj)
+ return -1;
+
+ if (sdb_llist_append(sock->open_connections, obj)) {
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to append "
+ "connection %s to list of open connections",
+ obj->name);
+ sdb_object_deref(obj);
+ return -1;
+ }
+
+ /* hand ownership over to the list */
+ sdb_object_deref(obj);
+ return 0;
+} /* connection_accept */
+
+static int
+socket_handle_incoming(sdb_fe_socket_t *sock,
+ fd_set *ready, fd_set *exceptions)
+{
+ sdb_llist_iter_t *iter;
+ size_t i;
+
+ for (i = 0; i < sock->listeners_num; ++i) {
+ listener_t *listener = sock->listeners + i;
+ if (FD_ISSET(listener->sock_fd, ready))
+ if (connection_accept(sock, listener))
+ continue;
+ }
+
+ iter = sdb_llist_get_iter(sock->open_connections);
+ if (! iter) {
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
+ "for open connections");
+ return -1;
+ }
+
+ while (sdb_llist_iter_has_next(iter)) {
+ sdb_object_t *obj = sdb_llist_iter_get_next(iter);
+
+ if (FD_ISSET(CONN(obj)->fd, exceptions))
+ sdb_log(SDB_LOG_INFO, "Exception on fd %d",
+ CONN(obj)->fd);
+
+ if (FD_ISSET(CONN(obj)->fd, ready)) {
+ sdb_llist_iter_remove_current(iter);
+ sdb_channel_write(sock->chan, &obj);
+ }
+ }
+ sdb_llist_iter_destroy(iter);
+ return 0;
+} /* socket_handle_incoming */
+
+/*
+ * public API
+ */
+
+sdb_fe_socket_t *
+sdb_fe_sock_create(void)
+{
+ sdb_fe_socket_t *sock;
+
+ sock = calloc(1, sizeof(*sock));
+ if (! sock)
+ return NULL;
+
+ sock->open_connections = sdb_llist_create();
+ if (! sock->open_connections) {
+ sdb_fe_sock_destroy(sock);
+ return NULL;
+ }
+ return sock;
+} /* sdb_fe_sock_create */
+
+void
+sdb_fe_sock_destroy(sdb_fe_socket_t *sock)
+{
+ size_t i;
+
+ if (! sock)
+ return;
+
+ for (i = 0; i < sock->listeners_num; ++i) {
+ listener_destroy(sock->listeners + i);
+ }
+ if (sock->listeners)
+ free(sock->listeners);
+ sock->listeners = NULL;
+
+ sdb_llist_destroy(sock->open_connections);
+ sock->open_connections = NULL;
+ free(sock);
+} /* sdb_fe_sock_destroy */
+
+int
+sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address)
+{
+ listener_t *listener;
+
+ if ((! sock) || (! address))
+ return -1;
+
+ listener = listener_create(sock, address);
+ if (! listener)
+ return -1;
+ return 0;
+} /* sdb_fe_sock_add_listener */
+
+int
+sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop)
+{
+ fd_set sockets;
+ int max_listen_fd = 0;
+ size_t i;
+
+ /* XXX: make the number of threads configurable */
+ pthread_t handler_threads[5];
+
+ if ((! sock) || (! sock->listeners_num) || (! loop) || sock->chan)
+ return -1;
+
+ FD_ZERO(&sockets);
+ for (i = 0; i < sock->listeners_num; ++i) {
+ listener_t *listener = sock->listeners + i;
+
+ if (listener_listen(listener)) {
+ socket_close(sock);
+ return -1;
+ }
+
+ FD_SET(listener->sock_fd, &sockets);
+ if (listener->sock_fd > max_listen_fd)
+ max_listen_fd = listener->sock_fd;
+ }
+
+ sock->chan = sdb_channel_create(1024, sizeof(connection_obj_t *));
+ if (! sock->chan) {
+ socket_close(sock);
+ return -1;
+ }
+
+ memset(&handler_threads, 0, sizeof(handler_threads));
+ /* XXX: error handling */
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(handler_threads); ++i)
+ pthread_create(&handler_threads[i], /* attr = */ NULL,
+ connection_handler, /* arg = */ sock);
+
+ while (loop->do_loop) {
+ struct timeval timeout = { 1, 0 }; /* one second */
+ sdb_llist_iter_t *iter;
+
+ int max_fd = max_listen_fd;
+ fd_set ready;
+ fd_set exceptions;
+ int n;
+
+ FD_ZERO(&ready);
+ FD_ZERO(&exceptions);
+
+ ready = sockets;
+
+ iter = sdb_llist_get_iter(sock->open_connections);
+ if (! iter) {
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to acquire iterator "
+ "for open connections");
+ break;
+ }
+
+ while (sdb_llist_iter_has_next(iter)) {
+ sdb_object_t *obj = sdb_llist_iter_get_next(iter);
+ FD_SET(CONN(obj)->fd, &ready);
+ FD_SET(CONN(obj)->fd, &exceptions);
+
+ if (CONN(obj)->fd > max_fd)
+ max_fd = CONN(obj)->fd;
+ }
+ sdb_llist_iter_destroy(iter);
+
+ errno = 0;
+ n = select(max_fd + 1, &ready, NULL, &exceptions, &timeout);
+ if (n < 0) {
+ char buf[1024];
+
+ if (errno == EINTR)
+ continue;
+
+ sdb_log(SDB_LOG_ERR, "frontend: Failed to monitor sockets: %s",
+ sdb_strerror(errno, buf, sizeof(buf)));
+ break;
+ }
+ else if (! n)
+ continue;
+
+ /* handle new and open connections */
+ if (socket_handle_incoming(sock, &ready, &exceptions))
+ break;
+ }
+
+ socket_close(sock);
+
+ sdb_log(SDB_LOG_INFO, "frontend: Waiting for connection handler threads "
+ "to terminate");
+ if (! sdb_channel_shutdown(sock->chan))
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(handler_threads); ++i)
+ pthread_join(handler_threads[i], NULL);
+ /* else: we tried our best; let the operating system clean up */
+
+ sdb_channel_destroy(sock->chan);
+ sock->chan = NULL;
+ return 0;
+} /* sdb_fe_sock_listen_and_server */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index 6a679ce8a17baa5900573ee38cf0deb3e22882cb..a4217ac814aa35c632f334aaae8d22e13f79a46d 100644 (file)
/*
* sdb_plugin_init_all:
* Initialize all plugins using their registered "init" function.
+ *
+ * Returns:
+ * The number of failed initializations.
*/
int
sdb_plugin_init_all(void);
index 6d4b0d3bac074ffd1bc748216dcfbc3a56e0080e..0fe278d731ccd3e639f294506e02eafeb0743a7d 100644 (file)
#ifndef DAEMON_CONFIG_H
#define DAEMON_CONFIG_H 1
+/*
+ * parse result values
+ */
+
+extern char **listen_addresses;
+extern size_t listen_addresses_num;
+
+/*
+ * daemon_parse_config:
+ * Parse the specified configuration file.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value when loading the configuration failed because of errors
+ * in the daemon or libsysdb
+ * - a positive value on parser errors
+ */
int
daemon_parse_config(const char *filename);
diff --git a/src/include/frontend/sock.h b/src/include/frontend/sock.h
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SysDB - src/include/frontend/sock.h
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDB_FRONTEND_SOCK_H
+#define SDB_FRONTEND_SOCK_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* manage a front-end listener loop */
+typedef struct {
+ _Bool do_loop;
+} sdb_fe_loop_t;
+#define SDB_FE_LOOP_INIT { 1 }
+
+/*
+ * sdb_fe_socket_t:
+ * A front-end socket accepting connections from clients.
+ */
+typedef struct sdb_fe_socket sdb_fe_socket_t;
+
+/*
+ * sdb_fe_sock_create:
+ * Create a socket object.
+ *
+ * Returns:
+ * - a socket object on success
+ * - NULL else
+ */
+sdb_fe_socket_t *
+sdb_fe_sock_create(void);
+
+/*
+ * sdb_fe_sock_destroy:
+ * Shut down all listeners and destroy a socket object.
+ */
+void
+sdb_fe_sock_destroy(sdb_fe_socket_t *sock);
+
+/*
+ * sdb_fe_sock_add_listener:
+ * Tell the specified socket to listen on the specified 'address'.
+ * The address has to be specified as <type>:<address> where the following
+ * types are currently supported:
+ *
+ * - unix: listen on a UNIX socket
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_fe_sock_add_listener(sdb_fe_socket_t *sock, const char *address);
+
+/*
+ * sdb_fe_sock_listen_and_serve:
+ * Listen on the specified socket and serve client requests. The loop
+ * terminates on error or when the loop condition turns to false. All
+ * listening sockets will be closed at that time.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_fe_sock_listen_and_serve(sdb_fe_socket_t *sock, sdb_fe_loop_t *loop);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! SDB_FRONTEND_SOCK_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/include/utils/channel.h b/src/include/utils/channel.h
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SysDB - src/include/utils/channel.h
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SDB_UTILS_CHANNEL_H
+#define SDB_UTILS_CHANNEL_H 1
+
+#include "core/object.h"
+
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A channel is an asynchronous I/O multiplexer supporting multiple parallel
+ * readers and writers. A channel may be buffered (depending on its 'size'
+ * attribute). Writing fails unless buffer space is available and reading
+ * fails if no data is available.
+ */
+
+struct sdb_channel;
+typedef struct sdb_channel sdb_channel_t;
+
+/*
+ * sdb_channel_create:
+ * Create a new channel for elements of size 'elem_size'. At most, 'size'
+ * elements can be buffered in the channel (default: 1).
+ *
+ * Returns:
+ * - a channel object on success
+ * - a negative value else
+ */
+sdb_channel_t *
+sdb_channel_create(size_t size, size_t elem_size);
+
+/*
+ * sdb_channel_destroy:
+ * Removing all pending data and destroy the specified channel freeing its
+ * memory.
+ */
+void
+sdb_channel_destroy(sdb_channel_t *chan);
+
+/*
+ * sdb_channel_write:
+ * Write an element to a channel. The memory pointed to by 'data' is copied to
+ * the buffer based on the channel's element size.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_channel_write(sdb_channel_t *chan, const void *data);
+
+/*
+ * sdb_channel_read:
+ * Read an element from a channel. The element is copied to the location
+ * pointed to by 'data' which needs to be large enough to store a whole
+ * element based on the channel's element size.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_channel_read(sdb_channel_t *chan, void *data);
+
+/*
+ * sdb_channel_select:
+ * Wait for a channel to become "ready" for I/O. A channel is considered ready
+ * if it is possible to perform the corresponding I/O operation successfully
+ * *in some thread*. In case 'wantread' or 'read_data' is non-NULL, wait for
+ * data to be available in the channel for reading. In case 'wantwrite' or
+ * 'write_data' is non-NULL, wait for buffer space to be available for writing
+ * to the channel. If non-NULL, the value pointed to by the 'want...'
+ * arguments will be "true" iff the respective operation is ready. If the
+ * '..._data' arguments are non-NULL, the respective operation is executed
+ * atomically once the channel is ready for it. If 'timeout' is specified, the
+ * operation will time out with an error after the specified time has passed.
+ */
+int
+sdb_channel_select(sdb_channel_t *chan, int *wantread, void *read_data,
+ int *wantwrite, void *write_data, const struct timespec *timeout);
+
+/* sdb_channel_shutdown:
+ * Initiate a shutdown of the channel. Any subsequent writes will fail. Read
+ * operations will still be possible until the channel buffer is empty and
+ * then fail as well. Failing operations set errno to EBADF.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_channel_shutdown(sdb_channel_t *chan);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! SDB_UTILS_CHANNEL_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
index 4bf9d605ad74e8d46e28c262a8c92470acf6dc47..2742139f17db051d12b36c279889ff0c0bbfb48f 100644 (file)
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:
/*
* 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
*/
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:
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
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.
sdb_object_t *
sdb_llist_iter_get_next(sdb_llist_iter_t *iter);
+/*
+ * sdb_llist_iter_remove_current:
+ * Remove the current object from the list, that is, the object which was
+ * returned by the last call to sdb_llist_iter_get_next().
+ *
+ * This operation is not safe if another iterator is in use at the same time.
+ *
+ * Returns:
+ * - 0 on success
+ * - a negative value else
+ */
+int
+sdb_llist_iter_remove_current(sdb_llist_iter_t *iter);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
index 31435497f6d9b0a77673b76b74fd46d9966f44f6..9577a64f2956ce0a600ea9498adc485cf892f2c8 100644 (file)
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
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
--- /dev/null
+++ b/src/utils/channel.c
@@ -0,0 +1,279 @@
+/*
+ * SysDB - src/utils/channel.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils/channel.h"
+
+#include <assert.h>
+
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <time.h>
+
+#include <pthread.h>
+
+/*
+ * private data types
+ */
+
+struct sdb_channel {
+ pthread_mutex_t lock;
+
+ /* signaling for select() operation */
+ pthread_cond_t cond;
+
+ /* maybe TODO: add support for 'nil' values using a boolean area */
+
+ void *data;
+ size_t data_len;
+ size_t elem_size;
+
+ size_t head;
+ size_t tail;
+ _Bool full;
+
+ _Bool shutdown;
+};
+
+/*
+ * private helper functions
+ */
+
+#define NEXT_WRITE(chan) (((chan)->tail + 1) % (chan)->data_len)
+#define NEXT_READ(chan) (((chan)->head + 1) % (chan)->data_len)
+
+#define ELEM(chan, i) \
+ (void *)((char *)(chan)->data + (i) * (chan)->elem_size)
+#define TAIL(chan) ELEM(chan, (chan)->tail)
+#define HEAD(chan) ELEM(chan, (chan)->head)
+
+/* Insert a new element at the end; chan->lock must be held.
+ * Returns 0 if data has been written or if data may be written
+ * if 'data' is NULL. */
+static int
+channel_write(sdb_channel_t *chan, const void *data)
+{
+ assert(chan);
+
+ if (chan->full || chan->shutdown)
+ return -1;
+ else if (! data)
+ return 0;
+
+ memcpy(TAIL(chan), data, chan->elem_size);
+ chan->tail = NEXT_WRITE(chan);
+
+ chan->full = chan->tail == chan->head;
+ pthread_cond_broadcast(&chan->cond);
+ return 0;
+} /* channel_write */
+
+/* Retrieve the first element; chan->lock must be held.
+ * Returns 0 if data has been read or if data is available
+ * if 'data' is NULL. */
+static int
+channel_read(sdb_channel_t *chan, void *data)
+{
+ assert(chan);
+
+ if ((chan->head == chan->tail) && (! chan->full))
+ return -1;
+ else if (! data)
+ return 0;
+
+ memcpy(data, HEAD(chan), chan->elem_size);
+ chan->head = NEXT_READ(chan);
+
+ chan->full = 0;
+ pthread_cond_broadcast(&chan->cond);
+ return 0;
+} /* channel_read */
+
+/*
+ * public API
+ */
+
+sdb_channel_t *
+sdb_channel_create(size_t size, size_t elem_size)
+{
+ sdb_channel_t *chan;
+
+ if (! elem_size)
+ return NULL;
+ if (! size)
+ size = 1;
+
+ chan = calloc(1, sizeof(*chan));
+ if (! chan)
+ return NULL;
+
+ chan->data = calloc(size, elem_size);
+ if (! chan->data) {
+ sdb_channel_destroy(chan);
+ return NULL;
+ }
+
+ chan->data_len = size;
+ chan->elem_size = elem_size;
+
+ pthread_mutex_init(&chan->lock, /* attr = */ NULL);
+ pthread_cond_init(&chan->cond, /* attr = */ NULL);
+
+ chan->head = chan->tail = 0;
+ return chan;
+} /* sdb_channel_create */
+
+void
+sdb_channel_destroy(sdb_channel_t *chan)
+{
+ if (! chan)
+ return;
+
+ pthread_mutex_lock(&chan->lock);
+ free(chan->data);
+ chan->data = NULL;
+ chan->data_len = 0;
+
+ pthread_cond_destroy(&chan->cond);
+
+ pthread_mutex_unlock(&chan->lock);
+ pthread_mutex_destroy(&chan->lock);
+ free(chan);
+} /* sdb_channel_destroy */
+
+int
+sdb_channel_select(sdb_channel_t *chan, int *wantread, void *read_data,
+ int *wantwrite, void *write_data, const struct timespec *timeout)
+{
+ int status = 0;
+
+ if (! chan) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((! wantread) && (! read_data) && (! wantwrite) && (! write_data)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ pthread_mutex_lock(&chan->lock);
+ while (! status) {
+ int read_status, write_status;
+
+ read_status = channel_read(chan, read_data);
+ write_status = channel_write(chan, write_data);
+
+ if ((! read_status) || (! write_status)) {
+ if (wantread)
+ *wantread = read_status == 0;
+ if (wantwrite)
+ *wantwrite = write_status == 0;
+
+ if (((wantread || read_data) && (! read_status))
+ || ((wantwrite || write_data) && (! write_status)))
+ break;
+ }
+
+ if (chan->shutdown) {
+ if (read_status)
+ status = EBADF;
+ break;
+ }
+
+ if (timeout) {
+ struct timespec abstime;
+
+ if (clock_gettime(CLOCK_REALTIME, &abstime)) {
+ pthread_mutex_unlock(&chan->lock);
+ return -1;
+ }
+
+ abstime.tv_sec += timeout->tv_sec;
+ abstime.tv_nsec += timeout->tv_nsec;
+
+ if (abstime.tv_nsec > 1000000000) {
+ abstime.tv_nsec -= 1000000000;
+ abstime.tv_sec += 1;
+ }
+
+ status = pthread_cond_timedwait(&chan->cond, &chan->lock,
+ &abstime);
+ }
+ else
+ status = pthread_cond_wait(&chan->cond, &chan->lock);
+ }
+ pthread_mutex_unlock(&chan->lock);
+
+ if (status) {
+ errno = status;
+ return -1;
+ }
+ return 0;
+} /* sdb_channel_select */
+
+int
+sdb_channel_write(sdb_channel_t *chan, const void *data)
+{
+ int status;
+
+ if ((! chan) || (! data))
+ return -1;
+
+ pthread_mutex_lock(&chan->lock);
+ status = channel_write(chan, data);
+ pthread_mutex_unlock(&chan->lock);
+ return status;
+} /* sdb_channel_write */
+
+int
+sdb_channel_read(sdb_channel_t *chan, void *data)
+{
+ int status;
+
+ if ((! chan) || (! data))
+ return -1;
+
+ pthread_mutex_lock(&chan->lock);
+ status = channel_read(chan, data);
+ pthread_mutex_unlock(&chan->lock);
+ return status;
+} /* sdb_channel_read */
+
+int
+sdb_channel_shutdown(sdb_channel_t *chan)
+{
+ if (! chan)
+ return -1;
+ chan->shutdown = 1;
+ return 0;
+} /* sdb_channel_shutdown */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/utils/llist.c b/src/utils/llist.c
index dfc8fb99d18ecfeb6e38cc0a7339426854691704..0a4665d09296c2387bcf1f369f81f5e751c20fa4 100644 (file)
--- a/src/utils/llist.c
+++ b/src/utils/llist.c
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)
{
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)
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)
{
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 7c24b3a8a75733382274d3f34f4ea46a46283f80..4d8fc8b91ba234df10edd8d15d552169bcbf9930 100644 (file)
--- a/src/utils/strbuf.c
+++ b/src/utils/strbuf.c
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
+#include <string.h>
+
+#include <unistd.h>
/*
* private data structures
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)
{
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 e4cc8036641c6ac221dd2e80318812077f0681b7..1e4419dd7fcf053fae573e60278ce82520397ece 100644 (file)
--- a/t/Makefile.am
+++ b/t/Makefile.am
libsysdb_test_SOURCES = \
libsysdb_test.c libsysdb_test.h \
+ utils/channel_test.c \
utils/dbi_test.c \
utils/llist_test.c \
utils/strbuf_test.c
diff --git a/t/libsysdb_test.c b/t/libsysdb_test.c
index 3241aba495796d1b24261f317d8dbf271e25bcf4..7ce41247c679a8b9a2f636d5e9def467d8c1e311 100644 (file)
--- a/t/libsysdb_test.c
+++ b/t/libsysdb_test.c
size_t i;
suite_creator_t creators[] = {
- { util_llist_suite, NULL },
+ { util_channel_suite, NULL },
{ util_dbi_suite, NULL },
+ { util_llist_suite, NULL },
{ util_strbuf_suite, NULL },
};
diff --git a/t/libsysdb_test.h b/t/libsysdb_test.h
index 9e4be80d0a22b84e2210891cf9cfe517dfdfda40..da57246fe8f7e5fad93db503dea004b26fc5baf0 100644 (file)
--- a/t/libsysdb_test.h
+++ b/t/libsysdb_test.h
* test suites
*/
+/* t/utils/channel_test */
+Suite *
+util_channel_suite(void);
+
/* t/utils/dbi_test */
Suite *
util_dbi_suite(void);
diff --git a/t/utils/channel_test.c b/t/utils/channel_test.c
--- /dev/null
+++ b/t/utils/channel_test.c
@@ -0,0 +1,370 @@
+/*
+ * SysDB - t/utils/channel_test.c
+ * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils/channel.h"
+#include "libsysdb_test.h"
+
+#include <check.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+static struct {
+ int data;
+ int expected_write;
+ int expected_read;
+} golden_data_int[] = {
+ { 5, 0, 0 },
+ { 15, 0, 0 },
+ { -3, 0, 0 },
+ { INT_MAX, 0, 0 },
+ { 27, 0, 0 },
+ { 42, 0, 0 },
+ { 6, 0, 0 },
+ { 2854, 0, 0 },
+ { 10562, 0, 0 },
+ { 0, 0, 0 },
+
+ /* exceeding buffer size */
+ { 20, -1, -1 },
+ { 42, -1, -1 },
+};
+
+static struct {
+ char *data;
+ int expected_write;
+ int expected_read;
+} golden_data_string[] = {
+ { "c", 0, 0 },
+ { "", 0, 0 },
+ { "abc", 0, 0 },
+ { "foobar", 0, 0 },
+ { "qux", 0, 0 },
+ { "a b c", 0, 0 },
+ { "123", 0, 0 },
+ { "xyz", 0, 0 },
+ { "b", 0, 0 },
+ { "a", 0, 0 },
+
+ /* exceeding buffer size */
+ { "err1", -1, -1 },
+ { "err2", -1, -1 },
+};
+
+static sdb_channel_t *chan;
+
+static void
+setup_int(void)
+{
+ chan = sdb_channel_create(10, sizeof(int));
+ fail_unless(chan != NULL,
+ "sdb_channel_create(10, sizeof(int)) = NULL; "
+ "expected list object");
+} /* setup_int */
+
+static void
+setup_string(void)
+{
+ chan = sdb_channel_create(10, sizeof(char *));
+ fail_unless(chan != NULL,
+ "sdb_chan_create(10, sizeof(char *))) = NULL; "
+ "expected channel object");
+} /* setup_string */
+
+static void
+teardown(void)
+{
+ sdb_channel_destroy(chan);
+ chan = NULL;
+} /* teardown */
+
+START_TEST(test_create)
+{
+ chan = sdb_channel_create(0, 0);
+ fail_unless(chan == NULL,
+ "sdb_channel_create(0, 0) = %p; expected: NULL", chan);
+
+ chan = sdb_channel_create(0, 1);
+ fail_unless(chan != NULL,
+ "sdb_channel_create(0, 1) = NULL; expected: channel object");
+ sdb_channel_destroy(chan);
+
+ chan = sdb_channel_create(42, 23);
+ fail_unless(chan != NULL,
+ "sdb_channel_create(32, 23) = NULL; expected: channel object");
+ sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_write_read)
+{
+ uint32_t data;
+ int check;
+
+ chan = sdb_channel_create(0, 1);
+ fail_unless(chan != NULL,
+ "sdb_channel_create(0, 0) = NULL; expected: channel object");
+
+ data = 0x00ffff00;
+ check = sdb_channel_write(chan, &data);
+ fail_unless(!check, "sdb_channel_write() = %i; expected: 0", check);
+ check = sdb_channel_write(chan, &data);
+ fail_unless(check, "sdb_channel_write() = 0; expected: <0");
+
+ data = 0xffffffff;
+ check = sdb_channel_read(chan, &data);
+ /* result depends on endianess */
+ fail_unless((data == 0xffffff00) || (data == 0x00ffffff),
+ "sdb_channel_read() returned data %x; "
+ "expected: 0xffffff00 || 0x00ffffff", data);
+
+ sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_select)
+{
+ struct timespec ts = { 0, 10 };
+ int check;
+ int data;
+
+ chan = sdb_channel_create(0, 1);
+
+ errno = 0;
+ check = sdb_channel_select(chan, &data, NULL, NULL, NULL, &ts);
+ fail_unless(check < ETIMEDOUT,
+ "sdb_channel_select() = %i; expected: <0", check);
+ fail_unless(errno == ETIMEDOUT,
+ "sdb_channel_select() set errno to %i; expected: %i (ETIMEDOUT)",
+ errno, ETIMEDOUT);
+
+ check = sdb_channel_select(chan, NULL, NULL, &data, NULL, NULL);
+ fail_unless(! check, "sdb_channel_select() = %i; expected: 0", check);
+ check = sdb_channel_select(chan, NULL, NULL, &data, NULL, &ts);
+ fail_unless(! check, "sdb_channel_select() = %i; expected: 0", check);
+
+ sdb_channel_destroy(chan);
+}
+END_TEST
+
+START_TEST(test_write_int)
+{
+ size_t i;
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+ int data = golden_data_int[i].data;
+ int expected = golden_data_int[i].expected_write;
+
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_write(chan, %i) = %i; expected: %i",
+ data, check, expected);
+ }
+}
+END_TEST
+
+START_TEST(test_read_int)
+{
+ size_t i;
+
+ /* populate */
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+ int data = golden_data_int[i].data;
+ int expected = golden_data_int[i].expected_write;
+
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_write(chan, %i) = %i; expected: %i",
+ data, check, expected);
+ }
+
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+ int data = (int)i;
+ int expected = golden_data_int[i].expected_read;
+
+ int check = sdb_channel_read(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_read(chan, %i) = %i; expected: %i",
+ data, check, expected);
+ if (check) {
+ fail_unless(data == (int)i,
+ "sdb_channel_read() modified data to '%i'; "
+ "expected: no modification", data);
+ }
+ else {
+ fail_unless(data == golden_data_int[i].data,
+ "sdb_channel_read() returned data %i; expected: %i",
+ data, golden_data_int[i].data);
+ }
+ }
+}
+END_TEST
+
+START_TEST(test_write_read_int)
+{
+ size_t i;
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_int); ++i) {
+ int data = golden_data_int[i].data;
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == 0,
+ "sdb_channel_write(chan, %i) = %i; expected: 0",
+ data, check);
+
+ data = (int)i;
+ check = sdb_channel_read(chan, &data);
+ fail_unless(check == 0,
+ "sdb_channel_read(chan, %i) = %i; expected: 0",
+ data, check);
+ if (check) {
+ fail_unless(data == (int)i,
+ "sdb_channel_read() modified data to '%i'; "
+ "expected: no modification", data);
+ }
+ else {
+ fail_unless(data == golden_data_int[i].data,
+ "sdb_channel_read() returned data %i; expected: %i",
+ data, golden_data_int[i].data);
+ }
+ }
+}
+END_TEST
+
+START_TEST(test_write_string)
+{
+ size_t i;
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+ char *data = golden_data_string[i].data;
+ int expected = golden_data_string[i].expected_write;
+
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_write(chan, '%s') = %i; expected: %i",
+ data, check, expected);
+ }
+}
+END_TEST
+
+START_TEST(test_read_string)
+{
+ size_t i;
+
+ /* populate */
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+ char *data = golden_data_string[i].data;
+ int expected = golden_data_string[i].expected_write;
+
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_write(chan, '%s') = %i; expected: %i",
+ data, check, expected);
+ }
+
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+ char *data = NULL;
+ int expected = golden_data_string[i].expected_read;
+
+ int check = sdb_channel_read(chan, &data);
+ fail_unless(check == expected,
+ "sdb_channel_read(chan, '') = %i; expected: %i",
+ check, expected);
+ if (check) {
+ fail_unless(data == NULL,
+ "sdb_channel_read() modified data to '%s'; "
+ "expected: no modification", data);
+ }
+ else {
+ fail_unless(data != NULL,
+ "sdb_channel_read() did not return any data");
+ fail_unless(!strcmp(data, golden_data_string[i].data),
+ "sdb_channel_read() returned data '%s'; expected: '%s'",
+ data, golden_data_string[i].data);
+ }
+ }
+}
+END_TEST
+
+START_TEST(test_write_read_string)
+{
+ size_t i;
+ for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data_string); ++i) {
+ char *data = golden_data_string[i].data;
+ int check = sdb_channel_write(chan, &data);
+ fail_unless(check == 0,
+ "sdb_channel_write(chan, '%s') = %i; expected: 0",
+ data, check);
+
+ data = NULL;
+ check = sdb_channel_read(chan, &data);
+ fail_unless(check == 0,
+ "sdb_channel_read(chan, '') = %i; expected: 0", check);
+ if (check) {
+ fail_unless(data == NULL,
+ "sdb_channel_read() modified data to '%s'; "
+ "expected: no modifications", data);
+ }
+ else {
+ fail_unless(data != NULL,
+ "sdb_channel_read() did not return any data");
+ fail_unless(!strcmp(data, golden_data_string[i].data),
+ "sdb_channel_read() returned data '%s'; expected: '%s'",
+ data, golden_data_string[i].data);
+ }
+ }
+}
+END_TEST
+
+Suite *
+util_channel_suite(void)
+{
+ Suite *s = suite_create("utils::channel");
+ TCase *tc;
+
+ tc = tcase_create("core");
+ tcase_add_test(tc, test_create);
+ tcase_add_test(tc, test_write_read);
+ tcase_add_test(tc, test_select);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("integer");
+ tcase_add_checked_fixture(tc, setup_int, teardown);
+ tcase_add_test(tc, test_write_int);
+ tcase_add_test(tc, test_read_int);
+ tcase_add_test(tc, test_write_read_int);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("string");
+ tcase_add_checked_fixture(tc, setup_string, teardown);
+ tcase_add_test(tc, test_write_string);
+ tcase_add_test(tc, test_read_string);
+ tcase_add_test(tc, test_write_read_string);
+ suite_add_tcase(s, tc);
+
+ return s;
+} /* util_llist_suite */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/t/utils/llist_test.c b/t/utils/llist_test.c
index 638d2fbdbeef1affe64e15753a7e6ca08cd7633e..ede8200a42a325d3f22707eb001febdc8ac9b51e 100644 (file)
--- a/t/utils/llist_test.c
+++ b/t/utils/llist_test.c
}
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)
{
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 f007cbe20425bf1562522a4e2e64fbc1dba7258e..9e96b984a2a764d5d322fc60e1065e2ee9cb2b6b 100644 (file)
--- a/t/utils/strbuf_test.c
+++ b/t/utils/strbuf_test.c
* 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;
}
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;
}
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;
Suite *s = suite_create("utils::strbuf");
TCase *tc;
+ tc = tcase_create("empty");
+ tcase_add_test(tc, test_empty);
+ suite_add_tcase(s, tc);
+
tc = tcase_create("core");
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, test_sdb_strbuf_create);
tcase_add_test(tc, test_sdb_strbuf_append);
tcase_add_test(tc, test_sdb_strbuf_sprintf);
+ tcase_add_test(tc, test_sdb_strbuf_memcpy);
+ tcase_add_test(tc, test_sdb_strbuf_memappend);
tcase_add_test(tc, test_sdb_strbuf_chomp);
+ tcase_add_test(tc, test_sdb_strbuf_skip);
tcase_add_test(tc, test_sdb_strbuf_string);
tcase_add_test(tc, test_sdb_strbuf_len);
suite_add_tcase(s, tc);