/* INI LIBRARY Module represents interface to the main INI object. Copyright (C) Dmitri Pal 2010 INI Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. INI Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with INI Library. If not, see . */ #define _GNU_SOURCE #include #include #include #include #include "config.h" #include "trace.h" #include "collection.h" #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, int type, void *data, int length, void *custom_data) { struct value_obj *vo = NULL; TRACE_FLOW_ENTRY(); /* Banary items are the values */ if(type == COL_TYPE_BINARY) { vo = *((struct value_obj **)(data)); value_destroy(vo); } TRACE_FLOW_EXIT(); } /* Traverse the collection and clean the object */ void ini_config_destroy(struct ini_cfgobj *ini_config) { TRACE_FLOW_ENTRY(); if (ini_config) { if(ini_config->cfg) { col_destroy_collection_with_cb(ini_config->cfg, ini_cleanup_cb, NULL); } free(ini_config); } TRACE_FLOW_EXIT(); } /* Create a config object */ int ini_config_create(struct ini_cfgobj **ini_config) { int error = EOK; struct ini_cfgobj *new_co = NULL; TRACE_FLOW_ENTRY(); if (!ini_config) { TRACE_ERROR_NUMBER("Invalid argument", EINVAL); return EINVAL; } errno = 0; new_co = malloc(sizeof(struct ini_cfgobj)); if (!new_co) { error = errno; TRACE_ERROR_NUMBER("Failed to allocate memory", ENOMEM); return ENOMEM; } new_co->cfg = NULL; new_co->boundary = INI_WRAP_BOUNDARY; /* Create a collection to hold configuration data */ error = col_create_collection(&(new_co->cfg), INI_CONFIG_NAME, COL_CLASS_INI_CONFIG); if (error != EOK) { TRACE_ERROR_NUMBER("Failed to create collection.", error); ini_config_destroy(new_co); return error; } *ini_config = new_co; TRACE_FLOW_EXIT(); return error; } /* Callback to set the boundary */ int ini_boundary_cb(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; uint32_t boundary; TRACE_FLOW_ENTRY(); boundary = *((uint32_t *)(custom_data)); /* Banary items are the values */ if(type == COL_TYPE_BINARY) { vo = *((struct value_obj **)(data)); error = value_set_boundary(vo, boundary); } TRACE_FLOW_EXIT(); return error; } /* Set the folding boundary for multiline values. * Use before serializing and saving to a file if the * default boundary of 80 characters does not work for you. */ int ini_config_set_wrap(struct ini_cfgobj *ini_config, uint32_t boundary) { int error = EOK; TRACE_FLOW_ENTRY(); if (!ini_config) { TRACE_ERROR_NUMBER("Invalid argument", EINVAL); return EINVAL; } ini_config->boundary = boundary; error = col_traverse_collection(ini_config->cfg, COL_TRAVERSE_DEFAULT, ini_boundary_cb, (void *)(&(ini_config->boundary))); if (error) { TRACE_ERROR_NUMBER("Failed to set wrapping boundary", error); return error; } TRACE_FLOW_EXIT(); return error; } /* Configuration copy callback */ static int ini_copy_cb(struct collection_item *item, void *ext_data, int *skip) { int error = EOK; struct value_obj *vo = NULL; struct value_obj *new_vo = NULL; TRACE_FLOW_ENTRY(); ext_data = NULL; *skip = 0; /* Banary items are the values */ if(col_get_item_type(item) == COL_TYPE_BINARY) { vo = *((struct value_obj **)(col_get_item_data(item))); error = value_copy(vo, &new_vo); if (error) { TRACE_ERROR_NUMBER("Failed to copy value", error); return error; } error = col_modify_binary_item(item, NULL, &new_vo, sizeof(struct value_obj *)); if (error) { TRACE_ERROR_NUMBER("Failed to copy value", error); value_destroy(new_vo); return error; } } TRACE_FLOW_EXIT(); return error; } /* Copy configuration */ int ini_config_copy(struct ini_cfgobj *ini_config, struct ini_cfgobj **ini_new) { int error = EOK; struct ini_cfgobj *new_co = NULL; TRACE_FLOW_ENTRY(); if ((!ini_config) || (!ini_new)) { TRACE_ERROR_NUMBER("Invalid argument", EINVAL); return EINVAL; } /* Create a new configuration object */ errno = 0; new_co = malloc(sizeof(struct ini_cfgobj)); if (!new_co) { error = errno; TRACE_ERROR_NUMBER("Failed to allocate memory", ENOMEM); return ENOMEM; } new_co->cfg = NULL; new_co->boundary = ini_config->boundary; error = col_copy_collection_with_cb(&(new_co->cfg), ini_config->cfg, INI_CONFIG_NAME, COL_COPY_NORMAL, ini_copy_cb, NULL); if (error) { TRACE_ERROR_NUMBER("Failed to copy collection", 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 { /* 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; }