Add libconfig implementation and tests
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Sat, 14 Dec 2013 06:01:29 +0000 (01:01 -0500)
committerDavid Goulet <dgoulet@efficios.com>
Tue, 17 Dec 2013 19:22:11 +0000 (14:22 -0500)
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Signed-off-by: David Goulet <dgoulet@efficios.com>
12 files changed:
.gitignore
configure.ac
src/common/config/Makefile.am
src/common/config/config.c [new file with mode: 0644]
src/common/config/config.h [new file with mode: 0644]
src/common/defaults.h
tests/unit/Makefile.am
tests/unit/ini_config/Makefile.am [new file with mode: 0644]
tests/unit/ini_config/ini_config.c [new file with mode: 0644]
tests/unit/ini_config/sample.ini [new file with mode: 0644]
tests/unit/ini_config/test_ini_config [new file with mode: 0755]
tests/unit_tests

index b11a8c3226c594b40494172ef03de62df71a4114..a3396951b308d22771a7569414877292852e4ac6 100644 (file)
@@ -79,5 +79,6 @@ tests/regression/ust/fork/fork2
 tests/regression/ust/libc-wrapper/prog
 tests/utils/testapp/gen-ust-nevents/gen-ust-nevents
 tests/regression/tools/live/live_test
+tests/unit/ini_config/ini_config
 
 benchmark/
