Hi, these three patches add infrastructure for this libini feature https://fedorahosted.org/sssd/wiki/DesignDocs/libini-config-file-checks
I did not add the patch for ini_allowed_sections validator. Will add that validator when these patches are in master. I also did not bump the version number for now. Will do this before release. Any comments are welcome. Thanks Michal
>From 0dfb08ae318869518a1c8dd042b3c6751895f932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzi...@redhat.com> Date: Wed, 3 Feb 2016 18:51:49 +0100 Subject: [PATCH 1/3] ini: Add infrastructure for validators Ticket: https://fedorahosted.org/sssd/ticket/133 Add infrastructure for implementing internal and extrenal config file validators. --- ini/ini_config_priv.h | 13 +++ ini/ini_configobj.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++ ini/ini_configobj.h | 173 ++++++++++++++++++++++++++++ ini/libini_config.sym | 11 ++ 4 files changed, 503 insertions(+) diff --git a/ini/ini_config_priv.h b/ini/ini_config_priv.h index 38813c9..a03eda5 100644 --- a/ini/ini_config_priv.h +++ b/ini/ini_config_priv.h @@ -109,5 +109,18 @@ int access_check_int(struct stat *file_stats, mode_t mode, mode_t mask); +struct ini_errmsg; + +struct ini_errobj { + size_t count; + struct ini_errmsg *first_msg; + struct ini_errmsg *last_msg; + struct ini_errmsg *cur_msg; +}; + +struct ini_errmsg { + char *str; + struct ini_errmsg *next; +}; #endif diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c index 8ae5ea7..b2c5000 100644 --- a/ini/ini_configobj.c +++ b/ini/ini_configobj.c @@ -25,6 +25,7 @@ #include <string.h> #include <stdint.h> #include <stdlib.h> +#include <stdarg.h> /* For error text */ #include <libintl.h> #define _(String) gettext (String) @@ -1039,3 +1040,308 @@ int ini_config_get_errors(struct ini_cfgobj *cfg_ctx, TRACE_FLOW_EXIT(); return error; } + +int ini_read_rules_from_file(const char *filename, + struct ini_cfgobj **_rules_obj) +{ + int ret; + struct ini_cfgfile *cfgfile = NULL; + + if (_rules_obj == NULL) { + return EINVAL; + } + + ret = ini_config_create(_rules_obj); + if (ret != EOK) { + return ret; + } + + ret = ini_config_file_open(filename, 0, &cfgfile); + if (ret != EOK) { + goto done; + } + + ret = ini_config_parse(cfgfile, 0 , INI_MV1S_ALLOW, 0, *_rules_obj); + if (ret != EOK) { + goto done; + } + +done: + if (ret != EOK) { + ini_config_destroy(*_rules_obj); + } + + ini_config_file_destroy(cfgfile); + return ret; +} + +/* This is used for testing only */ +static int ini_dummy_noerror(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + return 0; +} + +/* This is used for testing only */ +static int ini_dummy_error(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + return ini_errobj_add_msg(errobj, "Error"); +} + + +static ini_validator_func * +get_validator(char *validator_name, + struct ini_validator *validators, + int num_validators) +{ + /* First we check all internal validators */ + if (strcmp(validator_name, "ini_dummy_noerror") == 0) { + return ini_dummy_noerror; + } else if (strcmp(validator_name, "ini_dummy_error") == 0) { + return ini_dummy_error; + } + + /* Now check the custom validators */ + if (validators == NULL) { + return NULL; + } + + for (int i = 0; i < num_validators; i++) { + if (strcmp(validator_name, validators[i].name) == 0) { + return validators[i].func; + } + } + + return NULL; +} + +int ini_rules_check(struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_validator *extra_validators, + int num_extra_validators, + struct ini_errobj *errobj) +{ + char **sections; + int ret; + int num_sections; + char *vname; + ini_validator_func *vfunc; + struct value_obj *vo = NULL; + struct ini_errobj *localerr = NULL; + + /* Get all sections from the rules object */ + sections = ini_get_section_list(rules_obj, &num_sections, &ret); + if (ret != EOK) { + return ret; + } + + /* Now iterate through all the sections. If the section + * name begins with a prefix "rule/", then it is a rule + * name. */ + for (int i = 0; i < num_sections; i++) { + if (!strncmp(sections[i], "rule/", strlen("rule/"))) { + ret = ini_get_config_valueobj(sections[i], + "validator", + rules_obj, + INI_GET_FIRST_VALUE, + &vo); + if (ret != 0) { + /* Failed to get value object. This should not + * happen. */ + continue; + } + + if (vo == NULL) { + ret = ini_errobj_add_msg(errobj, + "Rule '%s' has no validator.", + sections[i]); + if (ret != EOK) { + return ret; + } + /* Skip problematic rule */ + continue; + } + + vname = ini_get_string_config_value(vo, NULL); + vfunc = get_validator(vname, extra_validators, + num_extra_validators); + if (vfunc == NULL) { + ret = ini_errobj_add_msg(errobj, + "Rule '%s' uses unknown " + "validator '%s'.", + sections[i], vname); + if (ret != EOK) { + goto done; + } + /* Skip problematic rule */ + free(vname); + continue; + } + free(vname); + + /* Do not pass global errobj to validators, they + * could corrupt it. Create local one for each + * validator. */ + ret = ini_errobj_create(&localerr); + if (ret != EOK) { + goto done; + } + + ret = vfunc(sections[i], rules_obj, config_obj, localerr); + if (ret != 0) { + /* Just report the error and continue normally, + * maybe there are some errors in localerr */ + ret = ini_errobj_add_msg(errobj, + "Rule '%s' returned error code '%d'", + sections[i], ret); + if (ret != EOK) { + goto done; + } + } + + /* Bad validator could destroy the localerr, check + * for NULL */ + if (localerr == NULL) { + continue; + } + + ini_errobj_reset(localerr); + while (!ini_errobj_no_more_msgs(localerr)) { + ret = ini_errobj_add_msg(errobj, + "[%s]: %s", + sections[i], + ini_errobj_get_msg(localerr)); + if (ret != EOK) { + goto done; + } + ini_errobj_next(localerr); + } + + ini_errobj_destroy(&localerr); + } + } + + ret = EOK; +done: + ini_free_section_list(sections); + ini_errobj_destroy(&localerr); + return ret; +} + +/* This is just convenience function, so that + * we manipulate with ini_rules_* functions. */ +void ini_rules_destroy(struct ini_cfgobj *rules) +{ + ini_config_destroy(rules); +} + +int ini_errobj_create(struct ini_errobj **_errobj) +{ + struct ini_errobj *new_errobj = NULL; + + if (_errobj == NULL) { + return EINVAL; + } + + new_errobj = calloc(1, sizeof(struct ini_errobj)); + if (new_errobj == NULL) { + return ENOMEM; + } + + *_errobj = new_errobj; + return EOK; +} + +void ini_errobj_destroy(struct ini_errobj **errobj) +{ + struct ini_errmsg *to_remove; + + if (errobj == NULL || *errobj == NULL) { + return; + } + + while ((*errobj)->first_msg) { + to_remove = (*errobj)->first_msg; + (*errobj)->first_msg = (*errobj)->first_msg->next; + free(to_remove->str); + free(to_remove); + } + + free(*errobj); + *errobj = NULL; +} + +int ini_errobj_add_msg(struct ini_errobj *errobj, const char *format, ...) +{ + int ret; + va_list args; + struct ini_errmsg *new; + + new = calloc(1, sizeof(struct ini_errmsg)); + if (new == NULL) { + return ENOMEM; + } + + va_start(args, format); + ret = vasprintf(&new->str, format, args); + va_end(args); + if (ret == -1) { + free(new); + return ENOMEM; + } + + if (errobj->count == 0) { + /* First addition to the list, all pointers are NULL */ + errobj->first_msg = new; + errobj->last_msg = new; + errobj->cur_msg = new; + errobj->count++; + } else { + errobj->last_msg->next = new; + errobj->last_msg = errobj->last_msg->next; + errobj->count++; + } + + return EOK; +} + +void ini_errobj_reset(struct ini_errobj *errobj) +{ + errobj->cur_msg = errobj->first_msg; +} + +const char *ini_errobj_get_msg(struct ini_errobj *errobj) +{ + if (errobj->cur_msg != NULL) { + return errobj->cur_msg->str; + } + + /* Should this be allowed? */ + return NULL; +} + +void ini_errobj_next(struct ini_errobj *errobj) +{ + if (errobj->cur_msg != NULL) { + errobj->cur_msg = errobj->cur_msg->next; + } + + /* If we can not move next, just return */ + return; +} + +int ini_errobj_no_more_msgs(struct ini_errobj *errobj) +{ + return errobj->cur_msg == NULL; +} + +size_t ini_errobj_count(struct ini_errobj *errobj) +{ + return errobj->count; +} + diff --git a/ini/ini_configobj.h b/ini/ini_configobj.h index 6f2d692..0b82d44 100644 --- a/ini/ini_configobj.h +++ b/ini/ini_configobj.h @@ -2046,6 +2046,179 @@ void ini_free_long_config_array(long *array); */ void ini_free_double_config_array(double *array); +/** @brief Structure that holds error messages + * generated by validators. + */ +struct ini_errobj; + +/** + * @brief Create structure to hold error messages. + * + * This function initiates structure that can be used to + * hold error messages from generators. To add messages to + * the structure use \ref ini_errobj_add_msg. + * + * @param[out] errobj container for errors. + * + * @return Zero on success, nonzero value in case of error. + */ +int ini_errobj_create(struct ini_errobj **_errobj); + +/** + * @brief Free structure that holds error messages. + * + * This function is used to free structure + * previously created by \ref ini_errobj_create. + * + * @param[in] errobj container for errors. + */ +void ini_errobj_destroy(struct ini_errobj **errobj); + +/** + * @brief Add new printf formated message to errobj. + * + * This function initiates structure that can be used to + * hold error messages from generators. To add messages to + * the structure use \ref ini_errobj_add_msg. + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + * @param[in] format printf format string + * + * @return Zero on success, nonzero value in case of error. + */ +int ini_errobj_add_msg(struct ini_errobj *errobj, const char *format, ...); + +/** + * @brief Reset iterator in errobj. + * + * After calling this function, the iterator in errobj + * will point to the first error message. Use this if + * you need to accesss the list multiple times in a loop. + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + */ +void ini_errobj_reset(struct ini_errobj *errobj); + +/** + * @brief Get pointer to current message in errobj. + * + * This function returns pointer to current message + * pointed by the internal iterator. The returned string can + * not be changed and will point to valid data only + * until \ref ini_errobj_destroy is called. + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + * @return String inside the errobj structure. String + * is valid until errobj is destroyed. + */ +const char *ini_errobj_get_msg(struct ini_errobj *errobj); + +/** + * @brief Move to the next message in errobj. + * + * This function moves the internal iterator of errobj + * to the next message in list. + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + */ +void ini_errobj_next(struct ini_errobj *errobj); + +/** + * @brief Check if errobj has more messages. + * + * This function returns true if errobj's internal iterator + * reached end of list and no longer points to a message + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + * @return True if internal iterator reached end of list. + */ +int ini_errobj_no_more_msgs(struct ini_errobj *errobj); + +/** + * @brief Return number of messages in errobj + * + * This function returns number of messages inside errobj + * + * @param[in] errobj container for errors previously + * created by \ref ini_errobj_create. + * @return Number of messages stored in errobj. + */ +size_t ini_errobj_count(struct ini_errobj *errobj); + +typedef int (ini_validator_func)(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj); + +/** @brief Structure used to define application specific + * (external to libini) validator + */ +struct ini_validator { + const char *name; + ini_validator_func *func; +}; + +/** + * @brief Read rules from INI file + * + * This function is used to read rules from INI file + * and store them in config object. This special + * config object is passed to \ref ini_rules_check + * together with config object representing the + * configuration that will be checked. + * + * @param[in] filename Name of file with rules + * @param[out] _rules_obj config object representing the rules + * @return Zero on success. Non zero value on error. + */ +int ini_read_rules_from_file(const char *filename, + struct ini_cfgobj **_rules_obj); + +/** + * @brief Check configuration file using rules + * + * This function is used to check if configuration + * file applies to rules previously loaded by + * \ref ini_read_rules_from_file. Any errors + * detected in the configuration are stored in the + * errobj structure. Error code returned by this + * function indicates some internal error with + * validators or memory allocation error (not + * rule violation). + * + * @param[in] rules_obj config object representing the rules + * @param[in] config_obj config object representing the + * configuration + * @param[in] extra_validators Array of extrenal validators. Can be + * NULL if no external validators are + * used. + * @param[in] num_extra_validators Number of external validators in + * extra_validators array. + * + * @param[in] errobj errobj to store generated errors + * from validators. + * + * @return Zero on success. Non zero value on error. + */ +int ini_rules_check(struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_validator *extra_validators, + int num_extra_validators, + struct ini_errobj *errobj); + +/** + * @brief Free the rules + * + * This function is just wrapper around ini_config_destroy + */ +void ini_rules_destroy(struct ini_cfgobj *ini_config); + + /** * @} */ diff --git a/ini/libini_config.sym b/ini/libini_config.sym index 8c34e04..2359c6b 100644 --- a/ini/libini_config.sym +++ b/ini/libini_config.sym @@ -90,6 +90,17 @@ global: ini_free_string_config_array; ini_free_long_config_array; ini_free_double_config_array; + ini_errobj_create; + ini_errobj_destroy; + ini_errobj_add_msg; + ini_errobj_reset; + ini_errobj_get_msg; + ini_errobj_count; + ini_errobj_next; + ini_errobj_no_more_msgs; + ini_read_rules_from_file; + ini_rules_check; + ini_rules_destroy; /* ini_valueobj.h */ value_create_from_refarray; -- 2.5.0
>From 349f2a5198289b268bd9f8e0d4faed19df23319d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzi...@redhat.com> Date: Tue, 8 Mar 2016 17:15:54 +0100 Subject: [PATCH 2/3] ini: Add internal validator ini_allowed_options Ticket: https://fedorahosted.org/sssd/ticket/133 This validator allows to specify a per section list of known options. Error message is generated if unknown option is found. --- ini/ini_configobj.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c index b2c5000..56158c6 100644 --- a/ini/ini_configobj.c +++ b/ini/ini_configobj.c @@ -20,6 +20,8 @@ */ #include "config.h" +#include <sys/types.h> +#include <regex.h> #include <errno.h> #include <stdio.h> #include <string.h> @@ -1094,6 +1096,187 @@ static int ini_dummy_error(const char *rule_name, } +static int check_if_allowed(char *section, char *attr, char **allowed, int num_allowed, + struct ini_errobj *errobj) +{ + int is_allowed = 0; + int ret; + + for (int i = 0; i < num_allowed; i++) { + if (strcmp(attr, allowed[i]) == 0) { + is_allowed = 1; + break; + } + } + + if (!is_allowed) { + ret = ini_errobj_add_msg(errobj, "Attribute '%s' is not allowed in " + "section '%s'. Check for typos.", + attr, section); + return ret; + } + + return 0; +} + +static int ini_allowed_options(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + struct value_obj *vo = NULL; + int ret; + char *section_regex; + int num_sections; + char **sections; + char **attributes; + int num_attributes; + int num_opts = 0; + regex_t preg; + size_t buf_size; + char *err_str = NULL; + int reg_err; + char *option; + char **allowed = NULL; + + /* Get section regex */ + ret = ini_get_config_valueobj(rule_name, + "section_re", + rules_obj, + INI_GET_FIRST_VALUE, + &vo); + if (ret != 0) { + return ret; + } + + if (vo == NULL) { + ret = ini_errobj_add_msg(errobj, "Validator misses 'section' parameter"); + if (ret) { + return ret; + } + return EINVAL; + } + + section_regex = ini_get_string_config_value(vo, NULL); + if (section_regex == NULL || section_regex[0] == '\0') { + ret = ini_errobj_add_msg(errobj, "Validator misses 'section_re' parameter"); + if (ret) { + return ret; + } + + free(section_regex); + return EINVAL; + } + + /* compile the regular expression */ + reg_err = regcomp(&preg, section_regex, REG_NOSUB); + if (reg_err) { + buf_size = regerror(reg_err, &preg, NULL, 0); + err_str = malloc(buf_size); + if (err_str == NULL) { + ret = ENOMEM; + goto done; + } + + regerror(reg_err, &preg, err_str, buf_size); + ret = ini_errobj_add_msg(errobj, "Validator misses 'section_re' " + "parameter"); + ret = ret ? ret : EINVAL; + goto done; + } + + /* Get all sections from config_obj */ + sections = ini_get_section_list(config_obj, &num_sections, &ret); + if (ret != EOK) { + goto done;; + } + + /* Get number of 'option' attributes in this rule + * and create an array long enough to store them all */ + attributes = ini_get_attribute_list(rules_obj, + rule_name, + &num_attributes, + NULL); + if (attributes == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < num_attributes; i++) { + if (strcmp("option", attributes[i]) == 0) { + num_opts++; + } + } + + ini_free_attribute_list(attributes); + attributes = NULL; + + allowed = calloc(num_opts, sizeof(char *)); + + for (int i = 0; i < num_opts; i++) { + ret = ini_get_config_valueobj(rule_name, + "option", + rules_obj, + INI_GET_NEXT_VALUE, + &vo); + if (ret) { + goto done; + } + + option = ini_get_string_config_value(vo, NULL); + if (option == NULL) { + ret = EINVAL; + goto done; + } + allowed[i] = strdup(option); + free(option); + if (allowed[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + for (int i = 0; i < num_sections; i++) { + if (regexec(&preg, sections[i], 0, NULL, 0) == 0) { + /* Regex matched section */ + /* Get options from this section */ + attributes = ini_get_attribute_list(config_obj, + sections[i], + &num_attributes, + NULL); + if (attributes == NULL) { + ret = ENOMEM; + goto done; + } + + for (int a = 0; a < num_attributes; a++) { + ret = check_if_allowed(sections[i], attributes[a], allowed, + num_opts, errobj); + if (ret != 0) { + goto done; + } + } + ini_free_attribute_list(attributes); + attributes = NULL; + } + } + + ret = 0; +done: + for (int i = 0; i < num_opts; i++) { + free(allowed[i]); + } + free(allowed); + ini_free_section_list(sections); + free(section_regex); + ini_free_attribute_list(attributes); + regfree(&preg); + free(err_str); + return ret; +} + + + static ini_validator_func * get_validator(char *validator_name, struct ini_validator *validators, @@ -1104,6 +1287,8 @@ get_validator(char *validator_name, return ini_dummy_noerror; } else if (strcmp(validator_name, "ini_dummy_error") == 0) { return ini_dummy_error; + } else if (strcmp(validator_name, "ini_allowed_options") == 0) { + return ini_allowed_options; } /* Now check the custom validators */ -- 2.5.0
>From 62aa18362a489e0043c6be9a46dde7f4286e6bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzi...@redhat.com> Date: Tue, 15 Mar 2016 17:35:05 +0100 Subject: [PATCH 3/3] tests: Tests for rules/validators infrastructure Ticket: https://fedorahosted.org/sssd/ticket/133 --- Makefile.am | 9 +- ini/ini_validators_ut_check.c | 510 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 ini/ini_validators_ut_check.c diff --git a/Makefile.am b/Makefile.am index 5b1da33..1038860 100644 --- a/Makefile.am +++ b/Makefile.am @@ -69,8 +69,8 @@ libpath_utils_la_LDFLAGS = \ -Wl,--version-script=$(top_srcdir)/path_utils/libpath_utils.sym if HAVE_CHECK - check_PROGRAMS += path_utils_ut ini_configmod_ut_check - TESTS += path_utils_ut ini_configmod_ut_check + check_PROGRAMS += path_utils_ut ini_configmod_ut_check ini_validators_ut_check + TESTS += path_utils_ut ini_configmod_ut_check ini_validators_ut_check endif path_utils_ut_SOURCES = path_utils/path_utils_ut.c @@ -348,6 +348,11 @@ ini_configmod_ut_check_LDADD = libini_config.la libcollection.la \ libref_array.la \ $(CHECK_LIBS) +ini_validators_ut_check_SOURCES = ini/ini_validators_ut_check.c +ini_validators_ut_check_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS) +ini_validators_ut_check_LDADD = libini_config.la $(CHECK_LIBS) + + ini_save_ut_SOURCES = ini/ini_save_ut.c ini_save_ut_LDADD = libini_config.la libcollection.la \ libbasicobjects.la libpath_utils.la libref_array.la diff --git a/ini/ini_validators_ut_check.c b/ini/ini_validators_ut_check.c new file mode 100644 index 0000000..e4d8702 --- /dev/null +++ b/ini/ini_validators_ut_check.c @@ -0,0 +1,510 @@ +/* + INI LIBRARY + + Unit test for the configuration file validators API. + + Copyright (C) Michal Zidek <mzi...@redhat.com> 2016 + + INI Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + INI Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with INI Library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <check.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* #define TRACE_LEVEL 7 */ +#define TRACE_HOME +#include "trace.h" +#include "ini_configobj.h" +#include "ini_config_priv.h" + +#define TEST_DIR_PATH "" +#define TEST_RULES_FILE TEST_DIR_PATH"test_rules.ini" + +static void create_rules_from_str(const char *rules, + struct ini_cfgobj **_rules_obj) +{ + FILE *file; + size_t written; + int ret; + + /* We want to test actual reading from file using + * ini_read_rules_from_file, so we create the file here */ + file = fopen(TEST_RULES_FILE, "w"); + fail_if(file == NULL, "fopen() failed: %s", strerror(errno)); + written = fwrite(rules, 1, strlen(rules), file); + fail_unless(written == strlen(rules)); + + /* allow reading */ + ret = chmod(TEST_RULES_FILE, 0664); + fail_unless(ret == 0, "chmod() failed: %s", strerror(errno)); + + fclose(file); + + ret = ini_read_rules_from_file(TEST_RULES_FILE, _rules_obj); + fail_unless(ret == 0, "read_rules_from_file() failed: %s", strerror(ret)); +} + +static struct ini_cfgobj *get_ini_config_from_str(char input_data[], + size_t input_data_len) +{ + struct ini_cfgobj *in_cfg; + struct ini_cfgfile *file_ctx; + int ret; + + ret = ini_config_create(&in_cfg); + fail_unless(ret == EOK, "Failed to create config. Error %d.\n", ret); + + ret = ini_config_file_from_mem(input_data, input_data_len, &file_ctx); + fail_unless(ret == EOK, "Failed to load config. Error %d.\n", ret); + + ret = ini_config_parse(file_ctx, INI_STOP_ON_NONE, INI_MV1S_ALLOW, 0, + in_cfg); + fail_unless(ret == EOK, "Failed to parse config. Error %d.\n", ret); + + ini_config_file_destroy(file_ctx); + + return in_cfg; +} + +START_TEST(test_ini_errobj) +{ + struct ini_errobj *errobj; + int ret; + const char TEST_MSG1[] = "Test message one."; + const char TEST_MSG2[] = "Test message two."; + const char TEST_MSG3[] = "Test message three."; + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + /* We just created the errobj, it should be empty */ + fail_unless(ini_errobj_no_more_msgs(errobj)); + + /* Now add three messages, after adding each message, + * check if the errobj has correct content. */ + ret = ini_errobj_add_msg(errobj, TEST_MSG1); + fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret)); + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG1 was not found."); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ret = ini_errobj_add_msg(errobj, TEST_MSG2); + fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret)); + ini_errobj_reset(errobj); /* strart from first message */ + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG1 was not found."); + ini_errobj_next(errobj); + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG2, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG2 was not found."); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ret = ini_errobj_add_msg(errobj, TEST_MSG3); + fail_if(ret != 0, "ini_errobj_add_msg() failed: %s", strerror(ret)); + ini_errobj_reset(errobj); /* strart from first message */ + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG1, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG1 was not found."); + ini_errobj_next(errobj); + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG2, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG2 was not found."); + ini_errobj_next(errobj); + fail_if(ini_errobj_no_more_msgs(errobj)); + ret = strcmp(TEST_MSG3, ini_errobj_get_msg(errobj)); + fail_if(ret != 0, "TEST_MSG3 was not found."); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); +} +END_TEST + + +START_TEST(test_ini_noerror) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + + char input_rules[] = + "[rule/always_succeed]\n" + "validator = ini_dummy_noerror\n"; + + char input_cfg[] = + "[section]\n" + "# Content of this file should not matter\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +START_TEST(test_ini_error) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + const char *errmsg; + + char input_rules[] = + "[rule/generate_error]\n" + "validator = ini_dummy_error\n"; + + char input_cfg[] = + "[section]\n" + "# Content of this file should not matter\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate exactly one error */ + fail_if(ini_errobj_no_more_msgs(errobj)); + errmsg = ini_errobj_get_msg(errobj); + ret = strcmp(errmsg, "[rule/generate_error]: Error"); + fail_unless(ret == 0, "Got msg: %s", errmsg); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +START_TEST(test_unknown_validator) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + + char input_rules[] = + "[rule/always_succeed]\n" + "validator = nonexistent_validator\n"; + + char input_cfg[] = + "[section]\n" + "# Content of this file should not matter\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate exactly one error */ + fail_if(ini_errobj_no_more_msgs(errobj)); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +static int custom_noerror(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + return 0; +} + +static int custom_error(const char *rule_name, + struct ini_cfgobj *rules_obj, + struct ini_cfgobj *config_obj, + struct ini_errobj *errobj) +{ + return ini_errobj_add_msg(errobj, "Error"); +} + +START_TEST(test_custom_noerror) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + struct ini_validator noerror = {"custom_noerror", custom_noerror}; + + char input_rules[] = + "[rule/custom_succeed]\n" + "validator = custom_noerror\n"; + + char input_cfg[] = + "[section]\n" + "# Content of this file should not matter\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + /* Pass the custom validator to ini_rules_check() */ + ret = ini_rules_check(rules_obj, cfg_obj, &noerror, 1, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate no errors */ + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +START_TEST(test_custom_error) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + struct ini_validator error = {"custom_error", custom_error}; + const char *errmsg; + + char input_rules[] = + "[rule/custom_error]\n" + "validator = custom_error\n"; + + char input_cfg[] = + "[section]\n" + "# Content of this file should not matter\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + /* Pass the custom validator to ini_rules_check() */ + ret = ini_rules_check(rules_obj, cfg_obj, &error, 1, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate one error */ + fail_if(ini_errobj_no_more_msgs(errobj)); + errmsg = ini_errobj_get_msg(errobj); + ret = strcmp(errmsg, "[rule/custom_error]: Error"); + fail_unless(ret == 0, "Got msg: %s", errmsg); + ini_errobj_next(errobj); + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +START_TEST(test_ini_allowed_options_ok) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + + /* Only bar and baz are allowed for foo section */ + char input_rules[] = + "[rule/options_for_foo]\n" + "validator = ini_allowed_options\n" + "section_re = ^foo$\n" + "option = bar\n" + "option = baz\n"; + + /* Should check only foo section, other sections are + * irrelevant and can contain any option */ + char input_cfg[] = + "[foo]\n" + "bar = 0\n" + "baz = 0\n" + "[oof]\n" + "opt1 = 1\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate no errors */ + fail_unless(ini_errobj_no_more_msgs(errobj)); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +START_TEST(test_ini_allowed_options_no_section) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + size_t num_err; + + /* Ommit section_re to generate error */ + char input_rules[] = + "[rule/options_for_foo]\n" + "validator = ini_allowed_options\n" + /* "section_re = ^foo$\n" */ + "option = bar\n" + "option = baz\n"; + + /* Make 4 typos */ + char input_cfg[] = + "[foo]\n" + "bar = 0\n" + "baz = 0\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate 2 errors (one from rules_check and one + * from the validator itself) */ + fail_if(ini_errobj_no_more_msgs(errobj)); + + num_err = ini_errobj_count(errobj); + fail_unless(num_err == 2, "Expected 2 errors, got %d", num_err); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + + +START_TEST(test_ini_allowed_options_typos) +{ + struct ini_cfgobj *rules_obj; + struct ini_cfgobj *cfg_obj; + struct ini_errobj *errobj; + int ret; + size_t num_err; + + /* Only bar and baz are allowed for foo section */ + char input_rules[] = + "[rule/options_for_foo]\n" + "validator = ini_allowed_options\n" + "section_re = ^foo$\n" + "option = bar\n" + "option = baz\n"; + + /* Make 4 typos */ + char input_cfg[] = + "[foo]\n" + "br = 0\n" + "bra = 0\n" + "abr = 0\n" + "abz = 0\n"; + + create_rules_from_str(input_rules, &rules_obj); + cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg)); + + ret = ini_errobj_create(&errobj); + fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret)); + + ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj); + fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret)); + + /* Should generate 4 errors */ + fail_if(ini_errobj_no_more_msgs(errobj)); + + num_err = ini_errobj_count(errobj); + fail_unless(num_err == 4, "Expected 4 errors, got %d", num_err); + + ini_errobj_destroy(&errobj); + ini_config_destroy(cfg_obj); + ini_rules_destroy(rules_obj); +} +END_TEST + +static Suite *ini_configmod_utils_suite(void) +{ + Suite *s = suite_create("ini_validators"); + + TCase *tc_infrastructure = tcase_create("infrastructure"); + tcase_add_test(tc_infrastructure, test_ini_errobj); + tcase_add_test(tc_infrastructure, test_ini_noerror); + tcase_add_test(tc_infrastructure, test_ini_error); + tcase_add_test(tc_infrastructure, test_unknown_validator); + tcase_add_test(tc_infrastructure, test_custom_noerror); + tcase_add_test(tc_infrastructure, test_custom_error); + + TCase *tc_allowed_options = tcase_create("ini_allowed_options"); + tcase_add_test(tc_allowed_options, test_ini_allowed_options_ok); + tcase_add_test(tc_allowed_options, test_ini_allowed_options_typos); + tcase_add_test(tc_allowed_options, test_ini_allowed_options_no_section); + + suite_add_tcase(s, tc_infrastructure); + suite_add_tcase(s, tc_allowed_options); + + return s; +} + +int main(void) +{ + int number_failed; + + Suite *s = ini_configmod_utils_suite(); + SRunner *sr = srunner_create(s); + /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */ + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- 2.5.0
_______________________________________________ sssd-devel mailing list sssd-devel@lists.fedorahosted.org https://lists.fedorahosted.org/admin/lists/sssd-devel@lists.fedorahosted.org