settings: Add support to enumerate sections and key/value pairs with fallbacks
authorTobias Brunner <tobias@strongswan.org>
Mon, 10 Feb 2014 17:01:50 +0000 (18:01 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 12 Feb 2014 13:34:33 +0000 (14:34 +0100)
src/libstrongswan/tests/suites/test_settings.c
src/libstrongswan/utils/settings.c
src/libstrongswan/utils/settings.h

index b97a706..0964651 100644 (file)
@@ -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);
+
+       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", NULL);
-       values = linked_list_create_with_items("val1", NULL);
+       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");
index ce098c9..a2c8922 100644 (file)
@@ -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;
 }
 
 /**
@@ -348,6 +393,26 @@ static section_t *ensure_section(private_settings_t *this, section_t *section,
 }
 
 /**
+ * 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), &sections);
+       return sections;
+}
+
+/**
  * Check if the given fallback section already exists
  */
 static bool fallback_exists(section_t *section, section_t *fallback)
@@ -786,63 +851,127 @@ METHOD(settings_t, set_default_str, bool,
 }
 
 /**
+ * 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,
index 6154ad8..46403c4 100644 (file)
@@ -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.