From 675fe7aef88e2cfe4909c9c0b34e5f498233987b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzidek@redhat.com>
Date: Wed, 3 Feb 2016 18:51:49 +0100
Subject: [PATCH 1/4] 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