index 15c224471ada82886bfe78091ab70b7dfa4aa49e..1065200f1db22fbc1538171b5eedfd788e6a4a75 100644 (file)
@@ -431,6 +431,7 @@ AC_CONFIG_FILES([
        tests/regression/ust/java-jul/Makefile
        tests/stress/Makefile
        tests/unit/Makefile
+       tests/unit/ini_config/Makefile
        tests/utils/Makefile
        tests/utils/tap/Makefile
        tests/utils/testapp/Makefile
index 9e91b943dabfd41f415b1230762c6c4203741e94..057c137128a741c356cc777509fe3d99576b913d 100644 (file)
@@ -1,3 +1,5 @@
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src
+
 noinst_LTLIBRARIES = libconfig.la
 
-libconfig_la_SOURCES = ini.c ini.h
+libconfig_la_SOURCES = ini.c ini.h config.c config.h
diff --git a/src/common/config/config.c b/src/common/config/config.c
new file mode 100644 (file)
index 0000000..e945a19
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2013 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License, version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <config.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <common/defaults.h>
+#include <common/error.h>
+#include <common/macros.h>
+#include <common/utils.h>
+
+#include "config.h"
+
+struct handler_filter_args {
+       const char* section;
+       config_entry_handler_cb handler;
+       void *user_data;
+};
+
+const char * const config_str_yes = "yes";
+const char * const config_str_true = "true";
+const char * const config_str_on = "on";
+const char * const config_str_no = "no";
+const char * const config_str_false = "false";
+const char * const config_str_off = "off";
+
+static int config_entry_handler_filter(struct handler_filter_args *args,
+               const char *section, const char *name, const char *value)
+{
+       int ret = 0;
+       struct config_entry entry = { section, name, value };
+
+       assert(args);
+
+       if (!section || !name || !value) {
+               ret = -EIO;
+               goto end;
+       }
+
+       if (args->section) {
+               if (strcmp(args->section, section)) {
+                       goto end;
+               }
+       }
+
+       ret = args->handler(&entry, args->user_data);
+end:
+       return ret;
+}
+
+LTTNG_HIDDEN
+int config_get_section_entries(const char *override_path, const char *section,
+               config_entry_handler_cb handler, void *user_data)
+{
+       int ret = 0;
+       FILE *config_file = NULL;
+       struct handler_filter_args filter = { section, handler, user_data };
+
+       if (override_path) {
+               config_file = fopen(override_path, "r");
+               if (config_file) {
+                       DBG("Loaded daemon configuration file at %s",
+                               override_path);
+               } else {
+                       ERR("Failed to open daemon configuration file at %s",
+                               override_path);
+                       ret = -ENOENT;
+                       goto end;
+               }
+       } else {
+               char *path = utils_get_home_dir();
+
+               /* Try to open the user's daemon configuration file */
+               if (path) {
+                       ret = asprintf(&path, DEFAULT_DAEMON_HOME_CONFIGPATH, path);
+                       if (ret < 0) {
+                               goto end;
+                       }
+
+                       ret = 0;
+                       config_file = fopen(path, "r");
+                       if (config_file) {
+                               DBG("Loaded daemon configuration file at %s", path);
+                       }
+
+                       free(path);
+               }
+
+               /* Try to open the system daemon configuration file */
+               if (!config_file) {
+                       config_file = fopen(DEFAULT_DAEMON_HOME_CONFIGPATH, "r");
+               }
+       }
+
+       if (!config_file) {
+               DBG("No daemon configuration file found.");
+               goto end;
+       }
+
+       ret = ini_parse_file(config_file,
+                       (ini_entry_handler) config_entry_handler_filter, (void *) &filter);
+
+end:
+       return ret;
+}
+
+LTTNG_HIDDEN
+int config_parse_value(const char *value)
+{
+       int i, ret = 0;
+       char *endptr, *lower_str;
+       size_t len;
+       unsigned long v;
+
+       len = strlen(value);
+       if (!len) {
+               ret = -1;
+               goto end;
+       }
+
+       v = strtoul(value, &endptr, 10);
+       if (endptr != value) {
+               ret = v;
+               goto end;
+       }
+
+       lower_str = zmalloc(len + 1);
+       if (!lower_str) {
+               PERROR("zmalloc");
+               ret = -errno;
+               goto end;
+       }
+
+       for (i = 0; i < len; i++) {
+               lower_str[i] = tolower(value[i]);
+       }
+
+       if (!strcmp(lower_str, config_str_yes) ||
+               !strcmp(lower_str, config_str_true) ||
+               !strcmp(lower_str, config_str_on)) {
+               ret = 1;
+       } else if (!strcmp(lower_str, config_str_no) ||
+               !strcmp(lower_str, config_str_false) ||
+               !strcmp(lower_str, config_str_off)) {
+               ret = 0;
+       } else {
+               ret = -1;
+       }
+
+       free(lower_str);
+end:
+       return ret;
+}
diff --git a/src/common/config/config.h b/src/common/config/config.h
new file mode 100644 (file)
index 0000000..73e39fc
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License, version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+#include <common/config/ini.h>
+#include <common/macros.h>
+
+struct config_entry {
+       /* section is NULL if the entry is not in a section */
+       const char *section;
+       const char *name;
+       const char *value;
+};
+
+/*
+ * A config_entry_handler_cb receives config_entry structures belonging to the
+ * sections the handler has been registered to.
+ *
+ * The config_entry and its members are only valid for the duration of the call
+ * and must not be freed.
+ *
+ * config_entry_handler_cb may return negative value to indicate an error in
+ * the configuration file.
+ */
+typedef int (*config_entry_handler_cb)(const struct config_entry *, void *);
+
+/*
+ * Read a section's entries in an INI configuration file.
+ *
+ * path may be NULL, in which case the following paths will be tried:
+ *     1) $HOME/.lttng/lttng.conf
+ *     2) /etc/lttng/lttng.conf
+ *
+ * handler will only be called with entries belonging to the provided section.
+ * If section is NULL, all entries will be relayed to handler. If section is
+ * "", only the global entries are relayed.
+ *
+ * Returns 0 on success. Negative values are error codes. If the return value
+ * is positive, it represents the line number on which a parsing error occured.
+ */
+LTTNG_HIDDEN
+int config_get_section_entries(const char *path, const char *section,
+               config_entry_handler_cb handler, void *user_data);
+
+/*
+ * Parse a configuration value.
+ *
+ * This function expects either an unsigned integer or a boolean text option.
+ * The following strings are recognized: true, yes, on, false, no and off.
+ *
+ * Returns either the value of the parsed integer, or 0/1 if a boolean text
+ * string was recognized. Negative values indicate an error.
+ */
+LTTNG_HIDDEN
+int config_parse_value(const char *value);
+
+#endif /* _CONFIG_H */
index 6502345426fde7f1d3eb9c394c977a420797f7d6..7452280d9042fd0602283d8caf105ce48af26460 100644 (file)
 #define DEFAULT_GLOBAL_RELAY_HEALTH_UNIX_SOCK          DEFAULT_LTTNG_RUNDIR "/relayd/health-%d"
 #define DEFAULT_HOME_RELAY_HEALTH_UNIX_SOCK            DEFAULT_LTTNG_HOME_RUNDIR "/relayd/health-%d"
 
+/* Default daemon configuration file path */
+#define DEFAULT_DAEMON_CONFIG_FILE              "lttng.conf"
+#define DEFAULT_DAEMON_HOME_CONFIGPATH          DEFAULT_LTTNG_HOME_RUNDIR "/" \
+       DEFAULT_DAEMON_CONFIG_FILE
+#define DEFAULT_DAEMON_SYSTEM_CONFIGPATH        CONFIG_LTTNG_SYSTEM_CONFIGDIR \
+       "/lttng/" DEFAULT_DAEMON_CONFIG_FILE
+
 #define DEFAULT_GLOBAL_APPS_UNIX_SOCK \
        DEFAULT_LTTNG_RUNDIR "/" LTTNG_UST_SOCK_FILENAME
 #define DEFAULT_HOME_APPS_UNIX_SOCK \
index 945dd001ecb7149021c1f5256b42e95c69f8afc6..ec8d22f620638f8580f60af1b99e48a6845f47a5 100644 (file)
@@ -1,3 +1,5 @@
+SUBDIRS = ini_config
+
 AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/tests/utils/ -I$(srcdir)
 AM_LDFLAGS =
 
diff --git a/tests/unit/ini_config/Makefile.am b/tests/unit/ini_config/Makefile.am
new file mode 100644 (file)
index 0000000..98956f7
--- /dev/null
@@ -0,0 +1,26 @@
+AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/tests/utils/
+
+LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la
+LIBCONFIG=$(top_builddir)/src/common/config/libconfig.la
+LIBCOMMON=$(top_builddir)/src/common/libcommon.la
+LIBHASHTABLE=$(top_builddir)/src/common/hashtable/libhashtable.la
+
+noinst_PROGRAMS = ini_config
+EXTRA_DIST = test_ini_config
+
+ini_config_SOURCES = ini_config.c
+ini_config_LDADD = $(LIBTAP) $(LIBCONFIG) $(LIBCOMMON) $(LIBHASHTABLE)
+
+all-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       cp -f $(srcdir)/$$script $(builddir); \
+               done; \
+       fi
+
+clean-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       rm -f $(builddir)/$$script; \
+               done; \
+       fi
diff --git a/tests/unit/ini_config/ini_config.c b/tests/unit/ini_config/ini_config.c
new file mode 100644 (file)
index 0000000..38fe5f4
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) - 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by as
+ * published by the Free Software Foundation; only version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <tap/tap.h>
+#include <common/config/config.h>
+#include <common/utils.h>
+#include <string.h>
+
+struct state {
+       int section_1;
+       int section_2;
+       int section_3;
+       int section_global;
+       int text_entry;
+       int int_entry;
+};
+
+int lttng_opt_quiet = 1;
+int lttng_opt_verbose = 0;
+
+int entry_handler(const struct config_entry *entry,
+               struct state *state)
+{
+       int ret = 0;
+
+       if (!entry || !state) {
+               ret = -1;
+               goto end;
+       }
+
+       if (!strcmp(entry->section, "section1")) {
+               state->section_1 = 1;
+               if (!strcmp(entry->name, "section1_entry") &&
+                       !strcmp(entry->value, "42")) {
+                       state->int_entry = 1;
+               }
+       }
+
+       if (!strcmp(entry->section, "section2")) {
+               state->section_2 = 1;
+       }
+
+       if (!strcmp(entry->section, "section 3")) {
+               state->section_3 = 1;
+               if (!strcmp(entry->name, "name with a space") &&
+                       !strcmp(entry->value, "another value")) {
+                       state->text_entry = 1;
+               }
+       }
+
+       if (!strcmp(entry->section, "")) {
+               state->section_global = 1;
+       }
+end:
+       return ret;
+}
+
+int main(int argc, char **argv)
+{
+       char *path = NULL;
+       int ret;
+       struct state state = {};
+
+       if (argc < 2) {
+               diag("Usage: path_to_sample_INI_file");
+               goto end;
+       }
+
+       path = utils_expand_path(argv[1]);
+       if (!path) {
+               fail("Failed to resolve sample INI file path")
+       }
+
+       plan_no_plan();
+       ret = config_get_section_entries(path, NULL,
+               (config_entry_handler_cb)entry_handler, &state);
+       ok(ret == 0, "Successfully opened a config file, registered to all sections");
+       ok(state.section_1 && state.section_2 && state.section_3 &&
+               state.section_global, "Processed entries from each sections");
+       ok(state.text_entry, "Text value parsed correctly");
+
+       memset(&state, 0, sizeof(struct state));
+       ret = config_get_section_entries(path, "section1",
+               (config_entry_handler_cb)entry_handler, &state);
+       ok(ret == 0, "Successfully opened a config file, registered to one section");
+       ok(state.section_1 && !state.section_2 && !state.section_3 &&
+               !state.section_global, "Processed an entry from section1 only");
+       ok(state.int_entry, "Int value parsed correctly");
+
+       memset(&state, 0, sizeof(struct state));
+       ret = config_get_section_entries(path, "",
+               (config_entry_handler_cb)entry_handler, &state);
+       ok(ret == 0, "Successfully opened a config file, registered to the global section");
+       ok(!state.section_1 && !state.section_2 && !state.section_3 &&
+               state.section_global, "Processed an entry from the global section only");
+end:
+       free(path);
+       return exit_status();
+}
diff --git a/tests/unit/ini_config/sample.ini b/tests/unit/ini_config/sample.ini
new file mode 100644 (file)
index 0000000..2a9c4a9
--- /dev/null
@@ -0,0 +1,11 @@
+global_entry=yes
+
+[section1]
+section1_entry=42
+
+[section2]
+    section2_test_entry1=2
+section2_test_entry2=3
+
+[section 3]
+name with a space = another value
diff --git a/tests/unit/ini_config/test_ini_config b/tests/unit/ini_config/test_ini_config
new file mode 100755 (executable)
index 0000000..57a3710
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (C) 2013 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; only version 2
+# of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+CURDIR=$(dirname $0)/
+ROOTDIR=$CURDIR/../..
+
+$CURDIR/ini_config $CURDIR/sample.ini
index 561a94c77747a2c45fb433d6994fdea913546dbf..e577e0e327c5b2afd4e6ac5e1ebe040dad0dd143 100644 (file)
@@ -4,3 +4,4 @@ unit/test_uri
 unit/test_ust_data
 unit/test_utils_parse_size_suffix
 unit/test_utils_expand_path
+unit/ini_config/test_ini_config
This page took 0.048125 seconds and 4 git commands to generate.