2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2010, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Configuration File Parser
23 * \author Mark Spencer <markster@digium.com>
25 * Includes the Asterisk Realtime API - ARA
26 * See http://wiki.asterisk.org
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33 #include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */
34 #include "asterisk/network.h" /* we do some sockaddr manipulation here */
38 #include <math.h> /* HUGE_VAL */
40 #define AST_INCLUDE_GLOB 1
42 #include "asterisk/config.h"
43 #include "asterisk/cli.h"
44 #include "asterisk/lock.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/channel.h"
47 #include "asterisk/app.h"
48 #include "asterisk/astobj2.h"
49 #include "asterisk/strings.h" /* for the ast_str_*() API */
50 #include "asterisk/netsock2.h"
52 #define MAX_NESTED_COMMENTS 128
53 #define COMMENT_START ";--"
54 #define COMMENT_END "--;"
55 #define COMMENT_META ';'
56 #define COMMENT_TAG '-'
58 static char *extconfig_conf = "extconfig.conf";
61 /*! \brief Structure to keep comments for rewriting configuration files */
63 struct ast_comment *next;
67 /*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
68 struct cache_file_include {
69 AST_LIST_ENTRY(cache_file_include) list;
73 struct cache_file_mtime {
74 AST_LIST_ENTRY(cache_file_mtime) list;
75 AST_LIST_HEAD(includes, cache_file_include) includes;
76 unsigned int has_exec:1;
82 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
84 static int init_appendbuf(void *data)
86 struct ast_str **str = data;
87 *str = ast_str_create(16);
91 AST_THREADSTORAGE_CUSTOM(appendbuf, init_appendbuf, ast_free_ptr);
93 /* comment buffers are better implemented using the ast_str_*() API */
94 #define CB_SIZE 250 /* initial size of comment buffers */
96 static void CB_ADD(struct ast_str **cb, const char *str)
98 ast_str_append(cb, 0, "%s", str);
101 static void CB_ADD_LEN(struct ast_str **cb, const char *str, int len)
103 char *s = alloca(len + 1);
104 ast_copy_string(s, str, len);
105 ast_str_append(cb, 0, "%s", str);
108 static void CB_RESET(struct ast_str *cb, struct ast_str *llb)
118 static struct ast_comment *ALLOC_COMMENT(struct ast_str *buffer)
120 struct ast_comment *x = NULL;
121 if (!buffer || !ast_str_strlen(buffer)) {
124 if ((x = ast_calloc(1, sizeof(*x) + ast_str_strlen(buffer) + 1))) {
125 strcpy(x->cmt, ast_str_buffer(buffer)); /* SAFE */
130 /* I need to keep track of each config file, and all its inclusions,
131 so that we can track blank lines in each */
138 static int hash_string(const void *obj, const int flags)
140 char *str = ((struct inclfile *) obj)->fname;
143 for (total = 0; *str; str++) {
144 unsigned int tmp = total;
145 total <<= 1; /* multiply by 2 */
146 total += tmp; /* multiply by 3 */
147 total <<= 2; /* multiply by 12 */
148 total += tmp; /* multiply by 13 */
150 total += ((unsigned int) (*str));
158 static int hashtab_compare_strings(void *a, void *b, int flags)
160 const struct inclfile *ae = a, *be = b;
161 return !strcmp(ae->fname, be->fname) ? CMP_MATCH | CMP_STOP : 0;
164 static struct ast_config_map {
165 struct ast_config_map *next;
172 } *config_maps = NULL;
174 AST_MUTEX_DEFINE_STATIC(config_lock);
175 static struct ast_config_engine *config_engine_list;
177 #define MAX_INCLUDE_LEVEL 10
179 struct ast_category_template_instance {
180 char name[80]; /* redundant? */
181 const struct ast_category *inst;
182 AST_LIST_ENTRY(ast_category_template_instance) next;
185 struct ast_category {
187 int ignored; /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
189 char *file; /*!< the file name from whence this declaration was read */
191 AST_LIST_HEAD_NOLOCK(template_instance_list, ast_category_template_instance) template_instances;
192 struct ast_comment *precomments;
193 struct ast_comment *sameline;
194 struct ast_comment *trailing; /*!< the last object in the list will get assigned any trailing comments when EOF is hit */
195 struct ast_variable *root;
196 struct ast_variable *last;
197 struct ast_category *next;
201 struct ast_category *root;
202 struct ast_category *last;
203 struct ast_category *current;
204 struct ast_category *last_browse; /*!< used to cache the last category supplied via category_browse */
206 int max_include_level;
207 struct ast_config_include *includes; /*!< a list of inclusions, which should describe the entire tree */
210 struct ast_config_include {
211 char *include_location_file; /*!< file name in which the include occurs */
212 int include_location_lineno; /*!< lineno where include occurred */
213 int exec; /*!< set to non-zero if itsa #exec statement */
214 char *exec_file; /*!< if it's an exec, you'll have both the /var/tmp to read, and the original script */
215 char *included_file; /*!< file name included */
216 int inclusion_count; /*!< if the file is included more than once, a running count thereof -- but, worry not,
217 we explode the instances and will include those-- so all entries will be unique */
218 int output; /*!< a flag to indicate if the inclusion has been output */
219 struct ast_config_include *next; /*!< ptr to next inclusion in the list */
223 struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno)
225 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename)
228 struct ast_variable *variable;
229 int name_len = strlen(name) + 1;
230 int val_len = strlen(value) + 1;
231 int fn_len = strlen(filename) + 1;
234 if ((variable = __ast_calloc(1, name_len + val_len + fn_len + sizeof(*variable), file, lineno, func))) {
236 if ((variable = ast_calloc(1, name_len + val_len + fn_len + sizeof(*variable)))) {
238 char *dst = variable->stuff; /* writable space starts here */
239 variable->name = strcpy(dst, name);
241 variable->value = strcpy(dst, value);
243 variable->file = strcpy(dst, filename);
248 struct ast_config_include *ast_include_new(struct ast_config *conf, const char *from_file, const char *included_file, int is_exec, const char *exec_file, int from_lineno, char *real_included_file_name, int real_included_file_name_size)
250 /* a file should be included ONCE. Otherwise, if one of the instances is changed,
251 * then all be changed. -- how do we know to include it? -- Handling modified
252 * instances is possible, I'd have
253 * to create a new master for each instance. */
254 struct ast_config_include *inc;
257 inc = ast_include_find(conf, included_file);
260 inc->inclusion_count++;
261 snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
262 } while (stat(real_included_file_name, &statbuf) == 0);
263 ast_log(LOG_WARNING,"'%s', line %d: Same File included more than once! This data will be saved in %s if saved back to disk.\n", from_file, from_lineno, real_included_file_name);
265 *real_included_file_name = 0;
267 inc = ast_calloc(1,sizeof(struct ast_config_include));
268 inc->include_location_file = ast_strdup(from_file);
269 inc->include_location_lineno = from_lineno;
270 if (!ast_strlen_zero(real_included_file_name))
271 inc->included_file = ast_strdup(real_included_file_name);
273 inc->included_file = ast_strdup(included_file);
277 inc->exec_file = ast_strdup(exec_file);
279 /* attach this new struct to the conf struct */
280 inc->next = conf->includes;
281 conf->includes = inc;
286 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
288 struct ast_config_include *incl;
289 struct ast_category *cat;
290 struct ast_variable *v;
292 int from_len = strlen(from_file);
293 int to_len = strlen(to_file);
295 if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
298 /* the manager code allows you to read in one config file, then
299 * write it back out under a different name. But, the new arrangement
300 * ties output lines to the file name. So, before you try to write
301 * the config file to disk, better riffle thru the data and make sure
302 * the file names are changed.
304 /* file names are on categories, includes (of course), and on variables. So,
305 * traverse all this and swap names */
307 for (incl = conf->includes; incl; incl=incl->next) {
308 if (strcmp(incl->include_location_file,from_file) == 0) {
309 if (from_len >= to_len)
310 strcpy(incl->include_location_file, to_file);
312 free(incl->include_location_file);
313 incl->include_location_file = strdup(to_file);
317 for (cat = conf->root; cat; cat = cat->next) {
318 if (strcmp(cat->file,from_file) == 0) {
319 if (from_len >= to_len)
320 strcpy(cat->file, to_file);
323 cat->file = strdup(to_file);
326 for (v = cat->root; v; v = v->next) {
327 if (strcmp(v->file,from_file) == 0) {
328 if (from_len >= to_len)
329 strcpy(v->file, to_file);
332 v->file = strdup(to_file);
339 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
341 struct ast_config_include *x;
342 for (x=conf->includes;x;x=x->next) {
343 if (strcmp(x->included_file,included_file) == 0)
350 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
355 category->last->next = variable;
357 category->root = variable;
358 category->last = variable;
359 while (category->last->next)
360 category->last = category->last->next;
363 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
365 struct ast_variable *cur = category->root;
369 if (!variable || sscanf(line, "%30d", &insertline) != 1) {
373 variable->next = category->root;
374 category->root = variable;
376 for (lineno = 1; lineno < insertline; lineno++) {
382 variable->next = cur->next;
383 cur->next = variable;
387 static void ast_comment_destroy(struct ast_comment **comment)
389 struct ast_comment *n, *p;
391 for (p = *comment; p; p = n) {
399 static void ast_variable_destroy(struct ast_variable *doomed)
401 ast_comment_destroy(&doomed->precomments);
402 ast_comment_destroy(&doomed->sameline);
403 ast_comment_destroy(&doomed->trailing);
407 void ast_variables_destroy(struct ast_variable *v)
409 struct ast_variable *vn;
414 ast_variable_destroy(vn);
418 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
420 struct ast_category *cat = NULL;
422 if (category && config->last_browse && (config->last_browse->name == category)) {
423 cat = config->last_browse;
425 cat = ast_category_get(config, category);
428 return (cat) ? cat->root : NULL;
431 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
434 tmp = ast_variable_retrieve(cfg, cat, var);
436 tmp = ast_variable_retrieve(cfg, "general", var);
442 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
444 struct ast_variable *v;
447 for (v = ast_variable_browse(config, category); v; v = v->next) {
448 if (!strcasecmp(variable, v->name)) {
453 struct ast_category *cat;
455 for (cat = config->root; cat; cat = cat->next) {
456 for (v = cat->root; v; v = v->next) {
457 if (!strcasecmp(variable, v->name)) {
467 static struct ast_variable *variable_clone(const struct ast_variable *old)
469 struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
472 new->lineno = old->lineno;
473 new->object = old->object;
474 new->blanklines = old->blanklines;
475 /* TODO: clone comments? */
481 static void move_variables(struct ast_category *old, struct ast_category *new)
483 struct ast_variable *var = old->root;
486 /* we can just move the entire list in a single op */
487 ast_variable_append(new, var);
490 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
492 struct ast_category *category;
494 if ((category = ast_calloc(1, sizeof(*category))))
495 ast_copy_string(category->name, name, sizeof(category->name));
496 category->file = strdup(in_file);
497 category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
501 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
503 struct ast_category *cat;
505 /* try exact match first, then case-insensitive match */
506 for (cat = config->root; cat; cat = cat->next) {
507 if (cat->name == category_name && (ignored || !cat->ignored))
511 for (cat = config->root; cat; cat = cat->next) {
512 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
519 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
521 return category_get(config, category_name, 0);
524 int ast_category_exist(const struct ast_config *config, const char *category_name)
526 return !!ast_category_get(config, category_name);
529 void ast_category_append(struct ast_config *config, struct ast_category *category)
532 config->last->next = category;
534 config->root = category;
535 category->include_level = config->include_level;
536 config->last = category;
537 config->current = category;
540 void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
542 struct ast_category *cur_category;
546 if (!strcasecmp(config->root->name, match)) {
547 cat->next = config->root;
551 for (cur_category = config->root; cur_category; cur_category = cur_category->next) {
552 if (!strcasecmp(cur_category->next->name, match)) {
553 cat->next = cur_category->next;
554 cur_category->next = cat;
560 static void ast_destroy_template_list(struct ast_category *cat)
562 struct ast_category_template_instance *x;
564 while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
568 void ast_category_destroy(struct ast_category *cat)
570 ast_variables_destroy(cat->root);
575 ast_comment_destroy(&cat->precomments);
576 ast_comment_destroy(&cat->sameline);
577 ast_comment_destroy(&cat->trailing);
578 ast_destroy_template_list(cat);
582 static void ast_includes_destroy(struct ast_config_include *incls)
584 struct ast_config_include *incl,*inclnext;
586 for (incl=incls; incl; incl = inclnext) {
587 inclnext = incl->next;
588 if (incl->include_location_file)
589 free(incl->include_location_file);
591 free(incl->exec_file);
592 if (incl->included_file)
593 free(incl->included_file);
598 static struct ast_category *next_available_category(struct ast_category *cat)
600 for (; cat && cat->ignored; cat = cat->next);
605 /*! return the first var of a category */
606 struct ast_variable *ast_category_first(struct ast_category *cat)
608 return (cat) ? cat->root : NULL;
611 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
613 struct ast_category *category = ast_category_get(config, cat);
616 return category->root;
620 char *ast_category_browse(struct ast_config *config, const char *prev)
622 struct ast_category *cat = NULL;
624 if (prev && config->last_browse && (config->last_browse->name == prev))
625 cat = config->last_browse->next;
626 else if (!prev && config->root)
629 for (cat = config->root; cat; cat = cat->next) {
630 if (cat->name == prev) {
636 for (cat = config->root; cat; cat = cat->next) {
637 if (!strcasecmp(cat->name, prev)) {
646 cat = next_available_category(cat);
648 config->last_browse = cat;
649 return (cat) ? cat->name : NULL;
652 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
654 struct ast_variable *v;
663 void ast_category_rename(struct ast_category *cat, const char *name)
665 ast_copy_string(cat->name, name, sizeof(cat->name));
668 static void inherit_category(struct ast_category *new, const struct ast_category *base)
670 struct ast_variable *var;
671 struct ast_category_template_instance *x = ast_calloc(1,sizeof(struct ast_category_template_instance));
673 strcpy(x->name, base->name);
675 AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
676 for (var = base->root; var; var = var->next)
677 ast_variable_append(new, variable_clone(var));
680 struct ast_config *ast_config_new(void)
682 struct ast_config *config;
684 if ((config = ast_calloc(1, sizeof(*config))))
685 config->max_include_level = MAX_INCLUDE_LEVEL;
689 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
691 struct ast_variable *cur, *prev=NULL, *curn;
695 cur = category->root;
697 if (cur->name == variable) {
699 prev->next = cur->next;
700 if (cur == category->last)
701 category->last = prev;
703 category->root = cur->next;
704 if (cur == category->last)
705 category->last = NULL;
708 ast_variables_destroy(cur);
716 cur = category->root;
719 if ((!ast_strlen_zero(line) && lineno == atoi(line)) || (ast_strlen_zero(line) && !strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
721 prev->next = cur->next;
722 if (cur == category->last)
723 category->last = prev;
725 category->root = cur->next;
726 if (cur == category->last)
727 category->last = NULL;
730 ast_variables_destroy(cur);
741 int ast_variable_update(struct ast_category *category, const char *variable,
742 const char *value, const char *match, unsigned int object)
744 struct ast_variable *cur, *prev=NULL, *newer=NULL;
746 for (cur = category->root; cur; prev = cur, cur = cur->next) {
747 if (strcasecmp(cur->name, variable) ||
748 (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
751 if (!(newer = ast_variable_new(variable, value, cur->file)))
754 newer->next = cur->next;
755 newer->object = cur->object || object;
757 /* Preserve everything */
758 newer->lineno = cur->lineno;
759 newer->blanklines = cur->blanklines;
760 newer->precomments = cur->precomments; cur->precomments = NULL;
761 newer->sameline = cur->sameline; cur->sameline = NULL;
762 newer->trailing = cur->trailing; cur->trailing = NULL;
767 category->root = newer;
768 if (category->last == cur)
769 category->last = newer;
772 ast_variables_destroy(cur);
777 /* Could not find variable to update */
781 int ast_category_delete(struct ast_config *cfg, const char *category)
783 struct ast_category *prev=NULL, *cat;
787 if (cat->name == category) {
789 prev->next = cat->next;
790 if (cat == cfg->last)
793 cfg->root = cat->next;
794 if (cat == cfg->last)
797 ast_category_destroy(cat);
807 if (!strcasecmp(cat->name, category)) {
809 prev->next = cat->next;
810 if (cat == cfg->last)
813 cfg->root = cat->next;
814 if (cat == cfg->last)
817 ast_category_destroy(cat);
826 int ast_category_empty(struct ast_config *cfg, const char *category)
828 struct ast_category *cat;
830 for (cat = cfg->root; cat; cat = cat->next) {
831 if (!strcasecmp(cat->name, category))
833 ast_variables_destroy(cat->root);
842 void ast_config_destroy(struct ast_config *cfg)
844 struct ast_category *cat, *catn;
849 ast_includes_destroy(cfg->includes);
855 ast_category_destroy(catn);
860 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
865 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
867 /* cast below is just to silence compiler warning about dropping "const" */
868 cfg->current = (struct ast_category *) cat;
871 enum config_cache_attribute_enum {
872 ATTRIBUTE_INCLUDE = 0,
876 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
878 struct cache_file_mtime *cfmtime;
879 struct cache_file_include *cfinclude;
880 struct stat statbuf = { 0, };
882 /* Find our cached entry for this configuration file */
883 AST_LIST_LOCK(&cfmtime_head);
884 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
885 if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
889 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1 + strlen(who_asked) + 1);
891 AST_LIST_UNLOCK(&cfmtime_head);
894 AST_LIST_HEAD_INIT(&cfmtime->includes);
895 strcpy(cfmtime->filename, configfile);
896 cfmtime->who_asked = cfmtime->filename + strlen(configfile) + 1;
897 strcpy(cfmtime->who_asked, who_asked);
898 /* Note that the file mtime is initialized to 0, i.e. 1970 */
899 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
902 if (!stat(configfile, &statbuf))
905 cfmtime->mtime = statbuf.st_mtime;
908 case ATTRIBUTE_INCLUDE:
909 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
910 if (!strcmp(cfinclude->include, filename)) {
911 AST_LIST_UNLOCK(&cfmtime_head);
915 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
917 AST_LIST_UNLOCK(&cfmtime_head);
920 strcpy(cfinclude->include, filename);
921 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
924 cfmtime->has_exec = 1;
927 AST_LIST_UNLOCK(&cfmtime_head);
930 /*! \brief parse one line in the configuration.
932 * We can have a category header [foo](...)
933 * a directive #include / #exec
934 * or a regular line name = value
937 static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
938 char *buf, int lineno, const char *configfile, struct ast_flags flags,
939 struct ast_str *comment_buffer,
940 struct ast_str *lline_buffer,
941 const char *suggested_include_file,
942 struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
946 struct ast_variable *v;
947 char cmd[512], exec_file[512];
949 /* Actually parse the entry */
950 if (cur[0] == '[') { /* A category header */
951 /* format is one of the following:
952 * [foo] define a new category named 'foo'
953 * [foo](!) define a new template category named 'foo'
954 * [foo](+) append to category 'foo', error if foo does not exist.
955 * [foo](a) define a new category and inherit from category or template a.
956 * You can put a comma-separated list of categories and templates
957 * and '!' and '+' between parentheses, with obvious meaning.
959 struct ast_category *newcat = NULL;
962 c = strchr(cur, ']');
964 ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
972 if (!(*cat = newcat = ast_category_new(catname,
973 S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
977 (*cat)->lineno = lineno;
982 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
983 newcat->precomments = ALLOC_COMMENT(comment_buffer);
984 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
985 newcat->sameline = ALLOC_COMMENT(lline_buffer);
986 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
987 CB_RESET(comment_buffer, lline_buffer);
989 /* If there are options or categories to inherit from, process them now */
991 if (!(cur = strchr(c, ')'))) {
992 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
996 while ((cur = strsep(&c, ","))) {
997 if (!strcasecmp(cur, "!")) {
999 } else if (!strcasecmp(cur, "+")) {
1000 *cat = category_get(cfg, catname, 1);
1003 ast_category_destroy(newcat);
1004 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
1008 move_variables(newcat, *cat);
1009 ast_category_destroy(newcat);
1013 struct ast_category *base;
1015 base = category_get(cfg, cur, 1);
1017 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
1020 inherit_category(*cat, base);
1025 ast_category_append(cfg, *cat);
1026 } else if (cur[0] == '#') { /* A directive - #include or #exec */
1028 char real_inclusion_name[256];
1029 int do_include = 0; /* otherwise, it is exec */
1033 while (*c && (*c > 32)) {
1039 /* Find real argument */
1040 c = ast_strip(c + 1);
1047 if (!strcasecmp(cur, "include")) {
1049 } else if (!strcasecmp(cur, "exec")) {
1050 if (!ast_opt_exec_includes) {
1051 ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
1052 return 0; /* XXX is this correct ? or we should return -1 ? */
1055 ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
1056 return 0; /* XXX is this correct ? or we should return -1 ? */
1060 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
1061 do_include ? "include" : "exec",
1062 do_include ? "filename" : "/path/to/executable",
1065 return 0; /* XXX is this correct ? or we should return -1 ? */
1069 /* Strip off leading and trailing "'s and <>'s */
1071 if ((*c == '"') || (*c == '<')) {
1072 char quote_char = *c;
1073 if (quote_char == '<') {
1077 if (*(c + strlen(c) - 1) == quote_char) {
1079 *(c + strlen(c) - 1) = '\0';
1084 /* #exec </path/to/executable>
1085 We create a tmp file, then we #include it, then we delete it. */
1087 struct timeval now = ast_tvnow();
1088 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1089 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
1090 snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
1091 snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
1092 ast_safe_system(cmd);
1095 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1096 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
1097 exec_file[0] = '\0';
1100 /* record this inclusion */
1101 ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
1103 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
1104 if (!ast_strlen_zero(exec_file))
1107 ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
1110 /* XXX otherwise what ? the default return is 0 anyways */
1113 /* Just a line (variable = value) */
1116 ast_log(LOG_WARNING,
1117 "parse error: No category context for line %d of %s\n", lineno, configfile);
1120 c = strchr(cur, '=');
1122 if (c && c > cur && (*(c - 1) == '+')) {
1123 struct ast_variable *var, *replace = NULL;
1124 struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
1126 if (!str || !*str) {
1132 cur = ast_strip(cur);
1134 /* Must iterate through category until we find last variable of same name (since there could be multiple) */
1135 for (var = ast_category_first(*cat); var; var = var->next) {
1136 if (!strcmp(var->name, cur)) {
1142 /* Nothing to replace; just set a variable normally. */
1143 goto set_new_variable;
1146 ast_str_set(str, 0, "%s", replace->value);
1147 ast_str_append(str, 0, "%s", c);
1148 ast_str_trim_blanks(*str);
1149 ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
1153 /* Ignore > in => */
1159 if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
1164 /* Put and reset comments */
1166 ast_variable_append(*cat, v);
1168 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1169 v->precomments = ALLOC_COMMENT(comment_buffer);
1170 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1171 v->sameline = ALLOC_COMMENT(lline_buffer);
1172 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1173 CB_RESET(comment_buffer, lline_buffer);
1179 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
1185 static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
1188 #if defined(LOW_MEMORY)
1193 char *new_buf, *comment_p, *process_buf;
1196 int comment = 0, nest[MAX_NESTED_COMMENTS];
1197 struct ast_category *cat = NULL;
1199 struct stat statbuf;
1200 struct cache_file_mtime *cfmtime = NULL;
1201 struct cache_file_include *cfinclude;
1202 struct ast_variable *last_var = 0;
1203 struct ast_category *last_cat = 0;
1204 /*! Growable string buffer */
1205 struct ast_str *comment_buffer = NULL; /*!< this will be a comment collector.*/
1206 struct ast_str *lline_buffer = NULL; /*!< A buffer for stuff behind the ; */
1209 cat = ast_config_get_current_category(cfg);
1211 if (filename[0] == '/') {
1212 ast_copy_string(fn, filename, sizeof(fn));
1214 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
1217 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1218 comment_buffer = ast_str_create(CB_SIZE);
1220 lline_buffer = ast_str_create(CB_SIZE);
1221 if (!lline_buffer) {
1223 ast_free(comment_buffer);
1224 ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1228 #ifdef AST_INCLUDE_GLOB
1232 globbuf.gl_offs = 0; /* initialize it to silence gcc */
1233 glob_ret = glob(fn, MY_GLOB_FLAGS, NULL, &globbuf);
1234 if (glob_ret == GLOB_NOSPACE)
1235 ast_log(LOG_WARNING,
1236 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
1237 else if (glob_ret == GLOB_ABORTED)
1238 ast_log(LOG_WARNING,
1239 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
1241 /* loop over expanded files */
1243 for (i=0; i<globbuf.gl_pathc; i++) {
1244 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1247 * The following is not a loop, but just a convenient way to define a block
1248 * (using do { } while(0) ), and be able to exit from it with 'continue'
1249 * or 'break' in case of errors. Nice trick.
1252 if (stat(fn, &statbuf))
1255 if (!S_ISREG(statbuf.st_mode)) {
1256 ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1260 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
1261 /* Find our cached entry for this configuration file */
1262 AST_LIST_LOCK(&cfmtime_head);
1263 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
1264 if (!strcmp(cfmtime->filename, fn) && !strcmp(cfmtime->who_asked, who_asked))
1268 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1 + strlen(who_asked) + 1);
1271 AST_LIST_HEAD_INIT(&cfmtime->includes);
1272 strcpy(cfmtime->filename, fn);
1273 cfmtime->who_asked = cfmtime->filename + strlen(fn) + 1;
1274 strcpy(cfmtime->who_asked, who_asked);
1275 /* Note that the file mtime is initialized to 0, i.e. 1970 */
1276 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
1280 if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
1281 /* File is unchanged, what about the (cached) includes (if any)? */
1283 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
1284 /* We must glob here, because if we did not, then adding a file to globbed directory would
1285 * incorrectly cause no reload to be necessary. */
1287 #ifdef AST_INCLUDE_GLOB
1289 glob_t glob_buf = { .gl_offs = 0 };
1290 glob_return = glob(cfinclude->include, MY_GLOB_FLAGS, NULL, &glob_buf);
1291 /* On error, we reparse */
1292 if (glob_return == GLOB_NOSPACE || glob_return == GLOB_ABORTED)
1295 /* loop over expanded files */
1297 for (j = 0; j < glob_buf.gl_pathc; j++) {
1298 ast_copy_string(fn2, glob_buf.gl_pathv[j], sizeof(fn2));
1300 ast_copy_string(fn2, cfinclude->include);
1302 if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "", who_asked) == NULL) {
1303 /* that second-to-last field needs to be looked at in this case... TODO */
1305 /* One change is enough to short-circuit and reload the whole shebang */
1308 #ifdef AST_INCLUDE_GLOB
1315 AST_LIST_UNLOCK(&cfmtime_head);
1316 return CONFIG_STATUS_FILEUNCHANGED;
1319 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1320 AST_LIST_UNLOCK(&cfmtime_head);
1322 /* If cfg is NULL, then we just want an answer */
1327 cfmtime->mtime = statbuf.st_mtime;
1329 ast_verb(2, "Parsing '%s': ", fn);
1331 if (!(f = fopen(fn, "r"))) {
1332 ast_debug(1, "No file to parse: %s\n", fn);
1333 ast_verb(2, "Not found (%s)\n", strerror(errno));
1337 /* If we get to this point, then we're loading regardless */
1338 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
1339 ast_debug(1, "Parsing %s\n", fn);
1340 ast_verb(2, "Found\n");
1343 if (fgets(buf, sizeof(buf), f)) {
1344 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && lline_buffer && ast_str_strlen(lline_buffer)) {
1345 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1346 ast_str_reset(lline_buffer); /* erase the lline buffer */
1355 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer) && (ast_strlen_zero(buf) || strlen(buf) == strspn(buf," \t\n\r"))) {
1356 /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
1357 CB_ADD(&comment_buffer, "\n"); /* add a newline to the comment buffer */
1358 continue; /* go get a new line, then */
1361 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1362 if ((comment_p > new_buf) && (*(comment_p - 1) == '\\')) {
1363 /* Escaped semicolons aren't comments. */
1364 new_buf = comment_p + 1;
1365 } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1366 /* Meta-Comment start detected ";--" */
1367 if (comment < MAX_NESTED_COMMENTS) {
1369 new_buf = comment_p + 3;
1371 nest[comment-1] = lineno;
1373 ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1375 } else if ((comment_p >= new_buf + 2) &&
1376 (*(comment_p - 1) == COMMENT_TAG) &&
1377 (*(comment_p - 2) == COMMENT_TAG)) {
1378 /* Meta-Comment end detected */
1380 new_buf = comment_p + 1;
1382 /* Back to non-comment now */
1384 /* Actually have to move what's left over the top, then continue */
1386 oldptr = process_buf + strlen(process_buf);
1387 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1388 CB_ADD(&comment_buffer, ";");
1389 CB_ADD_LEN(&comment_buffer, oldptr+1, new_buf-oldptr-1);
1392 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1395 process_buf = new_buf;
1399 /* If ; is found, and we are not nested in a comment,
1400 we immediately stop all comment processing */
1401 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1402 CB_ADD(&lline_buffer, comment_p);
1405 new_buf = comment_p;
1407 new_buf = comment_p + 1;
1410 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf ) {
1411 CB_ADD(&comment_buffer, buf); /* the whole line is a comment, store it */
1415 char *buffer = ast_strip(process_buf);
1416 if (!ast_strlen_zero(buffer)) {
1417 if (process_text_line(cfg, &cat, buffer, lineno, fn, flags, comment_buffer, lline_buffer, suggested_include_file, &last_cat, &last_var, who_asked)) {
1418 cfg = CONFIG_STATUS_FILEINVALID;
1425 /* end of file-- anything in a comment buffer? */
1427 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1428 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1429 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1430 ast_str_reset(lline_buffer); /* erase the lline buffer */
1432 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
1434 } else if (last_var) {
1435 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1436 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1437 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1438 ast_str_reset(lline_buffer); /* erase the lline buffer */
1440 last_var->trailing = ALLOC_COMMENT(comment_buffer);
1443 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1444 ast_debug(1, "Nothing to attach comments to, discarded: %s\n", ast_str_buffer(comment_buffer));
1447 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1448 CB_RESET(comment_buffer, lline_buffer);
1453 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1455 #ifdef AST_INCLUDE_GLOB
1456 if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
1465 if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1467 ast_free(comment_buffer);
1469 ast_free(lline_buffer);
1470 comment_buffer = NULL;
1471 lline_buffer = NULL;
1481 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1482 which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1483 recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1484 be shocked and mystified as to why things are not showing up in the files!
1486 Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1487 and line number are stored for each include, plus the name of the file included, so that these statements may be
1488 included in the output files on a file_save operation.
1490 The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1491 are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1492 the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1493 and a header gets added.
1495 vars and category heads are output in the order they are stored in the config file. So, if the software
1496 shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1497 file/lineno data probably won't get changed.
1501 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1507 ast_copy_string(date, ctime(&t), sizeof(date));
1509 fprintf(f1, ";!\n");
1510 fprintf(f1, ";! Automatically generated configuration file\n");
1511 if (strcmp(configfile, fn))
1512 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1514 fprintf(f1, ";! Filename: %s\n", configfile);
1515 fprintf(f1, ";! Generator: %s\n", generator);
1516 fprintf(f1, ";! Creation Date: %s", date);
1517 fprintf(f1, ";!\n");
1520 static void inclfile_destroy(void *obj)
1522 const struct inclfile *o = obj;
1529 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset, struct inclfile **fi)
1531 struct inclfile lookup;
1533 if (!file || file[0] == 0) {
1534 if (configfile[0] == '/')
1535 ast_copy_string(fn, configfile, fn_size);
1537 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1538 } else if (file[0] == '/')
1539 ast_copy_string(fn, file, fn_size);
1541 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1543 *fi = ao2_find(fileset, &lookup, OBJ_POINTER);
1545 /* set up a file scratch pad */
1546 struct inclfile *fx = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
1547 fx->fname = ast_strdup(fn);
1550 ao2_link(fileset, fx);
1554 static int count_linefeeds(char *str)
1566 static int count_linefeeds_in_comments(struct ast_comment *x)
1571 count += count_linefeeds(x->cmt);
1577 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
1579 int precomment_lines = count_linefeeds_in_comments(precomments);
1582 /* I don't have to worry about those ;! comments, they are
1583 stored in the precomments, but not printed back out.
1584 I did have to make sure that comments following
1585 the ;! header comments were not also deleted in the process */
1586 if (lineno - precomment_lines - fi->lineno < 0) { /* insertions can mess up the line numbering and produce negative numbers that mess things up */
1588 } else if (lineno == 0) {
1589 /* Line replacements also mess things up */
1591 } else if (lineno - precomment_lines - fi->lineno < 5) {
1592 /* Only insert less than 5 blank lines; if anything more occurs,
1593 * it's probably due to context deletion. */
1594 for (i = fi->lineno; i < lineno - precomment_lines; i++) {
1598 /* Deletion occurred - insert a single blank line, for separation of
1603 fi->lineno = lineno + 1; /* Advance the file lineno */
1606 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1608 return ast_config_text_file_save(configfile, cfg, generator);
1611 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1615 struct ast_variable *var;
1616 struct ast_category *cat;
1617 struct ast_comment *cmt;
1618 struct ast_config_include *incl;
1620 struct ao2_container *fileset = ao2_container_alloc(180000, hash_string, hashtab_compare_strings);
1621 struct inclfile *fi = 0;
1623 /* reset all the output flags, in case this isn't our first time saving this data */
1625 for (incl=cfg->includes; incl; incl = incl->next)
1628 /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1629 are all truncated to zero bytes and have that nice header*/
1631 for (incl=cfg->includes; incl; incl = incl->next)
1633 if (!incl->exec) { /* leave the execs alone -- we'll write out the #exec directives, but won't zero out the include files or exec files*/
1636 set_fn(fn, sizeof(fn), incl->included_file, configfile, fileset, &fi); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
1639 gen_header(f1, configfile, fn, generator);
1640 fclose(f1); /* this should zero out the file */
1642 ast_debug(1, "Unable to open for writing: %s\n", fn);
1643 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1645 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1650 set_fn(fn, sizeof(fn), 0, configfile, fileset, &fi); /* just set fn to absolute ver of configfile */
1652 if ((f = fopen(fn, "w+"))) {
1654 if ((f = fopen(fn, "w"))) {
1656 ast_verb(2, "Saving '%s': ", fn);
1657 gen_header(f, configfile, fn, generator);
1660 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1662 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1663 /* since each var, cat, and associated comments can come from any file, we have to be
1664 mobile, and open each file, print, and close it on an entry-by-entry basis */
1667 set_fn(fn, sizeof(fn), cat->file, configfile, fileset, &fi);
1671 ast_debug(1, "Unable to open for writing: %s\n", fn);
1672 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1673 ao2_ref(fileset, -1);
1677 /* dump any includes that happen before this category header */
1678 for (incl=cfg->includes; incl; incl = incl->next) {
1679 if (strcmp(incl->include_location_file, cat->file) == 0){
1680 if (cat->lineno > incl->include_location_lineno && !incl->output) {
1682 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1684 fprintf(f,"#include \"%s\"\n", incl->included_file);
1690 insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
1691 /* Dump section with any appropriate comment */
1692 for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1693 char *cmtp = cmt->cmt;
1694 while (*cmtp == ';' && *(cmtp+1) == '!') {
1695 char *cmtp2 = strchr(cmtp+1, '\n');
1701 fprintf(f,"%s", cmtp);
1703 fprintf(f, "[%s]", cat->name);
1704 if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
1709 if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
1712 if (!AST_LIST_EMPTY(&cat->template_instances)) {
1713 struct ast_category_template_instance *x;
1714 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1715 fprintf(f,"%s",x->name);
1716 if (x != AST_LIST_LAST(&cat->template_instances))
1722 for(cmt = cat->sameline; cmt; cmt=cmt->next)
1724 fprintf(f,"%s", cmt->cmt);
1728 for (cmt = cat->trailing; cmt; cmt=cmt->next) {
1729 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1730 fprintf(f,"%s", cmt->cmt);
1733 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1738 struct ast_category_template_instance *x;
1740 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1741 struct ast_variable *v;
1742 for (v = x->inst->root; v; v = v->next) {
1743 if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
1755 set_fn(fn, sizeof(fn), var->file, configfile, fileset, &fi);
1759 ast_debug(1, "Unable to open for writing: %s\n", fn);
1760 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1761 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1763 ao2_ref(fileset, -1);
1767 /* dump any includes that happen before this category header */
1768 for (incl=cfg->includes; incl; incl = incl->next) {
1769 if (strcmp(incl->include_location_file, var->file) == 0){
1770 if (var->lineno > incl->include_location_lineno && !incl->output) {
1772 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1774 fprintf(f,"#include \"%s\"\n", incl->included_file);
1780 insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
1781 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1782 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1783 fprintf(f,"%s", cmt->cmt);
1786 fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1788 fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1789 for (cmt = var->trailing; cmt; cmt=cmt->next) {
1790 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1791 fprintf(f,"%s", cmt->cmt);
1793 if (var->blanklines) {
1794 blanklines = var->blanklines;
1795 while (blanklines--)
1800 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1808 ast_verb(2, "Saved\n");
1810 ast_debug(1, "Unable to open for writing: %s\n", fn);
1811 ast_verb(2, "Unable to write (%s)", strerror(errno));
1812 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1813 ao2_ref(fileset, -1);
1817 /* Now, for files with trailing #include/#exec statements,
1818 we have to make sure every entry is output */
1820 for (incl=cfg->includes; incl; incl = incl->next) {
1821 if (!incl->output) {
1822 /* open the respective file */
1823 set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset, &fi);
1827 ast_debug(1, "Unable to open for writing: %s\n", fn);
1828 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1829 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1831 ao2_ref(fileset, -1);
1835 /* output the respective include */
1837 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1839 fprintf(f,"#include \"%s\"\n", incl->included_file);
1842 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1846 ao2_ref(fileset, -1); /* this should destroy the hash container */
1851 static void clear_config_maps(void)
1853 struct ast_config_map *map;
1855 ast_mutex_lock(&config_lock);
1857 while (config_maps) {
1859 config_maps = config_maps->next;
1863 ast_mutex_unlock(&config_lock);
1866 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
1868 struct ast_config_map *map;
1871 length = sizeof(*map);
1872 length += strlen(name) + 1;
1873 length += strlen(driver) + 1;
1874 length += strlen(database) + 1;
1876 length += strlen(table) + 1;
1878 if (!(map = ast_calloc(1, length)))
1881 map->name = map->stuff;
1882 strcpy(map->name, name);
1883 map->driver = map->name + strlen(map->name) + 1;
1884 strcpy(map->driver, driver);
1885 map->database = map->driver + strlen(map->driver) + 1;
1886 strcpy(map->database, database);
1888 map->table = map->database + strlen(map->database) + 1;
1889 strcpy(map->table, table);
1891 map->priority = priority;
1892 map->next = config_maps;
1894 ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1900 int read_config_maps(void)
1902 struct ast_config *config, *configtmp;
1903 struct ast_variable *v;
1904 char *driver, *table, *database, *textpri, *stringp, *tmp;
1905 struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
1908 clear_config_maps();
1910 configtmp = ast_config_new();
1911 configtmp->max_include_level = 1;
1912 config = ast_config_internal_load(extconfig_conf, configtmp, flags, "", "extconfig");
1913 if (config == CONFIG_STATUS_FILEINVALID) {
1915 } else if (!config) {
1916 ast_config_destroy(configtmp);
1920 for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1922 ast_copy_string(buf, v->value, sizeof(buf));
1924 driver = strsep(&stringp, ",");
1926 if ((tmp = strchr(stringp, '\"')))
1929 /* check if the database text starts with a double quote */
1930 if (*stringp == '"') {
1932 database = strsep(&stringp, "\"");
1933 strsep(&stringp, ",");
1935 /* apparently this text has no quotes */
1936 database = strsep(&stringp, ",");
1939 table = strsep(&stringp, ",");
1940 textpri = strsep(&stringp, ",");
1941 if (!textpri || !(pri = atoi(textpri))) {
1945 if (!strcmp(v->name, extconfig_conf)) {
1946 ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1950 if (!strcmp(v->name, "asterisk.conf")) {
1951 ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1955 if (!strcmp(v->name, "logger.conf")) {
1956 ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1960 if (!driver || !database)
1962 if (!strcasecmp(v->name, "sipfriends")) {
1963 ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sipusers and sippeers, though they can point to the same table.\n");
1964 append_mapping("sipusers", driver, database, table ? table : "sipfriends", pri);
1965 append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri);
1966 } else if (!strcasecmp(v->name, "iaxfriends")) {
1967 ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n");
1968 append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri);
1969 append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri);
1971 append_mapping(v->name, driver, database, table, pri);
1974 ast_config_destroy(config);
1978 int ast_config_engine_register(struct ast_config_engine *new)
1980 struct ast_config_engine *ptr;
1982 ast_mutex_lock(&config_lock);
1984 if (!config_engine_list) {
1985 config_engine_list = new;
1987 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1991 ast_mutex_unlock(&config_lock);
1992 ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1997 int ast_config_engine_deregister(struct ast_config_engine *del)
1999 struct ast_config_engine *ptr, *last=NULL;
2001 ast_mutex_lock(&config_lock);
2003 for (ptr = config_engine_list; ptr; ptr=ptr->next) {
2006 last->next = ptr->next;
2008 config_engine_list = ptr->next;
2014 ast_mutex_unlock(&config_lock);
2019 /*! \brief Find realtime engine for realtime family */
2020 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz)
2022 struct ast_config_engine *eng, *ret = NULL;
2023 struct ast_config_map *map;
2025 ast_mutex_lock(&config_lock);
2027 for (map = config_maps; map; map = map->next) {
2028 if (!strcasecmp(family, map->name) && (priority == map->priority)) {
2030 ast_copy_string(database, map->database, dbsiz);
2032 ast_copy_string(table, map->table ? map->table : family, tabsiz);
2037 /* Check if the required driver (engine) exist */
2039 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
2040 if (!strcasecmp(eng->name, map->driver))
2045 ast_mutex_unlock(&config_lock);
2047 /* if we found a mapping, but the engine is not available, then issue a warning */
2049 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
2054 static struct ast_config_engine text_file_engine = {
2056 .load_func = config_text_file_load,
2059 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file, const char *who_asked)
2063 struct ast_config_engine *loader = &text_file_engine;
2064 struct ast_config *result;
2066 /* The config file itself bumps include_level by 1 */
2067 if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
2068 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
2072 cfg->include_level++;
2074 if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
2075 struct ast_config_engine *eng;
2077 eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
2080 if (eng && eng->load_func) {
2083 eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
2084 if (eng && eng->load_func)
2089 result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
2091 if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
2092 result->include_level--;
2093 else if (result != CONFIG_STATUS_FILEINVALID)
2094 cfg->include_level--;
2099 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
2101 struct ast_config *cfg;
2102 struct ast_config *result;
2104 cfg = ast_config_new();
2108 result = ast_config_internal_load(filename, cfg, flags, "", who_asked);
2109 if (!result || result == CONFIG_STATUS_FILEUNCHANGED || result == CONFIG_STATUS_FILEINVALID)
2110 ast_config_destroy(cfg);
2115 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
2117 struct ast_config_engine *eng;
2120 struct ast_variable *res=NULL;
2123 for (i = 1; ; i++) {
2124 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2125 if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) {
2136 struct ast_variable *ast_load_realtime_all(const char *family, ...)
2138 struct ast_variable *res;
2141 va_start(ap, family);
2142 res = ast_load_realtime_helper(family, ap);
2148 struct ast_variable *ast_load_realtime(const char *family, ...)
2150 struct ast_variable *res;
2151 struct ast_variable *cur;
2152 struct ast_variable **prev;
2155 va_start(ap, family);
2156 res = ast_load_realtime_helper(family, ap);
2159 /* Filter the list. */
2163 if (ast_strlen_zero(cur->value)) {
2164 /* Eliminate empty entries */
2165 struct ast_variable *next;
2169 ast_variable_destroy(cur);
2172 /* Make blank entries empty and keep them. */
2173 if (cur->value[0] == ' ' && cur->value[1] == '\0') {
2174 char *vptr = (char *) cur->value;
2186 /*! \brief Check if realtime engine is configured for family */
2187 int ast_check_realtime(const char *family)
2189 struct ast_config_engine *eng;
2190 if (!ast_realtime_enabled()) {
2191 return 0; /* There are no engines at all so fail early */
2194 eng = find_engine(family, 1, NULL, 0, NULL, 0);
2200 /*! \brief Check if there's any realtime engines loaded */
2201 int ast_realtime_enabled()
2203 return config_maps ? 1 : 0;
2206 int ast_realtime_require_field(const char *family, ...)
2208 struct ast_config_engine *eng;
2214 va_start(ap, family);
2215 for (i = 1; ; i++) {
2216 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2217 /* If the require succeeds, it returns 0. */
2218 if (eng->require_func && !(res = eng->require_func(db, table, ap))) {
2230 int ast_unload_realtime(const char *family)
2232 struct ast_config_engine *eng;
2237 for (i = 1; ; i++) {
2238 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2239 if (eng->unload_func) {
2240 /* Do this for ALL engines */
2241 res = eng->unload_func(db, table);
2250 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
2252 struct ast_config_engine *eng;
2255 struct ast_config *res = NULL;
2259 va_start(ap, family);
2260 for (i = 1; ; i++) {
2261 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2262 if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) {
2274 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2276 struct ast_config_engine *eng;
2282 va_start(ap, lookup);
2283 for (i = 1; ; i++) {
2284 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2285 /* If the update succeeds, it returns 0. */
2286 if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) {
2298 int ast_update2_realtime(const char *family, ...)
2300 struct ast_config_engine *eng;
2306 va_start(ap, family);
2307 for (i = 1; ; i++) {
2308 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2309 if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) {
2321 int ast_store_realtime(const char *family, ...)
2323 struct ast_config_engine *eng;
2329 va_start(ap, family);
2330 for (i = 1; ; i++) {
2331 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2332 /* If the store succeeds, it returns 0. */
2333 if (eng->store_func && !(res = eng->store_func(db, table, ap))) {
2345 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2347 struct ast_config_engine *eng;
2353 va_start(ap, lookup);
2354 for (i = 1; ; i++) {
2355 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2356 if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) {
2368 char *ast_realtime_decode_chunk(char *chunk)
2371 for (; *chunk; chunk++) {
2372 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
2373 sscanf(chunk + 1, "%02hhX", chunk);
2374 memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
2380 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
2382 if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
2383 ast_str_set(dest, maxlen, "%s", chunk);
2385 ast_str_reset(*dest);
2386 for (; *chunk; chunk++) {
2387 if (strchr(";^", *chunk)) {
2388 ast_str_append(dest, maxlen, "^%02hhX", *chunk);
2390 ast_str_append(dest, maxlen, "%c", *chunk);
2394 return ast_str_buffer(*dest);
2397 /*! \brief Helper function to parse arguments
2398 * See documentation in config.h
2400 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
2401 void *p_result, ...)
2406 va_start(ap, p_result);
2407 switch (flags & PARSE_TYPE) {
2410 int32_t *result = p_result;
2411 int32_t x, def = result ? *result : 0,
2412 high = (int32_t)0x7fffffff,
2413 low = (int32_t)0x80000000;
2414 /* optional argument: first default value, then range */
2415 if (flags & PARSE_DEFAULT)
2416 def = va_arg(ap, int32_t);
2417 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2418 /* range requested, update bounds */
2419 low = va_arg(ap, int32_t);
2420 high = va_arg(ap, int32_t);
2422 x = strtol(arg, NULL, 0);
2423 error = (x < low) || (x > high);
2424 if (flags & PARSE_OUT_RANGE)
2427 *result = error ? def : x;
2429 "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
2431 result ? *result : x, error);
2437 uint32_t *result = p_result;
2438 uint32_t x, def = result ? *result : 0,
2439 low = 0, high = (uint32_t)~0;
2440 /* optional argument: first default value, then range */
2441 if (flags & PARSE_DEFAULT)
2442 def = va_arg(ap, uint32_t);
2443 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2444 /* range requested, update bounds */
2445 low = va_arg(ap, uint32_t);
2446 high = va_arg(ap, uint32_t);
2448 x = strtoul(arg, NULL, 0);
2449 error = (x < low) || (x > high);
2450 if (flags & PARSE_OUT_RANGE)
2453 *result = error ? def : x;
2455 "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
2457 result ? *result : x, error);
2463 double *result = p_result;
2464 double x, def = result ? *result : 0,
2465 low = -HUGE_VAL, high = HUGE_VAL;
2467 /* optional argument: first default value, then range */
2468 if (flags & PARSE_DEFAULT)
2469 def = va_arg(ap, double);
2470 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2471 /* range requested, update bounds */
2472 low = va_arg(ap, double);
2473 high = va_arg(ap, double);
2475 x = strtod(arg, NULL);
2476 error = (x < low) || (x > high);
2477 if (flags & PARSE_OUT_RANGE)
2480 *result = error ? def : x;
2482 "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
2484 result ? *result : x, error);
2489 struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
2491 if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
2495 ast_debug(3, "extract addr from %s gives %s(%d)\n",
2496 arg, ast_sockaddr_stringify(addr), error);
2500 case PARSE_INADDR: /* TODO Remove this (use PARSE_ADDR instead). */
2503 struct sockaddr_in _sa_buf; /* buffer for the result */
2504 struct sockaddr_in *sa = p_result ?
2505 (struct sockaddr_in *)p_result : &_sa_buf;
2506 /* default is either the supplied value or the result itself */
2507 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
2508 va_arg(ap, struct sockaddr_in *) : sa;
2510 struct ast_hostent ahp;
2512 memset(&_sa_buf, '\0', sizeof(_sa_buf)); /* clear buffer */
2513 /* duplicate the string to strip away the :port */
2514 port = ast_strdupa(arg);
2515 buf = strsep(&port, ":");
2516 sa->sin_family = AF_INET; /* assign family */
2518 * honor the ports flag setting, assign default value
2519 * in case of errors or field unset.
2521 flags &= PARSE_PORT_MASK; /* the only flags left to process */
2523 if (flags == PARSE_PORT_FORBID) {
2524 error = 1; /* port was forbidden */
2525 sa->sin_port = def->sin_port;
2526 } else if (flags == PARSE_PORT_IGNORE)
2527 sa->sin_port = def->sin_port;
2528 else /* accept or require */
2529 sa->sin_port = htons(strtol(port, NULL, 0));
2531 sa->sin_port = def->sin_port;
2532 if (flags == PARSE_PORT_REQUIRE)
2535 /* Now deal with host part, even if we have errors before. */
2536 hp = ast_gethostbyname(buf, &ahp);
2537 if (hp) /* resolved successfully */
2538 memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
2541 sa->sin_addr = def->sin_addr;
2544 "extract inaddr from [%s] gives [%s:%d](%d)\n",
2545 arg, ast_inet_ntoa(sa->sin_addr),
2546 ntohs(sa->sin_port), error);
2554 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2556 struct ast_config_engine *eng;
2557 struct ast_config_map *map;
2561 e->command = "core show config mappings";
2563 "Usage: core show config mappings\n"
2564 " Shows the filenames to config engines.\n";
2570 ast_mutex_lock(&config_lock);
2572 if (!config_engine_list) {
2573 ast_cli(a->fd, "No config mappings found.\n");
2575 for (eng = config_engine_list; eng; eng = eng->next) {
2576 ast_cli(a->fd, "Config Engine: %s\n", eng->name);
2577 for (map = config_maps; map; map = map->next) {
2578 if (!strcasecmp(map->driver, eng->name)) {
2579 ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
2580 map->table ? map->table : map->name);
2586 ast_mutex_unlock(&config_lock);
2591 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2593 struct cache_file_mtime *cfmtime;
2594 char *prev = "", *completion_value = NULL;
2595 int wordlen, which = 0;
2599 e->command = "config reload";
2601 "Usage: config reload <filename.conf>\n"
2602 " Reloads all modules that reference <filename.conf>\n";
2609 wordlen = strlen(a->word);
2611 AST_LIST_LOCK(&cfmtime_head);
2612 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2613 /* Skip duplicates - this only works because the list is sorted by filename */
2614 if (strcmp(cfmtime->filename, prev) == 0) {
2618 /* Core configs cannot be reloaded */
2619 if (ast_strlen_zero(cfmtime->who_asked)) {
2623 if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
2624 completion_value = ast_strdup(cfmtime->filename);
2628 /* Otherwise save that we've seen this filename */
2629 prev = cfmtime->filename;
2631 AST_LIST_UNLOCK(&cfmtime_head);
2633 return completion_value;
2637 return CLI_SHOWUSAGE;
2640 AST_LIST_LOCK(&cfmtime_head);
2641 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2642 if (!strcmp(cfmtime->filename, a->argv[2])) {
2643 char *buf = alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1);
2644 sprintf(buf, "module reload %s", cfmtime->who_asked);
2645 ast_cli_command(a->fd, buf);
2648 AST_LIST_UNLOCK(&cfmtime_head);
2653 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2655 struct cache_file_mtime *cfmtime;
2659 e->command = "config list";
2661 "Usage: config list\n"
2662 " Show all modules that have loaded a configuration file\n";
2668 AST_LIST_LOCK(&cfmtime_head);
2669 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2670 ast_cli(a->fd, "%-20.20s %-50s\n", S_OR(cfmtime->who_asked, "core"), cfmtime->filename);
2672 AST_LIST_UNLOCK(&cfmtime_head);
2677 static struct ast_cli_entry cli_config[] = {
2678 AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
2679 AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"),
2680 AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
2683 int register_config_cli()
2685 ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));