From 0cb67ccb52ff931df232ae7666412c6df134fb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BDidek?= Date: Wed, 3 Feb 2016 18:51:49 +0100 Subject: [PATCH 1/8] ini: Add infrastructure for validators Ticket: https://fedorahosted.org/sssd/ticket/133 Add infrastructure for implementing internal and extrenal config file validators. Signed-off-by: Lukas Slebodnik --- ini/ini_config_priv.h | 13 +++ ini/ini_configobj.c | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++ ini/ini_configobj.h | 172 ++++++++++++++++++++++++++++ ini/libini_config.sym | 15 +++ 4 files changed, 512 insertions(+) diff --git a/ini/ini_config_priv.h b/ini/ini_config_priv.h index 38813c9e2ab6acd41af2e775d0fa4cf816b4f591..a03eda5a0ce8191b1e17c014bc1a05c724d8db7a 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 8ae5ea707693959770bfb7b8f659c24cf5cfa4ce..3fdb8e84197cd0a4c0c7c3d6ec4cda6ecc28521b 100644 --- a/ini/ini_configobj.c +++ b/ini/ini_configobj.c @@ -25,6 +25,7 @@ #include #include #include +#include /* For error text */ #include #define _(String) gettext (String) @@ -1039,3 +1040,314 @@ int ini_config_get_errors(struct ini_cfgobj *cfg_ctx, TRACE_FLOW_EXIT(); return error; } + +int ini_rules_read_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); + *_rules_obj = NULL; + } + + 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) +{ + int i; + + /* 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 (i = 0; i < num_validators; i++) { + /* Skip invalid external validator. Name is required */ + if (validators[i].name == NULL) { + continue; + } + 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; + int i; + + /* 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 (i = 0; i < num_sections; i++) { + if (!strncmp(sections[i], "rule/", sizeof("rule/") - 1)) { + 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 6f2d6920ea94e4ee14bf2a19db7603c8442d116d..48811ac8e34ecb08c77877a84ead034a7d268175 100644 --- a/ini/ini_configobj.h +++ b/ini/ini_configobj.h @@ -2046,6 +2046,178 @@ 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_rules_read_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_rules_read_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 8c34e04592b001b90d762618280ac4b3eec18caa..3ce3e929ddeddc5cb0314c71912b251810e64ba2 100644 --- a/ini/libini_config.sym +++ b/ini/libini_config.sym @@ -177,3 +177,18 @@ global: ini_config_set_bom; } INI_CONFIG_1.1.0; +INI_CONFIG_1.3.0 { +global: + /* ini_configobj.h */ + 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_rules_read_from_file; + ini_rules_check; + ini_rules_destroy; +} INI_CONFIG_1.2.0; -- 2.7.4