From 02c14799196cd1ffd0f53c6b2e995ac17a7f8917 Mon Sep 17 00:00:00 2001
From: Dmitri Pal <dpal@redhat.com>
Date: Mon, 3 Jan 2011 14:41:03 -0500
Subject: [PATCH] [INI] Function to merge two configurations

This patch provides first draft of the implementation
of the code to merge different configurations.
It is similar to the merge code that is implemented inside
the parser but different since it is not done during parsing
of one file but addresses use case when the configuration
is provided by different files that need to be merged together.

NOTE: It would make more sence to review function by function
from bottom of the patch rather than from the top.
---
 ini/ini_configobj.c |  456 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 456 insertions(+), 0 deletions(-)

diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c
index 5e5e40b566abdf3ce2cbc225babe4b1e20c2dd47..d8583a224e8eebc4365df3909b6bac7c98477dc5 100644
--- a/ini/ini_configobj.c
+++ b/ini/ini_configobj.c
@@ -30,11 +30,19 @@
 #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
 
+struct merge_data {
+    struct collection_item *ci;
+    uint32_t flags;
+    int error;
+    int found;
+};
+
 /* Callback */
 void ini_cleanup_cb(const char *property,
                     int property_len,
@@ -254,3 +262,451 @@ int ini_config_copy(struct ini_cfgobj *ini_config,
     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 {
+
+                /* 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)) {
+                TRACE_INFO_STRING("Preseved exisitng value",
+                                  property);
+            }
+            else {
+                /* Check if this is a critical error or not */
+                if ((mergemode == INI_MV1S_ERROR) && (error == EEXIST)) {
+                    TRACE_ERROR_NUMBER("Failed to add value object "
+                                       "to the section", 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 *acceptor,
+                              struct collection_item *donor,
+                              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();
+
+    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(acceptor,
+                                                       donor,
+                                                       passed_data->flags);
+                            if (error) {
+                                TRACE_ERROR_NUMBER("Failed to merge "
+                                                    "sections", error);
+                                return error;
+                            }
+                            break;
+
+    case INI_MS_DETECT:     /* Detect mode */
+                            TRACE_INFO_STRING("Detect mode", "");
+                            passed_data->error = EEXIST;
+                            passed_data->found = 0;
+                            break;
+
+    case INI_MS_MERGE:      /* Merge */
+    default:                TRACE_INFO_STRING("Merge mode", "");
+                            error = merge_two_sections(acceptor,
+                                                       donor,
+                                                       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();
+
+    passed_data = (struct merge_data *)(custom_data);
+
+    /* All section are subcollections */
+    if(type == COL_TYPE_COLLECTIONREF) {
+
+        acceptor_data.flags = passed_data->flags;
+        acceptor_data.ci = *((struct collection_item **)(data));
+        acceptor_data.error = 0;
+        acceptor_data.found = 0;
+
+        /* Try to resolve collision only non ALLOW modes */
+        if (!(acceptor_data.flags & INI_MS_ALLOW)) {
+            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_DETECT) ||
+                    ((passed_data->flags & INI_MS_MERGE) &&
+                     (passed_data->flags & 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;
+}
+
+
+/* Internal function to merge two configs */
+static int merge_configs(struct ini_cfgobj *first,
+                         struct ini_cfgobj *second,
+                         uint32_t collision_flags)
+{
+    int error = EOK;
+    struct merge_data data;
+
+    TRACE_FLOW_ENTRY();
+
+    data.ci = first->cfg;
+    data.flags = collision_flags;
+    data.error = 0;
+    data.found = 0;
+
+    error = col_traverse_collection(second->cfg,
+                                    COL_TRAVERSE_ONELEVEL,
+                                    donor_handler,
+                                    (void *)(&data));
+    if (error) {
+        TRACE_ERROR_NUMBER("Merge failed", error);
+        return error;
+    }
+
+    /* If boundaries are different re-align the values */
+    if (first->boundary != second->boundary) {
+
+        error = ini_config_set_wrap(first, first->boundary);
+        if (error) {
+            TRACE_ERROR_NUMBER("Failed to re-align", error);
+            return 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;
+    }
+
+    /* Create a copy */
+    /* TBD - create a COMPACT copy */
+    error = ini_config_copy(first, &new_co);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to copy collection", error);
+        return error;
+    }
+
+    /* Merge configs */
+    error = merge_configs(new_co, second, collision_flags);
+    if (error) {
+        TRACE_ERROR_NUMBER("Failed to copy collection", error);
+        ini_config_destroy(new_co);
+        return error;
+    }
+
+    *result = new_co;
+
+    TRACE_FLOW_EXIT();
+    return error;
+
+}
-- 
1.5.5.6

