From 50ab0c5802e45bf3c3c6bc224b5d5159e2cb05ef Mon Sep 17 00:00:00 2001
From: Dmitri Pal <dpal@redhat.com>
Date: Sat, 27 Nov 2010 22:03:24 -0500
Subject: [PATCH] [INI] Allow merging values

This patch is the first pass at merging functionality.
It implements merging of values that belong to the same
section. Patch includes:
* Definition of merge flags in doxy format
* Definition of the masks in internal header
* Changes to parser to handle the merging of values.
* Also swithed parser to not use ini_config.h as
  I want to switch implementation of the current
  interface to new interface at some point.
* New unit test was created.
* New config file for this unit test was added to ini.d

Main code changes are in ini_parse.c
---
 ini/ini.d/foo.conf  |   12 +++
 ini/ini_configobj.h |   87 ++++++++++++++++++++++
 ini/ini_defines.h   |    5 +
 ini/ini_parse.c     |  114 ++++++++++++++++++++++++++---
 ini/ini_parse_ut.c  |  203 +++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 410 insertions(+), 11 deletions(-)
 create mode 100644 ini/ini.d/foo.conf

diff --git a/ini/ini.d/foo.conf b/ini/ini.d/foo.conf
new file mode 100644
index 0000000000000000000000000000000000000000..44d56a14f68038dec63c7d43209d755ff4844c4e
--- /dev/null
+++ b/ini/ini.d/foo.conf
@@ -0,0 +1,12 @@
+#Hoho section
+[hoho]
+#Hoho value
+val= hoho
+#End of hoho
+#Start of section
+[foo]
+#First value
+bar = first value
+#Second value
+bar = second value
+#End of section
diff --git a/ini/ini_configobj.h b/ini/ini_configobj.h
index 21198db064c44acedd7fd83664d01b112ddac344..88a8704277facb0f8883b2df28a709b26ae311f7 100644
--- a/ini/ini_configobj.h
+++ b/ini/ini_configobj.h
@@ -29,6 +29,7 @@
 #include <stdio.h>
 #include "simplebuffer.h"
 
+
 /**
  * @defgroup errorlevel Error tolerance constants
  *
@@ -79,6 +80,92 @@
  * @}
  */
 
