/* * Copyright (C) 2013-2014 Tobias Brunner * HSR 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 . * * 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 #include /** * 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, va_list args) { setting_t *setting; section_t *parent; char **key, **value; VA_ARGS_VGET(args, key, value); 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 = enumerator_enumerate_default, .venumerate = _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; } CALLBACK(conn_filter, bool, void *unused, enumerator_t *orig, va_list args) { section_t *section; char **name; VA_ARGS_VGET(args, name); while (orig->enumerate(orig, §ion)) { if (!streq(section->name, "%default")) { *name = section->name; return TRUE; } } return FALSE; } CALLBACK(ca_filter, bool, void *unused, enumerator_t *orig, va_list args) { void *key; section_t *section; char **name; VA_ARGS_VGET(args, name); while (orig->enumerate(orig, &key, §ion)) { if (!streq(section->name, "%default")) { *name = section->name; return TRUE; } } return FALSE; } 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), conn_filter, NULL, NULL); case CONF_PARSER_CA: return enumerator_create_filter( this->cas->create_enumerator(this->cas), 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; }