From f2aaac72046ad622480e022eaab0e671299dc6df Mon Sep 17 00:00:00 2001
From: Dmitri Pal <dpal@redhat.com>
Date: Tue, 4 Dec 2012 19:36:58 -0500
Subject: [PATCH 06/10] [INI] Ability to merge configurations

This patch adds capability to merge two configuration objects.
---
 ini/ini_configobj.c |  586 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 581 insertions(+), 5 deletions(-)

diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c
index 193474b6b7730475dabefdd09d8d093af42dc40b..7de57e415467dace920f4578995ed9916c41e9ae 100644
--- a/ini/ini_configobj.c
+++ b/ini/ini_configobj.c
@@ -30,10 +30,15 @@
 #include "ini_config_priv.h"
 #include "ini_defines.h"
 #include "ini_valueobj.h"
+#include "ini_configobj.h"
 
-/* This constant belongs to ini_defines.h. Move from ini_config - TBD */
-#define COL_CLASS_INI_BASE        20000
-#define COL_CLASS_INI_CONFIG      COL_CLASS_INI_BASE + 0
+/* Internal structure used during the merge operation */
+struct merge_data {
+    struct collection_item *ci;
+    uint32_t flags;
+    int error;
+    int found;
+};
 
 /* Callback */
 void ini_cleanup_cb(const char *property,
@@ -46,6 +51,7 @@ void ini_cleanup_cb(const char *property,
     struct value_obj *vo = NULL;
 
     TRACE_FLOW_ENTRY();
+    TRACE_INFO_STRING("Cleaning ", property);
 
     /* Banary items are the values */
     if(type == COL_TYPE_BINARY) {
@@ -85,12 +91,15 @@ void ini_config_destroy(struct ini_cfgobj *ini_config)
     ini_config_clean_state(ini_config);
 
     if (ini_config) {
-        if(ini_config->cfg) {
+        if (ini_config->cfg) {
 
             col_destroy_collection_with_cb(ini_config->cfg,
                                            ini_cleanup_cb,
                                            NULL);
         }
+        if (ini_config->last_comment) {
+            ini_comment_destroy(ini_config->last_comment);
+        }
         free(ini_config);
     }
 
@@ -120,6 +129,7 @@ int ini_config_create(struct ini_cfgobj **ini_config)
 
     new_co->cfg = NULL;
     new_co->boundary = INI_WRAP_BOUNDARY;
+    new_co->last_comment = NULL;
     new_co->section = NULL;
     new_co->name = NULL;
     new_co->section_len = 0;
@@ -212,7 +222,7 @@ static int ini_copy_cb(struct collection_item *item,
 
     *skip = 0;
 
-    /* Banary items are the values */
+    /* Binary items are the values */
     if(col_get_item_type(item) == COL_TYPE_BINARY) {
         vo = *((struct value_obj **)(col_get_item_data(item)));
 
@@ -263,6 +273,7 @@ int ini_config_copy(struct ini_cfgobj *ini_config,
 
     new_co->cfg = NULL;
     new_co->boundary = ini_config->boundary;
+    new_co->last_comment = NULL;
     new_co->section = NULL;
     new_co->name = NULL;
     new_co->section_len = 0;
@@ -281,8 +292,573 @@ int ini_config_copy(struct ini_cfgobj *ini_config,
         return error;
     }
 
+    if (ini_config->last_comment) {
+        error = ini_comment_copy(ini_config->last_comment,
+                                 &(new_co->last_comment));
+        if (error) {
+            TRACE_ERROR_NUMBER("Failed to copy comment", error);
+            ini_config_destroy(new_co);
+            return error;
+        }
+    }
+
     *ini_new = new_co;
 
     TRACE_FLOW_EXIT();
     return error;
 }
+
+
+/* Callback to process merging of the sections */
+static int merge_section_handler(const char *property,
+                                 int property_len,
+                                 int type,
+                                 void *data,
+                                 int length,
+                                 void *custom_data,
+                                 int *dummy)
+{
+    int error = EOK;
+    struct value_obj *vo = NULL;
+    struct value_obj *new_vo = NULL;
+    struct value_obj *vo_old = NULL;
+    struct merge_data *passed_data;
+    struct collection_item *acceptor = NULL;
+    struct collection_item *item = NULL;
+    unsigned insertmode;
+    uint32_t mergemode;
+    int suppress = 0;
+    int doinsert = 0;
+
+    TRACE_FLOW_ENTRY();
+
+    if ((type != COL_TYPE_BINARY) ||
+        ((type == COL_TYPE_BINARY) &&
+         (strncmp(property, INI_SECTION_KEY,
+                     sizeof(INI_SECTION_KEY)) == 0))) {
+        /* Skip items we do not care about */
+        TRACE_FLOW_EXIT();
+        return EOK;
+    }
+
+    /* Get value */
+    vo = *((struct value_obj **)(data));
+
+    /* Copy it */
+    error = value_copy(vo, &new_vo);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to copy value", error);
+        return error;
+    }
+
+    passed_data = (struct merge_data *)(custom_data);
+    acceptor = passed_data->ci;
+    mergemode = passed_data->flags & INI_MV2S_MASK;
+
+    switch (mergemode) {
+    case INI_MV2S_ERROR:     insertmode = COL_INSERT_DUPERROR;
+                             doinsert = 1;
+                             break;
+    case INI_MV2S_PRESERVE:  insertmode = COL_INSERT_DUPERROR;
+                             doinsert = 1;
+                             suppress = 1;
+                             break;
+    case INI_MV2S_ALLOW:     insertmode = COL_INSERT_NOCHECK;
+                             doinsert = 1;
+                             break;
+    case INI_MV2S_OVERWRITE: /* Special handling */
+    case INI_MV2S_DETECT:
+    default:
+                             break;
+    }
+
+    /* Do not insert but search for dups first */
+    if (!doinsert) {
+        TRACE_INFO_STRING("Overwrite mode. Looking for:",
+                          property);
+
+        error = col_get_item(acceptor,
+                             property,
+                             COL_TYPE_BINARY,
+                             COL_TRAVERSE_DEFAULT,
+                             &item);
+
+        if (error) {
+            TRACE_ERROR_NUMBER("Failed searching for dup", error);
+            value_destroy(new_vo);
+            return error;
+        }
+
+        /* Check if there is a dup */
+        if (item) {
+            /* Check if we are in the detect mode */
+            if (mergemode == INI_MV2S_DETECT) {
+                passed_data->error = EEXIST;
+                doinsert = 1;
+                insertmode = COL_INSERT_NOCHECK;
+            }
+            else {
+
+                /* We are in the OVERWRITE mode.
+                 * Dup exists - update it.
+                 */
+                vo_old = *((struct value_obj **)(col_get_item_data(item)));
+                error = col_modify_binary_item(item,
+                                               NULL,
+                                               &new_vo,
+                                               sizeof(struct value_obj *));
+                if (error) {
+                    TRACE_ERROR_NUMBER("Failed updating the value", error);
+                    value_destroy(new_vo);
+                    return error;
+                }
+
+                /* If we failed to update it is better to leak then crash,
+                 * so destroy 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(acceptor,
+                                           NULL,
+                                           COL_DSP_END,
+                                           NULL,
+                                           0,
+                                           insertmode,
+                                           property,
+                                           &new_vo,
+                                           sizeof(struct value_obj *));
+        if (error) {
+            value_destroy(new_vo);
+
+            if ((suppress) && (error == EEXIST)) {
+                /* We are here is we do not allow dups
+                 * but found one and need to ignore it.
+                 */
+                TRACE_INFO_STRING("Preseved exisitng value",
+                                  property);
+                error = 0;
+            }
+            else {
+                /* Check if this is a critical error or not */
+                if ((mergemode == INI_MV2S_ERROR) && (error == EEXIST)) {
+                    TRACE_ERROR_NUMBER("Failed to add value object to "
+                                       "the section in error mode ", error);
+                    passed_data->error = EEXIST;
+                    *dummy = 1;
+                }
+                else {
+                    TRACE_ERROR_NUMBER("Failed to add value object"
+                                       " to the section", error);
+                    return error;
+                }
+            }
+        }
+    }
+
+    TRACE_FLOW_EXIT();
+    return error;
+}
+
+
+/* Internal function to merge two configs */
+static int merge_two_sections(struct collection_item *donor,
+                              struct collection_item *acceptor,
+                              uint32_t flags)
+{
+    int error = EOK;
+    struct merge_data data;
+
+    TRACE_FLOW_ENTRY();
+
+    data.ci = acceptor;
+    data.flags = flags;
+    data.error = 0;
+    data.found = 0;
+
+    error = col_traverse_collection(donor,
+                                    COL_TRAVERSE_ONELEVEL,
+                                    merge_section_handler,
+                                    (void *)(&data));
+    if (error) {
+        TRACE_ERROR_NUMBER("Merge values failed", error);
+        return error;
+    }
+
+    TRACE_FLOW_EXIT();
+    return data.error;
+}
+
+
+
+/* Callback to process the accepting config */
+static int acceptor_handler(const char *property,
+                            int property_len,
+                            int type,
+                            void *data,
+                            int length,
+                            void *custom_data,
+                            int *dummy)
+{
+    int error = EOK;
+    struct merge_data *passed_data;
+    struct collection_item *acceptor = NULL;
+    struct collection_item *donor = NULL;
+    uint32_t mergemode;
+
+    TRACE_FLOW_ENTRY();
+
+    /* This callback is called when the dup section is found */
+    passed_data = (struct merge_data *)(custom_data);
+    passed_data->found = 1;
+
+    donor = passed_data->ci;
+    acceptor = *((struct collection_item **)(data));
+
+    mergemode = passed_data->flags & INI_MS_MASK;
+
+    switch (mergemode) {
+    case INI_MS_ERROR:      /* Report error and return */
+                            TRACE_INFO_STRING("Error ",
+                                              "duplicate section");
+                            passed_data->error = EEXIST;
+                            break;
+
+    case INI_MS_PRESERVE:   /* Preserve what we have */
+                            TRACE_INFO_STRING("Preserve mode", "");
+                            break;
+
+    case INI_MS_OVERWRITE:  /* Empty existing section */
+                            TRACE_INFO_STRING("Ovewrite mode", "");
+                            error = empty_section(acceptor);
+                            if (error) {
+                                TRACE_ERROR_NUMBER("Failed to "
+                                                    "empty section",
+                                                    error);
+                                return error;
+                            }
+                            error = merge_two_sections(donor,
+                                                       acceptor,
+                                                       passed_data->flags);
+                            if (error) {
+                                TRACE_ERROR_NUMBER("Failed to merge "
+                                                    "sections", error);
+                                if (error == EEXIST) {
+                                    passed_data->error = error;
+                                }
+                                return error;
+                            }
+                            break;
+
+    case INI_MS_DETECT:     /* Detect mode */
+                            TRACE_INFO_STRING("Detect mode", "");
+                            passed_data->error = EEXIST;
+                            error = merge_two_sections(donor,
+                                                       acceptor,
+                                                       passed_data->flags);
+                            if (error) {
+                                if (error != EEXIST) {
+                                    TRACE_ERROR_NUMBER("Failed to merge "
+                                                       "sections", error);
+                                    return error;
+                                }
+                            }
+                            break;
+
+    case INI_MS_MERGE:      /* Merge */
+    default:                TRACE_INFO_STRING("Merge mode", "");
+                            error = merge_two_sections(donor,
+                                                       acceptor,
+                                                       passed_data->flags);
+                            if (error) {
+                                if (error != EEXIST) {
+                                    TRACE_ERROR_NUMBER("Failed to merge "
+                                                       "sections", error);
+                                    return error;
+                                }
+                                passed_data->error = error;
+                            }
+                            break;
+    }
+
+    *dummy = 1;
+    TRACE_FLOW_EXIT();
+    return EOK;
+}
+
+/* Callback to process the donating config */
+static int donor_handler(const char *property,
+                         int property_len,
+                         int type,
+                         void *data,
+                         int length,
+                         void *custom_data,
+                         int *dummy)
+{
+    int error = EOK;
+    struct merge_data *passed_data;
+    struct merge_data acceptor_data;
+    struct collection_item *new_ci = NULL;
+
+    TRACE_FLOW_ENTRY();
+
+    *dummy = 0;
+
+    /* Opaque data passed to callback is merge data */
+    passed_data = (struct merge_data *)(custom_data);
+
+    TRACE_INFO_STRING("Property: ", property);
+    TRACE_INFO_NUMBER("Type is: ", type);
+    TRACE_INFO_NUMBER("Flags: ", passed_data->flags);
+
+    /* All sections are subcollections */
+    if(type == COL_TYPE_COLLECTIONREF) {
+
+        /* Prepare data for the next callback */
+        acceptor_data.flags = passed_data->flags;
+        acceptor_data.ci = *((struct collection_item **)(data));
+        acceptor_data.error = 0;
+        acceptor_data.found = 0;
+
+        /* Try to find same section as the current one */
+        error = col_get_item_and_do(passed_data->ci,
+                                    property,
+                                    COL_TYPE_COLLECTIONREF,
+                                    COL_TRAVERSE_ONELEVEL,
+                                    acceptor_handler,
+                                    (void *)(&acceptor_data));
+        if (error) {
+            TRACE_ERROR_NUMBER("Critical error", error);
+            return error;
+        }
+
+        /* Was duplicate found ? */
+        if (acceptor_data.found) {
+            /* Check for logical error. It can be only EEXIST */
+            if (acceptor_data.error) {
+                /* Save error anyway */
+                passed_data->error = acceptor_data.error;
+                /* If it is section DETECT or MERGE+DETECT */
+                if (((passed_data->flags & INI_MS_MASK) == INI_MS_DETECT) ||
+                    (((passed_data->flags & INI_MS_MASK) != INI_MS_ERROR) &&
+                     ((passed_data->flags & INI_MV2S_MASK) ==
+                       INI_MV2S_DETECT))) {
+                    TRACE_INFO_NUMBER("Non-critical error",
+                                      acceptor_data.error);
+                }
+                else {
+                    /* In any other mode we need to stop */
+                    TRACE_INFO_NUMBER("Merge error detected",
+                                      acceptor_data.error);
+                    /* Force stop */
+                    *dummy = 1;
+                }
+            }
+        }
+        else {
+            /* Not found? Then create a copy... */
+            error = col_copy_collection_with_cb(&new_ci,
+                                                acceptor_data.ci,
+                                                NULL,
+                                                COL_COPY_NORMAL,
+                                                ini_copy_cb,
+                                                NULL);
+            if (error) {
+                TRACE_ERROR_NUMBER("Failed to copy collection", error);
+                return error;
+            }
+
+            /* ... and embed into the existing collection */
+            error = col_add_collection_to_collection(passed_data->ci,
+                                                     NULL,
+                                                     NULL,
+                                                     new_ci,
+                                                     COL_ADD_MODE_EMBED);
+            if (error) {
+                TRACE_ERROR_NUMBER("Failed to copy collection", error);
+                col_destroy_collection(new_ci);
+                return error;
+            }
+        }
+    }
+
+    TRACE_FLOW_EXIT();
+    return EOK;
+}
+
+static int merge_comment(struct ini_cfgobj *donor,
+                         struct ini_cfgobj *acceptor)
+{
+    int error = EOK;
+
+    TRACE_FLOW_ENTRY();
+
+    if (donor->last_comment) {
+
+        if (acceptor->last_comment) {
+
+            error = ini_comment_add(donor->last_comment,
+                                    acceptor->last_comment);
+            if (error) {
+                TRACE_ERROR_NUMBER("Merge comment failed", error);
+                return error;
+            }
+
+        }
+        else {
+            error = ini_comment_copy(donor->last_comment,
+                                     &(acceptor->last_comment));
+            if (error) {
+                TRACE_ERROR_NUMBER("Copy comment failed", error);
+                return error;
+            }
+        }
+    }
+
+    TRACE_FLOW_EXIT();
+    return EOK;
+}
+
+
+
+/* Internal function to merge two configs */
+static int merge_configs(struct ini_cfgobj *donor,
+                         struct ini_cfgobj *acceptor,
+                         uint32_t collision_flags)
+{
+    int error = EOK;
+    struct merge_data data;
+
+    TRACE_FLOW_ENTRY();
+
+    data.ci = acceptor->cfg;
+    data.flags = collision_flags;
+    data.error = 0;
+    data.found = 0;
+
+    /* Loop through the donor collection calling
+     * donor_handler callback for every section we find.
+     */
+    error = col_traverse_collection(donor->cfg,
+                                    COL_TRAVERSE_ONELEVEL,
+                                    donor_handler,
+                                    (void *)(&data));
+    if (error) {
+        TRACE_ERROR_NUMBER("Merge failed", error);
+        return error;
+    }
+
+    /* Check if we got error */
+    if ((data.error) &&
+        (((collision_flags & INI_MS_MASK) == INI_MS_ERROR) ||
+         ((collision_flags & INI_MV2S_MASK) == INI_MV2S_ERROR))) {
+        TRACE_ERROR_NUMBER("Got error in error mode", data.error);
+        return data.error;
+    }
+
+    /* If boundaries are different re-align the values */
+    if (acceptor->boundary != donor->boundary) {
+        error = ini_config_set_wrap(acceptor, acceptor->boundary);
+        if (error) {
+            TRACE_ERROR_NUMBER("Failed to re-align", error);
+            return error;
+        }
+    }
+
+    /* Merge last comment */
+    error = merge_comment(donor, acceptor);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to merge comment", error);
+        return error;
+    }
+
+    /* Check if we got error */
+    if ((data.error) &&
+        (((collision_flags & INI_MS_MASK) == INI_MS_DETECT) ||
+         ((collision_flags & INI_MV2S_MASK) == INI_MV2S_DETECT))) {
+        TRACE_ERROR_NUMBER("Got error in error or detect mode", data.error);
+        error = data.error;
+    }
+
+    TRACE_FLOW_EXIT();
+    return error;
+}
+
+/* Merge two configurations together creating a new one */
+int ini_config_merge(struct ini_cfgobj *first,
+                     struct ini_cfgobj *second,
+                     uint32_t collision_flags,
+                     struct ini_cfgobj **result)
+{
+    int error = EOK;
+    struct ini_cfgobj *new_co = NULL;
+
+    TRACE_FLOW_ENTRY();
+
+    /* Check input params */
+    if ((!first) ||
+        (!second) ||
+        (!result)) {
+        TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
+        return EINVAL;
+    }
+
+    /* Check collision flags */
+    if (!valid_collision_flags(collision_flags)) {
+        TRACE_ERROR_NUMBER("Invalid flags.", EINVAL);
+        return EINVAL;
+    }
+
+    /* NOTE: We assume that the configuration we merge to
+     * is consistent regarding duplicate values.
+     * For example, if the duplicates are not allowed,
+     * the parsing function should have been instructed
+     * to not allow duplicates.
+     * If in future we decide to be explicite we would need
+     * to introduce a "compacting" function and call it here
+     * after we create a copy.
+     * For now it is treated as a corner case and thus not worth
+     * implementing.
+     */
+
+    /* Create a new config object */
+    error = ini_config_copy(first, &new_co);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to copy configuration", error);
+        return error;
+    }
+
+    /* Merge configs */
+    error = merge_configs(second, new_co, collision_flags);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to merge configuration", error);
+        if ((error == EEXIST) &&
+            ((((collision_flags & INI_MS_MASK) == INI_MS_DETECT) &&
+              ((collision_flags & INI_MV2S_MASK) != INI_MV2S_ERROR)) ||
+             (((collision_flags & INI_MS_MASK) != INI_MS_ERROR) &&
+              ((collision_flags & INI_MV2S_MASK) == INI_MV2S_DETECT)))) {
+            TRACE_ERROR_NUMBER("Got error in detect mode", error);
+            /* Fall through! */
+        }
+        else {
+            /* Got an error in any other mode */
+            TRACE_ERROR_NUMBER("Got error in non detect mode", error);
+            ini_config_destroy(new_co);
+            return error;
+        }
+    }
+
+    *result = new_co;
+    TRACE_FLOW_EXIT();
+    return error;
+
+}
-- 
1.7.1