+
+/**
+ * @defgroup collisionflags Flags that define collision resolution logic.
+ *
+ * @{
+ */
+
+/**
+ * @defgroup onesecvalue Colliding values come from one section
+ *
+ * Flags that define collision resolution logic for values in
+ * the same section.
+ * These flags should be used during parsing to handle duplicate
+ * keys in the same section of the ini file.
+ *
+ * @{
+ */
+
+/** @brief Value with same key is ovewritten */
+#define INI_MV1S_OVERWRITE 0x0000
+/** @brief Collision causes error */
+#define INI_MV1S_ERROR     0x0001
+/** @brief Second value is discarded */
+#define INI_MV1S_PRESERVE  0x0002
+/** @brief Duplicates are allowed */
+#define INI_MV1S_ALLOW     0x0003
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup twosecvalue Colliding values come from two sections
+ *
+ * Flags that define collision resolution logic between two values
+ * that come from two sections with the same name.
+ * These flags should be used during parsing to handle duplicate
+ * keys coming from the same section scattered across the ini file.
+ * These flags also can be used to specify the rules of merging
+ * values that come from two files separate configuration files.
+ *
+ * @{
+ */
+/** @brief Value with same key is ovewritten */
+#define INI_MV2S_OVERWRITE 0x0000
+/** @brief Collision causes error */
+#define INI_MV2S_ERROR     0x0010
+/** @brief Second value is discarded */
+#define INI_MV2S_PRESERVE  0x0020
+/** @brief Duplicates are allowed */
+#define INI_MV2S_ALLOW     0x0030
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup mergesec Collision in two sections
+ *
+ * Flags that define collision resolution logic between two sections.
+ * These flags should be used during parsing to handle duplicate
+ * sections scattered across the ini file.
+ * These flags also can be used to specify the rules of merging
+ * sections that come from two separate configuration files.
+ *
+ * @{
+ */
+/** @brief Sections are merged */
+#define INI_MS_MERGE     0x0000
+/** @brief Collision causes error */
+#define INI_MS_ERROR     0x0100
+/** @brief First section is discarded */
+#define INI_MS_OVERWRITE 0x0200
+/** @brief Second section is discarded */
+#define INI_MS_PRESERVE  0x0300
+/** @brief Duplicates are allowed */
+#define INI_MS_ALLOW     0x0400
+
+/**
+ * @}
+ */
+/**
+ * @}
+ */
+
+
 /**
  * @defgroup structures Structures
  * @{
diff --git a/ini/ini_defines.h b/ini/ini_defines.h
index 1d03835c3262c0ac09c43f6225840ce4142592a7..b861f0e7e282fa8b3e20d4d3b60cb909bb1ef0d1 100644
--- a/ini/ini_defines.h
+++ b/ini/ini_defines.h
@@ -100,6 +100,11 @@
 #define INI_FAMILY_VALIDATION   1
 #define INI_FAMILY_GRAMMAR      2
 
+#define INI_MV1S_MASK      0x000F /* Merge values options mask
+                                   * for one section */
+#define INI_MV2S_MASK      0x00F0 /* Merge values options mask
+                                   * for two sections. */
+#define INI_MS_MASK        0x0F00 /* Merge section options mask */
 
 
 /* Different error string functions can be passed as callbacks */
diff --git a/ini/ini_parse.c b/ini/ini_parse.c
index c92a1633e3aee20227f2c0596bd9b27ae27fe809..acdb6081e734baec80967adb2a46c299c2d029fd 100644
--- a/ini/ini_parse.c
+++ b/ini/ini_parse.c
@@ -29,21 +29,36 @@
 #include "config.h"
 #include "trace.h"
 #include "ini_defines.h"
-#include "ini_config.h"
 #include "ini_valueobj.h"
 #include "ini_config_priv.h"
+#include "ini_configobj.h"
 #include "collection.h"
 #include "collection_queue.h"
 
 #define INI_WARNING 0xA0000000 /* Warning bit */
 
+/* This constant belongs to ini_defines.h. Move from ini_config - TBD */
+#define COL_CLASS_INI_BASE        20000
+#define COL_CLASS_INI_SECTION     COL_CLASS_INI_BASE + 1
+/**
+ * @brief Name of the default section.
+ *
+ * This is the name of the implied section where orphan key-value
+ * pairs will be put.
+ */
+#define INI_DEFAULT_SECTION "default"
+
+
 struct parser_obj {
     /* Externally passed and saved data */
     FILE *file;
     struct collection_item *top;
     struct collection_item *el;
     const char *filename;
+    /* Level of error reporting */
     int error_level;
+    /* Collistion flags */
+    uint32_t collision_flags;
     /* Wrapping boundary */
     uint32_t boundary;
     /* Action queue */
@@ -122,6 +137,7 @@ int parser_create(FILE *file,
                   const char *config_filename,
                   struct collection_item *ini_config,
                   int error_level,
+                  uint32_t collision_flags,
                   struct collection_item *error_list,
                   uint32_t boundary,
                   struct parser_obj **po)
@@ -160,6 +176,7 @@ int parser_create(FILE *file,
     new_po->el = error_list;
     new_po->filename = config_filename;
     new_po->error_level = error_level;
+    new_po->collision_flags = collision_flags;
     new_po->boundary = boundary;
 
     /* Initialize internal varibles */
@@ -287,6 +304,12 @@ static int complete_value_processing(struct parser_obj *po)
 {
     int error = EOK;
     struct value_obj *vo = NULL;
+    struct value_obj *vo_old = NULL;
+    unsigned insertmode;
+    uint32_t mergemode;
+    int suppress = 0;
+    int doinsert = 0;
+    struct collection_item *item = NULL;
 
     TRACE_FLOW_ENTRY();
 
@@ -322,17 +345,85 @@ static int complete_value_processing(struct parser_obj *po)
     po->raw_lines = NULL;
     po->raw_lengths = NULL;
 
+    mergemode = po->collision_flags & INI_MV1S_MASK;
+
+    switch (mergemode) {
+    case INI_MV1S_ERROR:     insertmode = COL_INSERT_DUPERROR;
+                             doinsert = 1;
+                             break;
+    case INI_MV1S_PRESERVE:  insertmode = COL_INSERT_DUPERROR;
+                             doinsert = 1;
+                             suppress = 1;
+                             break;
+    case INI_MV1S_ALLOW:     insertmode = COL_INSERT_NOCHECK;
+                             doinsert = 1;
+                             break;
+    case INI_MV1S_OVERWRITE: /* Special handling */
+    default:
+                             break;
+    }
 
-    /* Add value to collection */
-    error = col_add_binary_property(po->sec,
-                                    NULL,
-                                    po->key,
-                                    &vo,
-                                    sizeof(struct value_obj *));
-    if (error) {
-        TRACE_ERROR_NUMBER("Failed to add value object to the section", error);
-        value_destroy(vo);
-        return error;
+    /* Do not insert but search for dups first */
+    if (!doinsert) {
+        TRACE_INFO_STRING("Ovewrite mode. Lokking for:", po->key);
+
+        error = col_get_item(po->sec,
+                             po->key,
+                             COL_TYPE_BINARY,
+                             COL_TRAVERSE_DEFAULT,
+                             &item);
+
+        if (error) {
+            TRACE_ERROR_NUMBER("Failed searching for dup", error);
+            return error;
+        }
+
+        /* Check if there is a dup */
+        if (item) {
+            /* Dup exists - update it */
+            vo_old = *((struct value_obj **)(col_get_item_data(item)));
+            error = col_modify_binary_item(item,
+                                           NULL,
+                                           &vo,
+                                           sizeof(struct value_obj *));
+            if (error) {
+                TRACE_ERROR_NUMBER("Failed updating the value", error);
+                return error;
+            }
+            /* If we failed to update it is better to leak then crash,
+             * so desctroy original value only on the successful update.
+             */
+            value_destroy(vo_old);
+        }
+        else {
+            /* No dup found so we can insert with no check */
+            doinsert = 1;
+            insertmode = COL_INSERT_NOCHECK;
+        }
+    }
+
+    if (doinsert) {
+        /* Add value to collection */
+        error = col_insert_binary_property(po->sec,
+                                           NULL,
+                                           COL_DSP_END,
+                                           NULL,
+                                           0,
+                                           insertmode,
+                                           po->key,
+                                           &vo,
+                                           sizeof(struct value_obj *));
+        if (error) {
+            if ((suppress) && (error == EEXIST)) {
+                TRACE_INFO_STRING("Preseved exisitng value", po->key);
+                value_destroy(vo);
+            }
+            else {
+                TRACE_ERROR_NUMBER("Failed to add value object to the section", error);
+                value_destroy(vo);
+                return error;
+            }
+        }
     }
 
     free(po->key);
@@ -970,6 +1061,7 @@ int ini_config_parse(struct ini_cfgfile *file_ctx,
                           file_ctx->filename,
                           ini_config->cfg,
                           file_ctx->error_level,
+                          file_ctx->collision_flags,
                           file_ctx->error_list,
                           ini_config->boundary,
                           &po);
diff --git a/ini/ini_parse_ut.c b/ini/ini_parse_ut.c
index cc37c21936db40a1ca16d497af3bd261a241bca5..eed4649462c6bce908c2d02b33a87506621f4dd9 100644
--- a/ini/ini_parse_ut.c
+++ b/ini/ini_parse_ut.c
@@ -231,6 +231,208 @@ int read_again_test(void)
     return error;
 }
 
+int create_expect(char *checkname)
+{
+    FILE *ff;
+    int error = EOK;
+
+    errno = 0;
+    ff = fopen(checkname, "w");
+    if(!ff) {
+        error = errno;
+        printf("Failed to open file for writing. Error %d.\n", error);
+        return error;
+    }
+
+    /* Ovewrite */
+    fprintf(ff,"#Hoho section\n");
+    fprintf(ff,"[hoho]\n");
+    fprintf(ff,"#Hoho value\n");
+    fprintf(ff,"val = hoho\n");
+    fprintf(ff,"#End of hoho\n");
+    fprintf(ff,"#Start of section\n");
+    fprintf(ff,"[foo]\n");
+    fprintf(ff,"#Second value\n");
+    fprintf(ff,"bar = second value\n");
+    fprintf(ff,"#End of section\n");
+    /* Error */
+    fprintf(ff,"#Hoho section\n");
+    fprintf(ff,"[hoho]\n");
+    fprintf(ff,"#Hoho value\n");
+    fprintf(ff,"val = hoho\n");
+    /* No "#End of hoho" line is expected due to error */
+    /* Preserve */
+    fprintf(ff,"#Hoho section\n");
+    fprintf(ff,"[hoho]\n");
+    fprintf(ff,"#Hoho value\n");
+    fprintf(ff,"val = hoho\n");
+    fprintf(ff,"#End of hoho\n");
+    fprintf(ff,"#Start of section\n");
+    fprintf(ff,"[foo]\n");
+    fprintf(ff,"#First value\n");
+    fprintf(ff,"bar = first value\n");
+    fprintf(ff,"#End of section\n");
+    /* Allow */
+    fprintf(ff,"#Hoho section\n");
+    fprintf(ff,"[hoho]\n");
+    fprintf(ff,"#Hoho value\n");
+    fprintf(ff,"val = hoho\n");
+    fprintf(ff,"#End of hoho\n");
+    fprintf(ff,"#Start of section\n");
+    fprintf(ff,"[foo]\n");
+    fprintf(ff,"#First value\n");
+    fprintf(ff,"bar = first value\n");
+    fprintf(ff,"#Second value\n");
+    fprintf(ff,"bar = second value\n");
+    fprintf(ff,"#End of section\n");
+
+    fclose(ff);
+
+    return EOK;
+}
+
+
+
+/* Check merge modes */
+int merge_values_test(void)
+{
+    int error = EOK;
+    int i;
+    struct ini_cfgfile *file_ctx = NULL;
+    FILE *ff = NULL;
+    struct ini_cfgobj *ini_config = NULL;
+    char **error_list = NULL;
+    struct simplebuffer *sbobj = NULL;
+    uint32_t left = 0;
+    uint32_t mflags[] = { INI_MV1S_OVERWRITE,
+                          INI_MV1S_ERROR,
+                          INI_MV1S_PRESERVE,
+                          INI_MV1S_ALLOW };
+
+    const char *mstr[] = { "OVERWRITE",
+                           "ERROR",
+                           "PRESERVE",
+                           "ALLOW" };
+
+    char filename[PATH_MAX];
+    char resname[PATH_MAX];
+    char checkname[PATH_MAX];
+    char command[PATH_MAX * 3];
+    char *srcdir;
+
+    srcdir = getenv("srcdir");
+    sprintf(filename, "%s/ini/ini.d/foo.conf", (srcdir == NULL) ? "." : srcdir);
+    sprintf(resname, "%s/merge.conf", (srcdir == NULL) ? "." : srcdir);
+    sprintf(checkname, "%s/expect.conf", (srcdir == NULL) ? "." : srcdir);
+
+    error = simplebuffer_alloc(&sbobj);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to allocate dynamic string.", error);
+        return error;
+    }
+
+    for (i = 0; i < 4; i++) {
+
+        INIOUT(printf("<==== Testing mode %s  ====>\n", mstr[i]));
+
+        /* Create config collection */
+        error = ini_config_create(&ini_config);
+        if (error) {
+            printf("Failed to create collection. Error %d.\n", error);
+	        simplebuffer_free(sbobj);
+            return error;
+        }
+
+        error = ini_config_file_open(filename,
+                                     INI_STOP_ON_ANY,
+                                     mflags[i],
+                                     0, /* TBD */
+                                     &file_ctx);
+        if (error) {
+            printf("Failed to open file for reading. Error %d.\n",  error);
+            ini_config_destroy(ini_config);
+	        simplebuffer_free(sbobj);
+            return error;
+        }
+
+        error = ini_config_parse(file_ctx,
+                                 ini_config);
+        if (error) {
+            INIOUT(printf("Failed to parse configuration. Error %d.\n", error));
+
+            if (ini_config_error_count(file_ctx)) {
+                INIOUT(printf("Errors detected while parsing: %s\n",
+                       ini_config_get_filename(file_ctx)));
+                ini_config_get_errors(file_ctx, &error_list);
+                INIOUT(ini_print_errors(stdout, error_list));
+                ini_config_free_errors(error_list);
+            }
+
+            if ((mflags[i] != INI_MV1S_ERROR) ||
+                ((mflags[i] = INI_MV1S_ERROR) && (error != EEXIST))) {
+                printf("This is unexpected error %d in mode %d\n", error, mflags[i]);
+	            ini_config_destroy(ini_config);
+		        simplebuffer_free(sbobj);
+		        ini_config_file_close(file_ctx);
+                return error;
+            }
+            /* We do not return here intentionally */
+        }
+
+        ini_config_file_close(file_ctx);
+
+        INIOUT(col_debug_collection(ini_config->cfg, COL_TRAVERSE_DEFAULT));
+
+	    error = ini_config_serialize(ini_config, sbobj);
+	    if (error != EOK) {
+	        printf("Failed to serialize configuration. Error %d.\n", error);
+	        ini_config_destroy(ini_config);
+	        simplebuffer_free(sbobj);
+	        return error;
+	    }
+
+        ini_config_destroy(ini_config);
+	}
+
+    errno = 0;
+    ff = fopen(resname, "w");
+    if(!ff) {
+        error = errno;
+        printf("Failed to open file for writing. Error %d.\n", error);
+        simplebuffer_free(sbobj);
+        return error;
+    }
+
+    /* Save */
+    left = simplebuffer_get_len(sbobj);
+    while (left > 0) {
+        error = simplebuffer_write(fileno(ff), sbobj, &left);
+        if (error) {
+            printf("Failed to write back the configuration %d.\n", error);
+            simplebuffer_free(sbobj);
+            fclose(ff);
+            return error;
+        }
+    }
+
+    simplebuffer_free(sbobj);
+    fclose(ff);
+
+    error = create_expect(checkname);
+    if (error) {
+        printf("Failed to create file with expected contents %d.\n",  error);
+        return error;
+    }
+
+    sprintf(command,"diff -q %s %s", resname, checkname);
+    error = system(command);
+    INIOUT(printf("Comparison of %s %s returned: %d\n",
+                  resname, checkname, error));
+
+    return error;
+
+
+}
 
 /* Main function of the unit test */
 int main(int argc, char *argv[])
@@ -238,6 +440,7 @@ int main(int argc, char *argv[])
     int error = 0;
     test_fn tests[] = { read_save_test,
                         read_again_test,
+                        merge_values_test,
                         NULL };
     test_fn t;
     int i = 0;
-- 
1.5.5.6

