diff --git a/apps/app_directory.c b/apps/app_directory.c index 350401e464..267e2e8c34 100644 --- a/apps/app_directory.c +++ b/apps/app_directory.c @@ -528,7 +528,7 @@ static struct ast_config *realtime_directory(char *context) } /* Does the context exist within the config file? If not, make one */ - if (!(cat = ast_category_get(cfg, ctx))) { + if (!(cat = ast_category_get(cfg, ctx, NULL))) { if (!(cat = ast_category_new(ctx, "", 99999))) { ast_log(LOG_WARNING, "Out of memory\n"); ast_config_destroy(cfg); diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 45f9ebfea5..c881e49d4b 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -1821,7 +1821,7 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword) new = ast_alloca((strlen(value) + strlen(newpassword) + 1)); sprintf(new, "%s%s", newpassword, value); } - if (!(cat = ast_category_get(cfg, category))) { + if (!(cat = ast_category_get(cfg, category, NULL))) { ast_log(AST_LOG_WARNING, "Failed to get category structure.\n"); break; } @@ -1858,7 +1858,7 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword) } new = ast_alloca(strlen(newpassword) + 1); sprintf(new, "%s", newpassword); - if (!(cat = ast_category_get(cfg, category))) { + if (!(cat = ast_category_get(cfg, category, NULL))) { ast_debug(4, "failed to get category!\n"); ast_free(var); break; @@ -7791,7 +7791,7 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char duration_buf[12]; *duration += prepend_duration; - msg_cat = ast_category_get(msg_cfg, "message"); + msg_cat = ast_category_get(msg_cfg, "message", NULL); snprintf(duration_buf, 11, "%ld", *duration); if (!ast_variable_update(msg_cat, "duration", duration_buf, NULL, 0)) { ast_config_text_file_save(textfile, msg_cfg, "app_voicemail"); @@ -11930,7 +11930,7 @@ static int add_message_id(struct ast_config *msg_cfg, char *dir, int msg, char * return -1; } - cat = ast_category_get(msg_cfg, "message"); + cat = ast_category_get(msg_cfg, "message", NULL); if (!cat) { ast_log(LOG_ERROR, "Voicemail data file %s/%d.txt has no [message] category?\n", dir, msg); ast_variables_destroy(var); diff --git a/include/asterisk/config.h b/include/asterisk/config.h index 541fea4ff7..98cd71445c 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -203,10 +203,30 @@ void ast_config_sort_categories(struct ast_config *config, int descending, int (*comparator)(struct ast_category *p, struct ast_category *q)); /*! - * \brief Goes through categories + * \brief Browse categories with filters * * \param config Which config structure you wish to "browse" - * \param prev A pointer to a previous category. + * \param category_name An optional category name. + * Pass NULL to not restrict by category name. + * \param prev A pointer to the starting category structure. + * Pass NULL to start at the beginning. + * \param filter An optional comma-separated list of = + * pairs. Only categories with matching variables will be returned. + * The special name 'TEMPLATES' can be used with the special values + * 'include' or 'restrict' to include templates in the result or + * restrict the result to only templates. + * + * \retval a category on success + * \retval NULL on failure/no-more-categories + */ +struct ast_category *ast_category_browse_filtered(struct ast_config *config, + const char *category_name, struct ast_category *prev, const char *filter); + +/*! + * \brief Browse categories + * + * \param config Which config structure you wish to "browse" + * \param prev_name A pointer to a previous category name. * * \details * This function is kind of non-intuitive in it's use. @@ -216,13 +236,20 @@ void ast_config_sort_categories(struct ast_config *config, int descending, * as the second pointer, and it will return a pointer to the category name * afterwards. * - * \retval a category on success + * \retval a category name on success * \retval NULL on failure/no-more-categories */ -char *ast_category_browse(struct ast_config *config, const char *prev); +char *ast_category_browse(struct ast_config *config, const char *prev_name); /*! - * \brief Goes through variables + * \brief Browse variables + * \param config Which config structure you wish to "browse" + * \param category_name Which category to "browse" + * \param filter an optional comma-separated list of = + * pairs. Only categories with matching variables will be browsed. + * The special name 'TEMPLATES' can be used with the special values + * 'include' or 'restrict' to include templates in the result or + * restrict the result to only templates. * * \details * Somewhat similar in intent as the ast_category_browse. @@ -231,7 +258,10 @@ char *ast_category_browse(struct ast_config *config, const char *prev); * \retval ast_variable list on success * \retval NULL on failure */ -struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category); +struct ast_variable *ast_variable_browse_filtered(const struct ast_config *config, + const char *category_name, const char *filter); +struct ast_variable *ast_variable_browse(const struct ast_config *config, + const char *category_name); /*! * \brief given a pointer to a category, return the root variable. @@ -243,25 +273,50 @@ struct ast_variable *ast_variable_browse(const struct ast_config *config, const struct ast_variable *ast_category_first(struct ast_category *cat); /*! - * \brief Gets a variable + * \brief Gets a variable by context and variable names * * \param config which (opened) config to use * \param category category under which the variable lies * \param variable which variable you wish to get the data for + * \param filter an optional comma-separated list of = + * pairs. Only categories with matching variables will be searched. + * The special name 'TEMPLATES' can be used with the special values + * 'include' or 'restrict' to include templates in the result or + * restrict the result to only templates. + * + * \retval The variable value on success + * \retval NULL if unable to find it. + */ +const char *ast_variable_retrieve_filtered(struct ast_config *config, + const char *category, const char *variable, const char *filter); +const char *ast_variable_retrieve(struct ast_config *config, + const char *category, const char *variable); + +/*! + * \brief Gets a variable from a specific category structure + * + * \param category category structure under which the variable lies + * \param variable which variable you wish to get the data for * * \details - * Goes through a given config file in the given category and searches for the given variable + * Goes through a given category and searches for the given variable * * \retval The variable value on success * \retval NULL if unable to find it. */ -const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable); +const char *ast_variable_find(const struct ast_category *category, const char *variable); /*! * \brief Retrieve a category if it exists * * \param config which config to use * \param category_name name of the category you're looking for + * \param filter If a config contains more than 1 category with the same name, + * you can specify a filter to narrow the search. The filter is a comma-separated + * list of = pairs. Only a category with matching + * variables will be returned. The special name 'TEMPLATES' can be used with the + * special values 'include' or 'restrict' to include templates in the result or + * restrict the result to only templates. * * \details * This will search through the categories within a given config file for a match. @@ -269,20 +324,57 @@ const char *ast_variable_retrieve(const struct ast_config *config, const char *c * \retval pointer to category if found * \retval NULL if not. */ -struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name); +struct ast_category *ast_category_get(const struct ast_config *config, + const char *category_name, const char *filter); + +/*! + * \brief Return the name of the category + * + * \param category category structure + * + * \retval pointer to category name if found + * \retval NULL if not. + */ +const char *ast_category_get_name(const struct ast_category *category); + +/*! + * \brief Check if category is a template + * + * \param category category structure + * + * \retval 1 if a template. + * \retval 0 if not. + */ +int ast_category_is_template(const struct ast_category *category); + +/*! + * \brief Return the template names this category inherits from + * + * \param category category structure + * + * \return an ast_str (which must be freed after use) with a comma + * separated list of templates names or NULL if there were no templates. + */ +struct ast_str *ast_category_get_templates(const struct ast_category *category); /*! * \brief Check for category duplicates * * \param config which config to use * \param category_name name of the category you're looking for + * \param filter an optional comma-separated list of = + * pairs. Only categories with matching variables will be returned. + * The special name 'TEMPLATES' can be used with the special values + * 'include' or 'restrict' to include templates in the result or + * restrict the result to only templates. * * \details * This will search through the categories within a given config file for a match. * * \return non-zero if found */ -int ast_category_exist(const struct ast_config *config, const char *category_name); +int ast_category_exist(const struct ast_config *config, const char *category_name, + const char *filter); /*! * \brief Retrieve realtime configuration @@ -661,9 +753,23 @@ void ast_config_set_current_category(struct ast_config *cfg, const struct ast_ca */ const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var); -/*! \brief Create a category structure */ +/*! + * \brief Create a category + * + * \param name name of new category + * \param in_file filename which contained the new config + * \param lineno line number + */ struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno); -void ast_category_append(struct ast_config *config, struct ast_category *cat); + +/*! + * \brief Create a category making it a template + * + * \param name name of new template + * \param in_file filename which contained the new config + * \param lineno line number + */ +struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno); /*! * \brief Inserts new category @@ -677,17 +783,49 @@ void ast_category_append(struct ast_config *config, struct ast_category *cat); * matching the match parameter. * * \retval 0 if succeeded - * \retval -1 if NULL parameters or match category was not found + * \retval -1 if the specified match category wasn't found */ int ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match); -int ast_category_delete(struct ast_config *cfg, const char *category); /*! - * \brief Removes and destroys all variables within a category - * \retval 0 if the category was found and emptied - * \retval -1 if the category was not found + * \brief Delete a category + * + * \param config which config to use + * \param category category to delete + * + * \return the category after the deleted one which could be NULL. + */ +struct ast_category *ast_category_delete(struct ast_config *cfg, struct ast_category *category); + +/*! + * \brief Appends a category to a config + * + * \param config which config to use + * \param cat category to insert + */ +void ast_category_append(struct ast_config *config, struct ast_category *cat); + +/*! + * \brief Applies base (template) to category. + * + * \param existing existing category + * \param base base category + * + * \details + * This function is used to apply a base (template) to an existing category + */ +void ast_category_inherit(struct ast_category *existing, const struct ast_category *base); + +/*! + * \brief Removes and destroys all variables in a category + * + * \param category category to empty + * + * \retval 0 if succeeded + * \retval -1 if categopry is NULL */ -int ast_category_empty(struct ast_config *cfg, const char *category); +int ast_category_empty(struct ast_category *category); + void ast_category_destroy(struct ast_category *cat); struct ast_variable *ast_category_detach_variables(struct ast_category *cat); void ast_category_rename(struct ast_category *cat, const char *name); diff --git a/main/config.c b/main/config.c index 55c40a9555..7009e7d08e 100644 --- a/main/config.c +++ b/main/config.c @@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include #include /* HUGE_VAL */ +#include #define AST_INCLUDE_GLOB 1 @@ -71,6 +72,7 @@ static char *extconfig_conf = "extconfig.conf"; static struct ao2_container *cfg_hooks; static void config_hook_exec(const char *filename, const char *module, const struct ast_config *cfg); inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2); +static int does_category_match(struct ast_category *cat, const char *category_name, const char *match); /*! \brief Structure to keep comments for rewriting configuration files */ struct ast_comment { @@ -232,6 +234,8 @@ struct ast_category { struct ast_variable *root; /*! Last category variable in the list. */ struct ast_variable *last; + /*! Previous node in the list. */ + struct ast_category *prev; /*! Next node in the list. */ struct ast_category *next; }; @@ -599,16 +603,12 @@ void ast_variables_destroy(struct ast_variable *v) struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category) { - struct ast_category *cat = NULL; - - if (!category) { - return NULL; - } + struct ast_category *cat; if (config->last_browse && (config->last_browse->name == category)) { cat = config->last_browse; } else { - cat = ast_category_get(config, category); + cat = ast_category_get(config, category, NULL); } return (cat) ? cat->root : NULL; @@ -677,29 +677,37 @@ const char *ast_config_option(struct ast_config *cfg, const char *cat, const cha return tmp; } +const char *ast_variable_retrieve(struct ast_config *config, + const char *category, const char *variable) +{ + return ast_variable_retrieve_filtered(config, category, variable, NULL); +} -const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable) +const char *ast_variable_retrieve_filtered(struct ast_config *config, + const char *category, const char *variable, const char *filter) { - struct ast_variable *v; + struct ast_category *cat = NULL; + const char *value; - if (category) { - for (v = ast_variable_browse(config, category); v; v = v->next) { - if (!strcasecmp(variable, v->name)) { - return v->value; - } + while ((cat = ast_category_browse_filtered(config, category, cat, filter))) { + value = ast_variable_find(cat, variable); + if (value) { + return value; } - } else { - struct ast_category *cat; + } - for (cat = config->root; cat; cat = cat->next) { - for (v = cat->root; v; v = v->next) { - if (!strcasecmp(variable, v->name)) { - return v->value; - } - } + return NULL; +} + +const char *ast_variable_find(const struct ast_category *category, const char *variable) +{ + struct ast_variable *v; + + for (v = category->root; v; v = v->next) { + if (!strcasecmp(variable, v->name)) { + return v->value; } } - return NULL; } @@ -726,7 +734,99 @@ static void move_variables(struct ast_category *old, struct ast_category *new) ast_variable_append(new, var); } -struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) +/*! \brief Returns true if ALL of the regex expressions and category name match. + * Both can be NULL (I.E. no predicate) which results in a true return; + */ +static int does_category_match(struct ast_category *cat, const char *category_name, const char *match) +{ + char *dupmatch; + char *nvp = NULL; + int match_found = 0, match_expressions = 0; + int template_ok = 0; + + /* Only match on category name if it's not a NULL or empty string */ + if (!ast_strlen_zero(category_name) && strcasecmp(cat->name, category_name)) { + return 0; + } + + /* If match is NULL or empty, automatically match if not a template */ + if (ast_strlen_zero(match)) { + return !cat->ignored; + } + + dupmatch = ast_strdupa(match); + + while ((nvp = ast_strsep(&dupmatch, ',', AST_STRSEP_STRIP))) { + struct ast_variable *v; + char *match_name; + char *match_value = NULL; + char *regerr; + int rc; + regex_t r_name, r_value; + + match_expressions++; + + match_name = ast_strsep(&nvp, '=', AST_STRSEP_STRIP); + match_value = ast_strsep(&nvp, '=', AST_STRSEP_STRIP); + + /* an empty match value is OK. A NULL match value (no =) is NOT. */ + if (match_value == NULL) { + break; + } + + if (!strcmp("TEMPLATES", match_name)) { + if (!strcasecmp("include", match_value)) { + if (cat->ignored) { + template_ok = 1; + } + match_found++; + } else if (!strcasecmp("restrict", match_value)) { + if (cat->ignored) { + match_found++; + template_ok = 1; + } else { + break; + } + } + continue; + } + + if ((rc = regcomp(&r_name, match_name, REG_EXTENDED | REG_NOSUB))) { + regerr = ast_alloca(128); + regerror(rc, &r_name, regerr, 128); + ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n", + match_name, regerr); + regfree(&r_name); + return 0; + } + if ((rc = regcomp(&r_value, match_value, REG_EXTENDED | REG_NOSUB))) { + regerr = ast_alloca(128); + regerror(rc, &r_value, regerr, 128); + ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n", + match_value, regerr); + regfree(&r_name); + regfree(&r_value); + return 0; + } + + for (v = cat->root; v; v = v->next) { + if (!regexec(&r_name, v->name, 0, NULL, 0) + && !regexec(&r_value, v->value, 0, NULL, 0)) { + match_found++; + break; + } + } + regfree(&r_name); + regfree(&r_value); + } + if (match_found == match_expressions && (!cat->ignored || template_ok)) { + return 1; + } + return 0; +} + + +static struct ast_category *new_category(const char *name, const char *in_file, int lineno, int template) { struct ast_category *category; @@ -741,44 +841,85 @@ struct ast_category *ast_category_new(const char *name, const char *in_file, int } ast_copy_string(category->name, name, sizeof(category->name)); category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */ + category->ignored = template; return category; } -static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored) +struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) { - struct ast_category *cat; + return new_category(name, in_file, lineno, 0); +} - /* try exact match first, then case-insensitive match */ - for (cat = config->root; cat; cat = cat->next) { - if (cat->name == category_name && (ignored || !cat->ignored)) - return cat; - } +struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno) +{ + return new_category(name, in_file, lineno, 1); +} + +struct ast_category *ast_category_get(const struct ast_config *config, + const char *category_name, const char *filter) +{ + struct ast_category *cat; for (cat = config->root; cat; cat = cat->next) { - if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored)) + if (does_category_match(cat, category_name, filter)) { return cat; + } } return NULL; } -struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name) +const char *ast_category_get_name(const struct ast_category *category) { - return category_get(config, category_name, 0); + return category->name; } -int ast_category_exist(const struct ast_config *config, const char *category_name) +int ast_category_is_template(const struct ast_category *category) { - return !!ast_category_get(config, category_name); + return category->ignored; +} + +struct ast_str *ast_category_get_templates(const struct ast_category *category) +{ + struct ast_category_template_instance *template; + struct ast_str *str; + int first = 1; + + if (AST_LIST_EMPTY(&category->template_instances)) { + return NULL; + } + + str = ast_str_create(128); + if (!str) { + return NULL; + } + + AST_LIST_TRAVERSE(&category->template_instances, template, next) { + ast_str_append(&str, 0, "%s%s", first ? "" : ",", template->name); + first = 0; + } + + return str; +} + +int ast_category_exist(const struct ast_config *config, const char *category_name, + const char *filter) +{ + return !!ast_category_get(config, category_name, filter); } void ast_category_append(struct ast_config *config, struct ast_category *category) { - if (config->last) + if (config->last) { config->last->next = category; - else + category->prev = config->last; + } else { config->root = category; + category->prev = NULL; + } + category->next = NULL; category->include_level = config->include_level; + config->last = category; config->current = category; } @@ -787,19 +928,26 @@ int ast_category_insert(struct ast_config *config, struct ast_category *cat, con { struct ast_category *cur_category; - if (!config || !cat || !match) { + if (!config || !config->root || !cat || !match) { return -1; } + if (!strcasecmp(config->root->name, match)) { cat->next = config->root; + cat->prev = NULL; + config->root->prev = cat; config->root = cat; return 0; } - for (cur_category = config->root; cur_category && cur_category->next; - cur_category = cur_category->next) { - if (!strcasecmp(cur_category->next->name, match)) { - cat->next = cur_category->next; - cur_category->next = cat; + + for (cur_category = config->root->next; cur_category; cur_category = cur_category->next) { + if (!strcasecmp(cur_category->name, match)) { + cat->prev = cur_category->prev; + cat->prev->next = cat; + + cat->next = cur_category; + cur_category->prev = cat; + return 0; } } @@ -841,9 +989,10 @@ static void ast_includes_destroy(struct ast_config_include *incls) } } -static struct ast_category *next_available_category(struct ast_category *cat) +static struct ast_category *next_available_category(struct ast_category *cat, + const char *name, const char *filter) { - for (; cat && cat->ignored; cat = cat->next); + for (; cat && !does_category_match(cat, name, filter); cat = cat->next); return cat; } @@ -856,7 +1005,7 @@ struct ast_variable *ast_category_first(struct ast_category *cat) struct ast_variable *ast_category_root(struct ast_config *config, char *cat) { - struct ast_category *category = ast_category_get(config, cat); + struct ast_category *category = ast_category_get(config, cat, NULL); if (category) return category->root; @@ -1021,12 +1170,29 @@ char *ast_category_browse(struct ast_config *config, const char *prev) } if (cat) - cat = next_available_category(cat); + cat = next_available_category(cat, NULL, NULL); config->last_browse = cat; return (cat) ? cat->name : NULL; } +struct ast_category *ast_category_browse_filtered(struct ast_config *config, + const char *category_name, struct ast_category *prev, const char *filter) +{ + struct ast_category *cat; + + if (!prev) { + prev = config->root; + } else { + prev = prev->next; + } + + cat = next_available_category(prev, category_name, filter); + config->last_browse = cat; + + return cat; +} + struct ast_variable *ast_category_detach_variables(struct ast_category *cat) { struct ast_variable *v; @@ -1043,7 +1209,7 @@ void ast_category_rename(struct ast_category *cat, const char *name) ast_copy_string(cat->name, name, sizeof(cat->name)); } -static void inherit_category(struct ast_category *new, const struct ast_category *base) +void ast_category_inherit(struct ast_category *new, const struct ast_category *base) { struct ast_variable *var; struct ast_category_template_instance *x; @@ -1147,65 +1313,45 @@ int ast_variable_update(struct ast_category *category, const char *variable, return -1; } -int ast_category_delete(struct ast_config *cfg, const char *category) +struct ast_category *ast_category_delete(struct ast_config *config, + struct ast_category *category) { - struct ast_category *prev=NULL, *cat; + struct ast_category *prev; - cat = cfg->root; - while (cat) { - if (cat->name == category) { - if (prev) { - prev->next = cat->next; - if (cat == cfg->last) - cfg->last = prev; - } else { - cfg->root = cat->next; - if (cat == cfg->last) - cfg->last = NULL; - } - ast_category_destroy(cat); - return 0; - } - prev = cat; - cat = cat->next; + if (!config || !category) { + return NULL; } - prev = NULL; - cat = cfg->root; - while (cat) { - if (!strcasecmp(cat->name, category)) { - if (prev) { - prev->next = cat->next; - if (cat == cfg->last) - cfg->last = prev; - } else { - cfg->root = cat->next; - if (cat == cfg->last) - cfg->last = NULL; - } - ast_category_destroy(cat); - return 0; - } - prev = cat; - cat = cat->next; + if (category->prev) { + category->prev->next = category->next; + } else { + config->root = category->next; } - return -1; + + if (category->next) { + category->next->prev = category->prev; + } else { + config->last = category->prev; + } + + prev = category->prev; + + ast_category_destroy(category); + + return prev; } -int ast_category_empty(struct ast_config *cfg, const char *category) +int ast_category_empty(struct ast_category *category) { - struct ast_category *cat; - - for (cat = cfg->root; cat; cat = cat->next) { - if (strcasecmp(cat->name, category)) - continue; - ast_variables_destroy(cat->root); - cat->root = NULL; - cat->last = NULL; - return 0; + if (!category) { + return -1; } - return -1; + ast_variables_destroy(category->root); + category->root = NULL; + category->last = NULL; + + return 0; } void ast_config_destroy(struct ast_config *cfg) @@ -1489,7 +1635,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat, if (!strcasecmp(cur, "!")) { (*cat)->ignored = 1; } else if (!strcasecmp(cur, "+")) { - *cat = category_get(cfg, catname, 1); + *cat = ast_category_get(cfg, catname, NULL); if (!(*cat)) { if (newcat) ast_category_destroy(newcat); @@ -1504,12 +1650,12 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat, } else { struct ast_category *base; - base = category_get(cfg, cur, 1); + base = ast_category_get(cfg, cur, "TEMPLATES=restrict"); if (!base) { ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile); return -1; } - inherit_category(*cat, base); + ast_category_inherit(*cat, base); } } } diff --git a/main/manager.c b/main/manager.c index 1bf1f3c08e..f180991923 100644 --- a/main/manager.c +++ b/main/manager.c @@ -362,10 +362,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Category in configuration file. + + A comma separated list of + name_regex=value_regex + expressions which will cause only categories whose variables match all expressions + to be considered. The special variable name TEMPLATES + can be used to control whether templates are included. Passing + include as the value will include templates + along with normal categories. Passing + restrict as the value will restrict the operation to + ONLY templates. Not specifying a TEMPLATES expression + results in the default behavior which is to not include templates. + This action will dump the contents of a configuration - file by category and contents or optionally by specified category only. + file by category and contents or optionally by specified category only. + In the case where a category name is non-unique, a filter may be specified + to match only categories with matching variable values. @@ -377,11 +391,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Configuration filename (e.g. foo.conf). + + Category in configuration file. + + + + This action will dump the contents of a configuration file by category - and contents in JSON format. This only makes sense to be used using rawman over - the HTTP interface. + and contents in JSON format or optionally by specified category only. + This only makes sense to be used using rawman over the HTTP interface. + In the case where a category name is non-unique, a filter may be specified + to match only categories with matching variable values. @@ -399,9 +421,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Whether or not a reload should take place (or name of specific module). - + Action to take. - X's represent 6 digit number beginning with 000000. + 0's represent 6 digit number beginning with 000000. @@ -413,25 +435,58 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - + Category to operate on. - + - + Variable to work on. - + - + Value to work on. - + - + Extra match required to match line. - + - + Line in category to operate on (used with delete and insert actions). - + + + + A comma separated list of action-specific options. + + One or more of the following... + + Allow duplicate category names. + This category is a template. + Templates from which to inherit. + + + + + The following actions share the same options... + + + + + + + + + + + + catfilter is most useful when a file + contains multiple categories with the same name and you wish to + operate on specific ones instead of all of them. + + + + + @@ -1183,7 +1238,8 @@ enum error_type { FAILURE_EMPTYCAT, FAILURE_UPDATE, FAILURE_DELETE, - FAILURE_APPEND + FAILURE_APPEND, + FAILURE_TEMPLATE }; enum add_filter_result { @@ -3165,9 +3221,11 @@ static int action_getconfig(struct mansession *s, const struct message *m) struct ast_config *cfg; const char *fn = astman_get_header(m, "Filename"); const char *category = astman_get_header(m, "Category"); + const char *filter = astman_get_header(m, "Filter"); + const char *category_name; int catcount = 0; int lineno = 0; - char *cur_category = NULL; + struct ast_category *cur_category = NULL; struct ast_variable *v; struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE }; @@ -3175,6 +3233,7 @@ static int action_getconfig(struct mansession *s, const struct message *m) astman_send_error(s, m, "Filename not specified"); return 0; } + cfg = ast_config_load2(fn, "manager", config_flags); if (cfg == CONFIG_STATUS_FILEMISSING) { astman_send_error(s, m, "Config file not found"); @@ -3185,19 +3244,34 @@ static int action_getconfig(struct mansession *s, const struct message *m) } astman_start_ack(s, m); - while ((cur_category = ast_category_browse(cfg, cur_category))) { - if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) { - lineno = 0; - astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category); - for (v = ast_variable_browse(cfg, cur_category); v; v = v->next) { - astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value); - } - catcount++; + while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) { + struct ast_str *templates; + + category_name = ast_category_get_name(cur_category); + lineno = 0; + astman_append(s, "Category-%06d: %s\r\n", catcount, category_name); + + if (ast_category_is_template(cur_category)) { + astman_append(s, "IsTemplate-%06d: %d\r\n", catcount, 1); + } + + if ((templates = ast_category_get_templates(cur_category)) + && ast_str_strlen(templates) > 0) { + astman_append(s, "Templates-%06d: %s\r\n", catcount, ast_str_buffer(templates)); + ast_free(templates); } + + for (v = ast_category_first(cur_category); v; v = v->next) { + astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value); + } + + catcount++; } + if (!ast_strlen_zero(category) && catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */ astman_append(s, "No categories found\r\n"); } + ast_config_destroy(cfg); astman_append(s, "\r\n"); @@ -3208,7 +3282,8 @@ static int action_listcategories(struct mansession *s, const struct message *m) { struct ast_config *cfg; const char *fn = astman_get_header(m, "Filename"); - char *category = NULL; + const char *match = astman_get_header(m, "Match"); + struct ast_category *category = NULL; struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE }; int catcount = 0; @@ -3216,6 +3291,7 @@ static int action_listcategories(struct mansession *s, const struct message *m) astman_send_error(s, m, "Filename not specified"); return 0; } + if (!(cfg = ast_config_load2(fn, "manager", config_flags))) { astman_send_error(s, m, "Config file not found"); return 0; @@ -3223,23 +3299,23 @@ static int action_listcategories(struct mansession *s, const struct message *m) astman_send_error(s, m, "Config file has invalid format"); return 0; } + astman_start_ack(s, m); - while ((category = ast_category_browse(cfg, category))) { - astman_append(s, "Category-%06d: %s\r\n", catcount, category); + while ((category = ast_category_browse_filtered(cfg, NULL, category, match))) { + astman_append(s, "Category-%06d: %s\r\n", catcount, ast_category_get_name(category)); catcount++; } + if (catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */ astman_append(s, "Error: no categories found\r\n"); } + ast_config_destroy(cfg); astman_append(s, "\r\n"); return 0; } - - - /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */ static void json_escape(char *out, const char *in) { @@ -3274,7 +3350,10 @@ static int action_getconfigjson(struct mansession *s, const struct message *m) { struct ast_config *cfg; const char *fn = astman_get_header(m, "Filename"); - char *category = NULL; + const char *filter = astman_get_header(m, "Filter"); + const char *category = astman_get_header(m, "Category"); + struct ast_category *cur_category = NULL; + const char *category_name; struct ast_variable *v; int comma1 = 0; struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE }; @@ -3294,14 +3373,30 @@ static int action_getconfigjson(struct mansession *s, const struct message *m) astman_start_ack(s, m); astman_append(s, "JSON: {"); - while ((category = ast_category_browse(cfg, category))) { + while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) { int comma2 = 0; + struct ast_str *templates; + category_name = ast_category_get_name(cur_category); astman_append(s, "%s\"", comma1 ? "," : ""); - astman_append_json(s, category); + astman_append_json(s, category_name); astman_append(s, "\":["); comma1 = 1; - for (v = ast_variable_browse(cfg, category); v; v = v->next) { + + if (ast_category_is_template(cur_category)) { + astman_append(s, "istemplate:1"); + comma2 = 1; + } + + if ((templates = ast_category_get_templates(cur_category)) + && ast_str_strlen(templates) > 0) { + astman_append(s, "%s", comma2 ? "," : ""); + astman_append(s, "templates:\"%s\"", ast_str_buffer(templates)); + ast_free(templates); + comma2 = 1; + } + + for (v = ast_category_first(cur_category); v; v = v->next) { astman_append(s, "%s\"", comma2 ? "," : ""); astman_append_json(s, v->name); astman_append(s, "\":\""); @@ -3309,6 +3404,7 @@ static int action_getconfigjson(struct mansession *s, const struct message *m) astman_append(s, "\""); comma2 = 1; } + astman_append(s, "]"); } astman_append(s, "}\r\n\r\n"); @@ -3323,19 +3419,28 @@ static enum error_type handle_updates(struct mansession *s, const struct message { int x; char hdr[40]; - const char *action, *cat, *var, *value, *match, *line; - struct ast_category *category; + const char *action, *cat, *var, *value, *match, *line, *options; struct ast_variable *v; struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16); enum error_type result = 0; for (x = 0; x < 100000; x++) { /* 100000 = the max number of allowed updates + 1 */ unsigned int object = 0; + char *dupoptions; + int allowdups = 0; + int istemplate = 0; + int ignoreerror = 0; + char *inherit = NULL; + char *catfilter = NULL; + char *token; + int foundvar = 0; + int foundcat = 0; + struct ast_category *category = NULL; snprintf(hdr, sizeof(hdr), "Action-%06d", x); action = astman_get_header(m, hdr); if (ast_strlen_zero(action)) /* breaks the for loop if no action header */ - break; /* this could cause problems if actions come in misnumbered */ + break; /* this could cause problems if actions come in misnumbered */ snprintf(hdr, sizeof(hdr), "Cat-%06d", x); cat = astman_get_header(m, hdr); @@ -3361,22 +3466,90 @@ static enum error_type handle_updates(struct mansession *s, const struct message snprintf(hdr, sizeof(hdr), "Line-%06d", x); line = astman_get_header(m, hdr); + snprintf(hdr, sizeof(hdr), "Options-%06d", x); + options = astman_get_header(m, hdr); + if (!ast_strlen_zero(options)) { + dupoptions = ast_strdupa(options); + while ((token = ast_strsep(&dupoptions, ',', AST_STRSEP_STRIP))) { + if (!strcasecmp("allowdups", token)) { + allowdups = 1; + continue; + } + if (!strcasecmp("template", token)) { + istemplate = 1; + continue; + } + if (!strcasecmp("ignoreerror", token)) { + ignoreerror = 1; + continue; + } + if (ast_begins_with(token, "inherit")) { + char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP); + c = ast_strsep(&token, '=', AST_STRSEP_STRIP); + if (c) { + inherit = ast_strdupa(c); + } + continue; + } + if (ast_begins_with(token, "catfilter")) { + char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP); + c = ast_strsep(&token, '=', AST_STRSEP_STRIP); + if (c) { + catfilter = ast_strdupa(c); + } + continue; + } + } + } + if (!strcasecmp(action, "newcat")) { - if (ast_category_get(cfg,cat)) { /* check to make sure the cat doesn't */ - result = FAILURE_NEWCAT; /* already exist */ - break; + struct ast_category *template; + char *tmpl_name = NULL; + + if (!allowdups) { + if (ast_category_get(cfg, cat, "TEMPLATES=include")) { + if (ignoreerror) { + continue; + } else { + result = FAILURE_NEWCAT; /* already exist */ + break; + } + } + } + + if (istemplate) { + category = ast_category_new_template(cat, dfn, -1); + } else { + category = ast_category_new(cat, dfn, -1); } - if (!(category = ast_category_new(cat, dfn, -1))) { + + if (!category) { result = FAILURE_ALLOCATION; break; } - if (ast_strlen_zero(match)) { - ast_category_append(cfg, category); - } else { - if (ast_category_insert(cfg, category, match)) { - result = FAILURE_NEWCAT; - ast_category_destroy(category); - break; + + if (inherit) { + while ((tmpl_name = ast_strsep(&inherit, ',', AST_STRSEP_STRIP))) { + if ((template = ast_category_get(cfg, tmpl_name, "TEMPLATES=restrict"))) { + ast_category_inherit(category, template); + } else { + ast_category_destroy(category); + category = NULL; + result = FAILURE_TEMPLATE; /* template not found */ + break; + } + } + } + + if (category != NULL) { + if (ast_strlen_zero(match)) { + ast_category_append(cfg, category); + } else { + if (ast_category_insert(cfg, category, match)) { + ast_category_destroy(category); + result = FAILURE_NEWCAT; + break; + } } } } else if (!strcasecmp(action, "renamecat")) { @@ -3384,19 +3557,37 @@ static enum error_type handle_updates(struct mansession *s, const struct message result = UNSPECIFIED_ARGUMENT; break; } - if (!(category = ast_category_get(cfg, cat))) { + + foundcat = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + ast_category_rename(category, value); + foundcat = 1; + } + + if (!foundcat) { result = UNKNOWN_CATEGORY; break; } - ast_category_rename(category, value); } else if (!strcasecmp(action, "delcat")) { - if (ast_category_delete(cfg, cat)) { - result = FAILURE_DELCAT; + foundcat = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + category = ast_category_delete(cfg, category); + foundcat = 1; + } + + if (!foundcat && !ignoreerror) { + result = UNKNOWN_CATEGORY; break; } } else if (!strcasecmp(action, "emptycat")) { - if (ast_category_empty(cfg, cat)) { - result = FAILURE_EMPTYCAT; + foundcat = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + ast_category_empty(category); + foundcat = 1; + } + + if (!foundcat) { + result = UNKNOWN_CATEGORY; break; } } else if (!strcasecmp(action, "update")) { @@ -3404,11 +3595,22 @@ static enum error_type handle_updates(struct mansession *s, const struct message result = UNSPECIFIED_ARGUMENT; break; } - if (!(category = ast_category_get(cfg,cat))) { + + foundcat = 0; + foundvar = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + if (!ast_variable_update(category, var, value, match, object)) { + foundvar = 1; + } + foundcat = 1; + } + + if (!foundcat) { result = UNKNOWN_CATEGORY; break; } - if (ast_variable_update(category, var, value, match, object)) { + + if (!foundvar) { result = FAILURE_UPDATE; break; } @@ -3417,12 +3619,23 @@ static enum error_type handle_updates(struct mansession *s, const struct message result = UNSPECIFIED_ARGUMENT; break; } - if (!(category = ast_category_get(cfg, cat))) { + + foundcat = 0; + foundvar = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + if (!ast_variable_delete(category, var, match, line)) { + foundvar = 1; + } + foundcat = 1; + } + + if (!foundcat) { result = UNKNOWN_CATEGORY; break; } - if (ast_variable_delete(category, var, match, line)) { - result = FAILURE_DELETE; + + if (!foundvar && !ignoreerror) { + result = FAILURE_UPDATE; break; } } else if (!strcasecmp(action, "append")) { @@ -3430,32 +3643,44 @@ static enum error_type handle_updates(struct mansession *s, const struct message result = UNSPECIFIED_ARGUMENT; break; } - if (!(category = ast_category_get(cfg, cat))) { - result = UNKNOWN_CATEGORY; - break; + + foundcat = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + if (!(v = ast_variable_new(var, value, dfn))) { + result = FAILURE_ALLOCATION; + break; + } + if (object || (match && !strcasecmp(match, "object"))) { + v->object = 1; + } + ast_variable_append(category, v); + foundcat = 1; } - if (!(v = ast_variable_new(var, value, dfn))) { - result = FAILURE_ALLOCATION; + + if (!foundcat) { + result = UNKNOWN_CATEGORY; break; } - if (object || (match && !strcasecmp(match, "object"))) { - v->object = 1; - } - ast_variable_append(category, v); } else if (!strcasecmp(action, "insert")) { if (ast_strlen_zero(var) || ast_strlen_zero(line)) { result = UNSPECIFIED_ARGUMENT; break; } - if (!(category = ast_category_get(cfg, cat))) { - result = UNKNOWN_CATEGORY; - break; + + foundcat = 0; + while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) { + if (!(v = ast_variable_new(var, value, dfn))) { + result = FAILURE_ALLOCATION; + break; + } + ast_variable_insert(category, v, line); + foundcat = 1; } - if (!(v = ast_variable_new(var, value, dfn))) { - result = FAILURE_ALLOCATION; + + if (!foundcat) { + result = UNKNOWN_CATEGORY; break; } - ast_variable_insert(category, v, line); } else { ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action); @@ -3541,6 +3766,9 @@ static int action_updateconfig(struct mansession *s, const struct message *m) case FAILURE_APPEND: astman_send_error(s, m, "Append did not complete successfully"); break; + case FAILURE_TEMPLATE: + astman_send_error(s, m, "Template category not found"); + break; } } return 0; diff --git a/pbx/pbx_realtime.c b/pbx/pbx_realtime.c index de62851e0f..6c8e671ed4 100644 --- a/pbx/pbx_realtime.c +++ b/pbx/pbx_realtime.c @@ -206,7 +206,7 @@ static struct ast_variable *realtime_switch_common(const char *table, const char match = ast_extension_match(cat, exten); } if (match) { - var = ast_category_detach_variables(ast_category_get(cfg, cat)); + var = ast_category_detach_variables(ast_category_get(cfg, cat, NULL)); break; } cat = ast_category_browse(cfg, cat); diff --git a/res/res_sorcery_config.c b/res/res_sorcery_config.c index d43a4958aa..176c7fe68f 100644 --- a/res/res_sorcery_config.c +++ b/res/res_sorcery_config.c @@ -234,6 +234,7 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s struct sorcery_config *config = data; struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; struct ast_config *cfg = ast_config_load2(config->filename, config->uuid, flags); + struct ast_category *category = NULL; RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup); const char *id = NULL; @@ -255,16 +256,17 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s return; } - while ((id = ast_category_browse(cfg, id))) { + while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) { RAII_VAR(void *, obj, NULL, ao2_cleanup); + id = ast_category_get_name(category); /* If given criteria has not been met skip the category, it is not applicable */ - if (!sorcery_is_criteria_met(ast_variable_browse(cfg, id), config->criteria)) { + if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) { continue; } if (!(obj = ast_sorcery_alloc(sorcery, type, id)) || - ast_sorcery_objectset_apply(sorcery, obj, ast_variable_browse(cfg, id))) { + ast_sorcery_objectset_apply(sorcery, obj, ast_category_first(category))) { if (config->file_integrity) { ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n", diff --git a/res/res_sorcery_realtime.c b/res/res_sorcery_realtime.c index 47cd736761..52b097f57b 100644 --- a/res/res_sorcery_realtime.c +++ b/res/res_sorcery_realtime.c @@ -177,7 +177,7 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery const char *family = data; RAII_VAR(struct ast_config *, rows, NULL, ast_config_destroy); RAII_VAR(struct ast_variable *, all, NULL, ast_variables_destroy); - char *row = NULL; + struct ast_category *row = NULL; if (!fields) { char field[strlen(UUID_FIELD) + 6], value[2]; @@ -197,9 +197,8 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery return; } - while ((row = ast_category_browse(rows, row))) { - struct ast_category *cat = ast_category_get(rows, row); - struct ast_variable *objectset = ast_category_detach_variables(cat); + while ((row = ast_category_browse_filtered(rows, NULL, row, NULL))) { + struct ast_variable *objectset = ast_category_detach_variables(row); RAII_VAR(struct ast_variable *, id, NULL, ast_variables_destroy); RAII_VAR(void *, object, NULL, ao2_cleanup); diff --git a/tests/test_config.c b/tests/test_config.c index a9a281ddcd..b1f55c782a 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -227,6 +227,533 @@ out: return res; } +AST_TEST_DEFINE(config_basic_ops) +{ + enum ast_test_result_state res = AST_TEST_FAIL; + struct ast_config *cfg = NULL; + struct ast_category *cat = NULL; + struct ast_variable *var; + char temp[32]; + const char *cat_name; + const char *var_value; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "config_basic_ops"; + info->category = "/main/config/"; + info->summary = "Test basic config ops"; + info->description = "Test basic config ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + cfg = ast_config_new(); + if (!cfg) { + return res; + } + + /* load the config */ + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + ast_category_append(cfg, ast_category_new(temp, "dummy", -1)); + } + + /* test0 test1 test2 test3 test4 */ + /* check the config has 5 elements */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* search for test2 */ + cat = ast_category_get(cfg, "test2", NULL); + if (!cat || strcmp(ast_category_get_name(cat), "test2")) { + ast_test_status_update(test, "Get failed %s != %s\n", ast_category_get_name(cat), "test2"); + goto out; + } + + /* delete test2 */ + cat = ast_category_delete(cfg, cat); + + /* Now: test0 test1 test3 test4 */ + /* make sure the curr category is test1 */ + if (!cat || strcmp(ast_category_get_name(cat), "test1")) { + ast_test_status_update(test, "Delete failed %s != %s\n", ast_category_get_name(cat), "test1"); + goto out; + } + + /* Now: test0 test1 test3 test4 */ + /* make sure the test2 is not found */ + cat = ast_category_get(cfg, "test2", NULL); + if (cat) { + ast_test_status_update(test, "Should not have found test2\n"); + goto out; + } + + /* Now: test0 test1 test3 test4 */ + /* make sure the sequence is correctly missing test2 */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + i++; + if (i == 2) { + i++; + } + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* insert test2 back in before test3 */ + ast_category_insert(cfg, ast_category_new("test2", "dummy", -1), "test3"); + + /* Now: test0 test1 test2 test3 test4 */ + /* make sure the sequence is correct again */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* Now: test0 test1 test2 test3 test4 */ + /* make sure non filtered browse still works */ + i = 0; + cat_name = NULL; + while ((cat_name = ast_category_browse(cfg, cat_name))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(cat_name, temp)) { + ast_test_status_update(test, "%s != %s\n", cat_name, temp); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* append another test2 */ + ast_category_append(cfg, ast_category_new("test2", "dummy", -1)); + /* Now: test0 test1 test2 test3 test4 test2*/ + /* make sure only test2's are returned */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, "test2", cat, NULL))) { + if (strcmp(ast_category_get_name(cat), "test2")) { + ast_test_status_update(test, "Should have returned test2 instead of %s\n", ast_category_get_name(cat)); + goto out; + } + i++; + } + /* make sure 2 test2's were found */ + if (i != 2) { + ast_test_status_update(test, "Should have found 2 test2's %d\n", i); + goto out; + } + + /* Test in-flight deletion using ast_category_browse_filtered */ + /* Now: test0 test1 test2 test3 test4 test2 */ + /* Delete the middle test2 and continue */ + cat = NULL; + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + cat = ast_category_browse_filtered(cfg, NULL, cat, NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, temp)) { + ast_test_status_update(test, "Should have returned %s instead of %s: %d\n", temp, cat_name, i); + goto out; + } + if (i == 2) { + cat = ast_category_delete(cfg, cat); + } + } + + /* Now: test0 test1 test3 test4 test2 */ + /* Test in-flight deletion using ast_category_browse */ + /* Delete test1 and continue */ + cat_name = NULL; + for(i = 0; i < 5; i++) { + if (i == 2) { /* 2 was already deleted above */ + continue; + } + snprintf(temp, sizeof(temp), "test%d", i); + cat_name = ast_category_browse(cfg, cat_name); + cat = ast_category_get(cfg, cat_name, NULL); + if (strcmp(cat_name, temp)) { + ast_test_status_update(test, "Should have returned %s instead of %s: %d\n", temp, cat_name, i); + goto out; + } + if (i == 1) { + ast_category_delete(cfg, cat); + } + } + + /* Now: test0 test3 test4 test2 */ + /* delete the head item */ + cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, "test0")) { + ast_test_status_update(test, "Should have returned test0 instead of %s\n", cat_name); + goto out; + } + ast_category_delete(cfg, cat); + /* Now: test3 test4 test2 */ + + /* make sure head got updated to the new first element */ + cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, "test3")) { + ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name); + goto out; + } + + /* delete the tail item */ + cat = ast_category_get(cfg, "test2", NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, "test2")) { + ast_test_status_update(test, "Should have returned test2 instead of %s\n", cat_name); + goto out; + } + ast_category_delete(cfg, cat); + /* Now: test3 test4 */ + + /* There should now only be 2 elements in the list */ + cat = NULL; + cat = ast_category_browse_filtered(cfg, NULL, cat, NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, "test3")) { + ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name); + goto out; + } + + cat = ast_category_browse_filtered(cfg, NULL, cat, NULL); + cat_name = ast_category_get_name(cat); + if (strcmp(cat_name, "test4")) { + ast_test_status_update(test, "Should have returned test4 instead of %s\n", cat_name); + goto out; + } + + /* There should be nothing more */ + cat = ast_category_browse_filtered(cfg, NULL, cat, NULL); + if (cat) { + ast_test_status_update(test, "Should not have returned anything\n"); + goto out; + } + + /* Test ast_variable retrieve. + * Get the second category. + */ + cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL); + cat = ast_category_browse_filtered(cfg, NULL, cat, NULL); + cat_name = ast_category_get_name(cat); + var = ast_variable_new("aaa", "bbb", "dummy"); + if (!var) { + ast_test_status_update(test, "Couldn't allocate variable.\n"); + goto out; + } + ast_variable_append(cat, var); + + /* Make sure we can retrieve with specific category name */ + var_value = ast_variable_retrieve(cfg, cat_name, "aaa"); + if (!var_value || strcmp(var_value, "bbb")) { + ast_test_status_update(test, "Variable not found or wrong value.\n"); + goto out; + } + + /* Make sure we can retrieve with NULL category name */ + var_value = ast_variable_retrieve(cfg, NULL, "aaa"); + if (!var_value || strcmp(var_value, "bbb")) { + ast_test_status_update(test, "Variable not found or wrong value.\n"); + goto out; + } + + res = AST_TEST_PASS; + +out: + ast_config_destroy(cfg); + return res; +} + +AST_TEST_DEFINE(config_filtered_ops) +{ + enum ast_test_result_state res = AST_TEST_FAIL; + struct ast_config *cfg = NULL; + struct ast_category *cat = NULL; + char temp[32]; + const char *value; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "config_filtered_ops"; + info->category = "/main/config/"; + info->summary = "Test filtered config ops"; + info->description = "Test filtered config ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + cfg = ast_config_new(); + if (!cfg) { + return res; + } + + /* load the config */ + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + cat = ast_category_new(temp, "dummy", -1); + ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0"); + ast_category_append(cfg, cat); + } + + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + cat = ast_category_new(temp, "dummy", -1); + ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0"); + ast_category_append(cfg, cat); + } + + /* check the config has 5 elements for each type*/ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=a"))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + value = ast_variable_find(cat, "type"); + if (!value || strcmp(value, "a")) { + ast_test_status_update(test, "Type %s != %s\n", "a", value); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=b"))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (!cat || strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + value = ast_variable_find(cat, "type"); + if (!value || strcmp(value, "b")) { + ast_test_status_update(test, "Type %s != %s\n", "b", value); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* Delete b3 and make sure it's gone and a3 is still there. + * Really this is a test of get since delete takes a specific category structure. + */ + cat = ast_category_get(cfg, "test3", "type=b"); + value = ast_variable_find(cat, "type"); + if (strcmp(value, "b")) { + ast_test_status_update(test, "Type %s != %s\n", "b", value); + goto out; + } + ast_category_delete(cfg, cat); + + cat = ast_category_get(cfg, "test3", "type=b"); + if (cat) { + ast_test_status_update(test, "Category b was not deleted.\n"); + goto out; + } + + cat = ast_category_get(cfg, "test3", "type=a"); + if (!cat) { + ast_test_status_update(test, "Category a was deleted.\n"); + goto out; + } + + value = ast_variable_find(cat, "type"); + if (strcmp(value, "a")) { + ast_test_status_update(test, "Type %s != %s\n", value, "a"); + goto out; + } + + /* Basic regex stuff is handled by regcomp/regexec so not testing here. + * Still need to test multiple name/value pairs though. + */ + ast_category_empty(cat); + ast_variable_insert(cat, ast_variable_new("type", "bx", "dummy"), "0"); + ast_variable_insert(cat, ast_variable_new("e", "z", "dummy"), "0"); + + cat = ast_category_get(cfg, "test3", "type=.,e=z"); + if (!cat) { + ast_test_status_update(test, "Category not found.\n"); + goto out; + } + + cat = ast_category_get(cfg, "test3", "type=.,e=zX"); + if (cat) { + ast_test_status_update(test, "Category found.\n"); + goto out; + } + + cat = ast_category_get(cfg, "test3", "TEMPLATE=restrict,type=.,e=z"); + if (cat) { + ast_test_status_update(test, "Category found.\n"); + goto out; + } + + res = AST_TEST_PASS; + +out: + ast_config_destroy(cfg); + return res; +} + +AST_TEST_DEFINE(config_template_ops) +{ + enum ast_test_result_state res = AST_TEST_FAIL; + struct ast_config *cfg = NULL; + struct ast_category *cat = NULL; + char temp[32]; + const char *value; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "config_template_ops"; + info->category = "/main/config/"; + info->summary = "Test template config ops"; + info->description = "Test template config ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + cfg = ast_config_new(); + if (!cfg) { + return res; + } + + /* load the config with 5 templates and 5 regular */ + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + cat = ast_category_new_template(temp, "dummy", -1); + ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0"); + ast_category_append(cfg, cat); + } + + for(i = 0; i < 5; i++) { + snprintf(temp, sizeof(temp), "test%d", i); + cat = ast_category_new(temp, "dummy", -1); + ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0"); + ast_category_append(cfg, cat); + } + + /* check the config has 5 template elements of type a */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=a"))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + value = ast_variable_find(cat, "type"); + if (!value || strcmp(value, "a")) { + ast_test_status_update(test, "Type %s != %s\n", value, "a"); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* Test again with 'include'. There should still only be 5 (type a) */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include,type=a"))) { + snprintf(temp, sizeof(temp), "test%d", i); + if (strcmp(ast_category_get_name(cat), temp)) { + ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp); + goto out; + } + value = ast_variable_find(cat, "type"); + if (!value || strcmp(value, "a")) { + ast_test_status_update(test, "Type %s != %s\n", value, "a"); + goto out; + } + i++; + } + if (i != 5) { + ast_test_status_update(test, "There were %d matches instead of 5.\n", i); + goto out; + } + + /* Test again with 'include' but no type. There should now be 10 (type a and type b) */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include"))) { + i++; + } + if (i != 10) { + ast_test_status_update(test, "There were %d matches instead of 10.\n", i); + goto out; + } + + /* Test again with 'restrict' and type b. There should 0 */ + i = 0; + cat = NULL; + while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=b"))) { + i++; + } + if (i != 0) { + ast_test_status_update(test, "There were %d matches instead of 0.\n", i); + goto out; + } + + res = AST_TEST_PASS; + +out: + ast_config_destroy(cfg); + return res; +} + /*! * \brief Write the config file to disk * @@ -937,6 +1464,9 @@ AST_TEST_DEFINE(config_options_test) static int unload_module(void) { + AST_TEST_UNREGISTER(config_basic_ops); + AST_TEST_UNREGISTER(config_filtered_ops); + AST_TEST_UNREGISTER(config_template_ops); AST_TEST_UNREGISTER(copy_config); AST_TEST_UNREGISTER(config_hook); AST_TEST_UNREGISTER(ast_parse_arg_test); @@ -946,6 +1476,9 @@ static int unload_module(void) static int load_module(void) { + AST_TEST_REGISTER(config_basic_ops); + AST_TEST_REGISTER(config_filtered_ops); + AST_TEST_REGISTER(config_template_ops); AST_TEST_REGISTER(copy_config); AST_TEST_REGISTER(config_hook); AST_TEST_REGISTER(ast_parse_arg_test); diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c index 2d811b19ee..cbdec0ce14 100644 --- a/tests/test_sorcery.c +++ b/tests/test_sorcery.c @@ -493,7 +493,7 @@ AST_TEST_DEFINE(apply_config) return AST_TEST_NOT_RUN; } - if (!ast_category_get(config, "test_sorcery_section")) { + if (!ast_category_get(config, "test_sorcery_section", NULL)) { ast_test_status_update(test, "Sorcery configuration file does not have test_sorcery section\n"); ast_config_destroy(config); return AST_TEST_NOT_RUN; @@ -2226,7 +2226,7 @@ AST_TEST_DEFINE(caching_wizard_behavior) return AST_TEST_NOT_RUN; } - if (!ast_category_get(config, "test_sorcery_cache")) { + if (!ast_category_get(config, "test_sorcery_cache", NULL)) { ast_test_status_update(test, "Sorcery configuration file does not contain 'test_sorcery_cache' section\n"); ast_config_destroy(config); return AST_TEST_NOT_RUN; diff --git a/tests/test_sorcery_realtime.c b/tests/test_sorcery_realtime.c index b64ad9389b..ab9c188148 100644 --- a/tests/test_sorcery_realtime.c +++ b/tests/test_sorcery_realtime.c @@ -139,15 +139,15 @@ static struct ast_config *realtime_sorcery_multi(const char *database, const cha static int realtime_sorcery_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) { - struct ast_category *object; + struct ast_category *object, *found; - if (!ast_category_exist(realtime_objects, entity)) { + if (!(found = ast_category_get(realtime_objects, entity, NULL))) { return 0; } else if (!(object = ast_category_new(entity, "", 0))) { return -1; } - ast_category_delete(realtime_objects, entity); + ast_category_delete(realtime_objects, found); ast_variable_append(object, ast_variables_dup((struct ast_variable*)fields)); ast_variable_append(object, ast_variable_new(keyfield, entity, "")); ast_category_append(realtime_objects, object); @@ -161,7 +161,7 @@ static int realtime_sorcery_store(const char *database, const char *table, const const struct ast_variable *keyfield = realtime_find_variable(fields, "id"); struct ast_category *object; - if (!keyfield || ast_category_exist(realtime_objects, keyfield->value) || !(object = ast_category_new(keyfield->value, "", 0))) { + if (!keyfield || ast_category_exist(realtime_objects, keyfield->value, NULL) || !(object = ast_category_new(keyfield->value, "", 0))) { return -1; } @@ -173,11 +173,12 @@ static int realtime_sorcery_store(const char *database, const char *table, const static int realtime_sorcery_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields) { - if (!ast_category_exist(realtime_objects, entity)) { + struct ast_category *found; + if (!(found = ast_category_get(realtime_objects, entity, NULL))) { return 0; } - ast_category_delete(realtime_objects, entity); + ast_category_delete(realtime_objects, found); return 1; } @@ -570,7 +571,7 @@ AST_TEST_DEFINE(object_retrieve_regex) ast_test_status_update(test, "Failed to retrieve a container of objects\n"); return AST_TEST_FAIL; } else if (ao2_container_count(objects) != 2) { - ast_test_status_update(test, "Received a container with incorrect number of objects in it\n"); + ast_test_status_update(test, "Received a container with incorrect number of objects in it: %d instead of 2\n", ao2_container_count(objects)); return AST_TEST_FAIL; }