From 1501a7f32e1abf3e805053d4241c9796882d56bc Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Sat, 14 Dec 2013 01:01:29 -0500 Subject: [PATCH] Add libconfig implementation and tests MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémie Galarneau Signed-off-by: David Goulet --- .gitignore | 1 + configure.ac | 1 + src/common/config/Makefile.am | 4 +- src/common/config/config.c | 173 ++++++++++++++++++++++++++ src/common/config/config.h | 73 +++++++++++ src/common/defaults.h | 7 ++ tests/unit/Makefile.am | 2 + tests/unit/ini_config/Makefile.am | 26 ++++ tests/unit/ini_config/ini_config.c | 113 +++++++++++++++++ tests/unit/ini_config/sample.ini | 11 ++ tests/unit/ini_config/test_ini_config | 22 ++++ tests/unit_tests | 1 + 12 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 src/common/config/config.c create mode 100644 src/common/config/config.h create mode 100644 tests/unit/ini_config/Makefile.am create mode 100644 tests/unit/ini_config/ini_config.c create mode 100644 tests/unit/ini_config/sample.ini create mode 100755 tests/unit/ini_config/test_ini_config diff --git a/.gitignore b/.gitignore index b11a8c322..a3396951b 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/configure.ac b/configure.ac index 15c224471..1065200f1 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/common/config/Makefile.am b/src/common/config/Makefile.am index 9e91b943d..057c13712 100644 --- a/src/common/config/Makefile.am +++ b/src/common/config/Makefile.am @@ -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 index 000000000..e945a19fb --- /dev/null +++ b/src/common/config/config.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013 - Jérémie Galarneau + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 index 000000000..73e39fc32 --- /dev/null +++ b/src/common/config/config.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2013 - Jérémie Galarneau + * + * 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 +#include + +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 */ diff --git a/src/common/defaults.h b/src/common/defaults.h index 650234542..7452280d9 100644 --- a/src/common/defaults.h +++ b/src/common/defaults.h @@ -108,6 +108,13 @@ #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 \ diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 945dd001e..ec8d22f62 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -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 index 000000000..98956f76a --- /dev/null +++ b/tests/unit/ini_config/Makefile.am @@ -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 index 000000000..38fe5f4f2 --- /dev/null +++ b/tests/unit/ini_config/ini_config.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) - 2013 Jérémie Galarneau + * + * 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 +#include +#include +#include + +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 index 000000000..2a9c4a9f6 --- /dev/null +++ b/tests/unit/ini_config/sample.ini @@ -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 index 000000000..57a3710e3 --- /dev/null +++ b/tests/unit/ini_config/test_ini_config @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Copyright (C) 2013 - Jérémie Galarneau +# +# 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 diff --git a/tests/unit_tests b/tests/unit_tests index 561a94c77..e577e0e32 100644 --- a/tests/unit_tests +++ b/tests/unit_tests @@ -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 -- 2.34.1