/
githubmirror
/
ding-libs
Обзор
Документация
Войти
/
githubmirror
/
ding-libs
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ini/ini_augment.c
953 строки
29 KB
Michal Židek
INI: Remove definiton of TRACE_LEVEL
16 ноя 2017, 12:07
16 ноя 2017, 12:07
a731d8c
Код
Авторство
О чём код?
/* INI LIBRARY Module represents part of the INI interface. The main function in this module allows to merge snippets of different config files. Copyright (C) Dmitri Pal <dpal@redhat.com> 2014 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 <http://www.gnu.org/licenses/>. */ #include "config.h" #include <errno.h> #include <stdarg.h> #include <dirent.h> #include <stdio.h> #include <string.h> #include <stddef.h> #include <limits.h> #include <sys/types.h> #include <regex.h> #include <unistd.h> #include "trace.h" #include "collection.h" #include "collection_tools.h" #include "ini_configobj.h" #include "ini_config_priv.h" #include "ini_defines.h" #include "path_utils.h" /* Constants to match */ #define INI_CURRENT_DIR "." #define INI_PARENT_DIR ".." /* Size of incremental growth for ref of the array of strings */ #define INI_AUG_ARR_SIZE_INC 50 /* Function to add an error to the array */ static void ini_aug_add_string(struct ref_array *ra, const char *format, ...) { va_list args; char *result = NULL; TRACE_FLOW_ENTRY(); va_start(args, format); if(vasprintf(&result, format, args )) { TRACE_INFO_STRING("String:", result); /* This is a best effort assignment. error is not checked */ (void)ref_array_append(ra, (void *)&result); } va_end(args); TRACE_FLOW_EXIT(); } /* Add error about opening directory */ static void add_dir_open_error(int error, char *dirname, struct ref_array *ra_err) { TRACE_FLOW_ENTRY(); switch(error) { case EACCES: ini_aug_add_string(ra_err, "Permission denied opening %s.", dirname); break; case EMFILE: case ENFILE: ini_aug_add_string(ra_err, "Too many file descriptors in use while opening %s.", dirname); break; case ENOENT: ini_aug_add_string(ra_err, "Directory %s does not exist.", dirname); break; case ENOTDIR: ini_aug_add_string(ra_err, "Path %s is not a directory.", dirname); break; case ENOMEM: ini_aug_add_string(ra_err, "Insufficient memory while opening %s.", dirname); break; default: ini_aug_add_string(ra_err, "Unknown error while opening %s.", dirname); break; } TRACE_FLOW_EXIT(); } /* Cleanup callback for regex array */ static void regex_cleanup(void *elem, ref_array_del_enum type, void *data) { TRACE_FLOW_ENTRY(); regfree(*((regex_t **)elem)); free(*((regex_t **)elem)); TRACE_FLOW_EXIT(); } /* Prepare array of regular expressions */ static int ini_aug_regex_prepare(const char *patterns[], struct ref_array *ra_err, struct ref_array **ra_regex) { int error = EOK; int reg_err = 0; char const *pat = NULL; struct ref_array *ra = NULL; regex_t *preg = NULL; size_t buf_size = 0; char *err_str = NULL; size_t i; TRACE_FLOW_ENTRY(); if (patterns) { /* Create array to mark bad patterns */ error = ref_array_create(&ra, sizeof(regex_t *), INI_AUG_ARR_SIZE_INC, regex_cleanup, NULL); if (error) { TRACE_ERROR_NUMBER("Failed to create array.", error); return error; } /* Run through the list and save precompiled patterns */ for (i = 0; patterns[i] != NULL; i++) { pat = patterns[i]; TRACE_INFO_STRING("Pattern:", pat); preg = calloc(1, sizeof(regex_t)); if (preg == NULL) { TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM); ref_array_destroy(ra); return ENOMEM; } reg_err = regcomp(preg, pat, REG_NOSUB); if (reg_err) { /* Get size, allocate buffer, record error... */ buf_size = regerror(reg_err, preg, NULL, 0); err_str = malloc (buf_size); if (err_str == NULL) { TRACE_ERROR_NUMBER("Failed to create array.", ENOMEM); ref_array_destroy(ra); free(preg); return ENOMEM; } regerror(reg_err, preg, err_str, buf_size); free(preg); ini_aug_add_string(ra_err, "Failed to process expression: %s." " Compilation returned error: %s", pat, err_str); free(err_str); /* All error processing is done - advance to next pattern */ pat++; continue; } /* In case of no error add compiled expression into the buffer */ error = ref_array_append(ra, (void *)&preg); if (error) { TRACE_ERROR_NUMBER("Failed to add element to array.", error); ref_array_destroy(ra); free(preg); return error; } } } *ra_regex = ra; /* ref_array_debug(*ra_regex, 1); */ TRACE_FLOW_EXIT(); return EOK; } /* Match file name */ static bool ini_aug_match_name(char *name, struct ref_array *ra_regex) { uint32_t len = 0; uint32_t i = 0; bool match = false; regex_t *preg = NULL; TRACE_FLOW_ENTRY(); len = ref_array_len(ra_regex); if (len == 0) { /* List is empty - nothing to do */ TRACE_FLOW_EXIT(); return true; } TRACE_INFO_STRING("Name to match:", name); TRACE_INFO_NUMBER("Number of regexes:", len); /* ref_array_debug(ra_regex, 1);*/ for (i = 0; i < len; i++) { preg = *((regex_t **)ref_array_get(ra_regex, i, NULL)); if (preg == NULL) continue; if (regexec(preg, name, 0, NULL, 0) == 0) { TRACE_INFO_NUMBER("Name matched regex number:", i); match = true; break; } } TRACE_FLOW_EXIT(); return match; } /* Check if this is a file and validate permission */ static bool ini_check_file_perm(char *name, struct access_check *check_perm, struct ref_array *ra_err) { bool ret = false; int error = EOK; struct stat file_info; TRACE_FLOW_ENTRY(); errno = 0; if (stat(name, &file_info) == -1) { error = errno; TRACE_ERROR_NUMBER("Failed to get file stats", error); ini_aug_add_string(ra_err, "Failed to read metadata for file %s." " Skipping.", name); return false; } if (!S_ISREG(file_info.st_mode)) { ini_aug_add_string(ra_err, "File %s is not a regular file. Skipping.", name); return false; } if ((check_perm) && (check_perm->flags)) { error = access_check_int(&file_info, check_perm->flags, check_perm->uid, check_perm->gid, check_perm->mode, check_perm->mask); if(error) { TRACE_ERROR_NUMBER("Access check returned", error); ini_aug_add_string(ra_err, "File %s did not pass access check. Skipping.", name); return false; } } ret = true; TRACE_FLOW_EXIT(); return ret; } /* Sort array */ static void ini_aug_sort_list(struct ref_array *ra_list) { unsigned len = 0, j = 0, k = 0; char **item1 = NULL; char **item2 = NULL; TRACE_FLOW_ENTRY(); len = ref_array_len(ra_list); if (len == 0) return; /* If have trace output array before sorting */ /* #ifdef HAVE_TRACE for (i = 0; i < len; i++) { TRACE_INFO_STRING("Before:", *((char **) ref_array_get(ra_list, i, NULL))); } #endif */ for (k = 0; k < len-1; k++) { j = k + 1; while (j > 0) { item1 = (char **) ref_array_get(ra_list, j - 1, NULL); item2 = (char **) ref_array_get(ra_list, j, NULL); /* Swap items if they are not NULL and string comparison * indicates that they need to be swapped or if the first * one is NULL but second is not. That would push * NULL elements of the array to the end of the array. */ if (((item1 && item2) && (strcoll(*item1,*item2)) > 0) || (!item1 && item2)) { ref_array_swap(ra_list, j - 1, j); } j--; } } /* And after sorting */ /* #ifdef HAVE_TRACE for (i = 0; i < len; i++) { TRACE_INFO_STRING("After:", *((char **) ref_array_get(ra_list, i, NULL))); } #endif */ TRACE_FLOW_EXIT(); } /* Construct snippet lists based on the directory */ static int ini_aug_construct_list(char *dirname , const char *patterns[], struct access_check *check_perm, struct ref_array *ra_list, struct ref_array *ra_err) { int error = EOK; DIR *dir = NULL; struct dirent *entryp = NULL; char *snipname = NULL; char fullname[PATH_MAX + 1] = {0}; struct ref_array *ra_regex = NULL; bool match = false; TRACE_FLOW_ENTRY(); /* Prepare patterns */ error = ini_aug_regex_prepare(patterns, ra_err, &ra_regex); if (error) { TRACE_ERROR_NUMBER("Failed to prepare regex array.", error); return error; } /* Open directory */ errno = 0; dir = opendir(dirname); if (!dir) { error = errno; if (error == ENOMEM) { TRACE_ERROR_NUMBER("No memory to open dir.", ENOMEM); ref_array_destroy(ra_regex); return ENOMEM; } /* Log an error, it is a recoverable error */ add_dir_open_error(error, dirname, ra_err); ref_array_destroy(ra_regex); return EOK; } /* Loop through the directory */ while (true) { errno = 0; entryp = readdir(dir); if (entryp == NULL && errno != 0) { error = errno; TRACE_ERROR_NUMBER("Failed to read directory.", error); ref_array_destroy(ra_regex); closedir(dir); return error; } /* Stop looping if we reached the end */ if (entryp == NULL) break; TRACE_INFO_STRING("Processing", entryp->d_name); /* Always skip current and parent dirs */ if ((strncmp(entryp->d_name, INI_CURRENT_DIR, sizeof(INI_CURRENT_DIR)) == 0) || (strncmp(entryp->d_name, INI_PARENT_DIR, sizeof(INI_PARENT_DIR)) == 0)) continue; error = path_concat(fullname, PATH_MAX, dirname, entryp->d_name); if (error != EOK) { TRACE_ERROR_NUMBER("path_concat failed.", error); ref_array_destroy(ra_regex); closedir(dir); return error; } /* Match names */ match = ini_aug_match_name(entryp->d_name, ra_regex); if (match) { if(ini_check_file_perm(fullname, check_perm, ra_err)) { /* Dup name and add to the array */ snipname = NULL; snipname = strdup(fullname); if (snipname == NULL) { TRACE_ERROR_NUMBER("Failed to dup string.", ENOMEM); ref_array_destroy(ra_regex); closedir(dir); return ENOMEM; } error = ref_array_append(ra_list, (void *)&snipname); if (error) { TRACE_ERROR_NUMBER("No memory to add file to " "the snippet list.", ENOMEM); ref_array_destroy(ra_regex); closedir(dir); return ENOMEM; } } } else { TRACE_INFO_STRING("File did not match provided patterns." " Skipping:", fullname); } } closedir(dir); ref_array_destroy(ra_regex); ini_aug_sort_list(ra_list); TRACE_FLOW_EXIT(); return EOK; } /* Construct the full dir path */ static int ini_aug_expand_path(const char *path, char **fullname) { int error = EOK; char *dirname = NULL; TRACE_FLOW_ENTRY(); TRACE_INFO_STRING("Input path", path); dirname = malloc(PATH_MAX + 1); if (!dirname) { TRACE_ERROR_NUMBER("Failed to allocate memory for file path.", ENOMEM); return ENOMEM; } /* Make the path */ error = make_normalized_absolute_path(dirname, PATH_MAX, path); if (error) { TRACE_ERROR_NUMBER("Failed to resolve path", error); free(dirname); /* This is a recoverable error */ *fullname = NULL; } else *fullname = dirname; TRACE_INFO_STRING("Output path", *fullname); TRACE_FLOW_EXIT(); return EOK; } /* Prepare the lists of the files that need to be merged */ static int ini_aug_preprare(const char *path, const char *patterns[], struct access_check *check_perm, struct ref_array *ra_list, struct ref_array *ra_err) { int error = EOK; char *dirname = NULL; TRACE_FLOW_ENTRY(); /* Contruct path */ error = ini_aug_expand_path(path, &dirname); if (error) { TRACE_ERROR_NUMBER("Failed to allocate memory for dir path.", error); return error; } /* Was it a good path? */ if (!dirname) { TRACE_ERROR_NUMBER("Failed to resolve path", error); ini_aug_add_string(ra_err, "Could not resolve directory path %s.", path); /* Path might not exist so it is a recoverable error */ return EOK; } /* Construct snipet lists */ error = ini_aug_construct_list(dirname, patterns, check_perm, ra_list, ra_err); free(dirname); TRACE_FLOW_EXIT(); return error; } /* Cleanup callback for string arrays */ static void array_cleanup(void *elem, ref_array_del_enum type, void *data) { TRACE_FLOW_ENTRY(); free(*((char **)elem)); TRACE_FLOW_EXIT(); } /* Check that sections are in the given list */ static int ini_aug_match_sec(struct ini_cfgobj *snip_cfg, struct ref_array *ra_regex, struct ref_array *ra_err, char *snip_name, bool *skip) { int error = EOK; char **section_list = NULL; char **section_iter = NULL; int size = 0; bool match = false; int match_count = 0; int section_count = 0; TRACE_FLOW_ENTRY(); section_list = ini_get_section_list(snip_cfg, &size, &error); if (error) { TRACE_ERROR_NUMBER("Failed create section list", error); return error; } if (section_list == NULL) { /* No sections in the file */ ini_aug_add_string(ra_err, "No sections found in file %s. Skipping.", snip_name); *skip = true; TRACE_FLOW_EXIT(); return EOK; } section_iter = section_list; while (*section_iter) { match = ini_aug_match_name(*section_iter, ra_regex); if (match) { match_count++; TRACE_INFO_STRING("Matched section", *section_iter); } else { TRACE_INFO_STRING("Section not matched", *section_iter); ini_aug_add_string(ra_err, "Section [%s] found in file %s is" " not allowed.", *section_iter, snip_name); } section_count++; section_iter++; } ini_free_section_list(section_list); /* Just in case check that we processed anything */ if (section_count == 0) { TRACE_INFO_STRING("No sections found in file. Skipping:", snip_name); *skip = true; TRACE_FLOW_EXIT(); return EOK; } /* Were all sections matched? */ if (section_count != match_count) { /* Snippet containes sections that are not allowed */ ini_aug_add_string(ra_err, "File %s contains sections that" " are not allowed. Skipping.", snip_name); *skip = true; TRACE_FLOW_EXIT(); return EOK; } /* Everything matched OK so we give green light to merge */ TRACE_INFO_STRING("File will be included", snip_name); *skip = false; TRACE_FLOW_EXIT(); return EOK; } /* Apply snippets */ static int ini_aug_apply(struct ini_cfgobj *cfg, struct ref_array *ra_list, const char *sections[], int error_level, uint32_t collision_flags, uint32_t parse_flags, uint32_t merge_flags, struct ref_array *ra_err, struct ref_array *ra_ok, struct ini_cfgobj **out_cfg) { int error = EOK; uint32_t len = 0; uint32_t i = 0; uint32_t j = 0; struct ini_cfgfile *file_ctx = NULL; struct ini_cfgobj *snip_cfg = NULL; struct ini_cfgobj *res_cfg = NULL; struct ini_cfgobj *tmp_cfg = NULL; char **error_list = NULL; unsigned cnt = 0; bool skip = false; struct ref_array *ra_regex = NULL; char *snip_name = NULL; char **snip_name_ptr = NULL; TRACE_FLOW_ENTRY(); error = ini_config_copy(cfg, &res_cfg); if (error) { TRACE_ERROR_NUMBER("Failed to copy config object", error); *out_cfg = NULL; return error; } len = ref_array_len(ra_list); if (len == 0) { /* List is empty - nothing to do */ *out_cfg = res_cfg; TRACE_FLOW_EXIT(); return EOK; } /* Prepare patterns */ error = ini_aug_regex_prepare(sections, ra_err, &ra_regex); if (error) { TRACE_ERROR_NUMBER("Failed to prepare regex array.", error); *out_cfg = res_cfg; return error; } /* Loop through the snippets */ for (i = 0; i < len; i++) { /* Prepare config object */ error = ini_config_create(&snip_cfg); if (error) { TRACE_ERROR_NUMBER("Failed to create config object", error); goto err; } /* Process snippet */ snip_name_ptr = (char **)ref_array_get (ra_list, i, NULL); if (snip_name_ptr == NULL) continue; snip_name = *snip_name_ptr; if (snip_name == NULL) continue; TRACE_INFO_STRING("Processing", snip_name); /* Open file */ error = ini_config_file_open(snip_name, INI_META_NONE, &file_ctx); if (error) { TRACE_ERROR_NUMBER("Failed to open snippet.", error); ini_aug_add_string(ra_err, "Failed to open file %s.", snip_name); ini_config_destroy(snip_cfg); /* We can recover so go on */ continue; } TRACE_INFO_NUMBER("Error level:", error_level); TRACE_INFO_NUMBER("Collision flags:", collision_flags); TRACE_INFO_NUMBER("Parse level:", parse_flags); /* Read config */ error = ini_config_parse(file_ctx, error_level, collision_flags, parse_flags, snip_cfg); ini_config_file_destroy(file_ctx); file_ctx = NULL; if (error) { TRACE_ERROR_NUMBER("Failed to parse configuration.", error); cnt = ini_config_error_count(snip_cfg); if (cnt) { ini_aug_add_string(ra_err, "Errors detected while parsing: %s.", snip_name); /* Extract errors */ error = ini_config_get_errors(snip_cfg, &error_list); if (error) { TRACE_ERROR_NUMBER("Can't get errors.", error); ini_config_destroy(snip_cfg); goto err; } /* Copy errors into error array */ for (j=0; j< cnt; j++) { ini_aug_add_string(ra_err, error_list[j]); } ini_config_free_errors(error_list); } /* The snippet was malformed, this is OK, go on */ if (error_level != INI_STOP_ON_NONE) { ini_aug_add_string(ra_err, "Due to errors file %s is not considered." " Skipping.", snip_name); ini_config_destroy(snip_cfg); continue; } /* If we are told to not stop try to process anyway */ } /* Validate that file contains only allowed sections */ if (sections) { /* Use a safe default, function should update it anyways * but it is better to not merge than to allow bad snippet */ skip = true; error = ini_aug_match_sec(snip_cfg, ra_regex, ra_err, snip_name, &skip); if (error) { TRACE_ERROR_NUMBER("Failed to validate section.", error); ini_config_destroy(snip_cfg); goto err; } } /* Merge */ if (!skip) { /* col_debug_collection(res_cfg->cfg, COL_TRAVERSE_DEFAULT); */ error = ini_config_merge(res_cfg, snip_cfg, merge_flags, &tmp_cfg); if (error) { if (error == ENOMEM) { TRACE_ERROR_NUMBER("Merge failed.", error); ini_config_destroy(snip_cfg); goto err; } else if ((error == EEXIST) && ((ini_flags_have(INI_MS_DETECT, merge_flags) && ((merge_flags & INI_MV2S_MASK) != INI_MV2S_ERROR)) || ((!ini_flags_have(INI_MS_ERROR, merge_flags)) && ((merge_flags & INI_MV2S_MASK) == INI_MV2S_DETECT)))) { TRACE_ERROR_NUMBER("Got error in detect mode", error); /* Fall through! */ ini_aug_add_string(ra_err, "Duplicate section detected " "in snippet: %s.", snip_name); } else { ini_aug_add_string(ra_err, "Errors during merge." " Snippet ignored %s.", snip_name); /* The snippet failed to merge, this is OK, go on */ TRACE_INFO_NUMBER("Merge failure.Continue. Error", error); ini_config_destroy(snip_cfg); continue; } } TRACE_INFO_STRING("Merged file.", snip_name); /* col_debug_collection(tmp_cfg->cfg, COL_TRAVERSE_DEFAULT); */ ini_config_destroy(res_cfg); res_cfg = tmp_cfg; /* Record that snippet was successfully merged */ ini_aug_add_string(ra_ok, "%s", snip_name); } /* Cleanup */ ini_config_destroy(snip_cfg); } ref_array_destroy(ra_regex); *out_cfg = res_cfg; TRACE_FLOW_EXIT(); return error; err: ini_config_destroy(res_cfg); ref_array_destroy(ra_regex); if (ini_config_copy(cfg, &res_cfg)) { TRACE_ERROR_NUMBER("Failed to copy config object", error); *out_cfg = NULL; return error; } *out_cfg = res_cfg; return error; } /* Function to merge additional snippets of the config file * from a provided directory. */ int ini_config_augment(struct ini_cfgobj *base_cfg, const char *path, const char *patterns[], const char *sections[], struct access_check *check_perm, int error_level, uint32_t collision_flags, uint32_t parse_flags, uint32_t merge_flags, struct ini_cfgobj **result_cfg, struct ref_array **error_list, struct ref_array **success_list) { int error = EOK; /* The internal list that will hold snippet file names */ struct ref_array *ra_list = NULL; /* List of error strings that will be returned to the caller */ struct ref_array *ra_err = NULL; /* List of files that were merged */ struct ref_array *ra_ok = NULL; /* Check arguments */ if (base_cfg == NULL) { TRACE_ERROR_NUMBER("Invalid argument", EINVAL); return EINVAL; } if (result_cfg == NULL) { TRACE_ERROR_NUMBER("Invalid argument", EINVAL); return EINVAL; } /* Create arrays for lists */ if ((ref_array_create(&ra_list, sizeof(char *), INI_AUG_ARR_SIZE_INC, array_cleanup, NULL) != 0) || (ref_array_create(&ra_err, sizeof(char *), INI_AUG_ARR_SIZE_INC * 5, array_cleanup, NULL) != 0) || (ref_array_create(&ra_ok, sizeof(char *), INI_AUG_ARR_SIZE_INC * 5, array_cleanup, NULL) != 0)) { TRACE_ERROR_NUMBER("Failed to allocate memory for arrays.", ENOMEM); ref_array_destroy(ra_list); ref_array_destroy(ra_err); ref_array_destroy(ra_ok); return ENOMEM; } /* Construct snipet lists */ error = ini_aug_preprare(path, patterns, check_perm, ra_list, ra_err); if (error) { TRACE_ERROR_NUMBER("Failed to prepare lists of snippets.", error); ref_array_destroy(ra_list); ref_array_destroy(ra_err); ref_array_destroy(ra_ok); return error; } /* Apply snippets */ error = ini_aug_apply(base_cfg, ra_list, sections, error_level, collision_flags, parse_flags, merge_flags, ra_err, ra_ok, result_cfg); /* Cleanup */ ref_array_destroy(ra_list); if (error_list) { *error_list = ra_err; } else { ref_array_destroy(ra_err); } if (success_list) { *success_list = ra_ok; } else { ref_array_destroy(ra_ok); } TRACE_FLOW_EXIT(); return error; }