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 doc/realtime.txt and doc/extconfig.txt
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 void ast_variables_destroy(struct ast_variable *v)
401 struct ast_variable *vn;
406 ast_comment_destroy(&vn->precomments);
407 ast_comment_destroy(&vn->sameline);
408 ast_comment_destroy(&vn->trailing);
413 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
415 struct ast_category *cat = NULL;
417 if (category && config->last_browse && (config->last_browse->name == category)) {
418 cat = config->last_browse;
420 cat = ast_category_get(config, category);
423 return (cat) ? cat->root : NULL;
426 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
429 tmp = ast_variable_retrieve(cfg, cat, var);
431 tmp = ast_variable_retrieve(cfg, "general", var);
437 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
439 struct ast_variable *v;
442 for (v = ast_variable_browse(config, category); v; v = v->next) {
443 if (!strcasecmp(variable, v->name)) {
448 struct ast_category *cat;
450 for (cat = config->root; cat; cat = cat->next) {
451 for (v = cat->root; v; v = v->next) {
452 if (!strcasecmp(variable, v->name)) {
462 static struct ast_variable *variable_clone(const struct ast_variable *old)
464 struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
467 new->lineno = old->lineno;
468 new->object = old->object;
469 new->blanklines = old->blanklines;
470 /* TODO: clone comments? */
476 static void move_variables(struct ast_category *old, struct ast_category *new)
478 struct ast_variable *var = old->root;
481 /* we can just move the entire list in a single op */
482 ast_variable_append(new, var);
485 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
487 struct ast_category *category;
489 if ((category = ast_calloc(1, sizeof(*category))))
490 ast_copy_string(category->name, name, sizeof(category->name));
491 category->file = strdup(in_file);
492 category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
496 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
498 struct ast_category *cat;
500 /* try exact match first, then case-insensitive match */
501 for (cat = config->root; cat; cat = cat->next) {
502 if (cat->name == category_name && (ignored || !cat->ignored))
506 for (cat = config->root; cat; cat = cat->next) {
507 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
514 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
516 return category_get(config, category_name, 0);
519 int ast_category_exist(const struct ast_config *config, const char *category_name)
521 return !!ast_category_get(config, category_name);
524 void ast_category_append(struct ast_config *config, struct ast_category *category)
527 config->last->next = category;
529 config->root = category;
530 category->include_level = config->include_level;
531 config->last = category;
532 config->current = category;
535 void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
537 struct ast_category *cur_category;
541 if (!strcasecmp(config->root->name, match)) {
542 cat->next = config->root;
546 for (cur_category = config->root; cur_category; cur_category = cur_category->next) {
547 if (!strcasecmp(cur_category->next->name, match)) {
548 cat->next = cur_category->next;
549 cur_category->next = cat;
555 static void ast_destroy_template_list(struct ast_category *cat)
557 struct ast_category_template_instance *x;
559 while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
563 void ast_category_destroy(struct ast_category *cat)
565 ast_variables_destroy(cat->root);
570 ast_comment_destroy(&cat->precomments);
571 ast_comment_destroy(&cat->sameline);
572 ast_comment_destroy(&cat->trailing);
573 ast_destroy_template_list(cat);
577 static void ast_includes_destroy(struct ast_config_include *incls)
579 struct ast_config_include *incl,*inclnext;
581 for (incl=incls; incl; incl = inclnext) {
582 inclnext = incl->next;
583 if (incl->include_location_file)
584 free(incl->include_location_file);
586 free(incl->exec_file);
587 if (incl->included_file)
588 free(incl->included_file);
593 static struct ast_category *next_available_category(struct ast_category *cat)
595 for (; cat && cat->ignored; cat = cat->next);
600 /*! return the first var of a category */
601 struct ast_variable *ast_category_first(struct ast_category *cat)
603 return (cat) ? cat->root : NULL;
606 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
608 struct ast_category *category = ast_category_get(config, cat);
611 return category->root;
615 char *ast_category_browse(struct ast_config *config, const char *prev)
617 struct ast_category *cat = NULL;
619 if (prev && config->last_browse && (config->last_browse->name == prev))
620 cat = config->last_browse->next;
621 else if (!prev && config->root)
624 for (cat = config->root; cat; cat = cat->next) {
625 if (cat->name == prev) {
631 for (cat = config->root; cat; cat = cat->next) {
632 if (!strcasecmp(cat->name, prev)) {
641 cat = next_available_category(cat);
643 config->last_browse = cat;
644 return (cat) ? cat->name : NULL;
647 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
649 struct ast_variable *v;
658 void ast_category_rename(struct ast_category *cat, const char *name)
660 ast_copy_string(cat->name, name, sizeof(cat->name));
663 static void inherit_category(struct ast_category *new, const struct ast_category *base)
665 struct ast_variable *var;
666 struct ast_category_template_instance *x = ast_calloc(1,sizeof(struct ast_category_template_instance));
668 strcpy(x->name, base->name);
670 AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
671 for (var = base->root; var; var = var->next)
672 ast_variable_append(new, variable_clone(var));
675 struct ast_config *ast_config_new(void)
677 struct ast_config *config;
679 if ((config = ast_calloc(1, sizeof(*config))))
680 config->max_include_level = MAX_INCLUDE_LEVEL;
684 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
686 struct ast_variable *cur, *prev=NULL, *curn;
690 cur = category->root;
692 if (cur->name == variable) {
694 prev->next = cur->next;
695 if (cur == category->last)
696 category->last = prev;
698 category->root = cur->next;
699 if (cur == category->last)
700 category->last = NULL;
703 ast_variables_destroy(cur);
711 cur = category->root;
714 if ((!ast_strlen_zero(line) && lineno == atoi(line)) || (ast_strlen_zero(line) && !strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
716 prev->next = cur->next;
717 if (cur == category->last)
718 category->last = prev;
720 category->root = cur->next;
721 if (cur == category->last)
722 category->last = NULL;
725 ast_variables_destroy(cur);
736 int ast_variable_update(struct ast_category *category, const char *variable,
737 const char *value, const char *match, unsigned int object)
739 struct ast_variable *cur, *prev=NULL, *newer=NULL;
741 for (cur = category->root; cur; prev = cur, cur = cur->next) {
742 if (strcasecmp(cur->name, variable) ||
743 (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
746 if (!(newer = ast_variable_new(variable, value, cur->file)))
749 newer->next = cur->next;
750 newer->object = cur->object || object;
752 /* Preserve everything */
753 newer->lineno = cur->lineno;
754 newer->blanklines = cur->blanklines;
755 newer->precomments = cur->precomments; cur->precomments = NULL;
756 newer->sameline = cur->sameline; cur->sameline = NULL;
757 newer->trailing = cur->trailing; cur->trailing = NULL;
762 category->root = newer;
763 if (category->last == cur)
764 category->last = newer;
767 ast_variables_destroy(cur);
772 /* Could not find variable to update */
776 int ast_category_delete(struct ast_config *cfg, const char *category)
778 struct ast_category *prev=NULL, *cat;
782 if (cat->name == category) {
784 prev->next = cat->next;
785 if (cat == cfg->last)
788 cfg->root = cat->next;
789 if (cat == cfg->last)
792 ast_category_destroy(cat);
802 if (!strcasecmp(cat->name, category)) {
804 prev->next = cat->next;
805 if (cat == cfg->last)
808 cfg->root = cat->next;
809 if (cat == cfg->last)
812 ast_category_destroy(cat);
821 int ast_category_empty(struct ast_config *cfg, const char *category)
823 struct ast_category *cat;
825 for (cat = cfg->root; cat; cat = cat->next) {
826 if (!strcasecmp(cat->name, category))
828 ast_variables_destroy(cat->root);
837 void ast_config_destroy(struct ast_config *cfg)
839 struct ast_category *cat, *catn;
844 ast_includes_destroy(cfg->includes);
850 ast_category_destroy(catn);
855 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
860 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
862 /* cast below is just to silence compiler warning about dropping "const" */
863 cfg->current = (struct ast_category *) cat;
866 enum config_cache_attribute_enum {
867 ATTRIBUTE_INCLUDE = 0,
871 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
873 struct cache_file_mtime *cfmtime;
874 struct cache_file_include *cfinclude;
875 struct stat statbuf = { 0, };
877 /* Find our cached entry for this configuration file */
878 AST_LIST_LOCK(&cfmtime_head);
879 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
880 if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
884 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1 + strlen(who_asked) + 1);
886 AST_LIST_UNLOCK(&cfmtime_head);
889 AST_LIST_HEAD_INIT(&cfmtime->includes);
890 strcpy(cfmtime->filename, configfile);
891 cfmtime->who_asked = cfmtime->filename + strlen(configfile) + 1;
892 strcpy(cfmtime->who_asked, who_asked);
893 /* Note that the file mtime is initialized to 0, i.e. 1970 */
894 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
897 if (!stat(configfile, &statbuf))
900 cfmtime->mtime = statbuf.st_mtime;
903 case ATTRIBUTE_INCLUDE:
904 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
905 if (!strcmp(cfinclude->include, filename)) {
906 AST_LIST_UNLOCK(&cfmtime_head);
910 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
912 AST_LIST_UNLOCK(&cfmtime_head);
915 strcpy(cfinclude->include, filename);
916 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
919 cfmtime->has_exec = 1;
922 AST_LIST_UNLOCK(&cfmtime_head);
925 /*! \brief parse one line in the configuration.
927 * We can have a category header [foo](...)
928 * a directive #include / #exec
929 * or a regular line name = value
932 static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
933 char *buf, int lineno, const char *configfile, struct ast_flags flags,
934 struct ast_str *comment_buffer,
935 struct ast_str *lline_buffer,
936 const char *suggested_include_file,
937 struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
941 struct ast_variable *v;
942 char cmd[512], exec_file[512];
944 /* Actually parse the entry */
945 if (cur[0] == '[') { /* A category header */
946 /* format is one of the following:
947 * [foo] define a new category named 'foo'
948 * [foo](!) define a new template category named 'foo'
949 * [foo](+) append to category 'foo', error if foo does not exist.
950 * [foo](a) define a new category and inherit from category or template a.
951 * You can put a comma-separated list of categories and templates
952 * and '!' and '+' between parentheses, with obvious meaning.
954 struct ast_category *newcat = NULL;
957 c = strchr(cur, ']');
959 ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
967 if (!(*cat = newcat = ast_category_new(catname,
968 S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
972 (*cat)->lineno = lineno;
977 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
978 newcat->precomments = ALLOC_COMMENT(comment_buffer);
979 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
980 newcat->sameline = ALLOC_COMMENT(lline_buffer);
981 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
982 CB_RESET(comment_buffer, lline_buffer);
984 /* If there are options or categories to inherit from, process them now */
986 if (!(cur = strchr(c, ')'))) {
987 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
991 while ((cur = strsep(&c, ","))) {
992 if (!strcasecmp(cur, "!")) {
994 } else if (!strcasecmp(cur, "+")) {
995 *cat = category_get(cfg, catname, 1);
998 ast_category_destroy(newcat);
999 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
1003 move_variables(newcat, *cat);
1004 ast_category_destroy(newcat);
1008 struct ast_category *base;
1010 base = category_get(cfg, cur, 1);
1012 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
1015 inherit_category(*cat, base);
1020 ast_category_append(cfg, *cat);
1021 } else if (cur[0] == '#') { /* A directive - #include or #exec */
1023 char real_inclusion_name[256];
1024 struct ast_config_include *inclu;
1025 int do_include = 0; /* otherwise, it is exec */
1029 while (*c && (*c > 32)) {
1035 /* Find real argument */
1036 c = ast_strip(c + 1);
1043 if (!strcasecmp(cur, "include")) {
1045 } else if (!strcasecmp(cur, "exec")) {
1046 if (!ast_opt_exec_includes) {
1047 ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
1048 return 0; /* XXX is this correct ? or we should return -1 ? */
1051 ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
1052 return 0; /* XXX is this correct ? or we should return -1 ? */
1056 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
1057 do_include ? "include" : "exec",
1058 do_include ? "filename" : "/path/to/executable",
1061 return 0; /* XXX is this correct ? or we should return -1 ? */
1065 /* Strip off leading and trailing "'s and <>'s */
1067 if ((*c == '"') || (*c == '<')) {
1068 char quote_char = *c;
1069 if (quote_char == '<') {
1073 if (*(c + strlen(c) - 1) == quote_char) {
1075 *(c + strlen(c) - 1) = '\0';
1080 /* #exec </path/to/executable>
1081 We create a tmp file, then we #include it, then we delete it. */
1083 struct timeval now = ast_tvnow();
1084 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1085 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
1086 snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
1087 snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
1088 ast_safe_system(cmd);
1091 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1092 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
1093 exec_file[0] = '\0';
1096 /* record this inclusion */
1097 inclu = ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
1099 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
1100 if (!ast_strlen_zero(exec_file))
1103 ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
1106 /* XXX otherwise what ? the default return is 0 anyways */
1109 /* Just a line (variable = value) */
1112 ast_log(LOG_WARNING,
1113 "parse error: No category context for line %d of %s\n", lineno, configfile);
1116 c = strchr(cur, '=');
1118 if (c && c > cur && (*(c - 1) == '+')) {
1119 struct ast_variable *var, *replace = NULL;
1120 struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
1122 if (!str || !*str) {
1128 cur = ast_strip(cur);
1130 /* Must iterate through category until we find last variable of same name (since there could be multiple) */
1131 for (var = ast_category_first(*cat); var; var = var->next) {
1132 if (!strcmp(var->name, cur)) {
1138 /* Nothing to replace; just set a variable normally. */
1139 goto set_new_variable;
1142 ast_str_set(str, 0, "%s", replace->value);
1143 ast_str_append(str, 0, "%s", c);
1144 ast_str_trim_blanks(*str);
1145 ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
1149 /* Ignore > in => */
1155 if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
1160 /* Put and reset comments */
1162 ast_variable_append(*cat, v);
1164 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1165 v->precomments = ALLOC_COMMENT(comment_buffer);
1166 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1167 v->sameline = ALLOC_COMMENT(lline_buffer);
1168 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1169 CB_RESET(comment_buffer, lline_buffer);
1175 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
1181 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)
1184 #if defined(LOW_MEMORY)
1189 char *new_buf, *comment_p, *process_buf;
1192 int comment = 0, nest[MAX_NESTED_COMMENTS];
1193 struct ast_category *cat = NULL;
1195 struct stat statbuf;
1196 struct cache_file_mtime *cfmtime = NULL;
1197 struct cache_file_include *cfinclude;
1198 struct ast_variable *last_var = 0;
1199 struct ast_category *last_cat = 0;
1200 /*! Growable string buffer */
1201 struct ast_str *comment_buffer = NULL; /*!< this will be a comment collector.*/
1202 struct ast_str *lline_buffer = NULL; /*!< A buffer for stuff behind the ; */
1205 cat = ast_config_get_current_category(cfg);
1207 if (filename[0] == '/') {
1208 ast_copy_string(fn, filename, sizeof(fn));
1210 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
1213 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1214 comment_buffer = ast_str_create(CB_SIZE);
1216 lline_buffer = ast_str_create(CB_SIZE);
1217 if (!lline_buffer) {
1219 ast_free(comment_buffer);
1220 ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1224 #ifdef AST_INCLUDE_GLOB
1228 globbuf.gl_offs = 0; /* initialize it to silence gcc */
1229 glob_ret = glob(fn, MY_GLOB_FLAGS, NULL, &globbuf);
1230 if (glob_ret == GLOB_NOSPACE)
1231 ast_log(LOG_WARNING,
1232 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
1233 else if (glob_ret == GLOB_ABORTED)
1234 ast_log(LOG_WARNING,
1235 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
1237 /* loop over expanded files */
1239 for (i=0; i<globbuf.gl_pathc; i++) {
1240 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1243 * The following is not a loop, but just a convenient way to define a block
1244 * (using do { } while(0) ), and be able to exit from it with 'continue'
1245 * or 'break' in case of errors. Nice trick.
1248 if (stat(fn, &statbuf))
1251 if (!S_ISREG(statbuf.st_mode)) {
1252 ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1256 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
1257 /* Find our cached entry for this configuration file */
1258 AST_LIST_LOCK(&cfmtime_head);
1259 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
1260 if (!strcmp(cfmtime->filename, fn) && !strcmp(cfmtime->who_asked, who_asked))
1264 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1 + strlen(who_asked) + 1);
1267 AST_LIST_HEAD_INIT(&cfmtime->includes);
1268 strcpy(cfmtime->filename, fn);
1269 cfmtime->who_asked = cfmtime->filename + strlen(fn) + 1;
1270 strcpy(cfmtime->who_asked, who_asked);
1271 /* Note that the file mtime is initialized to 0, i.e. 1970 */
1272 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
1276 if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
1277 /* File is unchanged, what about the (cached) includes (if any)? */
1279 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
1280 /* We must glob here, because if we did not, then adding a file to globbed directory would
1281 * incorrectly cause no reload to be necessary. */
1283 #ifdef AST_INCLUDE_GLOB
1285 glob_t glob_buf = { .gl_offs = 0 };
1286 glob_return = glob(cfinclude->include, MY_GLOB_FLAGS, NULL, &glob_buf);
1287 /* On error, we reparse */
1288 if (glob_return == GLOB_NOSPACE || glob_return == GLOB_ABORTED)
1291 /* loop over expanded files */
1293 for (j = 0; j < glob_buf.gl_pathc; j++) {
1294 ast_copy_string(fn2, glob_buf.gl_pathv[j], sizeof(fn2));
1296 ast_copy_string(fn2, cfinclude->include);
1298 if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "", who_asked) == NULL) {
1299 /* that second-to-last field needs to be looked at in this case... TODO */
1301 /* One change is enough to short-circuit and reload the whole shebang */
1304 #ifdef AST_INCLUDE_GLOB
1311 AST_LIST_UNLOCK(&cfmtime_head);
1312 return CONFIG_STATUS_FILEUNCHANGED;
1315 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1316 AST_LIST_UNLOCK(&cfmtime_head);
1318 /* If cfg is NULL, then we just want an answer */
1323 cfmtime->mtime = statbuf.st_mtime;
1325 ast_verb(2, "Parsing '%s': ", fn);
1327 if (!(f = fopen(fn, "r"))) {
1328 ast_debug(1, "No file to parse: %s\n", fn);
1329 ast_verb(2, "Not found (%s)\n", strerror(errno));
1333 /* If we get to this point, then we're loading regardless */
1334 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
1335 ast_debug(1, "Parsing %s\n", fn);
1336 ast_verb(2, "Found\n");
1339 if (fgets(buf, sizeof(buf), f)) {
1340 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && lline_buffer && ast_str_strlen(lline_buffer)) {
1341 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1342 ast_str_reset(lline_buffer); /* erase the lline buffer */
1351 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"))) {
1352 /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
1353 CB_ADD(&comment_buffer, "\n"); /* add a newline to the comment buffer */
1354 continue; /* go get a new line, then */
1357 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1358 if ((comment_p > new_buf) && (*(comment_p - 1) == '\\')) {
1359 /* Escaped semicolons aren't comments. */
1360 new_buf = comment_p + 1;
1361 } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1362 /* Meta-Comment start detected ";--" */
1363 if (comment < MAX_NESTED_COMMENTS) {
1365 new_buf = comment_p + 3;
1367 nest[comment-1] = lineno;
1369 ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1371 } else if ((comment_p >= new_buf + 2) &&
1372 (*(comment_p - 1) == COMMENT_TAG) &&
1373 (*(comment_p - 2) == COMMENT_TAG)) {
1374 /* Meta-Comment end detected */
1376 new_buf = comment_p + 1;
1378 /* Back to non-comment now */
1380 /* Actually have to move what's left over the top, then continue */
1382 oldptr = process_buf + strlen(process_buf);
1383 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1384 CB_ADD(&comment_buffer, ";");
1385 CB_ADD_LEN(&comment_buffer, oldptr+1, new_buf-oldptr-1);
1388 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1391 process_buf = new_buf;
1395 /* If ; is found, and we are not nested in a comment,
1396 we immediately stop all comment processing */
1397 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1398 CB_ADD(&lline_buffer, comment_p);
1401 new_buf = comment_p;
1403 new_buf = comment_p + 1;
1406 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf ) {
1407 CB_ADD(&comment_buffer, buf); /* the whole line is a comment, store it */
1411 char *buffer = ast_strip(process_buf);
1412 if (!ast_strlen_zero(buffer)) {
1413 if (process_text_line(cfg, &cat, buffer, lineno, fn, flags, comment_buffer, lline_buffer, suggested_include_file, &last_cat, &last_var, who_asked)) {
1414 cfg = CONFIG_STATUS_FILEINVALID;
1421 /* end of file-- anything in a comment buffer? */
1423 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1424 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1425 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1426 ast_str_reset(lline_buffer); /* erase the lline buffer */
1428 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
1430 } else if (last_var) {
1431 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1432 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1433 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1434 ast_str_reset(lline_buffer); /* erase the lline buffer */
1436 last_var->trailing = ALLOC_COMMENT(comment_buffer);
1439 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1440 ast_debug(1, "Nothing to attach comments to, discarded: %s\n", ast_str_buffer(comment_buffer));
1443 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1444 CB_RESET(comment_buffer, lline_buffer);
1449 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1451 #ifdef AST_INCLUDE_GLOB
1452 if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
1461 if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1463 ast_free(comment_buffer);
1465 ast_free(lline_buffer);
1466 comment_buffer = NULL;
1467 lline_buffer = NULL;
1477 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1478 which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1479 recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1480 be shocked and mystified as to why things are not showing up in the files!
1482 Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1483 and line number are stored for each include, plus the name of the file included, so that these statements may be
1484 included in the output files on a file_save operation.
1486 The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1487 are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1488 the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1489 and a header gets added.
1491 vars and category heads are output in the order they are stored in the config file. So, if the software
1492 shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1493 file/lineno data probably won't get changed.
1497 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1503 ast_copy_string(date, ctime(&t), sizeof(date));
1505 fprintf(f1, ";!\n");
1506 fprintf(f1, ";! Automatically generated configuration file\n");
1507 if (strcmp(configfile, fn))
1508 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1510 fprintf(f1, ";! Filename: %s\n", configfile);
1511 fprintf(f1, ";! Generator: %s\n", generator);
1512 fprintf(f1, ";! Creation Date: %s", date);
1513 fprintf(f1, ";!\n");
1516 static void inclfile_destroy(void *obj)
1518 const struct inclfile *o = obj;
1525 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset, struct inclfile **fi)
1527 struct inclfile lookup;
1529 if (!file || file[0] == 0) {
1530 if (configfile[0] == '/')
1531 ast_copy_string(fn, configfile, fn_size);
1533 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1534 } else if (file[0] == '/')
1535 ast_copy_string(fn, file, fn_size);
1537 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1539 *fi = ao2_find(fileset, &lookup, OBJ_POINTER);
1541 /* set up a file scratch pad */
1542 struct inclfile *fx = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
1543 fx->fname = ast_strdup(fn);
1546 ao2_link(fileset, fx);
1550 static int count_linefeeds(char *str)
1562 static int count_linefeeds_in_comments(struct ast_comment *x)
1567 count += count_linefeeds(x->cmt);
1573 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
1575 int precomment_lines = count_linefeeds_in_comments(precomments);
1578 /* I don't have to worry about those ;! comments, they are
1579 stored in the precomments, but not printed back out.
1580 I did have to make sure that comments following
1581 the ;! header comments were not also deleted in the process */
1582 if (lineno - precomment_lines - fi->lineno < 0) { /* insertions can mess up the line numbering and produce negative numbers that mess things up */
1584 } else if (lineno == 0) {
1585 /* Line replacements also mess things up */
1587 } else if (lineno - precomment_lines - fi->lineno < 5) {
1588 /* Only insert less than 5 blank lines; if anything more occurs,
1589 * it's probably due to context deletion. */
1590 for (i = fi->lineno; i < lineno - precomment_lines; i++) {
1594 /* Deletion occurred - insert a single blank line, for separation of
1599 fi->lineno = lineno + 1; /* Advance the file lineno */
1602 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1604 return ast_config_text_file_save(configfile, cfg, generator);
1607 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1611 struct ast_variable *var;
1612 struct ast_category *cat;
1613 struct ast_comment *cmt;
1614 struct ast_config_include *incl;
1616 struct ao2_container *fileset = ao2_container_alloc(180000, hash_string, hashtab_compare_strings);
1617 struct inclfile *fi = 0;
1619 /* reset all the output flags, in case this isn't our first time saving this data */
1621 for (incl=cfg->includes; incl; incl = incl->next)
1624 /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1625 are all truncated to zero bytes and have that nice header*/
1627 for (incl=cfg->includes; incl; incl = incl->next)
1629 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*/
1632 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 */
1635 gen_header(f1, configfile, fn, generator);
1636 fclose(f1); /* this should zero out the file */
1638 ast_debug(1, "Unable to open for writing: %s\n", fn);
1639 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1641 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1646 set_fn(fn, sizeof(fn), 0, configfile, fileset, &fi); /* just set fn to absolute ver of configfile */
1648 if ((f = fopen(fn, "w+"))) {
1650 if ((f = fopen(fn, "w"))) {
1652 ast_verb(2, "Saving '%s': ", fn);
1653 gen_header(f, configfile, fn, generator);
1656 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1658 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1659 /* since each var, cat, and associated comments can come from any file, we have to be
1660 mobile, and open each file, print, and close it on an entry-by-entry basis */
1663 set_fn(fn, sizeof(fn), cat->file, configfile, fileset, &fi);
1667 ast_debug(1, "Unable to open for writing: %s\n", fn);
1668 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1669 ao2_ref(fileset, -1);
1673 /* dump any includes that happen before this category header */
1674 for (incl=cfg->includes; incl; incl = incl->next) {
1675 if (strcmp(incl->include_location_file, cat->file) == 0){
1676 if (cat->lineno > incl->include_location_lineno && !incl->output) {
1678 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1680 fprintf(f,"#include \"%s\"\n", incl->included_file);
1686 insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
1687 /* Dump section with any appropriate comment */
1688 for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1689 char *cmtp = cmt->cmt;
1690 while (*cmtp == ';' && *(cmtp+1) == '!') {
1691 char *cmtp2 = strchr(cmtp+1, '\n');
1697 fprintf(f,"%s", cmtp);
1699 fprintf(f, "[%s]", cat->name);
1700 if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
1705 if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
1708 if (!AST_LIST_EMPTY(&cat->template_instances)) {
1709 struct ast_category_template_instance *x;
1710 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1711 fprintf(f,"%s",x->name);
1712 if (x != AST_LIST_LAST(&cat->template_instances))
1718 for(cmt = cat->sameline; cmt; cmt=cmt->next)
1720 fprintf(f,"%s", cmt->cmt);
1724 for (cmt = cat->trailing; cmt; cmt=cmt->next) {
1725 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1726 fprintf(f,"%s", cmt->cmt);
1729 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1734 struct ast_category_template_instance *x;
1736 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1737 struct ast_variable *v;
1738 for (v = x->inst->root; v; v = v->next) {
1739 if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
1751 set_fn(fn, sizeof(fn), var->file, configfile, fileset, &fi);
1755 ast_debug(1, "Unable to open for writing: %s\n", fn);
1756 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1757 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1759 ao2_ref(fileset, -1);
1763 /* dump any includes that happen before this category header */
1764 for (incl=cfg->includes; incl; incl = incl->next) {
1765 if (strcmp(incl->include_location_file, var->file) == 0){
1766 if (var->lineno > incl->include_location_lineno && !incl->output) {
1768 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1770 fprintf(f,"#include \"%s\"\n", incl->included_file);
1776 insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
1777 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1778 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1779 fprintf(f,"%s", cmt->cmt);
1782 fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1784 fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1785 for (cmt = var->trailing; cmt; cmt=cmt->next) {
1786 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1787 fprintf(f,"%s", cmt->cmt);
1789 if (var->blanklines) {
1790 blanklines = var->blanklines;
1791 while (blanklines--)
1796 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1804 ast_verb(2, "Saved\n");
1806 ast_debug(1, "Unable to open for writing: %s\n", fn);
1807 ast_verb(2, "Unable to write (%s)", strerror(errno));
1808 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1809 ao2_ref(fileset, -1);
1813 /* Now, for files with trailing #include/#exec statements,
1814 we have to make sure every entry is output */
1816 for (incl=cfg->includes; incl; incl = incl->next) {
1817 if (!incl->output) {
1818 /* open the respective file */
1819 set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset, &fi);
1823 ast_debug(1, "Unable to open for writing: %s\n", fn);
1824 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1825 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1827 ao2_ref(fileset, -1);
1831 /* output the respective include */
1833 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1835 fprintf(f,"#include \"%s\"\n", incl->included_file);
1838 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1842 ao2_ref(fileset, -1); /* this should destroy the hash container */
1847 static void clear_config_maps(void)
1849 struct ast_config_map *map;
1851 ast_mutex_lock(&config_lock);
1853 while (config_maps) {
1855 config_maps = config_maps->next;
1859 ast_mutex_unlock(&config_lock);
1862 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
1864 struct ast_config_map *map;
1867 length = sizeof(*map);
1868 length += strlen(name) + 1;
1869 length += strlen(driver) + 1;
1870 length += strlen(database) + 1;
1872 length += strlen(table) + 1;
1874 if (!(map = ast_calloc(1, length)))
1877 map->name = map->stuff;
1878 strcpy(map->name, name);
1879 map->driver = map->name + strlen(map->name) + 1;
1880 strcpy(map->driver, driver);
1881 map->database = map->driver + strlen(map->driver) + 1;
1882 strcpy(map->database, database);
1884 map->table = map->database + strlen(map->database) + 1;
1885 strcpy(map->table, table);
1887 map->priority = priority;
1888 map->next = config_maps;
1890 ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1896 int read_config_maps(void)
1898 struct ast_config *config, *configtmp;
1899 struct ast_variable *v;
1900 char *driver, *table, *database, *textpri, *stringp, *tmp;
1901 struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
1904 clear_config_maps();
1906 configtmp = ast_config_new();
1907 configtmp->max_include_level = 1;
1908 config = ast_config_internal_load(extconfig_conf, configtmp, flags, "", "extconfig");
1909 if (config == CONFIG_STATUS_FILEINVALID) {
1911 } else if (!config) {
1912 ast_config_destroy(configtmp);
1916 for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1918 ast_copy_string(buf, v->value, sizeof(buf));
1920 driver = strsep(&stringp, ",");
1922 if ((tmp = strchr(stringp, '\"')))
1925 /* check if the database text starts with a double quote */
1926 if (*stringp == '"') {
1928 database = strsep(&stringp, "\"");
1929 strsep(&stringp, ",");
1931 /* apparently this text has no quotes */
1932 database = strsep(&stringp, ",");
1935 table = strsep(&stringp, ",");
1936 textpri = strsep(&stringp, ",");
1937 if (!textpri || !(pri = atoi(textpri))) {
1941 if (!strcmp(v->name, extconfig_conf)) {
1942 ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1946 if (!strcmp(v->name, "asterisk.conf")) {
1947 ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1951 if (!strcmp(v->name, "logger.conf")) {
1952 ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1956 if (!driver || !database)
1958 if (!strcasecmp(v->name, "sipfriends")) {
1959 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");
1960 append_mapping("sipusers", driver, database, table ? table : "sipfriends", pri);
1961 append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri);
1962 } else if (!strcasecmp(v->name, "iaxfriends")) {
1963 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");
1964 append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri);
1965 append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri);
1967 append_mapping(v->name, driver, database, table, pri);
1970 ast_config_destroy(config);
1974 int ast_config_engine_register(struct ast_config_engine *new)
1976 struct ast_config_engine *ptr;
1978 ast_mutex_lock(&config_lock);
1980 if (!config_engine_list) {
1981 config_engine_list = new;
1983 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1987 ast_mutex_unlock(&config_lock);
1988 ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1993 int ast_config_engine_deregister(struct ast_config_engine *del)
1995 struct ast_config_engine *ptr, *last=NULL;
1997 ast_mutex_lock(&config_lock);
1999 for (ptr = config_engine_list; ptr; ptr=ptr->next) {
2002 last->next = ptr->next;
2004 config_engine_list = ptr->next;
2010 ast_mutex_unlock(&config_lock);
2015 /*! \brief Find realtime engine for realtime family */
2016 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz)
2018 struct ast_config_engine *eng, *ret = NULL;
2019 struct ast_config_map *map;
2021 ast_mutex_lock(&config_lock);
2023 for (map = config_maps; map; map = map->next) {
2024 if (!strcasecmp(family, map->name) && (priority == map->priority)) {
2026 ast_copy_string(database, map->database, dbsiz);
2028 ast_copy_string(table, map->table ? map->table : family, tabsiz);
2033 /* Check if the required driver (engine) exist */
2035 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
2036 if (!strcasecmp(eng->name, map->driver))
2041 ast_mutex_unlock(&config_lock);
2043 /* if we found a mapping, but the engine is not available, then issue a warning */
2045 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
2050 static struct ast_config_engine text_file_engine = {
2052 .load_func = config_text_file_load,
2055 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)
2059 struct ast_config_engine *loader = &text_file_engine;
2060 struct ast_config *result;
2062 /* The config file itself bumps include_level by 1 */
2063 if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
2064 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
2068 cfg->include_level++;
2070 if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
2071 struct ast_config_engine *eng;
2073 eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
2076 if (eng && eng->load_func) {
2079 eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
2080 if (eng && eng->load_func)
2085 result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
2087 if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
2088 result->include_level--;
2089 else if (result != CONFIG_STATUS_FILEINVALID)
2090 cfg->include_level--;
2095 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
2097 struct ast_config *cfg;
2098 struct ast_config *result;
2100 cfg = ast_config_new();
2104 result = ast_config_internal_load(filename, cfg, flags, "", who_asked);
2105 if (!result || result == CONFIG_STATUS_FILEUNCHANGED || result == CONFIG_STATUS_FILEINVALID)
2106 ast_config_destroy(cfg);
2111 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
2113 struct ast_config_engine *eng;
2116 struct ast_variable *res=NULL;
2119 for (i = 1; ; i++) {
2120 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2121 if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) {
2132 struct ast_variable *ast_load_realtime_all(const char *family, ...)
2134 struct ast_variable *res;
2137 va_start(ap, family);
2138 res = ast_load_realtime_helper(family, ap);
2144 struct ast_variable *ast_load_realtime(const char *family, ...)
2146 struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
2149 va_start(ap, family);
2150 res = ast_load_realtime_helper(family, ap);
2153 /* Eliminate blank entries */
2154 for (cur = res; cur; cur = cur->next) {
2160 if (ast_strlen_zero(cur->value)) {
2162 prev->next = cur->next;
2166 } else if (cur->value[0] == ' ' && cur->value[1] == '\0') {
2167 char *vptr = (char *) cur->value;
2177 /*! \brief Check if realtime engine is configured for family */
2178 int ast_check_realtime(const char *family)
2180 struct ast_config_engine *eng;
2181 if (!ast_realtime_enabled()) {
2182 return 0; /* There are no engines at all so fail early */
2185 eng = find_engine(family, 1, NULL, 0, NULL, 0);
2191 /*! \brief Check if there's any realtime engines loaded */
2192 int ast_realtime_enabled()
2194 return config_maps ? 1 : 0;
2197 int ast_realtime_require_field(const char *family, ...)
2199 struct ast_config_engine *eng;
2205 va_start(ap, family);
2206 for (i = 1; ; i++) {
2207 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2208 /* If the require succeeds, it returns 0. */
2209 if (eng->require_func && !(res = eng->require_func(db, table, ap))) {
2221 int ast_unload_realtime(const char *family)
2223 struct ast_config_engine *eng;
2228 for (i = 1; ; i++) {
2229 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2230 if (eng->unload_func) {
2231 /* Do this for ALL engines */
2232 res = eng->unload_func(db, table);
2241 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
2243 struct ast_config_engine *eng;
2246 struct ast_config *res = NULL;
2250 va_start(ap, family);
2251 for (i = 1; ; i++) {
2252 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2253 if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) {
2265 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2267 struct ast_config_engine *eng;
2273 va_start(ap, lookup);
2274 for (i = 1; ; i++) {
2275 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2276 /* If the update succeeds, it returns 0. */
2277 if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) {
2289 int ast_update2_realtime(const char *family, ...)
2291 struct ast_config_engine *eng;
2297 va_start(ap, family);
2298 for (i = 1; ; i++) {
2299 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2300 if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) {
2312 int ast_store_realtime(const char *family, ...)
2314 struct ast_config_engine *eng;
2320 va_start(ap, family);
2321 for (i = 1; ; i++) {
2322 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2323 /* If the store succeeds, it returns 0. */
2324 if (eng->store_func && !(res = eng->store_func(db, table, ap))) {
2336 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2338 struct ast_config_engine *eng;
2344 va_start(ap, lookup);
2345 for (i = 1; ; i++) {
2346 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2347 if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) {
2359 char *ast_realtime_decode_chunk(char *chunk)
2362 for (; *chunk; chunk++) {
2363 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
2364 sscanf(chunk + 1, "%02hhX", chunk);
2365 memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
2371 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
2373 if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
2374 ast_str_set(dest, maxlen, "%s", chunk);
2376 ast_str_reset(*dest);
2377 for (; *chunk; chunk++) {
2378 if (strchr(";^", *chunk)) {
2379 ast_str_append(dest, maxlen, "^%02hhX", *chunk);
2381 ast_str_append(dest, maxlen, "%c", *chunk);
2385 return ast_str_buffer(*dest);
2388 /*! \brief Helper function to parse arguments
2389 * See documentation in config.h
2391 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
2392 void *p_result, ...)
2397 va_start(ap, p_result);
2398 switch (flags & PARSE_TYPE) {
2401 int32_t *result = p_result;
2402 int32_t x, def = result ? *result : 0,
2403 high = (int32_t)0x7fffffff,
2404 low = (int32_t)0x80000000;
2405 /* optional argument: first default value, then range */
2406 if (flags & PARSE_DEFAULT)
2407 def = va_arg(ap, int32_t);
2408 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2409 /* range requested, update bounds */
2410 low = va_arg(ap, int32_t);
2411 high = va_arg(ap, int32_t);
2413 x = strtol(arg, NULL, 0);
2414 error = (x < low) || (x > high);
2415 if (flags & PARSE_OUT_RANGE)
2418 *result = error ? def : x;
2420 "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
2422 result ? *result : x, error);
2428 uint32_t *result = p_result;
2429 uint32_t x, def = result ? *result : 0,
2430 low = 0, high = (uint32_t)~0;
2431 /* optional argument: first default value, then range */
2432 if (flags & PARSE_DEFAULT)
2433 def = va_arg(ap, uint32_t);
2434 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2435 /* range requested, update bounds */
2436 low = va_arg(ap, uint32_t);
2437 high = va_arg(ap, uint32_t);
2439 x = strtoul(arg, NULL, 0);
2440 error = (x < low) || (x > high);
2441 if (flags & PARSE_OUT_RANGE)
2444 *result = error ? def : x;
2446 "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
2448 result ? *result : x, error);
2454 double *result = p_result;
2455 double x, def = result ? *result : 0,
2456 low = -HUGE_VAL, high = HUGE_VAL;
2458 /* optional argument: first default value, then range */
2459 if (flags & PARSE_DEFAULT)
2460 def = va_arg(ap, double);
2461 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2462 /* range requested, update bounds */
2463 low = va_arg(ap, double);
2464 high = va_arg(ap, double);
2466 x = strtod(arg, NULL);
2467 error = (x < low) || (x > high);
2468 if (flags & PARSE_OUT_RANGE)
2471 *result = error ? def : x;
2473 "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
2475 result ? *result : x, error);
2480 struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
2482 if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
2486 ast_debug(3, "extract addr from %s gives %s(%d)\n",
2487 arg, ast_sockaddr_stringify(addr), error);
2491 case PARSE_INADDR: /* TODO Remove this (use PARSE_ADDR instead). */
2494 struct sockaddr_in _sa_buf; /* buffer for the result */
2495 struct sockaddr_in *sa = p_result ?
2496 (struct sockaddr_in *)p_result : &_sa_buf;
2497 /* default is either the supplied value or the result itself */
2498 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
2499 va_arg(ap, struct sockaddr_in *) : sa;
2501 struct ast_hostent ahp;
2503 memset(&_sa_buf, '\0', sizeof(_sa_buf)); /* clear buffer */
2504 /* duplicate the string to strip away the :port */
2505 port = ast_strdupa(arg);
2506 buf = strsep(&port, ":");
2507 sa->sin_family = AF_INET; /* assign family */
2509 * honor the ports flag setting, assign default value
2510 * in case of errors or field unset.
2512 flags &= PARSE_PORT_MASK; /* the only flags left to process */
2514 if (flags == PARSE_PORT_FORBID) {
2515 error = 1; /* port was forbidden */
2516 sa->sin_port = def->sin_port;
2517 } else if (flags == PARSE_PORT_IGNORE)
2518 sa->sin_port = def->sin_port;
2519 else /* accept or require */
2520 sa->sin_port = htons(strtol(port, NULL, 0));
2522 sa->sin_port = def->sin_port;
2523 if (flags == PARSE_PORT_REQUIRE)
2526 /* Now deal with host part, even if we have errors before. */
2527 hp = ast_gethostbyname(buf, &ahp);
2528 if (hp) /* resolved successfully */
2529 memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
2532 sa->sin_addr = def->sin_addr;
2535 "extract inaddr from [%s] gives [%s:%d](%d)\n",
2536 arg, ast_inet_ntoa(sa->sin_addr),
2537 ntohs(sa->sin_port), error);
2545 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2547 struct ast_config_engine *eng;
2548 struct ast_config_map *map;
2552 e->command = "core show config mappings";
2554 "Usage: core show config mappings\n"
2555 " Shows the filenames to config engines.\n";
2561 ast_mutex_lock(&config_lock);
2563 if (!config_engine_list) {
2564 ast_cli(a->fd, "No config mappings found.\n");
2566 for (eng = config_engine_list; eng; eng = eng->next) {
2567 ast_cli(a->fd, "Config Engine: %s\n", eng->name);
2568 for (map = config_maps; map; map = map->next) {
2569 if (!strcasecmp(map->driver, eng->name)) {
2570 ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
2571 map->table ? map->table : map->name);
2577 ast_mutex_unlock(&config_lock);
2582 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2584 struct cache_file_mtime *cfmtime;
2585 char *prev = "", *completion_value = NULL;
2586 int wordlen, which = 0;
2590 e->command = "config reload";
2592 "Usage: config reload <filename.conf>\n"
2593 " Reloads all modules that reference <filename.conf>\n";
2600 wordlen = strlen(a->word);
2602 AST_LIST_LOCK(&cfmtime_head);
2603 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2604 /* Skip duplicates - this only works because the list is sorted by filename */
2605 if (strcmp(cfmtime->filename, prev) == 0) {
2609 /* Core configs cannot be reloaded */
2610 if (ast_strlen_zero(cfmtime->who_asked)) {
2614 if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
2615 completion_value = ast_strdup(cfmtime->filename);
2619 /* Otherwise save that we've seen this filename */
2620 prev = cfmtime->filename;
2622 AST_LIST_UNLOCK(&cfmtime_head);
2624 return completion_value;
2628 return CLI_SHOWUSAGE;
2631 AST_LIST_LOCK(&cfmtime_head);
2632 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2633 if (!strcmp(cfmtime->filename, a->argv[2])) {
2634 char *buf = alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1);
2635 sprintf(buf, "module reload %s", cfmtime->who_asked);
2636 ast_cli_command(a->fd, buf);
2639 AST_LIST_UNLOCK(&cfmtime_head);
2644 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2646 struct cache_file_mtime *cfmtime;
2650 e->command = "config list";
2652 "Usage: config list\n"
2653 " Show all modules that have loaded a configuration file\n";
2659 AST_LIST_LOCK(&cfmtime_head);
2660 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2661 ast_cli(a->fd, "%-20.20s %-50s\n", S_OR(cfmtime->who_asked, "core"), cfmtime->filename);
2663 AST_LIST_UNLOCK(&cfmtime_head);
2668 static struct ast_cli_entry cli_config[] = {
2669 AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
2670 AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"),
2671 AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
2674 int register_config_cli()
2676 ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));