691 lines
15 KiB
C
691 lines
15 KiB
C
/*
|
|
* 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, 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;
|
|
}
|