summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitri Pal <dpal@redhat.com>2012-12-04 19:36:58 -0500
committerOndrej Kos <okos@redhat.com>2012-12-06 14:23:26 +0100
commit85831de020e701104d0bcfbb274e5978faf31723 (patch)
treebd81d161545c5644f96b3f5782b5cf8653cf572f
parentded67f11c611f47f2ba5ffe277950c4b09ba0702 (diff)
downloadding-libs-85831de020e701104d0bcfbb274e5978faf31723.tar.gz
ding-libs-85831de020e701104d0bcfbb274e5978faf31723.tar.xz
ding-libs-85831de020e701104d0bcfbb274e5978faf31723.zip
Ability to merge configurations
This patch adds capability to merge two configuration objects.
-rw-r--r--ini/ini_configobj.c586
1 files changed, 581 insertions, 5 deletions
diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c
index 193474b..7de57e4 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;
+
+}