Merged revisions 283882 via svnmerge from
[asterisk/asterisk.git] / main / config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2010, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief Configuration File Parser
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * Includes the Asterisk Realtime API - ARA
26  * See doc/realtime.txt and doc/extconfig.txt
27  */
28
29 #include "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include "asterisk/paths.h"     /* use ast_config_AST_CONFIG_DIR */
34 #include "asterisk/network.h"   /* we do some sockaddr manipulation here */
35 #include <time.h>
36 #include <sys/stat.h>
37
38 #include <math.h>       /* HUGE_VAL */
39
40 #define AST_INCLUDE_GLOB 1
41
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"
51
52 #define MAX_NESTED_COMMENTS 128
53 #define COMMENT_START ";--"
54 #define COMMENT_END "--;"
55 #define COMMENT_META ';'
56 #define COMMENT_TAG '-'
57
58 static char *extconfig_conf = "extconfig.conf";
59
60
61 /*! \brief Structure to keep comments for rewriting configuration files */
62 struct ast_comment {
63         struct ast_comment *next;
64         char cmt[0];
65 };
66
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;
70         char include[0];
71 };
72
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;
77         time_t mtime;
78         char *who_asked;
79         char filename[0];
80 };
81
82 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
83
84 static int init_appendbuf(void *data)
85 {
86         struct ast_str **str = data;
87         *str = ast_str_create(16);
88         return *str ? 0 : -1;
89 }
90
91 AST_THREADSTORAGE_CUSTOM(appendbuf, init_appendbuf, ast_free_ptr);
92
93 /* comment buffers are better implemented using the ast_str_*() API */
94 #define CB_SIZE 250     /* initial size of comment buffers */
95
96 static void  CB_ADD(struct ast_str **cb, const char *str)
97 {
98         ast_str_append(cb, 0, "%s", str);
99 }
100
101 static void  CB_ADD_LEN(struct ast_str **cb, const char *str, int len)
102 {
103         char *s = alloca(len + 1);
104         ast_copy_string(s, str, len);
105         ast_str_append(cb, 0, "%s", str);
106 }
107
108 static void CB_RESET(struct ast_str *cb, struct ast_str *llb)  
109
110         if (cb) {
111                 ast_str_reset(cb);
112         }
113         if (llb) {
114                 ast_str_reset(llb);
115         }
116 }
117
118 static struct ast_comment *ALLOC_COMMENT(struct ast_str *buffer)
119
120         struct ast_comment *x = NULL;
121         if (!buffer || !ast_str_strlen(buffer)) {
122                 return NULL;
123         }
124         if ((x = ast_calloc(1, sizeof(*x) + ast_str_strlen(buffer) + 1))) {
125                 strcpy(x->cmt, ast_str_buffer(buffer)); /* SAFE */
126         }
127         return x;
128 }
129
130 /* I need to keep track of each config file, and all its inclusions,
131    so that we can track blank lines in each */
132
133 struct inclfile {
134         char *fname;
135         int lineno;
136 };
137
138 static int hash_string(const void *obj, const int flags)
139 {
140         char *str = ((struct inclfile *) obj)->fname;
141         int total;
142
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 */
149
150                 total += ((unsigned int) (*str));
151         }
152         if (total < 0) {
153                 total = -total;
154         }
155         return total;
156 }
157
158 static int hashtab_compare_strings(void *a, void *b, int flags)
159 {
160         const struct inclfile *ae = a, *be = b;
161         return !strcmp(ae->fname, be->fname) ? CMP_MATCH | CMP_STOP : 0;
162 }
163
164 static struct ast_config_map {
165         struct ast_config_map *next;
166         int priority;
167         char *name;
168         char *driver;
169         char *database;
170         char *table;
171         char stuff[0];
172 } *config_maps = NULL;
173
174 AST_MUTEX_DEFINE_STATIC(config_lock);
175 static struct ast_config_engine *config_engine_list;
176
177 #define MAX_INCLUDE_LEVEL 10
178
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;
183 };
184
185 struct ast_category {
186         char name[80];
187         int ignored;                    /*!< do not let user of the config see this category -- set by (!) after the category decl; a template */
188         int include_level;
189         char *file;                /*!< the file name from whence this declaration was read */
190         int lineno;
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;
198 };
199
200 struct ast_config {
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 */
205         int include_level;
206         int max_include_level;
207         struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
208 };
209
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 */
220 };
221
222 #ifdef MALLOC_DEBUG
223 struct ast_variable *_ast_variable_new(const char *name, const char *value, const char *filename, const char *file, const char *func, int lineno) 
224 #else
225 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename) 
226 #endif
227 {
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;      
232
233 #ifdef MALLOC_DEBUG
234         if ((variable = __ast_calloc(1, name_len + val_len + fn_len + sizeof(*variable), file, lineno, func))) {
235 #else
236         if ((variable = ast_calloc(1, name_len + val_len + fn_len + sizeof(*variable)))) {
237 #endif
238                 char *dst = variable->stuff;    /* writable space starts here */
239                 variable->name = strcpy(dst, name);
240                 dst += name_len;
241                 variable->value = strcpy(dst, value);
242                 dst += val_len;
243                 variable->file = strcpy(dst, filename);
244         }
245         return variable;
246 }
247
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)
249 {
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;
255         struct stat statbuf;
256         
257         inc = ast_include_find(conf, included_file);
258         if (inc) {
259                 do {
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);
264         } else
265                 *real_included_file_name = 0;
266         
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);
272         else
273                 inc->included_file = ast_strdup(included_file);
274         
275         inc->exec = is_exec;
276         if (is_exec)
277                 inc->exec_file = ast_strdup(exec_file);
278         
279         /* attach this new struct to the conf struct */
280         inc->next = conf->includes;
281         conf->includes = inc;
282         
283         return inc;
284 }
285
286 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
287 {
288         struct ast_config_include *incl;
289         struct ast_category *cat;
290         struct ast_variable *v;
291         
292         int from_len = strlen(from_file);
293         int to_len = strlen(to_file);
294         
295         if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
296                 return;
297         
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.
303          */
304         /* file names are on categories, includes (of course), and on variables. So,
305          * traverse all this and swap names */
306
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);
311                         else {
312                                 free(incl->include_location_file);
313                                 incl->include_location_file = strdup(to_file);
314                         }
315                 }
316         }
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);
321                         else {
322                                 free(cat->file);
323                                 cat->file = strdup(to_file);
324                         }
325                 }
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);
330                                 else {
331                                         free(v->file);
332                                         v->file = strdup(to_file);
333                                 }
334                         }
335                 }
336         }
337 }
338
339 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
340 {
341         struct ast_config_include *x;
342         for (x=conf->includes;x;x=x->next) {
343                 if (strcmp(x->included_file,included_file) == 0)
344                         return x;
345         }
346         return 0;
347 }
348
349
350 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
351 {
352         if (!variable)
353                 return;
354         if (category->last)
355                 category->last->next = variable;
356         else
357                 category->root = variable;
358         category->last = variable;
359         while (category->last->next)
360                 category->last = category->last->next;
361 }
362
363 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
364 {
365         struct ast_variable *cur = category->root;
366         int lineno;
367         int insertline;
368
369         if (!variable || sscanf(line, "%30d", &insertline) != 1) {
370                 return;
371         }
372         if (!insertline) {
373                 variable->next = category->root;
374                 category->root = variable;
375         } else {
376                 for (lineno = 1; lineno < insertline; lineno++) {
377                         cur = cur->next;
378                         if (!cur->next) {
379                                 break;
380                         }
381                 }
382                 variable->next = cur->next;
383                 cur->next = variable;
384         }
385 }
386
387 static void ast_comment_destroy(struct ast_comment **comment)
388 {
389         struct ast_comment *n, *p;
390
391         for (p = *comment; p; p = n) {
392                 n = p->next;
393                 ast_free(p);
394         }
395
396         *comment = NULL;
397 }
398
399 void ast_variables_destroy(struct ast_variable *v)
400 {
401         struct ast_variable *vn;
402
403         while (v) {
404                 vn = v;
405                 v = v->next;
406                 ast_comment_destroy(&vn->precomments);
407                 ast_comment_destroy(&vn->sameline);
408                 ast_comment_destroy(&vn->trailing);
409                 ast_free(vn);
410         }
411 }
412
413 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
414 {
415         struct ast_category *cat = NULL;
416
417         if (category && config->last_browse && (config->last_browse->name == category)) {
418                 cat = config->last_browse;
419         } else {
420                 cat = ast_category_get(config, category);
421         }
422
423         return (cat) ? cat->root : NULL;
424 }
425
426 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
427 {
428         const char *tmp;
429         tmp = ast_variable_retrieve(cfg, cat, var);
430         if (!tmp) {
431                 tmp = ast_variable_retrieve(cfg, "general", var);
432         }
433         return tmp;
434 }
435
436
437 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
438 {
439         struct ast_variable *v;
440
441         if (category) {
442                 for (v = ast_variable_browse(config, category); v; v = v->next) {
443                         if (!strcasecmp(variable, v->name)) {
444                                 return v->value;
445                         }
446                 }
447         } else {
448                 struct ast_category *cat;
449
450                 for (cat = config->root; cat; cat = cat->next) {
451                         for (v = cat->root; v; v = v->next) {
452                                 if (!strcasecmp(variable, v->name)) {
453                                         return v->value;
454                                 }
455                         }
456                 }
457         }
458
459         return NULL;
460 }
461
462 static struct ast_variable *variable_clone(const struct ast_variable *old)
463 {
464         struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
465
466         if (new) {
467                 new->lineno = old->lineno;
468                 new->object = old->object;
469                 new->blanklines = old->blanklines;
470                 /* TODO: clone comments? */
471         }
472
473         return new;
474 }
475  
476 static void move_variables(struct ast_category *old, struct ast_category *new)
477 {
478         struct ast_variable *var = old->root;
479
480         old->root = NULL;
481         /* we can just move the entire list in a single op */
482         ast_variable_append(new, var);
483 }
484
485 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) 
486 {
487         struct ast_category *category;
488
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 */
493         return category;
494 }
495
496 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
497 {
498         struct ast_category *cat;
499
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))
503                         return cat;
504         }
505
506         for (cat = config->root; cat; cat = cat->next) {
507                 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
508                         return cat;
509         }
510
511         return NULL;
512 }
513
514 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
515 {
516         return category_get(config, category_name, 0);
517 }
518
519 int ast_category_exist(const struct ast_config *config, const char *category_name)
520 {
521         return !!ast_category_get(config, category_name);
522 }
523
524 void ast_category_append(struct ast_config *config, struct ast_category *category)
525 {
526         if (config->last)
527                 config->last->next = category;
528         else
529                 config->root = category;
530         category->include_level = config->include_level;
531         config->last = category;
532         config->current = category;
533 }
534
535 void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
536 {
537         struct ast_category *cur_category;
538
539         if (!cat || !match)
540                 return;
541         if (!strcasecmp(config->root->name, match)) {
542                 cat->next = config->root;
543                 config->root = cat;
544                 return;
545         } 
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;
550                         break;
551                 }
552         }
553 }
554
555 static void ast_destroy_template_list(struct ast_category *cat)
556 {
557         struct ast_category_template_instance *x;
558
559         while ((x = AST_LIST_REMOVE_HEAD(&cat->template_instances, next)))
560                 free(x);
561 }
562
563 void ast_category_destroy(struct ast_category *cat)
564 {
565         ast_variables_destroy(cat->root);
566         if (cat->file) {
567                 free(cat->file);
568                 cat->file = 0;
569         }
570         ast_comment_destroy(&cat->precomments);
571         ast_comment_destroy(&cat->sameline);
572         ast_comment_destroy(&cat->trailing);
573         ast_destroy_template_list(cat);
574         ast_free(cat);
575 }
576
577 static void ast_includes_destroy(struct ast_config_include *incls)
578 {
579         struct ast_config_include *incl,*inclnext;
580         
581         for (incl=incls; incl; incl = inclnext) {
582                 inclnext = incl->next;
583                 if (incl->include_location_file)
584                         free(incl->include_location_file);
585                 if (incl->exec_file)
586                         free(incl->exec_file);
587                 if (incl->included_file)
588                         free(incl->included_file);
589                 free(incl);
590         }
591 }
592
593 static struct ast_category *next_available_category(struct ast_category *cat)
594 {
595         for (; cat && cat->ignored; cat = cat->next);
596
597         return cat;
598 }
599
600 /*! return the first var of a category */
601 struct ast_variable *ast_category_first(struct ast_category *cat)
602 {
603         return (cat) ? cat->root : NULL;
604 }
605
606 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
607 {
608         struct ast_category *category = ast_category_get(config, cat);
609
610         if (category)
611                 return category->root;
612         return NULL;
613 }
614
615 char *ast_category_browse(struct ast_config *config, const char *prev)
616 {       
617         struct ast_category *cat = NULL;
618
619         if (prev && config->last_browse && (config->last_browse->name == prev))
620                 cat = config->last_browse->next;
621         else if (!prev && config->root)
622                 cat = config->root;
623         else if (prev) {
624                 for (cat = config->root; cat; cat = cat->next) {
625                         if (cat->name == prev) {
626                                 cat = cat->next;
627                                 break;
628                         }
629                 }
630                 if (!cat) {
631                         for (cat = config->root; cat; cat = cat->next) {
632                                 if (!strcasecmp(cat->name, prev)) {
633                                         cat = cat->next;
634                                         break;
635                                 }
636                         }
637                 }
638         }
639         
640         if (cat)
641                 cat = next_available_category(cat);
642
643         config->last_browse = cat;
644         return (cat) ? cat->name : NULL;
645 }
646
647 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
648 {
649         struct ast_variable *v;
650
651         v = cat->root;
652         cat->root = NULL;
653         cat->last = NULL;
654
655         return v;
656 }
657
658 void ast_category_rename(struct ast_category *cat, const char *name)
659 {
660         ast_copy_string(cat->name, name, sizeof(cat->name));
661 }
662
663 static void inherit_category(struct ast_category *new, const struct ast_category *base)
664 {
665         struct ast_variable *var;
666         struct ast_category_template_instance *x = ast_calloc(1,sizeof(struct ast_category_template_instance));
667
668         strcpy(x->name, base->name);
669         x->inst = base;
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));
673 }
674
675 struct ast_config *ast_config_new(void) 
676 {
677         struct ast_config *config;
678
679         if ((config = ast_calloc(1, sizeof(*config))))
680                 config->max_include_level = MAX_INCLUDE_LEVEL;
681         return config;
682 }
683
684 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
685 {
686         struct ast_variable *cur, *prev=NULL, *curn;
687         int res = -1;
688         int lineno = 0;
689
690         cur = category->root;
691         while (cur) {
692                 if (cur->name == variable) {
693                         if (prev) {
694                                 prev->next = cur->next;
695                                 if (cur == category->last)
696                                         category->last = prev;
697                         } else {
698                                 category->root = cur->next;
699                                 if (cur == category->last)
700                                         category->last = NULL;
701                         }
702                         cur->next = NULL;
703                         ast_variables_destroy(cur);
704                         return 0;
705                 }
706                 prev = cur;
707                 cur = cur->next;
708         }
709
710         prev = NULL;
711         cur = category->root;
712         while (cur) {
713                 curn = cur->next;
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)))) {
715                         if (prev) {
716                                 prev->next = cur->next;
717                                 if (cur == category->last)
718                                         category->last = prev;
719                         } else {
720                                 category->root = cur->next;
721                                 if (cur == category->last)
722                                         category->last = NULL;
723                         }
724                         cur->next = NULL;
725                         ast_variables_destroy(cur);
726                         res = 0;
727                 } else
728                         prev = cur;
729
730                 cur = curn;
731                 lineno++;
732         }
733         return res;
734 }
735
736 int ast_variable_update(struct ast_category *category, const char *variable, 
737                                                 const char *value, const char *match, unsigned int object)
738 {
739         struct ast_variable *cur, *prev=NULL, *newer=NULL;
740
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)))
744                         continue;
745
746                 if (!(newer = ast_variable_new(variable, value, cur->file)))
747                         return -1;
748         
749                 newer->next = cur->next;
750                 newer->object = cur->object || object;
751
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;
758
759                 if (prev)
760                         prev->next = newer;
761                 else
762                         category->root = newer;
763                 if (category->last == cur)
764                         category->last = newer;
765
766                 cur->next = NULL;
767                 ast_variables_destroy(cur);
768
769                 return 0;
770         }
771
772         /* Could not find variable to update */
773         return -1;
774 }
775
776 int ast_category_delete(struct ast_config *cfg, const char *category)
777 {
778         struct ast_category *prev=NULL, *cat;
779
780         cat = cfg->root;
781         while (cat) {
782                 if (cat->name == category) {
783                         if (prev) {
784                                 prev->next = cat->next;
785                                 if (cat == cfg->last)
786                                         cfg->last = prev;
787                         } else {
788                                 cfg->root = cat->next;
789                                 if (cat == cfg->last)
790                                         cfg->last = NULL;
791                         }
792                         ast_category_destroy(cat);
793                         return 0;
794                 }
795                 prev = cat;
796                 cat = cat->next;
797         }
798
799         prev = NULL;
800         cat = cfg->root;
801         while (cat) {
802                 if (!strcasecmp(cat->name, category)) {
803                         if (prev) {
804                                 prev->next = cat->next;
805                                 if (cat == cfg->last)
806                                         cfg->last = prev;
807                         } else {
808                                 cfg->root = cat->next;
809                                 if (cat == cfg->last)
810                                         cfg->last = NULL;
811                         }
812                         ast_category_destroy(cat);
813                         return 0;
814                 }
815                 prev = cat;
816                 cat = cat->next;
817         }
818         return -1;
819 }
820
821 int ast_category_empty(struct ast_config *cfg, const char *category)
822 {
823         struct ast_category *cat;
824
825         for (cat = cfg->root; cat; cat = cat->next) {
826                 if (!strcasecmp(cat->name, category))
827                         continue;
828                 ast_variables_destroy(cat->root);
829                 cat->root = NULL;
830                 cat->last = NULL;
831                 return 0;
832         }
833
834         return -1;
835 }
836
837 void ast_config_destroy(struct ast_config *cfg)
838 {
839         struct ast_category *cat, *catn;
840
841         if (!cfg)
842                 return;
843
844         ast_includes_destroy(cfg->includes);
845
846         cat = cfg->root;
847         while (cat) {
848                 catn = cat;
849                 cat = cat->next;
850                 ast_category_destroy(catn);
851         }
852         ast_free(cfg);
853 }
854
855 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
856 {
857         return cfg->current;
858 }
859
860 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
861 {
862         /* cast below is just to silence compiler warning about dropping "const" */
863         cfg->current = (struct ast_category *) cat;
864 }
865
866 enum config_cache_attribute_enum {
867         ATTRIBUTE_INCLUDE = 0,
868         ATTRIBUTE_EXEC = 1,
869 };
870
871 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename, const char *who_asked)
872 {
873         struct cache_file_mtime *cfmtime;
874         struct cache_file_include *cfinclude;
875         struct stat statbuf = { 0, };
876
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))
881                         break;
882         }
883         if (!cfmtime) {
884                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1 + strlen(who_asked) + 1);
885                 if (!cfmtime) {
886                         AST_LIST_UNLOCK(&cfmtime_head);
887                         return;
888                 }
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);
895         }
896
897         if (!stat(configfile, &statbuf))
898                 cfmtime->mtime = 0;
899         else
900                 cfmtime->mtime = statbuf.st_mtime;
901
902         switch (attrtype) {
903         case ATTRIBUTE_INCLUDE:
904                 AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
905                         if (!strcmp(cfinclude->include, filename)) {
906                                 AST_LIST_UNLOCK(&cfmtime_head);
907                                 return;
908                         }
909                 }
910                 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
911                 if (!cfinclude) {
912                         AST_LIST_UNLOCK(&cfmtime_head);
913                         return;
914                 }
915                 strcpy(cfinclude->include, filename);
916                 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
917                 break;
918         case ATTRIBUTE_EXEC:
919                 cfmtime->has_exec = 1;
920                 break;
921         }
922         AST_LIST_UNLOCK(&cfmtime_head);
923 }
924
925 /*! \brief parse one line in the configuration.
926  * \verbatim
927  * We can have a category header        [foo](...)
928  * a directive                          #include / #exec
929  * or a regular line                    name = value
930  * \endverbatim
931  */
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)
938 {
939         char *c;
940         char *cur = buf;
941         struct ast_variable *v;
942         char cmd[512], exec_file[512];
943
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 template a.
951                  *              You can put a comma-separated list of templates and '!' and '+'
952                  *              between parentheses, with obvious meaning.
953                  */
954                 struct ast_category *newcat = NULL;
955                 char *catname;
956
957                 c = strchr(cur, ']');
958                 if (!c) {
959                         ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
960                         return -1;
961                 }
962                 *c++ = '\0';
963                 cur++;
964                 if (*c++ != '(')
965                         c = NULL;
966                 catname = cur;
967                 if (!(*cat = newcat = ast_category_new(catname,
968                                 S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile),
969                                 lineno))) {
970                         return -1;
971                 }
972                 (*cat)->lineno = lineno;
973                 *last_var = 0;
974                 *last_cat = newcat;
975                 
976                 /* add comments */
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);
983                 
984                 /* If there are options or categories to inherit from, process them now */
985                 if (c) {
986                         if (!(cur = strchr(c, ')'))) {
987                                 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
988                                 return -1;
989                         }
990                         *cur = '\0';
991                         while ((cur = strsep(&c, ","))) {
992                                 if (!strcasecmp(cur, "!")) {
993                                         (*cat)->ignored = 1;
994                                 } else if (!strcasecmp(cur, "+")) {
995                                         *cat = category_get(cfg, catname, 1);
996                                         if (!(*cat)) {
997                                                 if (newcat)
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);
1000                                                 return -1;
1001                                         }
1002                                         if (newcat) {
1003                                                 move_variables(newcat, *cat);
1004                                                 ast_category_destroy(newcat);
1005                                                 newcat = NULL;
1006                                         }
1007                                 } else {
1008                                         struct ast_category *base;
1009                                 
1010                                         base = category_get(cfg, cur, 1);
1011                                         if (!base) {
1012                                                 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
1013                                                 return -1;
1014                                         }
1015                                         inherit_category(*cat, base);
1016                                 }
1017                         }
1018                 }
1019                 if (newcat)
1020                         ast_category_append(cfg, *cat);
1021         } else if (cur[0] == '#') { /* A directive - #include or #exec */
1022                 char *cur2;
1023                 char real_inclusion_name[256];
1024                 struct ast_config_include *inclu;
1025                 int do_include = 0;     /* otherwise, it is exec */
1026
1027                 cur++;
1028                 c = cur;
1029                 while (*c && (*c > 32)) {
1030                         c++;
1031                 }
1032
1033                 if (*c) {
1034                         *c = '\0';
1035                         /* Find real argument */
1036                         c = ast_strip(c + 1);
1037                         if (!(*c)) {
1038                                 c = NULL;
1039                         }
1040                 } else {
1041                         c = NULL;
1042                 }
1043                 if (!strcasecmp(cur, "include")) {
1044                         do_include = 1;
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 ? */
1049                         }
1050                 } else {
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 ? */
1053                 }
1054
1055                 if (c == NULL) {
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",
1059                                         lineno,
1060                                         configfile);
1061                         return 0;       /* XXX is this correct ? or we should return -1 ? */
1062                 }
1063
1064                 cur = c;
1065                 /* Strip off leading and trailing "'s and <>'s */
1066                 /* Dequote */
1067                 if ((*c == '"') || (*c == '<')) {
1068                         char quote_char = *c;
1069                         if (quote_char == '<') {
1070                                 quote_char = '>';
1071                         }
1072
1073                         if (*(c + strlen(c) - 1) == quote_char) {
1074                                 cur++;
1075                                 *(c + strlen(c) - 1) = '\0';
1076                         }
1077                 }
1078                 cur2 = cur;
1079
1080                 /* #exec </path/to/executable>
1081                    We create a tmp file, then we #include it, then we delete it. */
1082                 if (!do_include) {
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);
1089                         cur = exec_file;
1090                 } else {
1091                         if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1092                                 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur, who_asked);
1093                         exec_file[0] = '\0';
1094                 }
1095                 /* A #include */
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));
1098
1099                 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name, who_asked) ? 1 : 0;
1100                 if (!ast_strlen_zero(exec_file))
1101                         unlink(exec_file);
1102                 if (!do_include) {
1103                         ast_log(LOG_ERROR, "The file '%s' was listed as a #include but it does not exist.\n", cur);
1104                         return -1;
1105                 }
1106                 /* XXX otherwise what ? the default return is 0 anyways */
1107
1108         } else {
1109                 /* Just a line (variable = value) */
1110                 int object = 0;
1111                 if (!(*cat)) {
1112                         ast_log(LOG_WARNING,
1113                                 "parse error: No category context for line %d of %s\n", lineno, configfile);
1114                         return -1;
1115                 }
1116                 c = strchr(cur, '=');
1117
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));
1121
1122                         if (!str || !*str) {
1123                                 return -1;
1124                         }
1125
1126                         *(c - 1) = '\0';
1127                         c++;
1128                         cur = ast_strip(cur);
1129
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)) {
1133                                         replace = var;
1134                                 }
1135                         }
1136
1137                         if (!replace) {
1138                                 /* Nothing to replace; just set a variable normally. */
1139                                 goto set_new_variable;
1140                         }
1141
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);
1146                 } else if (c) {
1147                         *c = 0;
1148                         c++;
1149                         /* Ignore > in => */
1150                         if (*c== '>') {
1151                                 object = 1;
1152                                 c++;
1153                         }
1154 set_new_variable:
1155                         if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), S_OR(suggested_include_file, cfg->include_level == 1 ? "" : configfile)))) {
1156                                 v->lineno = lineno;
1157                                 v->object = object;
1158                                 *last_cat = 0;
1159                                 *last_var = v;
1160                                 /* Put and reset comments */
1161                                 v->blanklines = 0;
1162                                 ast_variable_append(*cat, v);
1163                                 /* add comments */
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);
1170                                 
1171                         } else {
1172                                 return -1;
1173                         }
1174                 } else {
1175                         ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
1176                 }
1177         }
1178         return 0;
1179 }
1180
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)
1182 {
1183         char fn[256];
1184 #if defined(LOW_MEMORY)
1185         char buf[512];
1186 #else
1187         char buf[8192];
1188 #endif
1189         char *new_buf, *comment_p, *process_buf;
1190         FILE *f;
1191         int lineno=0;
1192         int comment = 0, nest[MAX_NESTED_COMMENTS];
1193         struct ast_category *cat = NULL;
1194         int count = 0;
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 ; */
1203
1204         if (cfg)
1205                 cat = ast_config_get_current_category(cfg);
1206
1207         if (filename[0] == '/') {
1208                 ast_copy_string(fn, filename, sizeof(fn));
1209         } else {
1210                 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
1211         }
1212
1213         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1214                 comment_buffer = ast_str_create(CB_SIZE);
1215                 if (comment_buffer)
1216                         lline_buffer = ast_str_create(CB_SIZE);
1217                 if (!lline_buffer) {
1218                         if (comment_buffer)
1219                                 ast_free(comment_buffer);
1220                         ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1221                         return NULL;
1222                 }
1223         }
1224 #ifdef AST_INCLUDE_GLOB
1225         {
1226                 int glob_ret;
1227                 glob_t globbuf;
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);
1236                 else  {
1237                         /* loop over expanded files */
1238                         int i;
1239                         for (i=0; i<globbuf.gl_pathc; i++) {
1240                                 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1241 #endif
1242         /*
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.
1246          */
1247         do {
1248                 if (stat(fn, &statbuf))
1249                         continue;
1250
1251                 if (!S_ISREG(statbuf.st_mode)) {
1252                         ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1253                         continue;
1254                 }
1255
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))
1261                                         break;
1262                         }
1263                         if (!cfmtime) {
1264                                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1 + strlen(who_asked) + 1);
1265                                 if (!cfmtime)
1266                                         continue;
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);
1273                         }
1274                 }
1275
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)? */
1278                         int unchanged = 1;
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. */
1282                                 char fn2[256];
1283 #ifdef AST_INCLUDE_GLOB
1284                                 int glob_return;
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)
1289                                         unchanged = 0;
1290                                 else  {
1291                                         /* loop over expanded files */
1292                                         int j;
1293                                         for (j = 0; j < glob_buf.gl_pathc; j++) {
1294                                                 ast_copy_string(fn2, glob_buf.gl_pathv[j], sizeof(fn2));
1295 #else
1296                                                 ast_copy_string(fn2, cfinclude->include);
1297 #endif
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 */
1300                                                         unchanged = 0;
1301                                                         /* One change is enough to short-circuit and reload the whole shebang */
1302                                                         break;
1303                                                 }
1304 #ifdef AST_INCLUDE_GLOB
1305                                         }
1306                                 }
1307 #endif
1308                         }
1309
1310                         if (unchanged) {
1311                                 AST_LIST_UNLOCK(&cfmtime_head);
1312                                 return CONFIG_STATUS_FILEUNCHANGED;
1313                         }
1314                 }
1315                 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1316                         AST_LIST_UNLOCK(&cfmtime_head);
1317
1318                 /* If cfg is NULL, then we just want an answer */
1319                 if (cfg == NULL)
1320                         return NULL;
1321
1322                 if (cfmtime)
1323                         cfmtime->mtime = statbuf.st_mtime;
1324
1325                 ast_verb(2, "Parsing '%s': ", fn);
1326                         fflush(stdout);
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));
1330                         continue;
1331                 }
1332                 count++;
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");
1337                 while (!feof(f)) {
1338                         lineno++;
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 */
1343                                 }
1344                                 
1345                                 new_buf = buf;
1346                                 if (comment) 
1347                                         process_buf = NULL;
1348                                 else
1349                                         process_buf = buf;
1350                                 
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 */
1355                                 }
1356                                 
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) {
1364                                                         *comment_p = '\0';
1365                                                         new_buf = comment_p + 3;
1366                                                         comment++;
1367                                                         nest[comment-1] = lineno;
1368                                                 } else {
1369                                                         ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1370                                                 }
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 */
1375                                                 comment--;
1376                                                 new_buf = comment_p + 1;
1377                                                 if (!comment) {
1378                                                         /* Back to non-comment now */
1379                                                         if (process_buf) {
1380                                                                 /* Actually have to move what's left over the top, then continue */
1381                                                                 char *oldptr;
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);
1386                                                                 }
1387                                                                 
1388                                                                 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1389                                                                 new_buf = oldptr;
1390                                                         } else
1391                                                                 process_buf = new_buf;
1392                                                 }
1393                                         } else {
1394                                                 if (!comment) {
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);
1399                                                         }
1400                                                         *comment_p = '\0'; 
1401                                                         new_buf = comment_p;
1402                                                 } else
1403                                                         new_buf = comment_p + 1;
1404                                         }
1405                                 }
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 */
1408                                 }
1409                                 
1410                                 if (process_buf) {
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;
1415                                                         break;
1416                                                 }
1417                                         }
1418                                 }
1419                         }
1420                 }
1421                 /* end of file-- anything in a comment buffer? */
1422                 if (last_cat) {
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 */
1427                                 }
1428                                 last_cat->trailing = ALLOC_COMMENT(comment_buffer);
1429                         }
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 */
1435                                 }
1436                                 last_var->trailing = ALLOC_COMMENT(comment_buffer);
1437                         }
1438                 } else {
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));
1441                         }
1442                 }
1443                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
1444                         CB_RESET(comment_buffer, lline_buffer);
1445
1446                 fclose(f);
1447         } while (0);
1448         if (comment) {
1449                 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1450         }
1451 #ifdef AST_INCLUDE_GLOB
1452                                         if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
1453                                                 break;
1454                                         }
1455                                 }
1456                                 globfree(&globbuf);
1457                         }
1458                 }
1459 #endif
1460
1461         if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg != CONFIG_STATUS_FILEINVALID && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1462                 if (comment_buffer)
1463                         ast_free(comment_buffer);
1464                 if (lline_buffer)
1465                         ast_free(lline_buffer);
1466                 comment_buffer = NULL;
1467                 lline_buffer = NULL;
1468         }
1469         
1470         if (count == 0)
1471                 return NULL;
1472
1473         return cfg;
1474 }
1475
1476
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! 
1481
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. 
1485
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.
1490
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.
1494
1495 */
1496
1497 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1498 {
1499         char date[256]="";
1500         time_t t;
1501
1502         time(&t);
1503         ast_copy_string(date, ctime(&t), sizeof(date));
1504
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);
1509         else
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");
1514 }
1515
1516 static void   inclfile_destroy(void *obj)
1517 {
1518         const struct inclfile *o = obj;
1519
1520         if (o->fname)
1521                 free(o->fname);
1522 }
1523
1524
1525 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile, struct ao2_container *fileset, struct inclfile **fi)
1526 {
1527         struct inclfile lookup;
1528         
1529         if (!file || file[0] == 0) {
1530                 if (configfile[0] == '/')
1531                         ast_copy_string(fn, configfile, fn_size);
1532                 else
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);
1536         else
1537                 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1538         lookup.fname = fn;
1539         *fi = ao2_find(fileset, &lookup, OBJ_POINTER);
1540         if (!(*fi)) {
1541                 /* set up a file scratch pad */
1542                 struct inclfile *fx = ao2_alloc(sizeof(struct inclfile), inclfile_destroy);
1543                 fx->fname = ast_strdup(fn);
1544                 fx->lineno = 1;
1545                 *fi = fx;
1546                 ao2_link(fileset, fx);
1547         }
1548 }
1549
1550 static int count_linefeeds(char *str)
1551 {
1552         int count = 0;
1553
1554         while (*str) {
1555                 if (*str =='\n')
1556                         count++;
1557                 str++;
1558         }
1559         return count;
1560 }
1561
1562 static int count_linefeeds_in_comments(struct ast_comment *x)
1563 {
1564         int count = 0;
1565
1566         while (x) {
1567                 count += count_linefeeds(x->cmt);
1568                 x = x->next;
1569         }
1570         return count;
1571 }
1572
1573 static void insert_leading_blank_lines(FILE *fp, struct inclfile *fi, struct ast_comment *precomments, int lineno)
1574 {
1575         int precomment_lines = count_linefeeds_in_comments(precomments);
1576         int i;
1577
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 */
1583                 return;
1584         } else if (lineno == 0) {
1585                 /* Line replacements also mess things up */
1586                 return;
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++) {
1591                         fprintf(fp, "\n");
1592                 }
1593         } else {
1594                 /* Deletion occurred - insert a single blank line, for separation of
1595                  * contexts. */
1596                 fprintf(fp, "\n");
1597         }
1598  
1599         fi->lineno = lineno + 1; /* Advance the file lineno */
1600 }
1601
1602 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1603 {
1604         return ast_config_text_file_save(configfile, cfg, generator);
1605 }
1606
1607 int ast_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1608 {
1609         FILE *f;
1610         char fn[256];
1611         struct ast_variable *var;
1612         struct ast_category *cat;
1613         struct ast_comment *cmt;
1614         struct ast_config_include *incl;
1615         int blanklines = 0;
1616         struct ao2_container *fileset = ao2_container_alloc(180000, hash_string, hashtab_compare_strings);
1617         struct inclfile *fi = 0;
1618
1619         /* reset all the output flags, in case this isn't our first time saving this data */
1620
1621         for (incl=cfg->includes; incl; incl = incl->next)
1622                 incl->output = 0;
1623
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*/
1626
1627         for (incl=cfg->includes; incl; incl = incl->next)
1628         {
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*/
1630                         FILE *f1;
1631
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 */
1633                         f1 = fopen(fn,"w");
1634                         if (f1) {
1635                                 gen_header(f1, configfile, fn, generator);
1636                                 fclose(f1); /* this should zero out the file */
1637                         } else {
1638                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1639                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1640                         }
1641                         ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1642                         fi = 0;
1643                 }
1644         }
1645
1646         set_fn(fn, sizeof(fn), 0, configfile, fileset, &fi); /* just set fn to absolute ver of configfile */
1647 #ifdef __CYGWIN__       
1648         if ((f = fopen(fn, "w+"))) {
1649 #else
1650         if ((f = fopen(fn, "w"))) {
1651 #endif      
1652                 ast_verb(2, "Saving '%s': ", fn);
1653                 gen_header(f, configfile, fn, generator);
1654                 cat = cfg->root;
1655                 fclose(f);
1656                 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1657                 
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 */
1661
1662                 while (cat) {
1663                         set_fn(fn, sizeof(fn), cat->file, configfile, fileset, &fi);
1664                         f = fopen(fn, "a");
1665                         if (!f)
1666                         {
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);
1670                                 return -1;
1671                         }
1672
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) {
1677                                                 if (incl->exec)
1678                                                         fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1679                                                 else
1680                                                         fprintf(f,"#include \"%s\"\n", incl->included_file);
1681                                                 incl->output = 1;
1682                                         }
1683                                 }
1684                         }
1685
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');
1692                                         if (cmtp2)
1693                                                 cmtp = cmtp2+1;
1694                                         else cmtp = 0;
1695                                 }
1696                                 if (cmtp)
1697                                         fprintf(f,"%s", cmtp);
1698                         }
1699                         fprintf(f, "[%s]", cat->name);
1700                         if (cat->ignored || !AST_LIST_EMPTY(&cat->template_instances)) {
1701                                 fprintf(f, "(");
1702                                 if (cat->ignored) {
1703                                         fprintf(f, "!");
1704                                 }
1705                                 if (cat->ignored && !AST_LIST_EMPTY(&cat->template_instances)) {
1706                                         fprintf(f, ",");
1707                                 }
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))
1713                                                         fprintf(f,",");
1714                                         }
1715                                 }
1716                                 fprintf(f, ")");
1717                         }
1718                         for(cmt = cat->sameline; cmt; cmt=cmt->next)
1719                         {
1720                                 fprintf(f,"%s", cmt->cmt);
1721                         }
1722                         if (!cat->sameline)
1723                                 fprintf(f,"\n");
1724                         for (cmt = cat->trailing; cmt; cmt=cmt->next) {
1725                                 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1726                                         fprintf(f,"%s", cmt->cmt);
1727                         }
1728                         fclose(f);
1729                         ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1730                         fi = 0;
1731                         
1732                         var = cat->root;
1733                         while (var) {
1734                                 struct ast_category_template_instance *x;
1735                                 int found = 0;
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)) {
1740                                                         found = 1;
1741                                                         break;
1742                                                 }
1743                                         }
1744                                         if (found)
1745                                                 break;
1746                                 }
1747                                 if (found) {
1748                                         var = var->next;
1749                                         continue;
1750                                 }
1751                                 set_fn(fn, sizeof(fn), var->file, configfile, fileset, &fi);
1752                                 f = fopen(fn, "a");
1753                                 if (!f)
1754                                 {
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 */
1758                                         fi = 0;
1759                                         ao2_ref(fileset, -1);
1760                                         return -1;
1761                                 }
1762                                 
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) {
1767                                                         if (incl->exec)
1768                                                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1769                                                         else
1770                                                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1771                                                         incl->output = 1;
1772                                                 }
1773                                         }
1774                                 }
1775                                 
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);
1780                                 }
1781                                 if (var->sameline) 
1782                                         fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1783                                 else    
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);
1788                                 }
1789                                 if (var->blanklines) {
1790                                         blanklines = var->blanklines;
1791                                         while (blanklines--)
1792                                                 fprintf(f, "\n");
1793                                 }
1794                                 
1795                                 fclose(f);
1796                                 ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1797                                 fi = 0;
1798                                 
1799                                 var = var->next;
1800                         }
1801                         cat = cat->next;
1802                 }
1803                 if (!option_debug)
1804                         ast_verb(2, "Saved\n");
1805         } else {
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);
1810                 return -1;
1811         }
1812
1813         /* Now, for files with trailing #include/#exec statements,
1814            we have to make sure every entry is output */
1815
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);
1820                         f = fopen(fn, "a");
1821                         if (!f)
1822                         {
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 */
1826                                 fi = 0;
1827                                 ao2_ref(fileset, -1);
1828                                 return -1;
1829                         }
1830                         
1831                         /* output the respective include */
1832                         if (incl->exec)
1833                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1834                         else
1835                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1836                         fclose(f);
1837                         incl->output = 1;
1838                         ao2_ref(fi,-1); /* we are giving up this reference to the object ptd to by fi */
1839                         fi = 0;
1840                 }
1841         }
1842         ao2_ref(fileset, -1); /* this should destroy the hash container */
1843                                 
1844         return 0;
1845 }
1846
1847 static void clear_config_maps(void) 
1848 {
1849         struct ast_config_map *map;
1850
1851         ast_mutex_lock(&config_lock);
1852
1853         while (config_maps) {
1854                 map = config_maps;
1855                 config_maps = config_maps->next;
1856                 ast_free(map);
1857         }
1858                 
1859         ast_mutex_unlock(&config_lock);
1860 }
1861
1862 static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority)
1863 {
1864         struct ast_config_map *map;
1865         int length;
1866
1867         length = sizeof(*map);
1868         length += strlen(name) + 1;
1869         length += strlen(driver) + 1;
1870         length += strlen(database) + 1;
1871         if (table)
1872                 length += strlen(table) + 1;
1873
1874         if (!(map = ast_calloc(1, length)))
1875                 return -1;
1876
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);
1883         if (table) {
1884                 map->table = map->database + strlen(map->database) + 1;
1885                 strcpy(map->table, table);
1886         }
1887         map->priority = priority;
1888         map->next = config_maps;
1889
1890         ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1891
1892         config_maps = map;
1893         return 0;
1894 }
1895
1896 int read_config_maps(void) 
1897 {
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 };
1902         int pri;
1903
1904         clear_config_maps();
1905
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) {
1910                 return -1;
1911         } else if (!config) {
1912                 ast_config_destroy(configtmp);
1913                 return 0;
1914         }
1915
1916         for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1917                 char buf[512];
1918                 ast_copy_string(buf, v->value, sizeof(buf));
1919                 stringp = buf;
1920                 driver = strsep(&stringp, ",");
1921
1922                 if ((tmp = strchr(stringp, '\"')))
1923                         stringp = tmp;
1924
1925                 /* check if the database text starts with a double quote */
1926                 if (*stringp == '"') {
1927                         stringp++;
1928                         database = strsep(&stringp, "\"");
1929                         strsep(&stringp, ",");
1930                 } else {
1931                         /* apparently this text has no quotes */
1932                         database = strsep(&stringp, ",");
1933                 }
1934
1935                 table = strsep(&stringp, ",");
1936                 textpri = strsep(&stringp, ",");
1937                 if (!textpri || !(pri = atoi(textpri))) {
1938                         pri = 1;
1939                 }
1940
1941                 if (!strcmp(v->name, extconfig_conf)) {
1942                         ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1943                         continue;
1944                 }
1945
1946                 if (!strcmp(v->name, "asterisk.conf")) {
1947                         ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1948                         continue;
1949                 }
1950
1951                 if (!strcmp(v->name, "logger.conf")) {
1952                         ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1953                         continue;
1954                 }
1955
1956                 if (!driver || !database)
1957                         continue;
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);
1966                 } else 
1967                         append_mapping(v->name, driver, database, table, pri);
1968         }
1969                 
1970         ast_config_destroy(config);
1971         return 0;
1972 }
1973
1974 int ast_config_engine_register(struct ast_config_engine *new) 
1975 {
1976         struct ast_config_engine *ptr;
1977
1978         ast_mutex_lock(&config_lock);
1979
1980         if (!config_engine_list) {
1981                 config_engine_list = new;
1982         } else {
1983                 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1984                 ptr->next = new;
1985         }
1986
1987         ast_mutex_unlock(&config_lock);
1988         ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1989
1990         return 1;
1991 }
1992
1993 int ast_config_engine_deregister(struct ast_config_engine *del) 
1994 {
1995         struct ast_config_engine *ptr, *last=NULL;
1996
1997         ast_mutex_lock(&config_lock);
1998
1999         for (ptr = config_engine_list; ptr; ptr=ptr->next) {
2000                 if (ptr == del) {
2001                         if (last)
2002                                 last->next = ptr->next;
2003                         else
2004                                 config_engine_list = ptr->next;
2005                         break;
2006                 }
2007                 last = ptr;
2008         }
2009
2010         ast_mutex_unlock(&config_lock);
2011
2012         return 0;
2013 }
2014
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) 
2017 {
2018         struct ast_config_engine *eng, *ret = NULL;
2019         struct ast_config_map *map;
2020
2021         ast_mutex_lock(&config_lock);
2022
2023         for (map = config_maps; map; map = map->next) {
2024                 if (!strcasecmp(family, map->name) && (priority == map->priority)) {
2025                         if (database)
2026                                 ast_copy_string(database, map->database, dbsiz);
2027                         if (table)
2028                                 ast_copy_string(table, map->table ? map->table : family, tabsiz);
2029                         break;
2030                 }
2031         }
2032
2033         /* Check if the required driver (engine) exist */
2034         if (map) {
2035                 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
2036                         if (!strcasecmp(eng->name, map->driver))
2037                                 ret = eng;
2038                 }
2039         }
2040
2041         ast_mutex_unlock(&config_lock);
2042         
2043         /* if we found a mapping, but the engine is not available, then issue a warning */
2044         if (map && !ret)
2045                 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
2046
2047         return ret;
2048 }
2049
2050 static struct ast_config_engine text_file_engine = {
2051         .name = "text",
2052         .load_func = config_text_file_load,
2053 };
2054
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)
2056 {
2057         char db[256];
2058         char table[256];
2059         struct ast_config_engine *loader = &text_file_engine;
2060         struct ast_config *result; 
2061
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);
2065                 return NULL;
2066         }
2067
2068         cfg->include_level++;
2069
2070         if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) {
2071                 struct ast_config_engine *eng;
2072
2073                 eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table));
2074
2075
2076                 if (eng && eng->load_func) {
2077                         loader = eng;
2078                 } else {
2079                         eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table));
2080                         if (eng && eng->load_func)
2081                                 loader = eng;
2082                 }
2083         }
2084
2085         result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file, who_asked);
2086
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--;
2091
2092         return result;
2093 }
2094
2095 struct ast_config *ast_config_load2(const char *filename, const char *who_asked, struct ast_flags flags)
2096 {
2097         struct ast_config *cfg;
2098         struct ast_config *result;
2099
2100         cfg = ast_config_new();
2101         if (!cfg)
2102                 return NULL;
2103
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);
2107
2108         return result;
2109 }
2110
2111 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
2112 {
2113         struct ast_config_engine *eng;
2114         char db[256];
2115         char table[256];
2116         struct ast_variable *res=NULL;
2117         int i;
2118
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))) {
2122                                 return res;
2123                         }
2124                 } else {
2125                         return NULL;
2126                 }
2127         }
2128
2129         return res;
2130 }
2131
2132 struct ast_variable *ast_load_realtime_all(const char *family, ...)
2133 {
2134         struct ast_variable *res;
2135         va_list ap;
2136
2137         va_start(ap, family);
2138         res = ast_load_realtime_helper(family, ap);
2139         va_end(ap);
2140
2141         return res;
2142 }
2143
2144 struct ast_variable *ast_load_realtime(const char *family, ...)
2145 {
2146         struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
2147         va_list ap;
2148
2149         va_start(ap, family);
2150         res = ast_load_realtime_helper(family, ap);
2151         va_end(ap);
2152
2153         /* Eliminate blank entries */
2154         for (cur = res; cur; cur = cur->next) {
2155                 if (freeme) {
2156                         ast_free(freeme);
2157                         freeme = NULL;
2158                 }
2159
2160                 if (ast_strlen_zero(cur->value)) {
2161                         if (prev)
2162                                 prev->next = cur->next;
2163                         else
2164                                 res = cur->next;
2165                         freeme = cur;
2166                 } else if (cur->value[0] == ' ' && cur->value[1] == '\0') {
2167                         char *vptr = (char *) cur->value;
2168                         vptr[0] = '\0';
2169                         prev = cur;
2170                 } else {
2171                         prev = cur;
2172                 }
2173         }
2174         return res;
2175 }
2176
2177 /*! \brief Check if realtime engine is configured for family */
2178 int ast_check_realtime(const char *family)
2179 {
2180         struct ast_config_engine *eng;
2181         if (!ast_realtime_enabled()) {
2182                 return 0;       /* There are no engines at all so fail early */
2183         }
2184
2185         eng = find_engine(family, 1, NULL, 0, NULL, 0);
2186         if (eng)
2187                 return 1;
2188         return 0;
2189 }
2190
2191 /*! \brief Check if there's any realtime engines loaded */
2192 int ast_realtime_enabled()
2193 {
2194         return config_maps ? 1 : 0;
2195 }
2196
2197 int ast_realtime_require_field(const char *family, ...)
2198 {
2199         struct ast_config_engine *eng;
2200         char db[256];
2201         char table[256];
2202         va_list ap;
2203         int res = -1, i;
2204
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))) {
2210                                 break;
2211                         }
2212                 } else {
2213                         break;
2214                 }
2215         }
2216         va_end(ap);
2217
2218         return res;
2219 }
2220
2221 int ast_unload_realtime(const char *family)
2222 {
2223         struct ast_config_engine *eng;
2224         char db[256];
2225         char table[256];
2226         int res = -1, i;
2227
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);
2233                         }
2234                 } else {
2235                         break;
2236                 }
2237         }
2238         return res;
2239 }
2240
2241 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
2242 {
2243         struct ast_config_engine *eng;
2244         char db[256];
2245         char table[256];
2246         struct ast_config *res = NULL;
2247         va_list ap;
2248         int i;
2249
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))) {
2254                                 break;
2255                         }
2256                 } else {
2257                         break;
2258                 }
2259         }
2260         va_end(ap);
2261
2262         return res;
2263 }
2264
2265 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2266 {
2267         struct ast_config_engine *eng;
2268         int res = -1, i;
2269         char db[256];
2270         char table[256];
2271         va_list ap;
2272
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))) {
2278                                 break;
2279                         }
2280                 } else {
2281                         break;
2282                 }
2283         }
2284         va_end(ap);
2285
2286         return res;
2287 }
2288
2289 int ast_update2_realtime(const char *family, ...)
2290 {
2291         struct ast_config_engine *eng;
2292         int res = -1, i;
2293         char db[256];
2294         char table[256];
2295         va_list ap;
2296
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))) {
2301                                 break;
2302                         }
2303                 } else {
2304                         break;
2305                 }
2306         }
2307         va_end(ap);
2308
2309         return res;
2310 }
2311
2312 int ast_store_realtime(const char *family, ...)
2313 {
2314         struct ast_config_engine *eng;
2315         int res = -1, i;
2316         char db[256];
2317         char table[256];
2318         va_list ap;
2319
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))) {
2325                                 break;
2326                         }
2327                 } else {
2328                         break;
2329                 }
2330         }
2331         va_end(ap);
2332
2333         return res;
2334 }
2335
2336 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...)
2337 {
2338         struct ast_config_engine *eng;
2339         int res = -1, i;
2340         char db[256];
2341         char table[256];
2342         va_list ap;
2343
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))) {
2348                                 break;
2349                         }
2350                 } else {
2351                         break;
2352                 }
2353         }
2354         va_end(ap);
2355
2356         return res;
2357 }
2358
2359 char *ast_realtime_decode_chunk(char *chunk)
2360 {
2361         char *orig = 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);
2366                 }
2367         }
2368         return orig;
2369 }
2370
2371 char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk)
2372 {
2373         if (!strchr(chunk, ';') && !strchr(chunk, '^')) {
2374                 ast_str_set(dest, maxlen, "%s", chunk);
2375         } else {
2376                 ast_str_reset(*dest);
2377                 for (; *chunk; chunk++) {
2378                         if (strchr(";^", *chunk)) {
2379                                 ast_str_append(dest, maxlen, "^%02hhX", *chunk);
2380                         } else {
2381                                 ast_str_append(dest, maxlen, "%c", *chunk);
2382                         }
2383                 }
2384         }
2385         return ast_str_buffer(*dest);
2386 }
2387
2388 /*! \brief Helper function to parse arguments
2389  * See documentation in config.h
2390  */
2391 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
2392         void *p_result, ...)
2393 {
2394         va_list ap;
2395         int error = 0;
2396
2397         va_start(ap, p_result);
2398         switch (flags & PARSE_TYPE) {
2399         case PARSE_INT32:
2400             {
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);
2412                 }
2413                 x = strtol(arg, NULL, 0);
2414                 error = (x < low) || (x > high);
2415                 if (flags & PARSE_OUT_RANGE)
2416                         error = !error;
2417                 if (result)
2418                         *result  = error ? def : x;
2419                 ast_debug(3,
2420                         "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
2421                         arg, low, high,
2422                         result ? *result : x, error);
2423                 break;
2424             }
2425
2426         case PARSE_UINT32:
2427             {
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);
2438                 }
2439                 x = strtoul(arg, NULL, 0);
2440                 error = (x < low) || (x > high);
2441                 if (flags & PARSE_OUT_RANGE)
2442                         error = !error;
2443                 if (result)
2444                         *result  = error ? def : x;
2445                 ast_debug(3,
2446                         "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
2447                         arg, low, high,
2448                         result ? *result : x, error);
2449                 break;
2450             }
2451
2452         case PARSE_DOUBLE:
2453             {
2454                 double *result = p_result;
2455                 double x, def = result ? *result : 0,
2456                         low = -HUGE_VAL, high = HUGE_VAL;
2457
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);
2465                 }
2466                 x = strtod(arg, NULL);
2467                 error = (x < low) || (x > high);
2468                 if (flags & PARSE_OUT_RANGE)
2469                         error = !error;
2470                 if (result)
2471                         *result  = error ? def : x;
2472                 ast_debug(3,
2473                         "extract double from [%s] in [%f, %f] gives [%f](%d)\n",
2474                         arg, low, high,
2475                         result ? *result : x, error);
2476                 break;
2477             }
2478         case PARSE_ADDR:
2479             {
2480                 struct ast_sockaddr *addr = (struct ast_sockaddr *)p_result;
2481
2482                 if (!ast_sockaddr_parse(addr, arg, flags & PARSE_PORT_MASK)) {
2483                         error = 1;
2484                 }
2485
2486                 ast_debug(3, "extract addr from %s gives %s(%d)\n",
2487                           arg, ast_sockaddr_stringify(addr), error);
2488
2489                 break;
2490             }
2491         case PARSE_INADDR:      /* TODO Remove this (use PARSE_ADDR instead). */
2492             {
2493                 char *port, *buf;
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;
2500                 struct hostent *hp;
2501                 struct ast_hostent ahp;
2502
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 */
2508                 /*
2509                  * honor the ports flag setting, assign default value
2510                  * in case of errors or field unset.
2511                  */
2512                 flags &= PARSE_PORT_MASK; /* the only flags left to process */
2513                 if (port) {
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));
2521                 } else {
2522                         sa->sin_port = def->sin_port;
2523                         if (flags == PARSE_PORT_REQUIRE)
2524                                 error = 1;
2525                 }
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));
2530                 else {
2531                         error = 1;
2532                         sa->sin_addr = def->sin_addr;
2533                 }
2534                 ast_debug(3,
2535                         "extract inaddr from [%s] gives [%s:%d](%d)\n",
2536                         arg, ast_inet_ntoa(sa->sin_addr),
2537                         ntohs(sa->sin_port), error);
2538                 break;
2539             }
2540         }
2541         va_end(ap);
2542         return error;
2543 }
2544
2545 static char *handle_cli_core_show_config_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2546 {
2547         struct ast_config_engine *eng;
2548         struct ast_config_map *map;
2549
2550         switch (cmd) {
2551         case CLI_INIT:
2552                 e->command = "core show config mappings";
2553                 e->usage =
2554                         "Usage: core show config mappings\n"
2555                         "       Shows the filenames to config engines.\n";
2556                 return NULL;
2557         case CLI_GENERATE:
2558                 return NULL;
2559         }
2560         
2561         ast_mutex_lock(&config_lock);
2562
2563         if (!config_engine_list) {
2564                 ast_cli(a->fd, "No config mappings found.\n");
2565         } else {
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);
2572                                 }
2573                         }
2574                 }
2575         }
2576         
2577         ast_mutex_unlock(&config_lock);
2578
2579         return CLI_SUCCESS;
2580 }
2581
2582 static char *handle_cli_config_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2583 {
2584         struct cache_file_mtime *cfmtime;
2585         char *prev = "", *completion_value = NULL;
2586         int wordlen, which = 0;
2587
2588         switch (cmd) {
2589         case CLI_INIT:
2590                 e->command = "config reload";
2591                 e->usage =
2592                         "Usage: config reload <filename.conf>\n"
2593                         "   Reloads all modules that reference <filename.conf>\n";
2594                 return NULL;
2595         case CLI_GENERATE:
2596                 if (a->pos > 2) {
2597                         return NULL;
2598                 }
2599
2600                 wordlen = strlen(a->word);
2601
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) {
2606                                 continue;
2607                         }
2608
2609                         /* Core configs cannot be reloaded */
2610                         if (ast_strlen_zero(cfmtime->who_asked)) {
2611                                 continue;
2612                         }
2613
2614                         if (++which > a->n && strncmp(cfmtime->filename, a->word, wordlen) == 0) {
2615                                 completion_value = ast_strdup(cfmtime->filename);
2616                                 break;
2617                         }
2618
2619                         /* Otherwise save that we've seen this filename */
2620                         prev = cfmtime->filename;
2621                 }
2622                 AST_LIST_UNLOCK(&cfmtime_head);
2623
2624                 return completion_value;
2625         }
2626
2627         if (a->argc != 3) {
2628                 return CLI_SHOWUSAGE;
2629         }
2630
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);
2637                 }
2638         }
2639         AST_LIST_UNLOCK(&cfmtime_head);
2640
2641         return CLI_SUCCESS;
2642 }
2643
2644 static char *handle_cli_config_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2645 {
2646         struct cache_file_mtime *cfmtime;
2647
2648         switch (cmd) {
2649         case CLI_INIT:
2650                 e->command = "config list";
2651                 e->usage =
2652                         "Usage: config list\n"
2653                         "   Show all modules that have loaded a configuration file\n";
2654                 return NULL;
2655         case CLI_GENERATE:
2656                 return NULL;
2657         }
2658
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);
2662         }
2663         AST_LIST_UNLOCK(&cfmtime_head);
2664
2665         return CLI_SUCCESS;
2666 }
2667
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"),
2672 };
2673
2674 int register_config_cli() 
2675 {
2676         ast_cli_register_multiple(cli_config, ARRAY_LEN(cli_config));
2677         return 0;
2678 }