ctdb-common: Add config file parsing code
authorAmitay Isaacs <amitay@gmail.com>
Wed, 13 Dec 2017 08:41:16 +0000 (19:41 +1100)
committerMartin Schwenke <martins@samba.org>
Sat, 12 May 2018 10:06:28 +0000 (12:06 +0200)
Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
ctdb/common/conf.c [new file with mode: 0644]
ctdb/common/conf.h [new file with mode: 0644]
ctdb/tests/cunit/conf_test_001.sh [new file with mode: 0755]
ctdb/tests/src/conf_test.c [new file with mode: 0644]
ctdb/wscript

diff --git a/ctdb/common/conf.c b/ctdb/common/conf.c
new file mode 100644 (file)
index 0000000..dccf661
--- /dev/null
@@ -0,0 +1,1259 @@
+/*
+   Configuration file handling on top of tini
+
+   Copyright (C) Amitay Isaacs  2017
+
+   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; either version 3 of the License, or
+   (at your option) any later version.
+
+   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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/locale.h"
+
+#include <talloc.h>
+
+#include "lib/util/dlinklist.h"
+#include "lib/util/tini.h"
+#include "lib/util/debug.h"
+
+#include "common/conf.h"
+
+struct conf_value {
+       enum conf_type type;
+       union {
+               const char *string;
+               int integer;
+               bool boolean;
+       } data;
+};
+
+union conf_pointer {
+       const char **string;
+       int *integer;
+       bool *boolean;
+};
+
+struct conf_option {
+       struct conf_option *prev, *next;
+
+       const char *name;
+       enum conf_type type;
+       void *validate;
+
+       struct conf_value default_value;
+       bool default_set;
+
+       struct conf_value *value, *new_value;
+       union conf_pointer ptr;
+       bool temporary_modified;
+};
+
+struct conf_section {
+       struct conf_section *prev, *next;
+
+       const char *name;
+       conf_validate_section_fn validate;
+       struct conf_option *option;
+};
+
+struct conf_context {
+       const char *filename;
+       struct conf_section *section;
+       bool define_failed;
+       bool ignore_unknown;
+       bool reload;
+       bool validation_active;
+};
+
+/*
+ * Functions related to conf_value
+ */
+
+static int string_to_string(TALLOC_CTX *mem_ctx,
+                           const char *str,
+                           const char **str_val)
+{
+       char *t;
+
+       if (str == NULL) {
+               return EINVAL;
+       }
+
+       t = talloc_strdup(mem_ctx, str);
+       if (t == NULL) {
+               return ENOMEM;
+       }
+
+       *str_val = t;
+       return 0;
+}
+
+static int string_to_integer(const char *str, int *int_val)
+{
+       long t;
+       char *endptr = NULL;
+
+       if (str == NULL) {
+               return EINVAL;
+       }
+
+       t = strtol(str, &endptr, 0);
+       if (*str != '\0' || endptr == NULL) {
+               if (t < 0 || t > INT_MAX) {
+                       return EINVAL;
+               }
+
+               *int_val = (int)t;
+               return 0;
+       }
+
+       return EINVAL;
+}
+
+static int string_to_boolean(const char *str, bool *bool_val)
+{
+       if (strcasecmp(str, "true") == 0) {
+               *bool_val = true;
+               return 0;
+       }
+
+       if (strcasecmp(str, "false") == 0) {
+               *bool_val = false;
+               return 0;
+       }
+
+       return EINVAL;
+}
+
+static int conf_value_from_string(TALLOC_CTX *mem_ctx,
+                                 const char *str,
+                                 struct conf_value *value)
+{
+       int ret;
+
+       switch (value->type) {
+       case CONF_STRING:
+               ret = string_to_string(mem_ctx, str, &value->data.string);
+               break;
+
+       case CONF_INTEGER:
+               ret = string_to_integer(str, &value->data.integer);
+               break;
+
+       case CONF_BOOLEAN:
+               ret = string_to_boolean(str, &value->data.boolean);
+               break;
+
+       default:
+               return ENOENT;
+       }
+
+       return ret;
+}
+
+static int conf_value_copy(TALLOC_CTX *mem_ctx,
+                          struct conf_value *src,
+                          struct conf_value *dst)
+{
+       if (src->type != dst->type) {
+               return EINVAL;
+       }
+
+       switch (src->type) {
+       case CONF_STRING:
+               if (dst->data.string != NULL) {
+                       talloc_free(discard_const(dst->data.string));
+               }
+               if (src->data.string == NULL) {
+                       dst->data.string = NULL;
+               } else {
+                       dst->data.string = talloc_strdup(
+                               mem_ctx, src->data.string);
+                       if (dst->data.string == NULL) {
+                               return ENOMEM;
+                       }
+               }
+               break;
+
+       case CONF_INTEGER:
+               dst->data.integer = src->data.integer;
+               break;
+
+       case CONF_BOOLEAN:
+               dst->data.boolean = src->data.boolean;
+               break;
+
+       default:
+               return ENOENT;
+       }
+
+       return 0;
+}
+
+static void conf_value_dump(const char *key,
+                           struct conf_value *value,
+                           bool is_default,
+                           bool is_temporary,
+                           FILE *fp)
+{
+       if ((value->type == CONF_STRING && value->data.string == NULL) ||
+           is_default) {
+               fprintf(fp, "\t# %s = ", key);
+       } else {
+               fprintf(fp, "\t%s = ", key);
+       }
+
+       switch (value->type) {
+       case CONF_STRING:
+               if (value->data.string != NULL) {
+                       fprintf(fp, "%s", value->data.string);
+               }
+               break;
+
+       case CONF_INTEGER:
+               fprintf(fp, "%d", value->data.integer);
+               break;
+
+       case CONF_BOOLEAN:
+               fprintf(fp, "%s", (value->data.boolean ? "true" : "false"));
+               break;
+       }
+
+       if (is_temporary) {
+               fprintf(fp, " # temporary");
+       }
+
+       fprintf(fp, "\n");
+}
+
+/*
+ * Functions related to conf_option
+ */
+
+static struct conf_option *conf_option_find(struct conf_section *s,
+                                           const char *key)
+{
+       struct conf_option *opt;
+
+       for (opt = s->option; opt != NULL; opt = opt->next) {
+               if (strcmp(opt->name, key) == 0) {
+                       return opt;
+               }
+       }
+
+       return NULL;
+}
+
+static void conf_option_set_ptr_value(struct conf_option *opt)
+{
+       switch (opt->type) {
+       case CONF_STRING:
+               if (opt->ptr.string != NULL) {
+                       *(opt->ptr.string) = opt->value->data.string;
+               }
+               break;
+
+       case CONF_INTEGER:
+               if (opt->ptr.integer != NULL) {
+                       *(opt->ptr.integer) = opt->value->data.integer;
+               }
+               break;
+
+       case CONF_BOOLEAN:
+               if (opt->ptr.boolean != NULL) {
+                       *(opt->ptr.boolean) = opt->value->data.boolean;
+               }
+               break;
+       }
+}
+
+static void conf_option_default(struct conf_option *opt);
+
+static int conf_option_add(struct conf_section *s,
+                          const char *key,
+                          enum conf_type type,
+                          void *validate,
+                          struct conf_option **popt)
+{
+       struct conf_option *opt;
+
+       opt = conf_option_find(s, key);
+       if (opt != NULL) {
+               D_ERR("conf: option \"%s\" already exists\n", key);
+               return EEXIST;
+       }
+
+       opt = talloc_zero(s, struct conf_option);
+       if (opt == NULL) {
+               return ENOMEM;
+       }
+
+       opt->name = talloc_strdup(opt, key);
+       if (opt->name == NULL) {
+               talloc_free(opt);
+               return ENOMEM;
+       }
+
+       opt->type = type;
+       opt->validate = validate;
+
+       DLIST_ADD_END(s->option, opt);
+
+       if (popt != NULL) {
+               *popt = opt;
+       }
+
+       return 0;
+}
+
+static int conf_option_set_default(struct conf_option *opt,
+                                  struct conf_value *default_value)
+{
+       int ret;
+
+       opt->default_value.type = opt->type;
+
+       ret = conf_value_copy(opt, default_value, &opt->default_value);
+       if (ret != 0) {
+               return ret;
+       }
+
+       opt->default_set = true;
+       opt->temporary_modified = false;
+
+       return 0;
+}
+
+static void conf_option_set_ptr(struct conf_option *opt,
+                               union conf_pointer *ptr)
+{
+       opt->ptr = *ptr;
+}
+
+static bool conf_option_validate_string(struct conf_option *opt,
+                                       struct conf_value *value,
+                                       enum conf_update_mode mode)
+{
+       conf_validate_string_option_fn validate =
+               (conf_validate_string_option_fn)opt->validate;
+
+       return validate(opt->name,
+                       opt->value->data.string,
+                       value->data.string,
+                       mode);
+}
+
+static bool conf_option_validate_integer(struct conf_option *opt,
+                                        struct conf_value *value,
+                                        enum conf_update_mode mode)
+{
+       conf_validate_integer_option_fn validate =
+               (conf_validate_integer_option_fn)opt->validate;
+
+       return validate(opt->name,
+                       opt->value->data.integer,
+                       value->data.integer,
+                       mode);
+}
+
+static bool conf_option_validate_boolean(struct conf_option *opt,
+                                        struct conf_value *value,
+                                        enum conf_update_mode mode)
+{
+       conf_validate_boolean_option_fn validate =
+               (conf_validate_boolean_option_fn)opt->validate;
+
+       return validate(opt->name,
+                       opt->value->data.boolean,
+                       value->data.boolean,
+                       mode);
+}
+
+static bool conf_option_validate(struct conf_option *opt,
+                                struct conf_value *value,
+                                enum conf_update_mode mode)
+{
+       int ret;
+
+       if (opt->validate == NULL) {
+               return true;
+       }
+
+       switch (opt->type) {
+       case CONF_STRING:
+               ret = conf_option_validate_string(opt, value, mode);
+               break;
+
+       case CONF_INTEGER:
+               ret = conf_option_validate_integer(opt, value, mode);
+               break;
+
+       case CONF_BOOLEAN:
+               ret = conf_option_validate_boolean(opt, value, mode);
+               break;
+
+       default:
+               ret = EINVAL;
+       }
+
+       return ret;
+}
+
+static int conf_option_new_value(struct conf_option *opt,
+                                struct conf_value *new_value,
+                                enum conf_update_mode mode)
+{
+       int ret;
+       bool ok;
+
+       ok = conf_option_validate(opt, new_value, mode);
+       if (!ok) {
+               D_ERR("conf: validation for option \"%s\" failed\n",
+                     opt->name);
+               return EINVAL;
+       }
+
+       TALLOC_FREE(opt->new_value);
+       opt->new_value = talloc_zero(opt, struct conf_value);
+       if (opt->new_value == NULL) {
+               return ENOMEM;
+       }
+
+       opt->new_value->type = opt->value->type;
+       ret = conf_value_copy(opt, new_value, opt->new_value);
+       if (ret != 0) {
+               return ret;
+       }
+
+       conf_option_set_ptr_value(opt);
+
+       if (mode == CONF_MODE_API) {
+               opt->temporary_modified = true;
+       } else {
+               opt->temporary_modified = false;
+       }
+
+       return 0;
+}
+
+static void conf_option_default(struct conf_option *opt)
+{
+       if (! opt->default_set) {
+               return;
+       }
+
+       if (opt->value != &opt->default_value) {
+               TALLOC_FREE(opt->value);
+       }
+
+       opt->value = &opt->default_value;
+       conf_option_set_ptr_value(opt);
+}
+
+static void conf_option_reset(struct conf_option *opt)
+{
+       TALLOC_FREE(opt->new_value);
+
+       conf_option_set_ptr_value(opt);
+}
+
+static void conf_option_update(struct conf_option *opt)
+{
+       if (opt->new_value == NULL) {
+               return;
+       }
+
+       if (opt->value != &opt->default_value) {
+               TALLOC_FREE(opt->value);
+       }
+
+       opt->value = opt->new_value;
+       opt->new_value = NULL;
+
+       conf_option_set_ptr_value(opt);
+}
+
+static bool conf_option_is_default(struct conf_option *opt)
+{
+       return (opt->value == &opt->default_value);
+}
+
+static void conf_option_dump(struct conf_option *opt, FILE *fp)
+{
+       bool is_default;
+
+       is_default = conf_option_is_default(opt);
+
+       conf_value_dump(opt->name,
+                       opt->value,
+                       is_default,
+                       opt->temporary_modified,
+                       fp);
+}
+
+/*
+ * Functions related to conf_section
+ */
+
+static struct conf_section *conf_section_find(struct conf_context *conf,
+                                             const char *section)
+{
+       struct conf_section *s;
+
+       for (s = conf->section; s != NULL; s = s->next) {
+               if (strcasecmp(s->name, section) == 0) {
+                       return s;
+               }
+       }
+
+       return NULL;
+}
+
+static int conf_section_add(struct conf_context *conf,
+                           const char *section,
+                           conf_validate_section_fn validate)
+{
+       struct conf_section *s;
+
+       s = conf_section_find(conf, section);
+       if (s != NULL) {
+               return EEXIST;
+       }
+
+       s = talloc_zero(conf, struct conf_section);
+       if (s == NULL) {
+               return ENOMEM;
+       }
+
+       s->name = talloc_strdup(s, section);
+       if (s->name == NULL) {
+               talloc_free(s);
+               return ENOMEM;
+       }
+
+       s->validate = validate;
+
+       DLIST_ADD_END(conf->section, s);
+       return 0;
+}
+
+static bool conf_section_validate(struct conf_context *conf,
+                                 struct conf_section *s,
+                                 enum conf_update_mode mode)
+{
+       bool ok;
+
+       if (s->validate == NULL) {
+               return true;
+       }
+
+       ok = s->validate(conf, s->name, mode);
+       if (!ok) {
+               D_ERR("conf: validation for section [%s] failed\n", s->name);
+       }
+
+       return ok;
+}
+
+static void conf_section_dump(struct conf_section *s, FILE *fp)
+{
+       fprintf(fp, "[%s]\n", s->name);
+}
+
+/*
+ * Functions related to conf_context
+ */
+
+static void conf_all_default(struct conf_context *conf)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       for (s = conf->section; s != NULL; s = s->next) {
+               for (opt = s->option; opt != NULL; opt = opt->next) {
+                       conf_option_default(opt);
+               }
+       }
+}
+
+static void conf_all_reset(struct conf_context *conf)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       for (s = conf->section; s != NULL; s = s->next) {
+               for (opt = s->option; opt != NULL; opt = opt->next) {
+                       conf_option_reset(opt);
+               }
+       }
+}
+
+static void conf_all_update(struct conf_context *conf)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       for (s = conf->section; s != NULL; s = s->next) {
+               for (opt = s->option; opt != NULL; opt = opt->next) {
+                       conf_option_update(opt);
+               }
+       }
+}
+
+/*
+ * API functions
+ */
+
+int conf_init(TALLOC_CTX *mem_ctx, struct conf_context **result)
+{
+       struct conf_context *conf;
+
+       conf = talloc_zero(mem_ctx, struct conf_context);
+       if (conf == NULL) {
+               return ENOMEM;
+       }
+
+       conf->define_failed = false;
+
+       *result = conf;
+       return 0;
+}
+
+void conf_define_section(struct conf_context *conf,
+                        const char *section,
+                        conf_validate_section_fn validate)
+{
+       int ret;
+
+       if (conf->define_failed) {
+               return;
+       }
+
+       if (section == NULL) {
+               conf->define_failed = true;
+               return;
+       }
+
+       ret = conf_section_add(conf, section, validate);
+       if (ret != 0) {
+               conf->define_failed = true;
+               return;
+       }
+}
+
+static struct conf_option *conf_define(struct conf_context *conf,
+                                      const char *section,
+                                      const char *key,
+                                      enum conf_type type,
+                                      conf_validate_string_option_fn validate)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+       int ret;
+
+       s = conf_section_find(conf, section);
+       if (s == NULL) {
+               D_ERR("conf: unknown section [%s]\n", section);
+               return NULL;
+       }
+
+       if (key == NULL) {
+               D_ERR("conf: option name null in section [%s]\n", section);
+               return NULL;
+       }
+
+       ret = conf_option_add(s, key, type, validate, &opt);
+       if (ret != 0) {
+               return NULL;
+       }
+
+       return opt;
+}
+
+static void conf_define_post(struct conf_context *conf,
+                            struct conf_option *opt,
+                            struct conf_value *default_value)
+{
+       int ret;
+
+       ret = conf_option_set_default(opt, default_value);
+       if (ret != 0) {
+               conf->define_failed = true;
+               return;
+       }
+
+       conf_option_default(opt);
+}
+
+void conf_define_string(struct conf_context *conf,
+                       const char *section,
+                       const char *key,
+                       const char *default_str_val,
+                       conf_validate_string_option_fn validate)
+{
+       struct conf_option *opt;
+       struct conf_value default_value;
+
+       if (! conf_valid(conf)) {
+               return;
+       }
+
+       opt = conf_define(conf, section, key, CONF_STRING, validate);
+       if (opt == NULL) {
+               conf->define_failed = true;
+               return;
+       }
+
+       default_value.type = CONF_STRING;
+       default_value.data.string = default_str_val;
+
+       conf_define_post(conf, opt, &default_value);
+}
+
+void conf_define_integer(struct conf_context *conf,
+                        const char *section,
+                        const char *key,
+                        const int default_int_val,
+                        conf_validate_integer_option_fn validate)
+{
+       struct conf_option *opt;
+       struct conf_value default_value;
+
+       if (! conf_valid(conf)) {
+               return;
+       }
+
+       opt = conf_define(conf, section, key, CONF_INTEGER, (void *)validate);
+       if (opt == NULL) {
+               conf->define_failed = true;
+               return;
+       }
+
+       default_value.type = CONF_INTEGER;
+       default_value.data.integer = default_int_val;
+
+       conf_define_post(conf, opt, &default_value);
+}
+
+
+void conf_define_boolean(struct conf_context *conf,
+                        const char *section,
+                        const char *key,
+                        const bool default_bool_val,
+                        conf_validate_boolean_option_fn validate)
+{
+       struct conf_option *opt;
+       struct conf_value default_value;
+
+       if (! conf_valid(conf)) {
+               return;
+       }
+
+       opt = conf_define(conf, section, key, CONF_BOOLEAN, (void *)validate);
+       if (opt == NULL) {
+               conf->define_failed = true;
+               return;
+       }
+
+       default_value.type = CONF_BOOLEAN;
+       default_value.data.boolean = default_bool_val;
+
+       conf_define_post(conf, opt, &default_value);
+}
+
+static struct conf_option *_conf_option(struct conf_context *conf,
+                                       const char *section,
+                                       const char *key)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       s = conf_section_find(conf, section);
+       if (s == NULL) {
+               return NULL;
+       }
+
+       opt = conf_option_find(s, key);
+       return opt;
+}
+
+void conf_assign_string_pointer(struct conf_context *conf,
+                               const char *section,
+                               const char *key,
+                               const char **str_ptr)
+{
+       struct conf_option *opt;
+       union conf_pointer ptr;
+
+       opt = _conf_option(conf, section, key);
+       if (opt == NULL) {
+               D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
+               conf->define_failed = true;
+               return;
+       }
+
+       if (opt->type != CONF_STRING) {
+               conf->define_failed = true;
+               return;
+       }
+
+       ptr.string = str_ptr;
+       conf_option_set_ptr(opt, &ptr);
+       conf_option_set_ptr_value(opt);
+}
+
+void conf_assign_integer_pointer(struct conf_context *conf,
+                                const char *section,
+                                const char *key,
+                                int *int_ptr)
+{
+       struct conf_option *opt;
+       union conf_pointer ptr;
+
+       opt = _conf_option(conf, section, key);
+       if (opt == NULL) {
+               D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
+               conf->define_failed = true;
+               return;
+       }
+
+       if (opt->type != CONF_INTEGER) {
+               conf->define_failed = true;
+               return;
+       }
+
+       ptr.integer = int_ptr;
+       conf_option_set_ptr(opt, &ptr);
+       conf_option_set_ptr_value(opt);
+}
+
+void conf_assign_boolean_pointer(struct conf_context *conf,
+                                const char *section,
+                                const char *key,
+                                bool *bool_ptr)
+{
+       struct conf_option *opt;
+       union conf_pointer ptr;
+
+       opt = _conf_option(conf, section, key);
+       if (opt == NULL) {
+               D_ERR("conf: unknown option [%s] -> \"%s\"\n", section, key);
+               conf->define_failed = true;
+               return;
+       }
+
+       if (opt->type != CONF_BOOLEAN) {
+               conf->define_failed = true;
+               return;
+       }
+
+       ptr.boolean = bool_ptr;
+       conf_option_set_ptr(opt, &ptr);
+       conf_option_set_ptr_value(opt);
+}
+
+bool conf_query(struct conf_context *conf,
+               const char *section,
+               const char *key,
+               enum conf_type *type)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       if (! conf_valid(conf)) {
+               return false;
+       }
+
+       s = conf_section_find(conf, section);
+       if (s == NULL) {
+               return false;
+       }
+
+       opt = conf_option_find(s, key);
+       if (opt == NULL) {
+               return false;
+       }
+
+       if (type != NULL) {
+               *type = opt->type;
+       }
+       return true;
+}
+
+bool conf_valid(struct conf_context *conf)
+{
+       if (conf->define_failed) {
+               return false;
+       }
+
+       return true;
+}
+
+void conf_set_defaults(struct conf_context *conf)
+{
+       conf_all_default(conf);
+}
+
+struct conf_load_state {
+       struct conf_context *conf;
+       struct conf_section *s;
+       enum conf_update_mode mode;
+       int err;
+};
+
+static bool conf_load_section(const char *section, void *private_data);
+static bool conf_load_option(const char *name,
+                            const char *value_str,
+                            void *private_data);
+
+static int conf_load_internal(struct conf_context *conf)
+{
+       struct conf_load_state state;
+       FILE *fp;
+       bool ok;
+
+       fp = fopen(conf->filename, "r");
+       if (fp == NULL) {
+               return errno;
+       }
+
+       state = (struct conf_load_state) {
+               .conf = conf,
+               .mode = (conf->reload ? CONF_MODE_RELOAD : CONF_MODE_LOAD),
+       };
+
+       ok = tini_parse(fp,
+                       false,
+                       conf_load_section,
+                       conf_load_option,
+                       &state);
+       fclose(fp);
+       if (!ok) {
+               goto fail;
+       }
+
+       /* Process the last section */
+       if (state.s != NULL) {
+               ok = conf_section_validate(conf, state.s, state.mode);
+               if (!ok) {
+                       state.err = EINVAL;
+                       goto fail;
+               }
+       }
+
+       conf_all_update(conf);
+       return 0;
+
+fail:
+       conf_all_reset(conf);
+       return state.err;
+}
+
+static bool conf_load_section(const char *section, void *private_data)
+{
+       struct conf_load_state *state =
+               (struct conf_load_state *)private_data;
+       bool ok;
+
+       if (state->s != NULL) {
+               ok = conf_section_validate(state->conf, state->s, state->mode);
+               if (!ok) {
+                       state->err = EINVAL;
+                       return false;
+               }
+       }
+
+       state->s = conf_section_find(state->conf, section);
+       if (state->s == NULL) {
+               if (state->conf->ignore_unknown) {
+                       D_DEBUG("conf: ignoring unknown section [%s]\n",
+                               section);
+               } else {
+                       D_ERR("conf: unknown section [%s]\n", section);
+                       state->err = EINVAL;
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static bool conf_load_option(const char *name,
+                            const char *value_str,
+                            void *private_data)
+{
+       struct conf_load_state *state =
+               (struct conf_load_state *)private_data;
+       struct conf_option *opt;
+       TALLOC_CTX *tmp_ctx;
+       struct conf_value value;
+       int ret;
+
+       if (state->s == NULL) {
+               if (state->conf->ignore_unknown) {
+                       D_DEBUG("conf: ignoring unknown option \"%s\"\n",
+                               name);
+                       return true;
+               } else {
+                       D_ERR("conf: unknown option \"%s\"\n", name);
+                       state->err = EINVAL;
+                       return false;
+               }
+       }
+
+       opt = conf_option_find(state->s, name);
+       if (opt == NULL) {
+               if (state->conf->ignore_unknown) {
+                       return true;
+               } else {
+                       state->err = ENOENT;
+                       return false;
+               }
+       }
+
+       tmp_ctx = talloc_new(state->conf);
+       if (tmp_ctx == NULL) {
+               state->err = ENOMEM;
+               return false;
+       }
+
+       value.type = opt->type;
+       ret = conf_value_from_string(tmp_ctx, value_str, &value);
+       if (ret != 0) {
+               talloc_free(tmp_ctx);
+               state->err = ret;
+               return false;
+       }
+
+       ret = conf_option_new_value(opt, &value, state->mode);
+       if (ret != 0) {
+               talloc_free(tmp_ctx);
+               state->err = ret;
+               return false;
+       }
+
+       talloc_free(tmp_ctx);
+       return true;
+
+}
+
+int conf_load(struct conf_context *conf,
+             const char *filename,
+             bool ignore_unknown)
+{
+       conf->filename = talloc_strdup(conf, filename);
+       if (conf->filename == NULL) {
+               return ENOMEM;
+       }
+
+       conf->ignore_unknown = ignore_unknown;
+
+       D_NOTICE("Reading config file %s\n", filename);
+
+       return conf_load_internal(conf);
+}
+
+int conf_reload(struct conf_context *conf)
+{
+       int ret;
+
+       if (conf->filename == NULL) {
+               return EPERM;
+       }
+
+       D_NOTICE("Re-reading config file %s\n", conf->filename);
+
+       conf->reload = true;
+       ret = conf_load_internal(conf);
+       conf->reload = false;
+
+       return ret;
+}
+
+static int conf_set(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   struct conf_value *value)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+       int ret;
+       bool ok;
+
+       s = conf_section_find(conf, section);
+       if (s == NULL) {
+               return ENOENT;
+       }
+
+       opt = conf_option_find(s, key);
+       if (opt == NULL) {
+               return ENOENT;
+       }
+
+       if (opt->type != value->type) {
+               return ENOENT;
+       }
+
+       ret = conf_option_new_value(opt, value, CONF_MODE_API);
+       if (ret != 0) {
+               conf_option_reset(opt);
+               return ret;
+       }
+
+       ok = conf_section_validate(conf, s, CONF_MODE_API);
+       if (!ok) {
+               conf_option_reset(opt);
+               return EINVAL;
+       }
+
+       conf_option_update(opt);
+       return 0;
+}
+
+int conf_set_string(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   const char *str_val)
+{
+       struct conf_value value;
+
+       value.type = CONF_STRING;
+       value.data.string = str_val;
+
+       return conf_set(conf, section, key, &value);
+}
+
+int conf_set_integer(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    int int_val)
+{
+       struct conf_value value;
+
+       value.type = CONF_INTEGER;
+       value.data.integer = int_val;
+
+       return conf_set(conf, section, key, &value);
+}
+
+int conf_set_boolean(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    bool bool_val)
+{
+       struct conf_value value;
+
+       value.type = CONF_BOOLEAN;
+       value.data.boolean = bool_val;
+
+       return conf_set(conf, section, key, &value);
+}
+
+static int conf_get(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   enum conf_type type,
+                   const struct conf_value **value,
+                   bool *is_default)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       s = conf_section_find(conf, section);
+       if (s == NULL) {
+               return ENOENT;
+       }
+
+       opt = conf_option_find(s, key);
+       if (opt == NULL) {
+               return ENOENT;
+       }
+
+       if (opt->type != type) {
+               return EINVAL;
+       }
+
+       *value = opt->value;
+       if (is_default != NULL) {
+               *is_default = conf_option_is_default(opt);
+       }
+
+       return 0;
+}
+
+int conf_get_string(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   const char **str_val,
+                   bool *is_default)
+{
+       const struct conf_value *value;
+       int ret;
+
+       ret = conf_get(conf, section, key, CONF_STRING, &value, is_default);
+       if (ret != 0) {
+               return ret;
+       }
+
+       *str_val = value->data.string;
+       return 0;
+}
+
+int conf_get_integer(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    int *int_val,
+                    bool *is_default)
+{
+       const struct conf_value *value;
+       int ret;
+
+       ret = conf_get(conf, section, key, CONF_INTEGER, &value, is_default);
+       if (ret != 0) {
+               return ret;
+       }
+
+       *int_val = value->data.integer;
+       return 0;
+}
+
+int conf_get_boolean(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    bool *bool_val,
+                    bool *is_default)
+{
+       const struct conf_value *value;
+       int ret;
+
+       ret = conf_get(conf, section, key, CONF_BOOLEAN, &value, is_default);
+       if (ret != 0) {
+               return ret;
+       }
+
+       *bool_val = value->data.boolean;
+       return 0;
+}
+
+void conf_dump(struct conf_context *conf, FILE *fp)
+{
+       struct conf_section *s;
+       struct conf_option *opt;
+
+       for (s = conf->section; s != NULL; s = s->next) {
+               conf_section_dump(s, fp);
+               for (opt = s->option; opt != NULL; opt = opt->next) {
+                       conf_option_dump(opt, fp);
+               }
+       }
+}
diff --git a/ctdb/common/conf.h b/ctdb/common/conf.h
new file mode 100644 (file)
index 0000000..6b152c1
--- /dev/null
@@ -0,0 +1,473 @@
+/*
+   Configuration file handling on top of tini
+
+   Copyright (C) Amitay Isaacs  2017
+
+   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; either version 3 of the License, or
+   (at your option) any later version.
+
+   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, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __CTDB_CONF_H__
+#define __CTDB_CONF_H__
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <talloc.h>
+
+/**
+ * @file conf.h
+ *
+ * @brief Configuration file handling with sections and key-value pairs
+ *
+ * CTDB settings can be written in a configuration file ctdb.conf (similar to
+ * samba's smb.conf).  Various daemons and tools will consult the configuration
+ * file for runtime settings.
+ *
+ * The configuration will be organized in sections depending on various
+ * components. Each section will have various configuration options in the form
+ * of key-value pairs.
+ *
+ * [section1]
+ *     key1 = value1
+ *     ...
+ *
+ * [section2]
+ *     key2 = value2
+ *     ...
+ *
+ * ...
+ *
+ */
+
+/**
+ * @brief Abstract data structure holding the configuration options
+ */
+struct conf_context;
+
+/**
+ * @brief configuration option update mode
+ *
+ * When a value of configuration option is changed, update mode is set
+ * appropriately.
+ *
+ * CONF_MODE_API - value modified using set functions
+ * CONF_MODE_LOAD - value modified via conf_load
+ * CONF_MODE_RELOAD - value modified via conf_reload
+ */
+enum conf_update_mode {
+       CONF_MODE_API,
+       CONF_MODE_LOAD,
+       CONF_MODE_RELOAD,
+};
+
+/**
+ * @brief configuration option type
+ */
+enum conf_type {
+       CONF_STRING,
+       CONF_INTEGER,
+       CONF_BOOLEAN,
+};
+
+/**
+ * @brief Configuration section validation function
+ *
+ * Check if all the configuration options are consistent with each-other
+ */
+typedef bool (*conf_validate_section_fn)(struct conf_context *conf,
+                                        const char *section,
+                                        enum conf_update_mode mode);
+
+/**
+ * @brief Configuration option validation function for string
+ *
+ * Check if a configuration option value is valid
+ */
+typedef bool (*conf_validate_string_option_fn)(const char *key,
+                                              const char *old_value,
+                                              const char *new_value,
+                                              enum conf_update_mode mode);
+
+/**
+ * @brief Configuration option validation function for integer
+ *
+ * Check if a configuration option value is valid
+ */
+typedef bool (*conf_validate_integer_option_fn)(const char *key,
+                                               int old_value,
+                                               int new_value,
+                                               enum conf_update_mode mode);
+
+/**
+ * @brief Configuration option validation function for boolean
+ *
+ * Check if a configuration option value is valid
+ */
+typedef bool (*conf_validate_boolean_option_fn)(const char *key,
+                                               bool old_value,
+                                               bool new_value,
+                                               enum conf_update_mode mode);
+
+/**
+ * @brief Initialize configuration option database
+ *
+ * This return a new configuration options context.  Freeing this context will
+ * free up all the memory associated with the configuration options.
+ *
+ * @param[in] mem_ctx  Talloc memory context
+ * @param[in] result  The new configuration options context
+ * @return 0 on success, errno on failure
+ */
+int conf_init(TALLOC_CTX *mem_ctx, struct conf_context **result);
+
+/**
+ * @brief Define a section for organizing configuration options
+ *
+ * This functions creates a section to organize configuration option.  The
+ * section names are case-insensitive and are always stored in lower case.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] validate  The validation function for configuration options
+ */
+void conf_define_section(struct conf_context *conf,
+                        const char *section,
+                        conf_validate_section_fn validate);
+
+/**
+ * @brief Define a configuration option which has a string value
+ *
+ * This functions adds a new configuration option organized under a given
+ * section.  Configuration options are case-insensitive and are always stored
+ * in lower case.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] default_value  The default value for the configuration option
+ * @param[in] validate  The validation function for the configuration option
+ */
+void conf_define_string(struct conf_context *conf,
+                       const char *section,
+                       const char *key,
+                       const char *default_value,
+                       conf_validate_string_option_fn validate);
+
+/**
+ * @brief Define a configuration option which has an integer value
+ *
+ * This functions adds a new configuration option organized under a given
+ * section.  Configuration options are case-insensitive and are always stored
+ * in lower case.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] default_value  The default value for the configuration option
+ * @param[in] validate  The validation function for the configuration option
+ */
+void conf_define_integer(struct conf_context *conf,
+                        const char *section,
+                        const char *key,
+                        const int default_value,
+                        conf_validate_integer_option_fn validate);
+
+/**
+ * @brief Define a configuration option which has an boolean value
+ *
+ * This functions adds a new configuration option organized under a given
+ * section.  Configuration options are case-insensitive and are always stored
+ * in lower case.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] default_value  The default value for the configuration option
+ * @param[in] validate  The validation function for the configuration option
+ */
+void conf_define_boolean(struct conf_context *conf,
+                        const char *section,
+                        const char *key,
+                        const bool default_value,
+                        conf_validate_boolean_option_fn validate);
+
+/**
+ * @brief Assign user-accessible pointer for string option
+ *
+ * This pointer can be used for accessing the value of configuration option
+ * directly without requiring a function call.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] ptr  User-accessible pointer to the value
+ */
+void conf_assign_string_pointer(struct conf_context *conf,
+                               const char *section,
+                               const char *key,
+                               const char **ptr);
+
+/**
+ * @brief Assign user-accessible pointer for integer option
+ *
+ * This pointer can be used for accessing the value of configuration option
+ * directly without requiring a function call.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] ptr  User-accessible pointer to the value
+ */
+void conf_assign_integer_pointer(struct conf_context *conf,
+                                const char *section,
+                                const char *key,
+                                int *ptr);
+
+/**
+ * @brief Assign user-accessible pointer for boolean option
+ *
+ * This pointer can be used for accessing the value of configuration option
+ * directly without requiring a function call.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[in] ptr  User-accessible pointer to the value
+ * @return true on success, false on failure
+ */
+void conf_assign_boolean_pointer(struct conf_context *conf,
+                                const char *section,
+                                const char *key,
+                                bool *ptr);
+
+/**
+ * @brief Query a configuration option
+ *
+ * This function checks if a configuration option is defined or not.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of the section
+ * @param[in] key  The name of the configuration option
+ * @param[out] type  The type of the configuration option
+ * @return true on success, false if section/option is not defined
+ */
+bool conf_query(struct conf_context *conf,
+               const char *section,
+               const char *key,
+               enum conf_type *type);
+
+/**
+ * @brief Check if the defined configuration options are valid
+ *
+ * This function must be called after creating configuration options
+ * to confirm that all the option definitions are valid.
+ *
+ * @param[in] conf  The configuration options context
+ * @return true on success, false on failure
+ */
+bool conf_valid(struct conf_context *conf);
+
+/**
+ * @brief Set the default values for all configuration options
+ *
+ * This function resets all the configuration options to their default values.
+ *
+ * @param[in] conf  The connfiguration options context
+ */
+void conf_set_defaults(struct conf_context *conf);
+
+/**
+ * @brief Load the values for configuration option values from a file
+ *
+ * This function will update the values of the configuration options from those
+ * specified in a file.  This function will fail in case it encounters an
+ * undefined option.  Any sections which are not defined, will be ignored.
+ *
+ * This function will call validation function (if specified) before updating
+ * the value of a configuration option.  After updating all the values for a
+ * section, the validation for section (if specified) will be called.  If any
+ * of the validation functions return error, then all the configuration
+ * options will be reset to their previous values.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] filename  The configuration file
+ * @param[in] skip_unknown  Whether unknown config options should be ignored
+ * @return 0 on success, errno on failure
+ */
+int conf_load(struct conf_context *conf,
+             const char *filename,
+             bool ignore_unknown);
+
+/**
+ * @brief Reload the values for configuration options
+ *
+ * This function will re-load the values of the configuration options.  This
+ * function can be called only after succesful call to conf_load().
+ *
+ * @see conf_load
+ *
+ * @param[in] conf  The configuration options context
+ * @return 0 on success, errno on failure.
+ */
+int conf_reload(struct conf_context *conf);
+
+/**
+ * @brief Set the string value of a configuration option
+ *
+ * This function can be used to update the value of a configuration option.
+ * This will call the validation function for that option (if defined) and
+ * the section validation function (if defined).
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option should not be changed via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[in] str_val  The string value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_set_string(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   const char *str_val);
+
+/**
+ * @brief Set the integer value of a configuration option
+ *
+ * This function can be used to update the value of a configuration option.
+ * This will call the validation function for that option (if defined) and
+ * the section validation function (if defined).
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option should not be changed via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[in] int_val  The integer value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_set_integer(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    int int_val);
+
+/**
+ * @brief Set the boolean value of a configuration option
+ *
+ * This function can be used to update the value of a configuration option.
+ * This will call the validation function for that option (if defined) and
+ * the section validation function (if defined).
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option should not be changed via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[in] bool_val  The boolean value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_set_boolean(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    bool bool_val);
+
+/**
+ * @brief Get the string value of a configuration option
+ *
+ * This function can be used to fetch the current value of a configuration
+ * option.
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option can be accessed directly via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[out] str_val  The string value of the configuration option
+ * @param[out] is_default  True if the value is default value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_get_string(struct conf_context *conf,
+                   const char *section,
+                   const char *key,
+                   const char **str_val,
+                   bool *is_default);
+
+/**
+ * @brief Get the integer value of a configuration option
+ *
+ * This function can be used to fetch the current value of a configuration
+ * option.
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option can be accessed directly via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[out] int_val  The integer value of the configuration option
+ * @param[out] is_default  True if the value is default value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_get_integer(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    int *int_val,
+                    bool *is_default);
+
+/**
+ * @brief Get the boolean value of a configuration option
+ *
+ * This function can be used to fetch the current value of a configuration
+ * option.
+ *
+ * If a user-defined storage pointer is provided, then the value of a
+ * configuration option can be accessed directly via that pointer.
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] section  The name of a section
+ * @param[in] key  The name of a configuration option
+ * @param[out] bool_val  The boolean value of the configuration option
+ * @param[out] is_default  True if the value is default value
+ * @return 0 on success, errno in case of failure
+ */
+int conf_get_boolean(struct conf_context *conf,
+                    const char *section,
+                    const char *key,
+                    bool *bool_val,
+                    bool *is_default);
+
+/**
+ * @brief Dump the configuration in a file
+ *
+ * All the configuration options are dumped with their current values.
+ * If an option has a default value, then it is commented.
+ *
+ * Here is a sample output:
+ *
+ * [section1]
+ *     key1 = value1
+ *     key2 = value2
+ *     # key3 = default_value3
+ * [section2]
+ *     key4 = value4
+ *
+ * @param[in] conf  The configuration options context
+ * @param[in] fp  File pointer
+ */
+void conf_dump(struct conf_context *conf, FILE *fp);
+
+#endif /* __CTDB_CONF_H__ */
diff --git a/ctdb/tests/cunit/conf_test_001.sh b/ctdb/tests/cunit/conf_test_001.sh
new file mode 100755 (executable)
index 0000000..e83e04c
--- /dev/null
@@ -0,0 +1,124 @@
+#!/bin/sh
+
+. "${TEST_SCRIPTS_DIR}/unit.sh"
+
+conffile="${TEST_VAR_DIR}/config.$$"
+
+remove_files ()
+{
+       rm -f "$conffile"
+}
+
+test_cleanup remove_files
+
+ok_null
+unit_test conf_test 1
+
+ok <<EOF
+conf: unknown section [section1]
+EOF
+unit_test conf_test 2
+
+ok <<EOF
+conf: option "key1" already exists
+EOF
+unit_test conf_test 3
+
+ok <<EOF
+conf: option "key1" already exists
+EOF
+unit_test conf_test 4
+
+ok_null
+unit_test conf_test 5
+
+ok_null
+unit_test conf_test 6
+
+ok <<EOF
+conf: validation for option "key1" failed
+conf: validation for option "key2" failed
+conf: validation for option "key3" failed
+EOF
+unit_test conf_test 7
+
+cat > "$conffile" <<EOF
+[section1]
+EOF
+
+required_result 22 <<EOF
+conf: validation for section [section1] failed
+[section1]
+       # key1 = default
+EOF
+unit_test conf_test 8 "$conffile"
+
+cat > "$conffile" <<EOF
+[section1]
+    key1 = unknown
+EOF
+
+required_result 22 <<EOF
+conf: validation for section [section1] failed
+[section1]
+       # key1 = default
+EOF
+unit_test conf_test 8 "$conffile"
+
+cat > "$conffile" <<EOF
+
+[section1]
+    key1 = value2  
+    key2 =     20  # comment
+key3    =    false
+EOF
+
+ok <<EOF
+[section1]
+       key1 = value2
+       key2 = 20
+       key3 = false
+EOF
+unit_test conf_test 9 "$conffile"
+
+cat > "$conffile" <<EOF
+[section1]
+key1 = value2  
+EOF
+
+ok <<EOF
+[section1]
+       key1 = value2
+       # key2 = 10
+       key3 = false # temporary
+EOF
+unit_test conf_test 9 "$conffile"
+
+cat > "$conffile" <<EOF
+[section2]
+    foo = bar
+EOF
+
+required_result 22 <<EOF
+conf: unknown section [section2]
+[section1]
+       # key1 = value1
+       # key2 = 10
+       key3 = false # temporary
+EOF
+unit_test conf_test 10 "$conffile"
+
+cat > "$conffile" <<EOF
+[section1]
+    key1 = value2
+    foo = bar
+    key2 = 20
+EOF
+
+required_result 2 <<EOF
+[section1]
+       # key1 = value1
+       # key2 = 10
+       key3 = false # temporary
+EOF
+unit_test conf_test 10 "$conffile"
diff --git a/ctdb/tests/src/conf_test.c b/ctdb/tests/src/conf_test.c
new file mode 100644 (file)
index 0000000..a1bca69
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+   Configuration file handling on top of tini
+
+   Copyright (C) Amitay Isaacs  2017
+
+   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; either version 3 of the License, or
+   (at your option) any later version.
+
+   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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <assert.h>
+
+#include "common/conf.c"
+
+static void test1(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_section(conf, NULL, NULL);
+       status = conf_valid(conf);
+       assert(status == false);
+
+       talloc_free(mem_ctx);
+}
+
+static void test2(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_string(conf, "section1", "key1", "default", NULL);
+       status = conf_valid(conf);
+       assert(status == false);
+
+       talloc_free(mem_ctx);
+}
+
+static void test3(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", NULL, NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", "value1", NULL);
+       status = conf_valid(conf);
+       assert(status == false);
+
+       talloc_free(mem_ctx);
+}
+
+static void test4(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", NULL, NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_integer(conf, "section1", "key1", 10, NULL);
+       status = conf_valid(conf);
+       assert(status == false);
+
+       talloc_free(mem_ctx);
+}
+
+static void test5(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       enum conf_type type;
+       int ret;
+       bool status;
+       const char *s_val;
+       int i_val;
+       bool b_val;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", "value1", NULL);
+       conf_define_integer(conf, "section1", "key2", 10, NULL);
+       conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+       conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+       conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+       conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+       status = conf_valid(conf);
+       assert(status == true);
+
+       status = conf_query(conf, "section1", "key1", &type);
+       assert(status == true);
+       assert(type == CONF_STRING);
+
+       status = conf_query(conf, "section1", "key2", &type);
+       assert(status == true);
+       assert(type == CONF_INTEGER);
+
+       status = conf_query(conf, "section1", "key3", &type);
+       assert(status == true);
+       assert(type == CONF_BOOLEAN);
+
+       assert(strcmp(s_val, "value1") == 0);
+       assert(i_val == 10);
+       assert(b_val == true);
+
+       conf_set_defaults(conf);
+
+       assert(strcmp(s_val, "value1") == 0);
+       assert(i_val == 10);
+       assert(b_val == true);
+
+       talloc_free(mem_ctx);
+}
+
+static void test6(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+       const char *s_val, *s2_val;
+       int i_val, i2_val;
+       bool b_val, b2_val, is_default;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", "default", NULL);
+       conf_define_integer(conf, "section1", "key2", 10, NULL);
+       conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+       conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+       conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+       conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+       status = conf_valid(conf);
+       assert(status == true);
+
+       is_default = false;
+       ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default);
+       assert(ret == 0);
+       assert(strcmp(s2_val, "default") == 0);
+       assert(is_default == true);
+
+       is_default = false;
+       ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default);
+       assert(ret == 0);
+       assert(i2_val == 10);
+       assert(is_default == true);
+
+       is_default = false;
+       ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default);
+       assert(ret == 0);
+       assert(b2_val == true);
+       assert(is_default == true);
+
+       ret = conf_set_string(conf, "section1", "key1", "foobar");
+       assert(ret == 0);
+
+       ret = conf_set_integer(conf, "section1", "key2", 20);
+       assert(ret == 0);
+
+       ret = conf_set_boolean(conf, "section1", "key3", false);
+       assert(ret == 0);
+
+       assert(strcmp(s_val, "foobar") == 0);
+       assert(i_val == 20);
+       assert(b_val == false);
+
+       is_default = true;
+       ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default);
+       assert(ret == 0);
+       assert(strcmp(s2_val, "foobar") == 0);
+       assert(is_default == false);
+
+       is_default = true;
+       ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default);
+       assert(ret == 0);
+       assert(i2_val == 20);
+       assert(is_default == false);
+
+       is_default = true;
+       ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default);
+       assert(ret == 0);
+       assert(b2_val == false);
+       assert(is_default == false);
+
+       conf_set_defaults(conf);
+
+       assert(strcmp(s_val, "default") == 0);
+       assert(i_val == 10);
+       assert(b_val == true);
+
+       talloc_free(mem_ctx);
+}
+
+static bool test7_validate_string(const char *key,
+                                 const char *old_value, const char *new_value,
+                                 enum conf_update_mode mode)
+{
+       return false;
+}
+
+static bool test7_validate_integer(const char *key,
+                                  int old_value, int new_value,
+                                  enum conf_update_mode mode)
+{
+       return false;
+}
+
+static bool test7_validate_boolean(const char *key,
+                                  bool old_value, bool new_value,
+                                  enum conf_update_mode mode)
+{
+       return false;
+}
+
+static void test7(void)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+       const char *s_val, *s2_val;
+       int i_val, i2_val;
+       bool b_val, b2_val;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", "default",
+                          test7_validate_string);
+       conf_define_integer(conf, "section1", "key2", 10,
+                           test7_validate_integer);
+       conf_define_boolean(conf, "section1", "key3", true,
+                           test7_validate_boolean);
+
+       conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+       conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+       conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+       status = conf_valid(conf);
+       assert(status == true);
+
+       ret = conf_set_string(conf, "section1", "key1", "foobar");
+       assert(ret == EINVAL);
+
+       ret = conf_set_integer(conf, "section1", "key2", 20);
+       assert(ret == EINVAL);
+
+       ret = conf_set_boolean(conf, "section1", "key3", false);
+       assert(ret == EINVAL);
+
+       assert(strcmp(s_val, "default") == 0);
+       assert(i_val == 10);
+       assert(b_val == true);
+
+       ret = conf_get_string(conf, "section1", "key2", &s2_val, NULL);
+       assert(ret == EINVAL);
+
+       ret = conf_get_integer(conf, "section1", "key3", &i2_val, NULL);
+       assert(ret == EINVAL);
+
+       ret = conf_get_boolean(conf, "section1", "key1", &b2_val, NULL);
+       assert(ret == EINVAL);
+
+       talloc_free(mem_ctx);
+}
+
+static bool test8_validate(struct conf_context *conf,
+                          const char *section,
+                          enum conf_update_mode mode)
+{
+       return false;
+}
+
+static void test8(const char *filename)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", test8_validate);
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_define_string(conf, "section1", "key1", "default", NULL);
+
+       status = conf_valid(conf);
+       assert(status == true);
+
+       ret = conf_load(conf, filename, true);
+       conf_dump(conf, stdout);
+
+       talloc_free(mem_ctx);
+       exit(ret);
+}
+
+static void test9(const char *filename, bool ignore_unknown)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       struct conf_context *conf;
+       int ret;
+       bool status;
+
+       ret = conf_init(mem_ctx, &conf);
+       assert(ret == 0);
+       assert(conf != NULL);
+
+       conf_define_section(conf, "section1", NULL);
+
+       conf_define_string(conf, "section1", "key1", "value1", NULL);
+       conf_define_integer(conf, "section1", "key2", 10, NULL);
+       conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+       status = conf_valid(conf);
+       assert(status == true);
+
+       conf_set_boolean(conf, "section1", "key3", false);
+
+       ret = conf_load(conf, filename, ignore_unknown);
+       conf_dump(conf, stdout);
+
+       talloc_free(mem_ctx);
+       exit(ret);
+}
+
+int main(int argc, const char **argv)
+{
+       int num;
+
+       if (argc < 2) {
+               fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]);
+               exit(1);
+       }
+
+       num = atoi(argv[1]);
+       if (num > 7 && argc != 3) {
+               fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]);
+               exit(1);
+       }
+
+       switch (num) {
+       case 1:
+               test1();
+               break;
+
+       case 2:
+               test2();
+               break;
+
+       case 3:
+               test3();
+               break;
+
+       case 4:
+               test4();
+               break;
+
+       case 5:
+               test5();
+               break;
+
+       case 6:
+               test6();
+               break;
+
+       case 7:
+               test7();
+               break;
+
+       case 8:
+               test8(argv[2]);
+               break;
+
+       case 9:
+               test9(argv[2], true);
+               break;
+
+       case 10:
+               test9(argv[2], false);
+               break;
+
+       }
+
+       return 0;
+}
index 797420b4c7ecaa779bafc2fa86048b01023910ad..7c417aab93eadbcc8e180e85f2e0ece29a4bc786 100644 (file)
@@ -400,7 +400,7 @@ def build(bld):
                                              pidfile.c run_proc.c
                                              hash_count.c run_event.c
                                              sock_client.c version.c
-                                             cmdline.c path.c
+                                             cmdline.c path.c conf.c
                                           '''),
                         deps='''samba-util sys_rw tevent-util
                                 replace talloc tevent tdb popt''')
@@ -756,7 +756,8 @@ def build(bld):
         'sock_io_test',
         'hash_count_test',
         'run_event_test',
-        'cmdline_test'
+        'cmdline_test',
+        'conf_test',
     ]
 
     for target in ctdb_unit_tests: