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;
625 /* First time browse. */
627 } else if (config->last_browse && (config->last_browse->name == prev)) {
628 /* Simple last browse found. */
629 cat = config->last_browse->next;
632 * Config changed since last browse.
634 * First try cheap last browse search. (Rebrowsing a different
635 * previous category?)
637 for (cat = config->root; cat; cat = cat->next) {
638 if (cat->name == prev) {
646 * Have to do it the hard way. (Last category was deleted and
649 for (cat = config->root; cat; cat = cat->next) {
650 if (!strcasecmp(cat->name, prev)) {
660 cat = next_available_category(cat);
662 config->last_browse = cat;
663 return (cat) ? cat->name : NULL;
666 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
668 struct ast_variable *v;
677 void ast_category_rename(struct ast_category *cat, const char *name)
679 ast_copy_string(cat->name, name, sizeof(cat->name));
682 static void inherit_category(struct ast_category *new, const struct ast_category *base)
684 struct ast_variable *var;
685 struct ast_category_template_instance *x = ast_calloc(1,sizeof(struct ast_category_template_instance));
687 strcpy(x->name, base->name);
689 AST_LIST_INSERT_TAIL(&new->template_instances, x, next);
690 for (var = base->root; var; var = var->next)
691 ast_variable_append(new, variable_clone(var));
694 struct ast_config *ast_config_new(void)
696 struct ast_config *config;
698 if ((config = ast_calloc(1, sizeof(*config))))
699 config->max_include_level = MAX_INCLUDE_LEVEL;
703 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
705 struct ast_variable *cur, *prev=NULL, *curn;
711 if (!ast_strlen_zero(line)) {
712 /* Requesting to delete by item number. */
713 if (sscanf(line, "%30d", &req_item) != 1
715 /* Invalid item number to delete. */
721 cur = category->root;
724 /* Delete by item number or by variable name with optional value. */
725 if ((0 <= req_item && num_item == req_item)
726 || (req_item < 0 && !strcasecmp(cur->name, variable)
727 && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
729 prev->next = cur->next;
730 if (cur == category->last)
731 category->last = prev;
733 category->root = cur->next;
734 if (cur == category->last)
735 category->last = NULL;
737 ast_variable_destroy(cur);
748 int ast_variable_update(struct ast_category *category, const char *variable,
749 const char *value, const char *match, unsigned int object)
751 struct ast_variable *cur, *prev=NULL, *newer=NULL;
753 for (cur = category->root; cur; prev = cur, cur = cur->next) {
754 if (strcasecmp(cur->name, variable) ||
755 (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
758 if (!(newer = ast_variable_new(variable, value, cur->file)))
761 newer->next = cur->next;
762 newer->object = cur->object || object;
764 /* Preserve everything */
765 newer->lineno = cur->lineno;
766 newer->blanklines = cur->blanklines;
767 newer->precomments = cur->precomments; cur->precomments = NULL;
768 newer->sameline = cur->sameline; cur->sameline = NULL;
769 newer->trailing = cur->trailing; cur->trailing = NULL;
774 category->root = newer;
775 if (category->last == cur)
776 category->last = newer;
779 ast_variables_destroy(cur);
784 /* Could not find variable to update */
788 int ast_category_delete(struct ast_config *cfg, const char *category)
790 struct ast_category *prev=NULL, *cat;
794 if (cat->name == category) {
796 prev->next = cat->next;
797 if (cat == cfg->last)
800 cfg->root = cat->next;
801 if (cat == cfg->last)
804 ast_category_destroy(cat);
814 if (!strcasecmp(cat->name, category)) {
816 prev->next = cat->next;
817 if (cat == cfg->last)
820 cfg->root = cat->next;
821 if (cat == cfg->last)
824 ast_category_destroy(cat);
833 int ast_category_empty(struct ast_config *cfg, const char *category)
835 struct ast_category *cat;
837 for (cat = cfg->root; cat; cat = cat->next) {
838 if (!strcasecmp(cat->name, category))
840 ast_variables_destroy(cat->root);
849 void ast_config_destroy(struct ast_config *cfg)
851 struct ast_category *cat, *catn;
856 ast_includes_destroy(cfg->includes);
862 ast_category_destroy(catn);
867 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
872 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
874 /* cast below is just to silence compiler warning about dropping "const" */
875 cfg->current = (struct ast_category *) cat;
878 enum config_cache_attribute_enum {
879 ATTRIBUTE_INCLUDE = 0,
883 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
885 struct cache_file_mtime *cfmtime;
886 struct cache_file_include *cfinclude;
887 struct stat statbuf = { 0, };
889 /* Find our cached entry for this configuration file */
890 AST_LIST_LOCK(&cfmtime_head);
891 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
892 if (!strcmp(cfmtime->filename, configfile) && !strcmp(cfmtime->who_asked, who_asked))
896 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1 + strlen(who_asked) + 1);
898 AST_LIST_UNLOCK(&cfmtime_head);
901 AST_LIST_HEAD_INIT(&cfmtime->includes);
902 strcpy(cfmtime->filename, configfile);
903 cfmtime->who_asked = cfmtime->filename + strlen(configfile) + 1;
904 strcpy(cfmtime->who_asked, who_asked);
905 /* Note that the file mtime is initialized to 0, i.e. 1970 */
906 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
909 if (!stat(configfile, &statbuf))
912 cfmtime->mtime = statbuf.st_mtime;
915 case ATTRIBUTE_INCLUDE:
916 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
917 if (!strcmp(cfinclude->include, filename)) {
918 AST_LIST_UNLOCK(&cfmtime_head);
922 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
924 AST_LIST_UNLOCK(&cfmtime_head);
927 strcpy(cfinclude->include, filename);
928 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
931 cfmtime->has_exec = 1;
934 AST_LIST_UNLOCK(&cfmtime_head);
937 /*! \brief parse one line in the configuration.
939 * We can have a category header [foo](...)
940 * a directive #include / #exec
941 * or a regular line name = value
944 static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
945 char *buf, int lineno, const char *configfile, struct ast_flags flags,
946 struct ast_str *comment_buffer,
947 struct ast_str *lline_buffer,
948 const char *suggested_include_file,
949 struct ast_category **last_cat, struct ast_variable **last_var, const char *who_asked)
953 struct ast_variable *v;
954 char cmd[512], exec_file[512];
956 /* Actually parse the entry */
957 if (cur[0] == '[') { /* A category header */
958 /* format is one of the following:
959 * [foo] define a new category named 'foo'
960 * [foo](!) define a new template category named 'foo'
961 * [foo](+) append to category 'foo', error if foo does not exist.
962 * [foo](a) define a new category and inherit from category or template a.
963 * You can put a comma-separated list of categories and templates
964 * and '!' and '+' between parentheses, with obvious meaning.
966 struct ast_category *newcat = NULL;
969 c = strchr(cur, ']');
971 ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
979 if (!(*cat = newcat = ast_category_new(catname,
980 S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
984 (*cat)->lineno = lineno;
989 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
990 newcat->precomments = ALLOC_COMMENT(comment_buffer);
991 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
992 newcat->sameline = ALLOC_COMMENT(lline_buffer);
993 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
994 CB_RESET(comment_buffer, lline_buffer);
996 /* If there are options or categories to inherit from, process them now */
998 if (!(cur = strchr(c, ')'))) {
999 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
1003 while ((cur = strsep(&c, ","))) {
1004 if (!strcasecmp(cur, "!")) {
1005 (*cat)->ignored = 1;
1006 } else if (!strcasecmp(cur, "+")) {
1007 *cat = category_get(cfg, catname, 1);
1010 ast_category_destroy(newcat);
1011 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
1015 move_variables(newcat, *cat);
1016 ast_category_destroy(newcat);
1020 struct ast_category *base;
1022 base = category_get(cfg, cur, 1);
1024 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
1027 inherit_category(*cat, base);
1032 ast_category_append(cfg, *cat);
1033 } else if (cur[0] == '#') { /* A directive - #include or #exec */
1035 char real_inclusion_name[256];
1036 int do_include = 0; /* otherwise, it is exec */
1040 while (*c && (*c > 32)) {
1046 /* Find real argument */
1047 c = ast_strip(c + 1);
1054 if (!strcasecmp(cur, "include")) {
1056 } else if (!strcasecmp(cur, "exec")) {
1057 if (!ast_opt_exec_includes) {
1058 ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
1059 return 0; /* XXX is this correct ? or we should return -1 ? */
1062 ast_log(LOG_WARNING, "Unknown directive '#%s' at line %d of %s\n", cur, lineno, configfile);
1063 return 0; /* XXX is this correct ? or we should return -1 ? */
1067 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
1068 do_include ? "include" : "exec",
1069 do_include ? "filename" : "/path/to/executable",
1072 return 0; /* XXX is this correct ? or we should return -1 ? */
1076 /* Strip off leading and trailing "'s and <>'s */
1078 if ((*c == '"') || (*c == '<')) {
1079 char quote_char = *c;
1080 if (quote_char == '<') {
1084 if (*(c + strlen(c) - 1) == quote_char) {
1086 *(c + strlen(c) - 1) = '\0';
1091 /* #exec </path/to/executable>
1092 We create a tmp file, then we #include it, then we delete it. */
1094 struct timeval now = ast_tvnow();
1095 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1096 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL, who_asked);
1097 snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d%d.%ld", (int)now.tv_sec, (int)now.tv_usec, (long)pthread_self());
1098 snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
1099 ast_safe_system(cmd);
1102 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1103 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
1104 exec_file[0] = '\0';
1107 /* record this inclusion */
1108 ast_include_new(cfg, cfg->include_level == 1 ? "" : configfile, cur, !do_include, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
1110 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
1111 if (!ast_strlen_zero(exec_file))
1114 ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
1117 /* XXX otherwise what ? the default return is 0 anyways */
1120 /* Just a line (variable = value) */
1123 ast_log(LOG_WARNING,
1124 "parse error: No category context for line %d of %s\n", lineno, configfile);
1127 c = strchr(cur, '=');
1129 if (c && c > cur && (*(c - 1) == '+')) {
1130 struct ast_variable *var, *replace = NULL;
1131 struct ast_str **str = ast_threadstorage_get(&appendbuf, sizeof(*str));
1133 if (!str || !*str) {
1139 cur = ast_strip(cur);
1141 /* Must iterate through category until we find last variable of same name (since there could be multiple) */
1142 for (var = ast_category_first(*cat); var; var = var->next) {
1143 if (!strcmp(var->name, cur)) {
1149 /* Nothing to replace; just set a variable normally. */
1150 goto set_new_variable;
1153 ast_str_set(str, 0, "%s", replace->value);
1154 ast_str_append(str, 0, "%s", c);
1155 ast_str_trim_blanks(*str);
1156 ast_variable_update(*cat, replace->name, ast_skip_blanks(ast_str_buffer(*str)), replace->value, object);
1160 /* Ignore > in => */
1166 if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
1171 /* Put and reset comments */
1173 ast_variable_append(*cat, v);
1175 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1176 v->precomments = ALLOC_COMMENT(comment_buffer);
1177 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1178 v->sameline = ALLOC_COMMENT(lline_buffer);
1179 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1180 CB_RESET(comment_buffer, lline_buffer);
1186 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
1192 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)
1195 #if defined(LOW_MEMORY)
1200 char *new_buf, *comment_p, *process_buf;
1203 int comment = 0, nest[MAX_NESTED_COMMENTS];
1204 struct ast_category *cat = NULL;
1206 struct stat statbuf;
1207 struct cache_file_mtime *cfmtime = NULL;
1208 struct cache_file_include *cfinclude;
1209 struct ast_variable *last_var = 0;
1210 struct ast_category *last_cat = 0;
1211 /*! Growable string buffer */
1212 struct ast_str *comment_buffer = NULL; /*!< this will be a comment collector.*/
1213 struct ast_str *lline_buffer = NULL; /*!< A buffer for stuff behind the ; */
1216 cat = ast_config_get_current_category(cfg);
1218 if (filename[0] == '/') {
1219 ast_copy_string(fn, filename, sizeof(fn));
1221 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
1224 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1225 comment_buffer = ast_str_create(CB_SIZE);
1227 lline_buffer = ast_str_create(CB_SIZE);
1228 if (!lline_buffer) {
1230 ast_free(comment_buffer);
1231 ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1235 #ifdef AST_INCLUDE_GLOB
1239 globbuf.gl_offs = 0; /* initialize it to silence gcc */
1240 glob_ret = glob(fn, MY_GLOB_FLAGS, NULL, &globbuf);
1241 if (glob_ret == GLOB_NOSPACE)
1242 ast_log(LOG_WARNING,
1243 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
1244 else if (glob_ret == GLOB_ABORTED)
1245 ast_log(LOG_WARNING,
1246 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
1248 /* loop over expanded files */
1250 for (i=0; i<globbuf.gl_pathc; i++) {
1251 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1254 * The following is not a loop, but just a convenient way to define a block
1255 * (using do { } while(0) ), and be able to exit from it with 'continue'
1256 * or 'break' in case of errors. Nice trick.
1259 if (stat(fn, &statbuf))
1262 if (!S_ISREG(statbuf.st_mode)) {
1263 ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1267 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
1268 /* Find our cached entry for this configuration file */
1269 AST_LIST_LOCK(&cfmtime_head);
1270 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
1271 if (!strcmp(cfmtime->filename, fn) && !strcmp(cfmtime->who_asked, who_asked))
1275 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1 + strlen(who_asked) + 1);
1278 AST_LIST_HEAD_INIT(&cfmtime->includes);
1279 strcpy(cfmtime->filename, fn);
1280 cfmtime->who_asked = cfmtime->filename + strlen(fn) + 1;
1281 strcpy(cfmtime->who_asked, who_asked);
1282 /* Note that the file mtime is initialized to 0, i.e. 1970 */
1283 AST_LIST_INSERT_SORTALPHA(&cfmtime_head, cfmtime, list, filename);
1287 if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
1288 /* File is unchanged, what about the (cached) includes (if any)? */
1290 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
1291 /* We must glob here, because if we did not, then adding a file to globbed directory would
1292 * incorrectly cause no reload to be necessary. */
1294 #ifdef AST_INCLUDE_GLOB
1296 glob_t glob_buf = { .gl_offs = 0 };
1297 glob_return = glob(cfinclude->include, MY_GLOB_FLAGS, NULL, &glob_buf);
1298 /* On error, we reparse */
1299 if (glob_return == GLOB_NOSPACE || glob_return == GLOB_ABORTED)
1302 /* loop over expanded files */
1304 for (j = 0; j < glob_buf.gl_pathc; j++) {
1305 ast_copy_string(fn2, glob_buf.gl_pathv[j], sizeof(fn2));
1307 ast_copy_string(fn2, cfinclude->include);
1309 if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "", who_asked) == NULL) {
1310 /* that second-to-last field needs to be looked at in this case... TODO */
1312 /* One change is enough to short-circuit and reload the whole shebang */
1315 #ifdef AST_INCLUDE_GLOB
1322 AST_LIST_UNLOCK(&cfmtime_head);
1323 return CONFIG_STATUS_FILEUNCHANGED;
1326 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1327 AST_LIST_UNLOCK(&cfmtime_head);
1329 /* If cfg is NULL, then we just want an answer */
1334 cfmtime->mtime = statbuf.st_mtime;
1336 ast_verb(2, "Parsing '%s': ", fn);
1338 if (!(f = fopen(fn, "r"))) {
1339 ast_debug(1, "No file to parse: %s\n", fn);
1340 ast_verb(2, "Not found (%s)\n", strerror(errno));
1344 /* If we get to this point, then we're loading regardless */
1345 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
1346 ast_debug(1, "Parsing %s\n", fn);
1347 ast_verb(2, "Found\n");
1350 if (fgets(buf, sizeof(buf), f)) {
1351 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && lline_buffer && ast_str_strlen(lline_buffer)) {
1352 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1353 ast_str_reset(lline_buffer); /* erase the lline buffer */
1362 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"))) {
1363 /* blank line? really? Can we add it to an existing comment and maybe preserve inter- and post- comment spacing? */
1364 CB_ADD(&comment_buffer, "\n"); /* add a newline to the comment buffer */
1365 continue; /* go get a new line, then */
1368 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1369 if ((comment_p > new_buf) && (*(comment_p - 1) == '\\')) {
1370 /* Escaped semicolons aren't comments. */
1371 new_buf = comment_p + 1;
1372 } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1373 /* Meta-Comment start detected ";--" */
1374 if (comment < MAX_NESTED_COMMENTS) {
1376 new_buf = comment_p + 3;
1378 nest[comment-1] = lineno;
1380 ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1382 } else if ((comment_p >= new_buf + 2) &&
1383 (*(comment_p - 1) == COMMENT_TAG) &&
1384 (*(comment_p - 2) == COMMENT_TAG)) {
1385 /* Meta-Comment end detected */
1387 new_buf = comment_p + 1;
1389 /* Back to non-comment now */
1391 /* Actually have to move what's left over the top, then continue */
1393 oldptr = process_buf + strlen(process_buf);
1394 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1395 CB_ADD(&comment_buffer, ";");
1396 CB_ADD_LEN(&comment_buffer, oldptr+1, new_buf-oldptr-1);
1399 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1402 process_buf = new_buf;
1406 /* If ; is found, and we are not nested in a comment,
1407 we immediately stop all comment processing */
1408 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1409 CB_ADD(&lline_buffer, comment_p);
1412 new_buf = comment_p;
1414 new_buf = comment_p + 1;
1417 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf ) {
1418 CB_ADD(&comment_buffer, buf); /* the whole line is a comment, store it */
1422 char *buffer = ast_strip(process_buf);
1423 if (!ast_strlen_zero(buffer)) {
1424 if (process_text_line(cfg, &cat, buffer, lineno, fn, flags, comment_buffer, lline_buffer, suggested_include_file, &last_cat, &last_var, who_asked)) {
1425 cfg = CONFIG_STATUS_FILEINVALID;
1432 /* end of file-- anything in a comment buffer? */
1434 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1435 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1436 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1437 ast_str_reset(lline_buffer); /* erase the lline buffer */
1439 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
1441 } else if (last_var) {
1442 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1443 if (lline_buffer && ast_str_strlen(lline_buffer)) {
1444 CB_ADD(&comment_buffer, ast_str_buffer(lline_buffer)); /* add the current lline buffer to the comment buffer */
1445 ast_str_reset(lline_buffer); /* erase the lline buffer */
1447 last_var->trailing = ALLOC_COMMENT(comment_buffer);
1450 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer && ast_str_strlen(comment_buffer)) {
1451 ast_debug(1, "Nothing to attach comments to, discarded: %s\n", ast_str_buffer(comment_buffer));
1454 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1455 CB_RESET(comment_buffer, lline_buffer);
1460 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1462 #ifdef AST_INCLUDE_GLOB
1463 if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
1472 if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1474 ast_free(comment_buffer);
1476 ast_free(lline_buffer);
1477 comment_buffer = NULL;
1478 lline_buffer = NULL;
1488 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1489 which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1490 recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1491 be shocked and mystified as to why things are not showing up in the files!
1493 Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1494 and line number are stored for each include, plus the name of the file included, so that these statements may be
1495 included in the output files on a file_save operation.
1497 The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1498 are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1499 the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1500 and a header gets added.
1502 vars and category heads are output in the order they are stored in the config file. So, if the software
1503 shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1504 file/lineno data probably won't get changed.
1508 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1514 ast_copy_string(date, ctime(&t), sizeof(date));
1516 fprintf(f1, ";!\n");
1517 fprintf(f1, ";! Automatically generated configuration file\n");
1518 if (strcmp(configfile, fn))
1519 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1521 fprintf(f1, ";! Filename: %s\n", configfile);
1522 fprintf(f1, ";! Generator: %s\n", generator);
1523 fprintf(f1, ";! Creation Date: %s", date);
1524 fprintf(f1, ";!\n");
1527 static void inclfile_destroy(void *obj)
1529 const struct inclfile *o = obj;
1536 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset, struct inclfile **fi)
1538 struct inclfile lookup;
1540 if (!file || file[0] == 0) {
1541 if (configfile[0] == '/')
1542 ast_copy_string(fn, configfile, fn_size);
1544 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1545 } else if (file[0] == '/')
1546 ast_copy_string(fn, file, fn_size);
1548 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1550 *fi = ao2_find(fileset, &lookup, OBJ_POINTER);
1552 /* set up a file scratch pad */
1553 struct inclfile *fx = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
1554 fx->fname = ast_strdup(fn);
1557 ao2_link(fileset, fx);
1561 static int count_linefeeds(char *str)
1573 static int count_linefeeds_in_comments(struct ast_comment *x)
1578 count += count_linefeeds(x->cmt);
1584 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
1586 int precomment_lines = count_linefeeds_in_comments(precomments);
1589 /* I don't have to worry about those ;! comments, they are
1590 stored in the precomments, but not printed back out.
1591 I did have to make sure that comments following
1592 the ;! header comments were not also deleted in the process */
1593 if (lineno - precomment_lines - fi->lineno < 0) { /* insertions can mess up the line numbering and produce negative numbers that mess things up */
1595 } else if (lineno == 0) {
1596 /* Line replacements also mess things up */
1598 } else if (lineno - precomment_lines - fi->lineno < 5) {
1599 /* Only insert less than 5 blank lines; if anything more occurs,
1600 * it's probably due to context deletion. */
1601 for (i = fi->lineno; i < lineno - precomment_lines; i++) {
1605 /* Deletion occurred - insert a single blank line, for separation of
1610 fi->lineno = lineno + 1; /* Advance the file lineno */
1613 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1615 return ast_config_text_file_save(configfile, cfg, generator);
1618 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1622 struct ast_variable *var;
1623 struct ast_category *cat;
1624 struct ast_comment *cmt;
1625 struct ast_config_include *incl;
1627 struct ao2_container *fileset = ao2_container_alloc(180000, hash_string, hashtab_compare_strings);
1628 struct inclfile *fi = 0;
1630 /* reset all the output flags, in case this isn't our first time saving this data */
1632 for (incl=cfg->includes; incl; incl = incl->next)
1635 /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1636 are all truncated to zero bytes and have that nice header*/
1638 for (incl=cfg->includes; incl; incl = incl->next)
1640 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*/
1643 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 */
1646 gen_header(f1, configfile, fn, generator);
1647 fclose(f1); /* this should zero out the file */
1649 ast_debug(1, "Unable to open for writing: %s\n", fn);
1650 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1652 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1657 set_fn(fn, sizeof(fn), 0, configfile, fileset, &fi); /* just set fn to absolute ver of configfile */
1659 if ((f = fopen(fn, "w+"))) {
1661 if ((f = fopen(fn, "w"))) {
1663 ast_verb(2, "Saving '%s': ", fn);
1664 gen_header(f, configfile, fn, generator);
1667 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1669 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1670 /* since each var, cat, and associated comments can come from any file, we have to be
1671 mobile, and open each file, print, and close it on an entry-by-entry basis */
1674 set_fn(fn, sizeof(fn), cat->file, configfile, fileset, &fi);
1678 ast_debug(1, "Unable to open for writing: %s\n", fn);
1679 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1680 ao2_ref(fileset, -1);
1684 /* dump any includes that happen before this category header */
1685 for (incl=cfg->includes; incl; incl = incl->next) {
1686 if (strcmp(incl->include_location_file, cat->file) == 0){
1687 if (cat->lineno > incl->include_location_lineno && !incl->output) {
1689 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1691 fprintf(f,"#include \"%s\"\n", incl->included_file);
1697 insert_leading_blank_lines(f, fi, cat->precomments, cat->lineno);
1698 /* Dump section with any appropriate comment */
1699 for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1700 char *cmtp = cmt->cmt;
1701 while (*cmtp == ';' && *(cmtp+1) == '!') {
1702 char *cmtp2 = strchr(cmtp+1, '\n');
1708 fprintf(f,"%s", cmtp);
1710 fprintf(f, "[%s]", cat->name);
1711 if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
1716 if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
1719 if (!AST_LIST_EMPTY(&cat->template_instances)) {
1720 struct ast_category_template_instance *x;
1721 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1722 fprintf(f,"%s",x->name);
1723 if (x != AST_LIST_LAST(&cat->template_instances))
1729 for(cmt = cat->sameline; cmt; cmt=cmt->next)
1731 fprintf(f,"%s", cmt->cmt);
1735 for (cmt = cat->trailing; cmt; cmt=cmt->next) {
1736 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1737 fprintf(f,"%s", cmt->cmt);
1740 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1745 struct ast_category_template_instance *x;
1747 AST_LIST_TRAVERSE(&cat->template_instances, x, next) {
1748 struct ast_variable *v;
1749 for (v = x->inst->root; v; v = v->next) {
1750 if (!strcasecmp(var->name, v->name) && !strcmp(var->value, v->value)) {
1762 set_fn(fn, sizeof(fn), var->file, configfile, fileset, &fi);
1766 ast_debug(1, "Unable to open for writing: %s\n", fn);
1767 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1768 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1770 ao2_ref(fileset, -1);
1774 /* dump any includes that happen before this category header */
1775 for (incl=cfg->includes; incl; incl = incl->next) {
1776 if (strcmp(incl->include_location_file, var->file) == 0){
1777 if (var->lineno > incl->include_location_lineno && !incl->output) {
1779 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1781 fprintf(f,"#include \"%s\"\n", incl->included_file);
1787 insert_leading_blank_lines(f, fi, var->precomments, var->lineno);
1788 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1789 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1790 fprintf(f,"%s", cmt->cmt);
1793 fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1795 fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1796 for (cmt = var->trailing; cmt; cmt=cmt->next) {
1797 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1798 fprintf(f,"%s", cmt->cmt);
1800 if (var->blanklines) {
1801 blanklines = var->blanklines;
1802 while (blanklines--)
1807 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1815 ast_verb(2, "Saved\n");
1817 ast_debug(1, "Unable to open for writing: %s\n", fn);
1818 ast_verb(2, "Unable to write (%s)", strerror(errno));
1819 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1820 ao2_ref(fileset, -1);
1824 /* Now, for files with trailing #include/#exec statements,
1825 we have to make sure every entry is output */
1827 for (incl=cfg->includes; incl; incl = incl->next) {
1828 if (!incl->output) {
1829 /* open the respective file */
1830 set_fn(fn, sizeof(fn), incl->include_location_file, configfile, fileset, &fi);
1834 ast_debug(1, "Unable to open for writing: %s\n", fn);
1835 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1836 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1838 ao2_ref(fileset, -1);
1842 /* output the respective include */
1844 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1846 fprintf(f,"#include \"%s\"\n", incl->included_file);
1849 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1853 ao2_ref(fileset, -1); /* this should destroy the hash container */
1858 static void clear_config_maps(void)
1860 struct ast_config_map *map;
1862 ast_mutex_lock(&config_lock);
1864 while (config_maps) {
1866 config_maps = config_maps->next;
1870 ast_mutex_unlock(&config_lock);
1873 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
1875 struct ast_config_map *map;
1878 length = sizeof(*map);
1879 length += strlen(name) + 1;
1880 length += strlen(driver) + 1;
1881 length += strlen(database) + 1;
1883 length += strlen(table) + 1;
1885 if (!(map = ast_calloc(1, length)))
1888 map->name = map->stuff;
1889 strcpy(map->name, name);
1890 map->driver = map->name + strlen(map->name) + 1;
1891 strcpy(map->driver, driver);
1892 map->database = map->driver + strlen(map->driver) + 1;
1893 strcpy(map->database, database);
1895 map->table = map->database + strlen(map->database) + 1;
1896 strcpy(map->table, table);
1898 map->priority = priority;
1899 map->next = config_maps;
1901 ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1907 int read_config_maps(void)
1909 struct ast_config *config, *configtmp;
1910 struct ast_variable *v;
1911 char *driver, *table, *database, *textpri, *stringp, *tmp;
1912 struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
1915 clear_config_maps();
1917 configtmp = ast_config_new();
1918 configtmp->max_include_level = 1;
1919 config = ast_config_internal_load(extconfig_conf, configtmp, flags, "", "extconfig");
1920 if (config == CONFIG_STATUS_FILEINVALID) {
1922 } else if (!config) {
1923 ast_config_destroy(configtmp);
1927 for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1929 ast_copy_string(buf, v->value, sizeof(buf));
1931 driver = strsep(&stringp, ",");
1933 if ((tmp = strchr(stringp, '\"')))
1936 /* check if the database text starts with a double quote */
1937 if (*stringp == '"') {
1939 database = strsep(&stringp, "\"");
1940 strsep(&stringp, ",");
1942 /* apparently this text has no quotes */
1943 database = strsep(&stringp, ",");
1946 table = strsep(&stringp, ",");
1947 textpri = strsep(&stringp, ",");
1948 if (!textpri || !(pri = atoi(textpri))) {
1952 if (!strcmp(v->name, extconfig_conf)) {
1953 ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1957 if (!strcmp(v->name, "asterisk.conf")) {
1958 ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1962 if (!strcmp(v->name, "logger.conf")) {
1963 ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1967 if (!driver || !database)
1969 if (!strcasecmp(v->name, "sipfriends")) {
1970 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");
1971 append_mapping("sipusers", driver, database, table ? table : "sipfriends", pri);
1972 append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri);
1973 } else if (!strcasecmp(v->name, "iaxfriends")) {
1974 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");
1975 append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri);
1976 append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri);
1978 append_mapping(v->name, driver, database, table, pri);
1981 ast_config_destroy(config);
1985 int ast_config_engine_register(struct ast_config_engine *new)
1987 struct ast_config_engine *ptr;
1989 ast_mutex_lock(&config_lock);
1991 if (!config_engine_list) {
1992 config_engine_list = new;
1994 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1998 ast_mutex_unlock(&config_lock);
1999 ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
2004 int ast_config_engine_deregister(struct ast_config_engine *del)
2006 struct ast_config_engine *ptr, *last=NULL;
2008 ast_mutex_lock(&config_lock);
2010 for (ptr = config_engine_list; ptr; ptr=ptr->next) {
2013 last->next = ptr->next;
2015 config_engine_list = ptr->next;
2021 ast_mutex_unlock(&config_lock);
2026 /*! \brief Find realtime engine for realtime family */
2027 static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz)
2029 struct ast_config_engine *eng, *ret = NULL;
2030 struct ast_config_map *map;
2032 ast_mutex_lock(&config_lock);
2034 for (map = config_maps; map; map = map->next) {
2035 if (!strcasecmp(family, map->name) && (priority == map->priority)) {
2037 ast_copy_string(database, map->database, dbsiz);
2039 ast_copy_string(table, map->table ? map->table : family, tabsiz);
2044 /* Check if the required driver (engine) exist */
2046 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
2047 if (!strcasecmp(eng->name, map->driver))
2052 ast_mutex_unlock(&config_lock);
2054 /* if we found a mapping, but the engine is not available, then issue a warning */
2056 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
2061 static struct ast_config_engine text_file_engine = {
2063 .load_func = config_text_file_load,
2066 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)
2070 struct ast_config_engine *loader = &text_file_engine;
2071 struct ast_config *result;
2073 /* The config file itself bumps include_level by 1 */
2074 if (cfg->max_include_level > 0 && cfg->include_level == cfg->max_include_level + 1) {
2075 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
2079 cfg->include_level++;
2081 if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
2082 struct ast_config_engine *eng;
2084 eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
2087 if (eng && eng->load_func) {
2090 eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
2091 if (eng && eng->load_func)
2096 result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
2098 if (result && result != CONFIG_STATUS_FILEINVALID && result != CONFIG_STATUS_FILEUNCHANGED)
2099 result->include_level--;
2100 else if (result != CONFIG_STATUS_FILEINVALID)
2101 cfg->include_level--;
2106 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
2108 struct ast_config *cfg;
2109 struct ast_config *result;
2111 cfg = ast_config_new();
2115 result = ast_config_internal_load(filename, cfg, flags, "", who_asked);
2116 if (!result || result == CONFIG_STATUS_FILEUNCHANGED || result == CONFIG_STATUS_FILEINVALID)
2117 ast_config_destroy(cfg);
2122 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
2124 struct ast_config_engine *eng;
2127 struct ast_variable *res=NULL;
2130 for (i = 1; ; i++) {
2131 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2132 if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) {
2143 struct ast_variable *ast_load_realtime_all(const char *family, ...)
2145 struct ast_variable *res;
2148 va_start(ap, family);
2149 res = ast_load_realtime_helper(family, ap);
2155 struct ast_variable *ast_load_realtime(const char *family, ...)
2157 struct ast_variable *res;
2158 struct ast_variable *cur;
2159 struct ast_variable **prev;
2162 va_start(ap, family);
2163 res = ast_load_realtime_helper(family, ap);
2166 /* Filter the list. */
2170 if (ast_strlen_zero(cur->value)) {
2171 /* Eliminate empty entries */
2172 struct ast_variable *next;
2176 ast_variable_destroy(cur);
2179 /* Make blank entries empty and keep them. */
2180 if (cur->value[0] == ' ' && cur->value[1] == '\0') {
2181 char *vptr = (char *) cur->value;
2193 /*! \brief Check if realtime engine is configured for family */
2194 int ast_check_realtime(const char *family)
2196 struct ast_config_engine *eng;
2197 if (!ast_realtime_enabled()) {
2198 return 0; /* There are no engines at all so fail early */
2201 eng = find_engine(family, 1, NULL, 0, NULL, 0);
2207 /*! \brief Check if there's any realtime engines loaded */
2208 int ast_realtime_enabled(void)
2210 return config_maps ? 1 : 0;
2213 int ast_realtime_require_field(const char *family, ...)
2215 struct ast_config_engine *eng;
2221 va_start(ap, family);
2222 for (i = 1; ; i++) {
2223 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2224 /* If the require succeeds, it returns 0. */
2225 if (eng->require_func && !(res = eng->require_func(db, table, ap))) {
2237 int ast_unload_realtime(const char *family)
2239 struct ast_config_engine *eng;
2244 for (i = 1; ; i++) {
2245 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2246 if (eng->unload_func) {
2247 /* Do this for ALL engines */
2248 res = eng->unload_func(db, table);
2257 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
2259 struct ast_config_engine *eng;
2262 struct ast_config *res = NULL;
2266 va_start(ap, family);
2267 for (i = 1; ; i++) {
2268 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2269 if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) {
2281 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2283 struct ast_config_engine *eng;
2289 va_start(ap, lookup);
2290 for (i = 1; ; i++) {
2291 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2292 /* If the update succeeds, it returns 0. */
2293 if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) {
2305 int ast_update2_realtime(const char *family, ...)
2307 struct ast_config_engine *eng;
2313 va_start(ap, family);
2314 for (i = 1; ; i++) {
2315 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2316 if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) {
2328 int ast_store_realtime(const char *family, ...)
2330 struct ast_config_engine *eng;
2336 va_start(ap, family);
2337 for (i = 1; ; i++) {
2338 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2339 /* If the store succeeds, it returns 0. */
2340 if (eng->store_func && !(res = eng->store_func(db, table, ap))) {
2352 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2354 struct ast_config_engine *eng;
2360 va_start(ap, lookup);
2361 for (i = 1; ; i++) {
2362 if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) {
2363 if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) {
2375 char *ast_realtime_decode_chunk(char *chunk)
2378 for (; *chunk; chunk++) {
2379 if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
2380 sscanf(chunk + 1, "%02hhX", chunk);
2381 memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
2387 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
2389 if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
2390 ast_str_set(dest, maxlen, "%s", chunk);
2392 ast_str_reset(*dest);
2393 for (; *chunk; chunk++) {
2394 if (strchr(";^", *chunk)) {
2395 ast_str_append(dest, maxlen, "^%02hhX", *chunk);
2397 ast_str_append(dest, maxlen, "%c", *chunk);
2401 return ast_str_buffer(*dest);
2404 /*! \brief Helper function to parse arguments
2405 * See documentation in config.h
2407 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
2408 void *p_result, ...)
2413 va_start(ap, p_result);
2414 switch (flags & PARSE_TYPE) {
2417 int32_t *result = p_result;
2418 int32_t x, def = result ? *result : 0,
2419 high = (int32_t)0x7fffffff,
2420 low = (int32_t)0x80000000;
2421 /* optional argument: first default value, then range */
2422 if (flags & PARSE_DEFAULT)
2423 def = va_arg(ap, int32_t);
2424 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2425 /* range requested, update bounds */
2426 low = va_arg(ap, int32_t);
2427 high = va_arg(ap, int32_t);
2429 x = strtol(arg, NULL, 0);
2430 error = (x < low) || (x > high);
2431 if (flags & PARSE_OUT_RANGE)
2434 *result = error ? def : x;
2436 "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
2438 result ? *result : x, error);
2444 uint32_t *result = p_result;
2445 uint32_t x, def = result ? *result : 0,
2446 low = 0, high = (uint32_t)~0;
2447 /* optional argument: first default value, then range */
2448 if (flags & PARSE_DEFAULT)
2449 def = va_arg(ap, uint32_t);
2450 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2451 /* range requested, update bounds */
2452 low = va_arg(ap, uint32_t);
2453 high = va_arg(ap, uint32_t);
2455 x = strtoul(arg, NULL, 0);
2456 error = (x < low) || (x > high);
2457 if (flags & PARSE_OUT_RANGE)
2460 *result = error ? def : x;
2462 "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
2464 result ? *result : x, error);
2470 double *result = p_result;
2471 double x, def = result ? *result : 0,
2472 low = -HUGE_VAL, high = HUGE_VAL;
2474 /* optional argument: first default value, then range */
2475 if (flags & PARSE_DEFAULT)
2476 def = va_arg(ap, double);
2477 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
2478 /* range requested, update bounds */
2479 low = va_arg(ap, double);
2480 high = va_arg(ap, double);
2482 x = strtod(arg, NULL);
2483 error = (x < low) || (x > high);
2484 if (flags & PARSE_OUT_RANGE)
2487 *result = error ? def : x;
2489 "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
2491 result ? *result : x, error);
2496 struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
2498 if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
2502 ast_debug(3, "extract addr from %s gives %s(%d)\n",
2503 arg, ast_sockaddr_stringify(addr), error);
2507 case PARSE_INADDR: /* TODO Remove this (use PARSE_ADDR instead). */
2510 struct sockaddr_in _sa_buf; /* buffer for the result */
2511 struct sockaddr_in *sa = p_result ?
2512 (struct sockaddr_in *)p_result : &_sa_buf;
2513 /* default is either the supplied value or the result itself */
2514 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
2515 va_arg(ap, struct sockaddr_in *) : sa;
2517 struct ast_hostent ahp;
2519 memset(&_sa_buf, '\0', sizeof(_sa_buf)); /* clear buffer */
2520 /* duplicate the string to strip away the :port */
2521 port = ast_strdupa(arg);
2522 buf = strsep(&port, ":");
2523 sa->sin_family = AF_INET; /* assign family */
2525 * honor the ports flag setting, assign default value
2526 * in case of errors or field unset.
2528 flags &= PARSE_PORT_MASK; /* the only flags left to process */
2530 if (flags == PARSE_PORT_FORBID) {
2531 error = 1; /* port was forbidden */
2532 sa->sin_port = def->sin_port;
2533 } else if (flags == PARSE_PORT_IGNORE)
2534 sa->sin_port = def->sin_port;
2535 else /* accept or require */
2536 sa->sin_port = htons(strtol(port, NULL, 0));
2538 sa->sin_port = def->sin_port;
2539 if (flags == PARSE_PORT_REQUIRE)
2542 /* Now deal with host part, even if we have errors before. */
2543 hp = ast_gethostbyname(buf, &ahp);
2544 if (hp) /* resolved successfully */
2545 memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
2548 sa->sin_addr = def->sin_addr;
2551 "extract inaddr from [%s] gives [%s:%d](%d)\n",
2552 arg, ast_inet_ntoa(sa->sin_addr),
2553 ntohs(sa->sin_port), error);
2561 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2563 struct ast_config_engine *eng;
2564 struct ast_config_map *map;
2568 e->command = "core show config mappings";
2570 "Usage: core show config mappings\n"
2571 " Shows the filenames to config engines.\n";
2577 ast_mutex_lock(&config_lock);
2579 if (!config_engine_list) {
2580 ast_cli(a->fd, "No config mappings found.\n");
2582 for (eng = config_engine_list; eng; eng = eng->next) {
2583 ast_cli(a->fd, "Config Engine: %s\n", eng->name);
2584 for (map = config_maps; map; map = map->next) {
2585 if (!strcasecmp(map->driver, eng->name)) {
2586 ast_cli(a->fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
2587 map->table ? map->table : map->name);
2593 ast_mutex_unlock(&config_lock);
2598 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2600 struct cache_file_mtime *cfmtime;
2601 char *prev = "", *completion_value = NULL;
2602 int wordlen, which = 0;
2606 e->command = "config reload";
2608 "Usage: config reload <filename.conf>\n"
2609 " Reloads all modules that reference <filename.conf>\n";
2616 wordlen = strlen(a->word);
2618 AST_LIST_LOCK(&cfmtime_head);
2619 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2620 /* Skip duplicates - this only works because the list is sorted by filename */
2621 if (strcmp(cfmtime->filename, prev) == 0) {
2625 /* Core configs cannot be reloaded */
2626 if (ast_strlen_zero(cfmtime->who_asked)) {
2630 if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
2631 completion_value = ast_strdup(cfmtime->filename);
2635 /* Otherwise save that we've seen this filename */
2636 prev = cfmtime->filename;
2638 AST_LIST_UNLOCK(&cfmtime_head);
2640 return completion_value;
2644 return CLI_SHOWUSAGE;
2647 AST_LIST_LOCK(&cfmtime_head);
2648 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2649 if (!strcmp(cfmtime->filename, a->argv[2])) {
2650 char *buf = alloca(strlen("module reload ") + strlen(cfmtime->who_asked) + 1);
2651 sprintf(buf, "module reload %s", cfmtime->who_asked);
2652 ast_cli_command(a->fd, buf);
2655 AST_LIST_UNLOCK(&cfmtime_head);
2660 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2662 struct cache_file_mtime *cfmtime;
2666 e->command = "config list";
2668 "Usage: config list\n"
2669 " Show all modules that have loaded a configuration file\n";
2675 AST_LIST_LOCK(&cfmtime_head);
2676 AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
2677 ast_cli(a->fd, "%-20.20s %-50s\n", S_OR(cfmtime->who_asked, "core"), cfmtime->filename);
2679 AST_LIST_UNLOCK(&cfmtime_head);
2684 static struct ast_cli_entry cli_config[] = {
2685 AST_CLI_DEFINE(handle_cli_core_show_config_mappings, "Display config mappings (file names to config engines)"),
2686 AST_CLI_DEFINE(handle_cli_config_reload, "Force a reload on modules using a particular configuration file"),
2687 AST_CLI_DEFINE(handle_cli_config_list, "Show all files that have loaded a configuration file"),
2690 int register_config_cli(void)
2692 ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));