settings: Add method that allows to define fallback sections for other sections
authorTobias Brunner <tobias@strongswan.org>
Tue, 28 Jan 2014 14:20:27 +0000 (15:20 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 12 Feb 2014 13:34:32 +0000 (14:34 +0100)
The fallbacks are currently only used for single value lookups.
Enumerators are not affected by them.

src/libstrongswan/tests/suites/test_settings.c
src/libstrongswan/utils/settings.c
src/libstrongswan/utils/settings.h

index 72ee94e..0172d52 100644 (file)
@@ -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;
 }
index c3ab52a..2727495 100644 (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"
@@ -78,6 +79,11 @@ struct section_t {
        char *name;
 
        /**
+        * fallback sections, as section_t
+        */
+       array_t *fallbacks;
+
+       /**
         * subsections, as section_t
         */
        linked_list_t *sections;
@@ -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);
 }
 
+/*
+ * forward declaration
+ */
+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
+ * Purge contents of a section, returns TRUE if section has to be kept due to
+ * any subsections.
  */
-static void section_purge(section_t *this)
+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;
@@ -309,13 +341,72 @@ static section_t *ensure_section(private_settings_t *this, section_t *section,
 }
 
 /**
+ * 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,
index df0c534..6154ad8 100644 (file)
@@ -269,6 +269,32 @@ struct settings_t {
                                                                                                 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.
         *
         * If merge is TRUE, existing sections are extended, existing values