From 85831de020e701104d0bcfbb274e5978faf31723 Mon Sep 17 00:00:00 2001 From: Dmitri Pal Date: Tue, 4 Dec 2012 19:36:58 -0500 Subject: Ability to merge configurations This patch adds capability to merge two configuration objects. --- ini/ini_configobj.c | 586 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file 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; + +} -- cgit