diff options
Diffstat (limited to 'src/starter/parser/conf_parser.c')
-rw-r--r-- | src/starter/parser/conf_parser.c | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/src/starter/parser/conf_parser.c b/src/starter/parser/conf_parser.c new file mode 100644 index 000000000..6d1c54d20 --- /dev/null +++ b/src/starter/parser/conf_parser.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2013-2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "conf_parser.h" + +#include <collections/array.h> +#include <collections/hashtable.h> + +/** + * Provided by the generated parser + */ +bool conf_parser_parse_file(conf_parser_t *this, char *file); + +typedef struct private_conf_parser_t private_conf_parser_t; +typedef struct section_t section_t; + +/** + * Private data + */ +struct private_conf_parser_t { + + /** + * Public interface + */ + conf_parser_t public; + + /** + * Path to config file + */ + char *file; + + /** + * TRUE while parsing the config file + */ + bool parsing; + + /** + * Hashtable for ca sections, as section_t + */ + hashtable_t *cas; + + /** + * Hashtable for conn sections, as section_t + */ + hashtable_t *conns; + + /** + * Array to keep track of the order of conn sections, as section_t + */ + array_t *conns_order; + + /** + * config setup section + */ + section_t *config_setup; + + /** + * Pointer to the current section (the one added last) during parsing + */ + section_t *current_section; + + /** + * Refcount for this parser instance (also used by dictionaries) + */ + refcount_t ref; +}; + +typedef struct { + /** Key of the setting */ + char *key; + /** Value of the setting */ + char *value; +} setting_t; + +int setting_find(const void *a, const void *b) +{ + const char *key = a; + const setting_t *setting = b; + return strcmp(key, setting->key); +} + +int setting_sort(const void *a, const void *b, void *user) +{ + const setting_t *sa = a, *sb = b; + return strcmp(sa->key, sb->key); +} + +static void setting_destroy(setting_t *this) +{ + free(this->key); + free(this->value); + free(this); +} + +struct section_t { + /** Name of the section */ + char *name; + /** Sorted array of settings, as setting_t */ + array_t *settings; + /** Array of also= settings (in reversed order, i.e. most important to least + * important), as setting_t */ + array_t *also; + /** Array of linearized parent objects derived from also= settings, in their + * order of importance (most to least, i.e. %default) as section_t + * NULL if not yet determined */ + array_t *parents; +}; + +static section_t *section_create(char *name) +{ + section_t *this; + + INIT(this, + .name = name, + ); + return this; +} + +static void section_destroy(section_t *this) +{ + array_destroy_function(this->settings, (void*)setting_destroy, NULL); + array_destroy_function(this->also, (void*)setting_destroy, NULL); + array_destroy(this->parents); + free(this->name); + free(this); +} + +typedef struct { + /** Public interface */ + dictionary_t public; + /** Parser object */ + private_conf_parser_t *parser; + /** Section object */ + section_t *section; +} section_dictionary_t; + +typedef struct { + /** Public interface */ + enumerator_t public; + /** Current settings enumerator */ + enumerator_t *settings; + /** Enumerator into parent list */ + enumerator_t *parents; + /** Hashtable to keep track of already enumerated settings */ + hashtable_t *seen; +} dictionary_enumerator_t; + +METHOD(enumerator_t, dictionary_enumerate, bool, + dictionary_enumerator_t *this, char **key, char **value) +{ + setting_t *setting; + section_t *parent; + + while (TRUE) + { + if (this->settings && + this->settings->enumerate(this->settings, &setting)) + { + if (this->seen->get(this->seen, setting->key)) + { + continue; + } + this->seen->put(this->seen, setting->key, setting->key); + if (!setting->value) + { + continue; + } + if (key) + { + *key = setting->key; + } + if (value) + { + *value = setting->value; + } + return TRUE; + } + DESTROY_IF(this->settings); + this->settings = NULL; + if (this->parents && + this->parents->enumerate(this->parents, &parent)) + { + if (parent->settings) + { + this->settings = array_create_enumerator(parent->settings); + } + continue; + } + DESTROY_IF(this->parents); + this->parents = NULL; + break; + } + return FALSE; +} + +METHOD(enumerator_t, dictionary_enumerator_destroy, void, + dictionary_enumerator_t *this) +{ + DESTROY_IF(this->settings); + DESTROY_IF(this->parents); + this->seen->destroy(this->seen); + free(this); +} + +METHOD(dictionary_t, dictionary_create_enumerator, enumerator_t*, + section_dictionary_t *this) +{ + dictionary_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_dictionary_enumerate, + .destroy = _dictionary_enumerator_destroy, + }, + .seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8), + ); + if (this->section->settings) + { + enumerator->settings = array_create_enumerator(this->section->settings); + } + if (this->section->parents) + { + enumerator->parents = array_create_enumerator(this->section->parents); + } + return &enumerator->public; +} + +METHOD(dictionary_t, dictionary_get, void*, + section_dictionary_t *this, const void *key) +{ + enumerator_t *parents; + section_t *section; + setting_t *setting; + char *value = NULL; + + section = this->section; + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + return setting->value; + } + parents = array_create_enumerator(section->parents); + while (parents->enumerate(parents, §ion)) + { + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + value = setting->value; + break; + } + } + parents->destroy(parents); + return value; +} + +METHOD(dictionary_t, dictionary_destroy, void, + section_dictionary_t *this) +{ + this->parser->public.destroy(&this->parser->public); + free(this); +} + +static dictionary_t *section_dictionary_create(private_conf_parser_t *parser, + section_t *section) +{ + section_dictionary_t *this; + + INIT(this, + .public = { + .create_enumerator = _dictionary_create_enumerator, + .get = _dictionary_get, + .destroy = _dictionary_destroy, + }, + .parser = parser, + .section = section, + ); + + ref_get(&parser->ref); + + return &this->public; +} + +static bool conn_filter(void *unused, section_t **section, char **name) +{ + if (streq((*section)->name, "%default")) + { + return FALSE; + } + *name = (*section)->name; + return TRUE; +} + +static bool ca_filter(void *unused, void *key, char **name, section_t **section) +{ + if (streq((*section)->name, "%default")) + { + return FALSE; + } + *name = (*section)->name; + return TRUE; +} + +METHOD(conf_parser_t, get_sections, enumerator_t*, + private_conf_parser_t *this, conf_parser_section_t type) +{ + switch (type) + { + case CONF_PARSER_CONN: + return enumerator_create_filter( + array_create_enumerator(this->conns_order), + (void*)conn_filter, NULL, NULL); + case CONF_PARSER_CA: + return enumerator_create_filter( + this->cas->create_enumerator(this->cas), + (void*)ca_filter, NULL, NULL); + case CONF_PARSER_CONFIG_SETUP: + default: + return enumerator_create_empty(); + } +} + +METHOD(conf_parser_t, get_section, dictionary_t*, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + section_t *section = NULL; + + if (name && streq(name, "%default")) + { + return NULL; + } + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + break; + case CONF_PARSER_CONN: + section = this->conns->get(this->conns, name); + break; + case CONF_PARSER_CA: + section = this->cas->get(this->cas, name); + break; + default: + break; + } + return section ? section_dictionary_create(this, section) : NULL; +} + +METHOD(conf_parser_t, add_section, bool, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + hashtable_t *sections = this->conns; + array_t *order = this->conns_order; + section_t *section = NULL; + bool exists = FALSE; + + if (!this->parsing) + { + free(name); + return exists; + } + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + /* we don't expect a name, but just in case */ + free(name); + break; + case CONF_PARSER_CA: + sections = this->cas; + order = NULL; + /* fall-through */ + case CONF_PARSER_CONN: + section = sections->get(sections, name); + if (!section) + { + section = section_create(name); + sections->put(sections, name, section); + if (order) + { + array_insert(order, ARRAY_TAIL, section); + } + } + else + { + exists = TRUE; + free(name); + } + break; + + } + this->current_section = section; + return exists; +} + +METHOD(conf_parser_t, add_setting, void, + private_conf_parser_t *this, char *key, char *value) +{ + section_t *section = this->current_section; + setting_t *setting; + + if (!this->parsing || !this->current_section) + { + free(key); + free(value); + return; + } + if (streq(key, "also")) + { + if (!value || !strlen(value) || streq(value, "%default")) + { /* we require a name, but all sections inherit from %default */ + free(key); + free(value); + return; + } + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->also, ARRAY_HEAD, setting); + return; + } + if (array_bsearch(section->settings, key, setting_find, &setting) == -1) + { + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->settings, ARRAY_TAIL, setting); + array_sort(section->settings, setting_sort, NULL); + } + else + { + free(setting->value); + setting->value = value; + free(key); + } +} + +/** + * Check if the given section is contained in the given array. The search + * starts at the given index. + */ +static bool is_contained_in(array_t *arr, section_t *section) +{ + section_t *current; + int i; + + for (i = 0; i < array_count(arr); i++) + { + array_get(arr, i, ¤t); + if (streq(section->name, current->name)) + { + return TRUE; + } + } + return FALSE; +} + +/** + * This algorithm to linearize sections uses a bottom-first depth-first + * semantic, with an additional elimination step that removes all but the + * last occurrence of each section. + * + * Consider this configuration: + * + * conn A + * conn B + * also=A + * conn C + * also=A + * conn D + * also=C + * also=B + * + * The linearization would yield D B A C A, which gets reduced to D B C A. + * + * Ambiguous configurations are handled pragmatically. + * + * Consider the following configuration: + * + * conn A + * conn B + * conn C + * also=A + * also=B + * conn D + * also=B + * also=A + * conn E + * also=C + * also=D + * + * It is ambiguous because D and C include the same two sections but in + * a different order. + * + * The linearization would yield E D A B C B A which gets reduced to E D C B A. + */ +static bool resolve_also_single(hashtable_t *sections, + section_t *section, section_t *def, array_t *stack) +{ + enumerator_t *enumerator; + array_t *parents; + section_t *parent, *grandparent; + setting_t *also; + bool success = TRUE; + int i; + + array_insert(stack, ARRAY_HEAD, section); + parents = array_create(0, 0); + enumerator = array_create_enumerator(section->also); + while (enumerator->enumerate(enumerator, &also)) + { + parent = sections->get(sections, also->value); + if (!parent || is_contained_in(stack, parent)) + { + if (!parent) + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' not " + "found", also->value, section->name); + } + else + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' causes " + "a loop", parent->name, section->name); + } + array_remove_at(section->also, enumerator); + setting_destroy(also); + success = FALSE; + continue; + } + if (!parent->parents) + { + if (!resolve_also_single(sections, parent, def, stack)) + { + success = FALSE; + continue; + } + } + /* add the grandparents and the parent to the list */ + array_insert(parents, ARRAY_TAIL, parent); + for (i = 0; i < array_count(parent->parents); i++) + { + array_get(parent->parents, i, &grandparent); + array_insert(parents, ARRAY_TAIL, grandparent); + } + } + enumerator->destroy(enumerator); + array_remove(stack, ARRAY_HEAD, NULL); + + if (success && def && !array_count(parents)) + { + array_insert(parents, ARRAY_TAIL, def); + } + + while (success && array_remove(parents, ARRAY_HEAD, &parent)) + { + if (!is_contained_in(parents, parent)) + { /* last occurrence of this section */ + array_insert_create(§ion->parents, ARRAY_TAIL, parent); + } + } + array_destroy(parents); + return success; +} + +/** + * Resolve also= statements. The functions returns TRUE if everything is fine, + * or FALSE if either a referenced section does not exist, or if the section + * inheritance can't be determined properly (e.g. if there are loops or if a + * section inherits from multiple sections - perhaps over several levels - in + * an ambiguous way). + */ +static bool resolve_also(hashtable_t *sections) +{ + enumerator_t *enumerator; + section_t *def, *section; + array_t *stack; + bool success = TRUE; + + stack = array_create(0, 0); + + def = sections->get(sections, "%default"); + if (def) + { /* the default section is the only one with an empty parents list */ + def->parents = array_create(0, 0); + } + enumerator = sections->create_enumerator(sections); + while (enumerator->enumerate(enumerator, NULL, §ion)) + { + if (section->parents) + { /* already determined */ + continue; + } + success = resolve_also_single(sections, section, def, stack) && success; + } + enumerator->destroy(enumerator); + array_destroy(stack); + return success; +} + +METHOD(conf_parser_t, parse, bool, + private_conf_parser_t *this) +{ + bool success; + + if (!this->file) + { /* no file, lets assume this is OK */ + return TRUE; + } + this->parsing = TRUE; + success = conf_parser_parse_file(&this->public, this->file); + this->parsing = FALSE; + return success && resolve_also(this->conns) && resolve_also(this->cas); +} + +METHOD(conf_parser_t, destroy, void, + private_conf_parser_t *this) +{ + if (ref_put(&this->ref)) + { + this->cas->destroy_function(this->cas, (void*)section_destroy); + this->conns->destroy_function(this->conns, (void*)section_destroy); + section_destroy(this->config_setup); + array_destroy(this->conns_order); + free(this->file); + free(this); + } +} + +/* + * Described in header + */ +conf_parser_t *conf_parser_create(const char *file) +{ + private_conf_parser_t *this; + + INIT(this, + .public = { + .parse = _parse, + .get_sections = _get_sections, + .get_section = _get_section, + .add_section = _add_section, + .add_setting = _add_setting, + .destroy = _destroy, + }, + .file = strdupnull(file), + .cas = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns_order = array_create(0, 0), + .config_setup = section_create(NULL), + .ref = 1, + ); + + return &this->public; +} |