settings: Add method that allows to define fallback sections for other sections

The fallbacks are currently only used for single value lookups.
Enumerators are not affected by them.
This commit is contained in:
Tobias Brunner
2014-02-12 14:34:32 +01:00
parent ef72d4cc3f
commit 1713d88278
3 changed files with 277 additions and 14 deletions
@@ -664,6 +664,109 @@ START_TEST(test_load_files_section)
}
END_TEST
START_SETUP(setup_fallback_config)
{
create_settings(chunk_from_str(
"main {\n"
" key1 = val1\n"
" sub1 {\n"
" key1 = val1\n"
" }\n"
"}\n"
"sub {\n"
" key1 = subval1\n"
" key2 = subval2\n"
" subsub {\n"
" subkey1 = subsubval1\n"
" }\n"
"}\n"
"base {\n"
" key1 = baseval1\n"
" key2 = baseval2\n"
" sub1 {\n"
" key1 = subbase1\n"
" key2 = subbase2\n"
" key3 = subbase3\n"
" subsub {\n"
" subkey1 = subsubbaseval1\n"
" subkey2 = subsubbaseval2\n"
" }\n"
" }\n"
" sub2 {\n"
" key4 = subbase4\n"
" }\n"
"}"));
}
END_SETUP
START_TEST(test_add_fallback)
{
linked_list_t *keys, *values;
settings->add_fallback(settings, "main.sub1", "sub");
verify_string("val1", "main.sub1.key1");
verify_string("subval2", "main.sub1.key2");
verify_string("subsubval1", "main.sub1.subsub.subkey1");
/* fallbacks are preserved even if the complete config is replaced */
settings->load_files(settings, path, FALSE);
verify_string("val1", "main.sub1.key1");
verify_string("subval2", "main.sub1.key2");
verify_string("subsubval1", "main.sub1.subsub.subkey1");
/* fallbacks currently have no effect on section & key/value enumerators */
keys = linked_list_create_with_items(NULL);
verify_sections(keys, "main.sub1");
keys = linked_list_create_with_items("key1", NULL);
values = linked_list_create_with_items("val1", NULL);
verify_key_values(keys, values, "main.sub1");
settings->add_fallback(settings, "main", "base");
verify_string("val1", "main.key1");
verify_string("baseval2", "main.key2");
verify_string("val1", "main.sub1.key1");
verify_string("subval2", "main.sub1.key2");
verify_string("subsubval1", "main.sub1.subsub.subkey1");
verify_string("subsubbaseval2", "main.sub1.subsub.subkey2");
verify_string("subbase3", "main.sub1.key3");
verify_string("subbase4", "main.sub2.key4");
keys = linked_list_create_with_items(NULL);
verify_sections(keys, "main.sub1");
keys = linked_list_create_with_items("sub1", NULL);
verify_sections(keys, "main");
keys = linked_list_create_with_items("key1", NULL);
values = linked_list_create_with_items("val1", NULL);
verify_key_values(keys, values, "main.sub1");
keys = linked_list_create_with_items("key1", NULL);
values = linked_list_create_with_items("val1", NULL);
verify_key_values(keys, values, "main");
settings->set_str(settings, "main.sub1.key2", "val2");
verify_string("val2", "main.sub1.key2");
settings->set_str(settings, "main.sub1.subsub.subkey2", "val2");
verify_string("val2", "main.sub1.subsub.subkey2");
verify_string("subsubval1", "main.sub1.subsub.subkey1");
}
END_TEST
START_TEST(test_add_fallback_printf)
{
settings->add_fallback(settings, "%s.sub1", "sub", "main");
verify_string("val1", "main.sub1.key1");
verify_string("subval2", "main.sub1.key2");
verify_string("subsubval1", "main.sub1.subsub.subkey1");
settings->add_fallback(settings, "%s.%s2", "%s.%s1", "main", "sub");
verify_string("val1", "main.sub2.key1");
verify_string("subval2", "main.sub2.key2");
verify_string("subsubval1", "main.sub2.subsub.subkey1");
}
END_TEST
Suite *settings_suite_create()
{
Suite *s;
@@ -721,5 +824,11 @@ Suite *settings_suite_create()
tcase_add_test(tc, test_load_files_section);
suite_add_tcase(s, tc);
tc = tcase_create("fallback");
tcase_add_checked_fixture(tc, setup_fallback_config, teardown_config);
tcase_add_test(tc, test_add_fallback);
tcase_add_test(tc, test_add_fallback_printf);
suite_add_tcase(s, tc);
return s;
}
+142 -14
View File
@@ -31,6 +31,7 @@
#include "settings.h"
#include "collections/array.h"
#include "collections/linked_list.h"
#include "threading/rwlock.h"
#include "utils/debug.h"
@@ -77,6 +78,11 @@ struct section_t {
*/
char *name;
/**
* fallback sections, as section_t
*/
array_t *fallbacks;
/**
* subsections, as section_t
*/
@@ -147,19 +153,45 @@ static void section_destroy(section_t *this)
{
this->kv->destroy_function(this->kv, (void*)kv_destroy);
this->sections->destroy_function(this->sections, (void*)section_destroy);
array_destroy(this->fallbacks);
free(this->name);
free(this);
}
/**
* Purge contents of a section
/*
* forward declaration
*/
static void section_purge(section_t *this)
static bool section_purge(section_t *this);
/**
* Check if it is safe to remove the given section.
*/
static bool section_remove(section_t *this)
{
if (section_purge(this))
{
return FALSE;
}
section_destroy(this);
return TRUE;
}
/**
* Purge contents of a section, returns TRUE if section has to be kept due to
* any subsections.
*/
static bool section_purge(section_t *this)
{
int count, removed;
this->kv->destroy_function(this->kv, (void*)kv_destroy);
this->kv = linked_list_create();
this->sections->destroy_function(this->sections, (void*)section_destroy);
this->sections = linked_list_create();
/* we ensure sections used as fallback, or configured with fallbacks (or
* having any such subsections) are not removed */
count = this->sections->get_count(this->sections);
removed = this->sections->remove(this->sections, NULL,
(void*)section_remove);
return this->fallbacks || removed < count;
}
/**
@@ -291,7 +323,7 @@ static section_t *find_section(private_settings_t *this, section_t *section,
* Ensure that the section with the given key exists (thread-safe).
*/
static section_t *ensure_section(private_settings_t *this, section_t *section,
char *key, va_list args)
const char *key, va_list args)
{
char buf[128], keybuf[512];
section_t *found;
@@ -308,14 +340,73 @@ static section_t *ensure_section(private_settings_t *this, section_t *section,
return found;
}
/**
* Check if the given fallback section already exists
*/
static bool fallback_exists(section_t *section, section_t *fallback)
{
if (section == fallback)
{
return TRUE;
}
else if (section->fallbacks)
{
section_t *existing;
int i;
for (i = 0; i < array_count(section->fallbacks); i++)
{
array_get(section->fallbacks, i, &existing);
if (existing == fallback)
{
return TRUE;
}
}
}
return FALSE;
}
/**
* Ensure that the section with the given key exists and add the given fallback
* section (thread-safe).
*/
static void add_fallback_to_section(private_settings_t *this,
section_t *section, const char *key, va_list args,
section_t *fallback)
{
char buf[128], keybuf[512];
section_t *found;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return;
}
this->lock->write_lock(this->lock);
found = find_section_buffered(section, keybuf, keybuf, args, buf,
sizeof(buf), TRUE);
if (!fallback_exists(found, fallback))
{
/* to ensure sections referred to as fallback are not purged, we create
* the array there too */
if (!fallback->fallbacks)
{
fallback->fallbacks = array_create(0, 0);
}
array_insert_create(&found->fallbacks, ARRAY_TAIL, fallback);
}
this->lock->unlock(this->lock);
}
/**
* Find the key/value pair for a key, using buffered key, reusable buffer
* If "ensure" is TRUE, the sections (and key/value pair) are created if they
* don't exist.
* Fallbacks are only considered if "ensure" is FALSE.
*/
static kv_t *find_value_buffered(section_t *section, char *start, char *key,
va_list args, char *buf, int len, bool ensure)
{
int i;
char *pos;
kv_t *kv = NULL;
section_t *found = NULL;
@@ -329,12 +420,12 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key,
if (pos)
{
*pos = '\0';
pos++;
if (!print_key(buf, len, start, key, args))
{
return NULL;
}
/* restore so we can retry for fallbacks */
*pos = '.';
if (!strlen(buf))
{
found = section;
@@ -343,15 +434,26 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key,
(linked_list_match_t)section_find,
(void**)&found, buf) != SUCCESS)
{
if (!ensure)
if (ensure)
{
return NULL;
found = section_create(buf);
section->sections->insert_last(section->sections, found);
}
}
if (found)
{
kv = find_value_buffered(found, start, pos+1, args, buf, len,
ensure);
}
if (!kv && !ensure && section->fallbacks)
{
for (i = 0; !kv && i < array_count(section->fallbacks); i++)
{
array_get(section->fallbacks, i, &found);
kv = find_value_buffered(found, start, key, args, buf, len,
ensure);
}
found = section_create(buf);
section->sections->insert_last(section->sections, found);
}
return find_value_buffered(found, start, pos, args, buf, len,
ensure);
}
else
{
@@ -367,6 +469,15 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key,
kv = kv_create(buf, NULL);
section->kv->insert_last(section->kv, kv);
}
else if (section->fallbacks)
{
for (i = 0; !kv && i < array_count(section->fallbacks); i++)
{
array_get(section->fallbacks, i, &found);
kv = find_value_buffered(found, start, key, args, buf, len,
ensure);
}
}
}
}
return kv;
@@ -727,6 +838,22 @@ METHOD(settings_t, create_key_value_enumerator, enumerator_t*,
(void*)kv_filter, this->lock, (void*)this->lock->unlock);
}
METHOD(settings_t, add_fallback, void,
private_settings_t *this, const char *key, const char *fallback, ...)
{
section_t *section;
va_list args;
/* find/create the fallback */
va_start(args, fallback);
section = ensure_section(this, this->top, fallback, args);
va_end(args);
va_start(args, fallback);
add_fallback_to_section(this, this->top, key, args, section);
va_end(args);
}
/**
* parse text, truncate "skip" chars, delimited by term respecting brackets.
*
@@ -1235,6 +1362,7 @@ settings_t *settings_create(char *file)
.set_default_str = _set_default_str,
.create_section_enumerator = _create_section_enumerator,
.create_key_value_enumerator = _create_key_value_enumerator,
.add_fallback = _add_fallback,
.load_files = _load_files,
.load_files_section = _load_files_section,
.destroy = _destroy,
+26
View File
@@ -268,6 +268,32 @@ struct settings_t {
enumerator_t* (*create_key_value_enumerator)(settings_t *this,
char *section, ...);
/**
* Add a fallback for the given section.
*
* Example: When the fallback 'section-two' is configured for
* 'section-one.two' any failed lookup for a section or key in
* 'section-one.two' will result in a lookup for the same section/key
* in 'section-two'.
*
* @note This has only an effect on single value lookups. Enumerators for
* sections and key/value-pairs are not affected. The reason is that it is
* rather tricky to implement such a merge without requiring allocations for
* each lookup. For instance, if charon.tls has libtls as fallback and
* charon has libstrongswan as fallback, the key/value-enumerator for
* charon.tls had to enumerate the libtls and libstrongswan.tls sections
* too, but it would have to keep track of the already enumerated keys.
*
* @note Additional arguments will be applied to both section format
* strings so they must be compatible.
*
* @param section section for which a fallback is configured, printf style
* @param fallback fallback section, printf style
* @param ... argument list for section and fallback
*/
void (*add_fallback)(settings_t *this, const char *section,
const char *fallback, ...);
/**
* Load settings from the files matching the given pattern.
*