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

Reply via email to