settings: Add support to enumerate sections and key/value pairs with fallbacks
This commit is contained in:
@@ -714,14 +714,23 @@ START_TEST(test_add_fallback)
|
||||
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);
|
||||
keys = linked_list_create_with_items("sub1", NULL);
|
||||
verify_sections(keys, "main");
|
||||
keys = linked_list_create_with_items("subsub", 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");
|
||||
|
||||
keys = linked_list_create_with_items("key1", "key2", NULL);
|
||||
values = linked_list_create_with_items("val1", "subval2", NULL);
|
||||
verify_key_values(keys, values, "main.sub1");
|
||||
|
||||
keys = linked_list_create_with_items("subkey1", NULL);
|
||||
values = linked_list_create_with_items("subsubval1", NULL);
|
||||
verify_key_values(keys, values, "main.sub1.subsub");
|
||||
|
||||
settings->add_fallback(settings, "main", "base");
|
||||
verify_string("val1", "main.key1");
|
||||
verify_string("baseval2", "main.key2");
|
||||
@@ -732,18 +741,23 @@ START_TEST(test_add_fallback)
|
||||
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);
|
||||
keys = linked_list_create_with_items("sub1", "sub2", NULL);
|
||||
verify_sections(keys, "main");
|
||||
keys = linked_list_create_with_items("subsub", NULL);
|
||||
verify_sections(keys, "main.sub1");
|
||||
|
||||
keys = linked_list_create_with_items("key1", "key2", NULL);
|
||||
values = linked_list_create_with_items("val1", "baseval2", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
keys = linked_list_create_with_items("key1", "key2", "key3", NULL);
|
||||
values = linked_list_create_with_items("val1", "subval2", "subbase3", 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");
|
||||
keys = linked_list_create_with_items("subkey1", "subkey2", NULL);
|
||||
values = linked_list_create_with_items("subsubval1", "subsubbaseval2", NULL);
|
||||
verify_key_values(keys, values, "main.sub1.subsub");
|
||||
|
||||
settings->set_str(settings, "main.sub1.key2", "val2");
|
||||
verify_string("val2", "main.sub1.key2");
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include "collections/array.h"
|
||||
#include "collections/hashtable.h"
|
||||
#include "collections/linked_list.h"
|
||||
#include "threading/rwlock.h"
|
||||
#include "utils/debug.h"
|
||||
@@ -307,23 +308,67 @@ static section_t *find_section_buffered(section_t *section,
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a section by a given key (thread-safe).
|
||||
* Find all sections via a given key considering fallbacks, using buffered key,
|
||||
* reusable buffer.
|
||||
*/
|
||||
static section_t *find_section(private_settings_t *this, section_t *section,
|
||||
char *key, va_list args)
|
||||
static void find_sections_buffered(section_t *section, char *start, char *key,
|
||||
va_list args, char *buf, int len, array_t **sections)
|
||||
{
|
||||
char buf[128], keybuf[512];
|
||||
section_t *found;
|
||||
section_t *found = NULL, *fallback;
|
||||
char *pos;
|
||||
int i;
|
||||
|
||||
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
|
||||
if (!section)
|
||||
{
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
pos = strchr(key, '.');
|
||||
if (pos)
|
||||
{
|
||||
*pos = '\0';
|
||||
}
|
||||
if (!print_key(buf, len, start, key, args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (pos)
|
||||
{ /* restore so we can follow fallbacks */
|
||||
*pos = '.';
|
||||
}
|
||||
if (!strlen(buf))
|
||||
{
|
||||
found = section;
|
||||
}
|
||||
else
|
||||
{
|
||||
array_bsearch(section->sections, buf, section_find, &found);
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
if (pos)
|
||||
{
|
||||
find_sections_buffered(found, start, pos+1, args, buf, len,
|
||||
sections);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_insert_create(sections, ARRAY_TAIL, found);
|
||||
for (i = 0; i < array_count(found->fallbacks); i++)
|
||||
{
|
||||
array_get(found->fallbacks, i, &fallback);
|
||||
array_insert_create(sections, ARRAY_TAIL, fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (section->fallbacks)
|
||||
{
|
||||
for (i = 0; i < array_count(section->fallbacks); i++)
|
||||
{
|
||||
array_get(section->fallbacks, i, &fallback);
|
||||
find_sections_buffered(fallback, start, key, args, buf, len,
|
||||
sections);
|
||||
}
|
||||
}
|
||||
this->lock->read_lock(this->lock);
|
||||
found = find_section_buffered(section, keybuf, keybuf, args, buf,
|
||||
sizeof(buf), FALSE);
|
||||
this->lock->unlock(this->lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,6 +392,26 @@ static section_t *ensure_section(private_settings_t *this, section_t *section,
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a section by a given key with its fallbacks (not thread-safe!).
|
||||
* Sections are returned in depth-first order (array is allocated). NULL is
|
||||
* returned if no sections are found.
|
||||
*/
|
||||
static array_t *find_sections(private_settings_t *this, section_t *section,
|
||||
char *key, va_list args)
|
||||
{
|
||||
char buf[128], keybuf[512];
|
||||
array_t *sections = NULL;
|
||||
|
||||
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
find_sections_buffered(section, keybuf, keybuf, args, buf,
|
||||
sizeof(buf), §ions);
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given fallback section already exists
|
||||
*/
|
||||
@@ -785,64 +850,128 @@ METHOD(settings_t, set_default_str, bool,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for enumerators
|
||||
*/
|
||||
typedef struct {
|
||||
/** settings_t instance */
|
||||
private_settings_t *settings;
|
||||
/** sections to enumerate */
|
||||
array_t *sections;
|
||||
/** sections/keys that were already enumerated */
|
||||
hashtable_t *seen;
|
||||
} enumerator_data_t;
|
||||
|
||||
/**
|
||||
* Destroy enumerator data
|
||||
*/
|
||||
static void enumerator_destroy(enumerator_data_t *this)
|
||||
{
|
||||
this->settings->lock->unlock(this->settings->lock);
|
||||
this->seen->destroy(this->seen);
|
||||
array_destroy(this->sections);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate section names, not sections
|
||||
*/
|
||||
static bool section_filter(void *null, section_t **in, char **out)
|
||||
static bool section_filter(hashtable_t *seen, section_t **in, char **out)
|
||||
{
|
||||
*out = (*in)->name;
|
||||
if (seen->get(seen, *out))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
seen->put(seen, *out, *out);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate sections of the given section
|
||||
*/
|
||||
static enumerator_t *section_enumerator(section_t *section,
|
||||
enumerator_data_t *data)
|
||||
{
|
||||
return enumerator_create_filter(array_create_enumerator(section->sections),
|
||||
(void*)section_filter, data->seen, NULL);
|
||||
}
|
||||
|
||||
METHOD(settings_t, create_section_enumerator, enumerator_t*,
|
||||
private_settings_t *this, char *key, ...)
|
||||
{
|
||||
section_t *section;
|
||||
enumerator_data_t *data;
|
||||
array_t *sections;
|
||||
va_list args;
|
||||
|
||||
this->lock->read_lock(this->lock);
|
||||
va_start(args, key);
|
||||
section = find_section(this, this->top, key, args);
|
||||
sections = find_sections(this, this->top, key, args);
|
||||
va_end(args);
|
||||
|
||||
if (!section)
|
||||
if (!sections)
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
return enumerator_create_empty();
|
||||
}
|
||||
this->lock->read_lock(this->lock);
|
||||
return enumerator_create_filter(
|
||||
array_create_enumerator(section->sections),
|
||||
(void*)section_filter, this->lock, (void*)this->lock->unlock);
|
||||
INIT(data,
|
||||
.settings = this,
|
||||
.sections = sections,
|
||||
.seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8),
|
||||
);
|
||||
return enumerator_create_nested(array_create_enumerator(sections),
|
||||
(void*)section_enumerator, data, (void*)enumerator_destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate key and values, not kv_t entries
|
||||
*/
|
||||
static bool kv_filter(void *null, kv_t **in, char **key,
|
||||
static bool kv_filter(hashtable_t *seen, kv_t **in, char **key,
|
||||
void *none, char **value)
|
||||
{
|
||||
*key = (*in)->key;
|
||||
if (seen->get(seen, *key))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
*value = (*in)->value;
|
||||
seen->put(seen, *key, *key);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate key/value pairs of the given section
|
||||
*/
|
||||
static enumerator_t *kv_enumerator(section_t *section, enumerator_data_t *data)
|
||||
{
|
||||
return enumerator_create_filter(array_create_enumerator(section->kv),
|
||||
(void*)kv_filter, data->seen, NULL);
|
||||
}
|
||||
|
||||
METHOD(settings_t, create_key_value_enumerator, enumerator_t*,
|
||||
private_settings_t *this, char *key, ...)
|
||||
{
|
||||
section_t *section;
|
||||
enumerator_data_t *data;
|
||||
array_t *sections;
|
||||
va_list args;
|
||||
|
||||
this->lock->read_lock(this->lock);
|
||||
va_start(args, key);
|
||||
section = find_section(this, this->top, key, args);
|
||||
sections = find_sections(this, this->top, key, args);
|
||||
va_end(args);
|
||||
|
||||
if (!section)
|
||||
if (!sections)
|
||||
{
|
||||
this->lock->unlock(this->lock);
|
||||
return enumerator_create_empty();
|
||||
}
|
||||
this->lock->read_lock(this->lock);
|
||||
return enumerator_create_filter(
|
||||
array_create_enumerator(section->kv),
|
||||
(void*)kv_filter, this->lock, (void*)this->lock->unlock);
|
||||
INIT(data,
|
||||
.settings = this,
|
||||
.sections = sections,
|
||||
.seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8),
|
||||
);
|
||||
return enumerator_create_nested(array_create_enumerator(sections),
|
||||
(void*)kv_enumerator, data, (void*)enumerator_destroy);
|
||||
}
|
||||
|
||||
METHOD(settings_t, add_fallback, void,
|
||||
|
||||
@@ -276,13 +276,12 @@ struct settings_t {
|
||||
* '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 Lookups are depth-first and currently strictly top-down.
|
||||
* For instance, if app.sec had lib1.sec as fallback and lib1 had lib2 as
|
||||
* fallback the keys/sections in lib2.sec would not be considered. But if
|
||||
* app had lib3 as fallback the contents of lib3.sec would (as app is passed
|
||||
* during the initial lookup). In the last example the order during
|
||||
* enumerations would be app.sec, lib1.sec, lib3.sec.
|
||||
*
|
||||
* @note Additional arguments will be applied to both section format
|
||||
* strings so they must be compatible.
|
||||
|
||||
Reference in New Issue
Block a user