Once we get past the file checks, we're loading, so clear the FILEUNCHANGED flag...
[asterisk/asterisk.git] / main / config.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, 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 <stdio.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <time.h>
39 #include <sys/stat.h>
40 #include <sys/socket.h>         /* for AF_INET */
41 #define AST_INCLUDE_GLOB 1
42 #ifdef AST_INCLUDE_GLOB
43 #if defined(__Darwin__) || defined(__CYGWIN__)
44 #define GLOB_ABORTED GLOB_ABEND
45 #endif
46 # include <glob.h>
47 #endif
48
49 #include "asterisk/config.h"
50 #include "asterisk/cli.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/options.h"
53 #include "asterisk/logger.h"
54 #include "asterisk/utils.h"
55 #include "asterisk/channel.h"
56 #include "asterisk/app.h"
57
58 #define MAX_NESTED_COMMENTS 128
59 #define COMMENT_START ";--"
60 #define COMMENT_END "--;"
61 #define COMMENT_META ';'
62 #define COMMENT_TAG '-'
63
64 static char *extconfig_conf = "extconfig.conf";
65
66
67 /*! \brief Structure to keep comments for rewriting configuration files */
68 struct ast_comment {
69         struct ast_comment *next;
70         char cmt[0];
71 };
72
73 /*! \brief Hold the mtime for config files, so if we don't need to reread our config, don't. */
74 struct cache_file_include {
75         AST_LIST_ENTRY(cache_file_include) list;
76         char include[0];
77 };
78
79 struct cache_file_mtime {
80         AST_LIST_ENTRY(cache_file_mtime) list;
81         AST_LIST_HEAD(includes, cache_file_include) includes;
82         unsigned int has_exec:1;
83         time_t mtime;
84         char filename[0];
85 };
86
87 static AST_LIST_HEAD_STATIC(cfmtime_head, cache_file_mtime);
88
89 #define CB_INCR 250
90
91 static void CB_INIT(char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size)
92 {
93         if (!(*comment_buffer)) {
94                 *comment_buffer = ast_malloc(CB_INCR);
95                 if (!(*comment_buffer))
96                         return;
97                 (*comment_buffer)[0] = 0;
98                 *comment_buffer_size = CB_INCR;
99                 *lline_buffer = ast_malloc(CB_INCR);
100                 if (!(*lline_buffer))
101                         return;
102                 (*lline_buffer)[0] = 0;
103                 *lline_buffer_size = CB_INCR;
104         } else {
105                 (*comment_buffer)[0] = 0;
106                 (*lline_buffer)[0] = 0;
107         }
108 }
109
110 static void  CB_ADD(char **comment_buffer, int *comment_buffer_size, char *str)
111 {
112         int rem = *comment_buffer_size - strlen(*comment_buffer) - 1;
113         int siz = strlen(str);
114         if (rem < siz+1) {
115                 *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + siz + 1);
116                 if (!(*comment_buffer))
117                         return;
118                 *comment_buffer_size += CB_INCR+siz+1;
119         }
120         strcat(*comment_buffer,str);
121 }
122
123 static void  CB_ADD_LEN(char **comment_buffer, int *comment_buffer_size, char *str, int len)
124 {
125         int cbl = strlen(*comment_buffer) + 1;
126         int rem = *comment_buffer_size - cbl;
127         if (rem < len+1) {
128                 *comment_buffer = ast_realloc(*comment_buffer, *comment_buffer_size + CB_INCR + len + 1);
129                 if (!(*comment_buffer))
130                         return;
131                 *comment_buffer_size += CB_INCR+len+1;
132         }
133         strncat(*comment_buffer,str,len);
134         (*comment_buffer)[cbl+len-1] = 0;
135 }
136
137 static void  LLB_ADD(char **lline_buffer, int *lline_buffer_size, char *str)
138 {
139         int rem = *lline_buffer_size - strlen(*lline_buffer) - 1;
140         int siz = strlen(str);
141         if (rem < siz+1) {
142                 *lline_buffer = ast_realloc(*lline_buffer, *lline_buffer_size + CB_INCR + siz + 1);
143                 if (!(*lline_buffer)) 
144                         return;
145                 *lline_buffer_size += CB_INCR + siz + 1;
146         }
147         strcat(*lline_buffer,str);
148 }
149
150 static void CB_RESET(char **comment_buffer, char **lline_buffer)  
151
152         (*comment_buffer)[0] = 0; 
153         (*lline_buffer)[0] = 0;
154 }
155
156
157 static struct ast_comment *ALLOC_COMMENT(const char *buffer)
158
159         struct ast_comment *x;
160         x = ast_calloc(1, sizeof(*x)+strlen(buffer)+1);
161         strcpy(x->cmt, buffer);
162         return x;
163 }
164
165
166 static struct ast_config_map {
167         struct ast_config_map *next;
168         char *name;
169         char *driver;
170         char *database;
171         char *table;
172         char stuff[0];
173 } *config_maps = NULL;
174
175 AST_MUTEX_DEFINE_STATIC(config_lock);
176 static struct ast_config_engine *config_engine_list;
177
178 #define MAX_INCLUDE_LEVEL 10
179
180 struct ast_category {
181         char name[80];
182         int ignored;                    /*!< do not let user of the config see this category */
183         int include_level;
184         char *file;                /*!< the file name from whence this declaration was read */
185         int lineno;
186         struct ast_comment *precomments;
187         struct ast_comment *sameline;
188         struct ast_variable *root;
189         struct ast_variable *last;
190         struct ast_category *next;
191 };
192
193 struct ast_config {
194         struct ast_category *root;
195         struct ast_category *last;
196         struct ast_category *current;
197         struct ast_category *last_browse;     /*!< used to cache the last category supplied via category_browse */
198         int include_level;
199         int max_include_level;
200         struct ast_config_include *includes;  /*!< a list of inclusions, which should describe the entire tree */
201 };
202
203 struct ast_config_include {
204         char *include_location_file;     /*!< file name in which the include occurs */
205         int  include_location_lineno;    /*!< lineno where include occurred */
206         int  exec;                       /*!< set to non-zero if itsa #exec statement */
207         char *exec_file;                 /*!< if it's an exec, you'll have both the /var/tmp to read, and the original script */
208         char *included_file;             /*!< file name included */
209         int inclusion_count;             /*!< if the file is included more than once, a running count thereof -- but, worry not,
210                                               we explode the instances and will include those-- so all entries will be unique */
211         int output;                      /*!< a flag to indicate if the inclusion has been output */
212         struct ast_config_include *next; /*!< ptr to next inclusion in the list */
213 };
214
215 struct ast_variable *ast_variable_new(const char *name, const char *value, const char *filename) 
216 {
217         struct ast_variable *variable;
218         int name_len = strlen(name) + 1;        
219
220         if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + strlen(filename) + 1 + sizeof(*variable)))) {
221                 variable->name = variable->stuff;
222                 variable->value = variable->stuff + name_len;           
223                 variable->file = variable->stuff + name_len + strlen(value) + 1;
224                 strcpy(variable->name,name);
225                 strcpy(variable->value,value);
226                 strcpy(variable->file,filename);
227         }
228         return variable;
229 }
230
231 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)
232 {
233         /* a file should be included ONCE. Otherwise, if one of the instances is changed,
234        then all be changed. -- how do we know to include it? -- Handling modified 
235        instances is possible, I'd have
236        to create a new master for each instance. */
237         struct ast_config_include *inc;
238         struct stat statbuf;
239         
240         inc = ast_include_find(conf, included_file);
241         if (inc) {
242                 do {
243                         inc->inclusion_count++;
244                         snprintf(real_included_file_name, real_included_file_name_size, "%s~~%d", included_file, inc->inclusion_count);
245                 } while (stat(real_included_file_name, &statbuf) == 0);
246                 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);
247         } else
248                 *real_included_file_name = 0;
249         
250         inc = ast_calloc(1,sizeof(struct ast_config_include));
251         inc->include_location_file = ast_strdup(from_file);
252         inc->include_location_lineno = from_lineno;
253         if (!ast_strlen_zero(real_included_file_name))
254                 inc->included_file = ast_strdup(real_included_file_name);
255         else
256                 inc->included_file = ast_strdup(included_file);
257         
258         inc->exec = is_exec;
259         if (is_exec)
260                 inc->exec_file = ast_strdup(exec_file);
261         
262         /* attach this new struct to the conf struct */
263         inc->next = conf->includes;
264         conf->includes = inc;
265         
266         return inc;
267 }
268
269 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file)
270 {
271         struct ast_config_include *incl;
272         struct ast_category *cat;
273         struct ast_variable *v;
274         
275         int from_len = strlen(from_file);
276         int to_len = strlen(to_file);
277         
278         if (strcmp(from_file, to_file) == 0) /* no use wasting time if the name is the same */
279                 return;
280         
281         /* the manager code allows you to read in one config file, then
282        write it back out under a different name. But, the new arrangement
283            ties output lines to the file name. So, before you try to write
284        the config file to disk, better riffle thru the data and make sure
285        the file names are changed.
286         */
287         /* file names are on categories, includes (of course), and on variables. So,
288            traverse all this and swap names */
289
290         for (incl = conf->includes; incl; incl=incl->next) {
291                 if (strcmp(incl->include_location_file,from_file) == 0) {
292                         if (from_len >= to_len)
293                                 strcpy(incl->include_location_file, to_file);
294                         else {
295                                 free(incl->include_location_file);
296                                 incl->include_location_file = strdup(to_file);
297                         }
298                 }
299         }
300         for (cat = conf->root; cat; cat = cat->next) {
301                 if (strcmp(cat->file,from_file) == 0) {
302                         if (from_len >= to_len)
303                                 strcpy(cat->file, to_file);
304                         else {
305                                 free(cat->file);
306                                 cat->file = strdup(to_file);
307                         }
308                 }
309                 for (v = cat->root; v; v = v->next) {
310                         if (strcmp(v->file,from_file) == 0) {
311                                 if (from_len >= to_len)
312                                         strcpy(v->file, to_file);
313                                 else {
314                                         free(v->file);
315                                         v->file = strdup(to_file);
316                                 }
317                         }
318                 }
319         }
320 }
321
322 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file)
323 {
324         struct ast_config_include *x;
325         for (x=conf->includes;x;x=x->next)
326         {
327                 if (strcmp(x->included_file,included_file) == 0)
328                         return x;
329         }
330         return 0;
331 }
332
333
334 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
335 {
336         if (!variable)
337                 return;
338         if (category->last)
339                 category->last->next = variable;
340         else
341                 category->root = variable;
342         category->last = variable;
343         while (category->last->next)
344                 category->last = category->last->next;
345 }
346
347 void ast_variables_destroy(struct ast_variable *v)
348 {
349         struct ast_variable *vn;
350
351         while (v) {
352                 vn = v;
353                 v = v->next;
354                 ast_free(vn);
355         }
356 }
357
358 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
359 {
360         struct ast_category *cat = NULL;
361
362         if (category && config->last_browse && (config->last_browse->name == category))
363                 cat = config->last_browse;
364         else
365                 cat = ast_category_get(config, category);
366
367         return (cat) ? cat->root : NULL;
368 }
369
370 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
371 {
372         const char *tmp;
373         tmp = ast_variable_retrieve(cfg, cat, var);
374         if (!tmp)
375                 tmp = ast_variable_retrieve(cfg, "general", var);
376         return tmp;
377 }
378
379
380 const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
381 {
382         struct ast_variable *v;
383
384         if (category) {
385                 for (v = ast_variable_browse(config, category); v; v = v->next) {
386                         if (!strcasecmp(variable, v->name))
387                                 return v->value;
388                 }
389         } else {
390                 struct ast_category *cat;
391
392                 for (cat = config->root; cat; cat = cat->next)
393                         for (v = cat->root; v; v = v->next)
394                                 if (!strcasecmp(variable, v->name))
395                                         return v->value;
396         }
397
398         return NULL;
399 }
400
401 static struct ast_variable *variable_clone(const struct ast_variable *old)
402 {
403         struct ast_variable *new = ast_variable_new(old->name, old->value, old->file);
404
405         if (new) {
406                 new->lineno = old->lineno;
407                 new->object = old->object;
408                 new->blanklines = old->blanklines;
409                 /* TODO: clone comments? */
410         }
411
412         return new;
413 }
414  
415 static void move_variables(struct ast_category *old, struct ast_category *new)
416 {
417         struct ast_variable *var = old->root;
418         old->root = NULL;
419 #if 1
420         /* we can just move the entire list in a single op */
421         ast_variable_append(new, var);
422 #else
423         while (var) {
424                 struct ast_variable *next = var->next;
425                 var->next = NULL;
426                 ast_variable_append(new, var);
427                 var = next;
428         }
429 #endif
430 }
431
432 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno) 
433 {
434         struct ast_category *category;
435
436         if ((category = ast_calloc(1, sizeof(*category))))
437                 ast_copy_string(category->name, name, sizeof(category->name));
438         category->file = strdup(in_file);
439         category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
440         return category;
441 }
442
443 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
444 {
445         struct ast_category *cat;
446
447         /* try exact match first, then case-insensitive match */
448         for (cat = config->root; cat; cat = cat->next) {
449                 if (cat->name == category_name && (ignored || !cat->ignored))
450                         return cat;
451         }
452
453         for (cat = config->root; cat; cat = cat->next) {
454                 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
455                         return cat;
456         }
457
458         return NULL;
459 }
460
461 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
462 {
463         return category_get(config, category_name, 0);
464 }
465
466 int ast_category_exist(const struct ast_config *config, const char *category_name)
467 {
468         return !!ast_category_get(config, category_name);
469 }
470
471 void ast_category_append(struct ast_config *config, struct ast_category *category)
472 {
473         if (config->last)
474                 config->last->next = category;
475         else
476                 config->root = category;
477         category->include_level = config->include_level;
478         config->last = category;
479         config->current = category;
480 }
481
482 void ast_category_destroy(struct ast_category *cat)
483 {
484         ast_variables_destroy(cat->root);
485         if (cat->file)
486                 free(cat->file);
487         
488         ast_free(cat);
489 }
490
491 static void ast_includes_destroy(struct ast_config_include *incls)
492 {
493         struct ast_config_include *incl,*inclnext;
494         
495         for (incl=incls; incl; incl = inclnext) {
496                 inclnext = incl->next;
497                 if (incl->include_location_file)
498                         free(incl->include_location_file);
499                 if (incl->exec_file)
500                         free(incl->exec_file);
501                 if (incl->included_file)
502                         free(incl->included_file);
503                 free(incl);
504         }
505 }
506
507 static struct ast_category *next_available_category(struct ast_category *cat)
508 {
509         for (; cat && cat->ignored; cat = cat->next);
510
511         return cat;
512 }
513
514 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
515 {
516         struct ast_category *category = ast_category_get(config, cat);
517         if (category)
518                 return category->root;
519         return NULL;
520 }
521
522 char *ast_category_browse(struct ast_config *config, const char *prev)
523 {       
524         struct ast_category *cat = NULL;
525
526         if (prev && config->last_browse && (config->last_browse->name == prev))
527                 cat = config->last_browse->next;
528         else if (!prev && config->root)
529                 cat = config->root;
530         else if (prev) {
531                 for (cat = config->root; cat; cat = cat->next) {
532                         if (cat->name == prev) {
533                                 cat = cat->next;
534                                 break;
535                         }
536                 }
537                 if (!cat) {
538                         for (cat = config->root; cat; cat = cat->next) {
539                                 if (!strcasecmp(cat->name, prev)) {
540                                         cat = cat->next;
541                                         break;
542                                 }
543                         }
544                 }
545         }
546         
547         if (cat)
548                 cat = next_available_category(cat);
549
550         config->last_browse = cat;
551         return (cat) ? cat->name : NULL;
552 }
553
554 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
555 {
556         struct ast_variable *v;
557
558         v = cat->root;
559         cat->root = NULL;
560         cat->last = NULL;
561
562         return v;
563 }
564
565 void ast_category_rename(struct ast_category *cat, const char *name)
566 {
567         ast_copy_string(cat->name, name, sizeof(cat->name));
568 }
569
570 static void inherit_category(struct ast_category *new, const struct ast_category *base)
571 {
572         struct ast_variable *var;
573
574         for (var = base->root; var; var = var->next)
575                 ast_variable_append(new, variable_clone(var));
576 }
577
578 struct ast_config *ast_config_new(void) 
579 {
580         struct ast_config *config;
581
582         if ((config = ast_calloc(1, sizeof(*config))))
583                 config->max_include_level = MAX_INCLUDE_LEVEL;
584         return config;
585 }
586
587 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match)
588 {
589         struct ast_variable *cur, *prev=NULL, *curn;
590         int res = -1;
591         cur = category->root;
592         while (cur) {
593                 if (cur->name == variable) {
594                         if (prev) {
595                                 prev->next = cur->next;
596                                 if (cur == category->last)
597                                         category->last = prev;
598                         } else {
599                                 category->root = cur->next;
600                                 if (cur == category->last)
601                                         category->last = NULL;
602                         }
603                         cur->next = NULL;
604                         ast_variables_destroy(cur);
605                         return 0;
606                 }
607                 prev = cur;
608                 cur = cur->next;
609         }
610
611         prev = NULL;
612         cur = category->root;
613         while (cur) {
614                 curn = cur->next;
615                 if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) {
616                         if (prev) {
617                                 prev->next = cur->next;
618                                 if (cur == category->last)
619                                         category->last = prev;
620                         } else {
621                                 category->root = cur->next;
622                                 if (cur == category->last)
623                                         category->last = NULL;
624                         }
625                         cur->next = NULL;
626                         ast_variables_destroy(cur);
627                         res = 0;
628                 } else
629                         prev = cur;
630
631                 cur = curn;
632         }
633         return res;
634 }
635
636 int ast_variable_update(struct ast_category *category, const char *variable, 
637                                                 const char *value, const char *match, unsigned int object)
638 {
639         struct ast_variable *cur, *prev=NULL, *newer=NULL;
640
641         for (cur = category->root; cur; prev = cur, cur = cur->next) {
642                 if (strcasecmp(cur->name, variable) ||
643                         (!ast_strlen_zero(match) && strcasecmp(cur->value, match)))
644                         continue;
645
646                 if (!(newer = ast_variable_new(variable, value, cur->file)))
647                         return -1;
648         
649                 newer->next = cur->next;
650                 newer->object = cur->object || object;
651                 if (prev)
652                         prev->next = newer;
653                 else
654                         category->root = newer;
655                 if (category->last == cur)
656                         category->last = newer;
657
658                 cur->next = NULL;
659                 ast_variables_destroy(cur);
660
661                 return 0;
662         }
663
664         if (prev)
665                 prev->next = newer;
666         else
667                 category->root = newer;
668
669         return 0;
670 }
671
672 int ast_category_delete(struct ast_config *cfg, const char *category)
673 {
674         struct ast_category *prev=NULL, *cat;
675         cat = cfg->root;
676         while (cat) {
677                 if (cat->name == category) {
678                         ast_variables_destroy(cat->root);
679                         if (prev) {
680                                 prev->next = cat->next;
681                                 if (cat == cfg->last)
682                                         cfg->last = prev;
683                         } else {
684                                 cfg->root = cat->next;
685                                 if (cat == cfg->last)
686                                         cfg->last = NULL;
687                         }
688                         ast_free(cat);
689                         return 0;
690                 }
691                 prev = cat;
692                 cat = cat->next;
693         }
694
695         prev = NULL;
696         cat = cfg->root;
697         while (cat) {
698                 if (!strcasecmp(cat->name, category)) {
699                         ast_variables_destroy(cat->root);
700                         if (prev) {
701                                 prev->next = cat->next;
702                                 if (cat == cfg->last)
703                                         cfg->last = prev;
704                         } else {
705                                 cfg->root = cat->next;
706                                 if (cat == cfg->last)
707                                         cfg->last = NULL;
708                         }
709                         ast_free(cat);
710                         return 0;
711                 }
712                 prev = cat;
713                 cat = cat->next;
714         }
715         return -1;
716 }
717
718 void ast_config_destroy(struct ast_config *cfg)
719 {
720         struct ast_category *cat, *catn;
721
722         if (!cfg)
723                 return;
724
725         ast_includes_destroy(cfg->includes);
726
727         cat = cfg->root;
728         while (cat) {
729                 ast_variables_destroy(cat->root);
730                 catn = cat;
731                 cat = cat->next;
732                 ast_free(catn);
733         }
734         ast_free(cfg);
735 }
736
737 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
738 {
739         return cfg->current;
740 }
741
742 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
743 {
744         /* cast below is just to silence compiler warning about dropping "const" */
745         cfg->current = (struct ast_category *) cat;
746 }
747
748 enum config_cache_attribute_enum {
749         ATTRIBUTE_INCLUDE = 0,
750         ATTRIBUTE_EXEC = 1,
751 };
752
753 static void config_cache_attribute(const char *configfile, enum config_cache_attribute_enum attrtype, const char *filename)
754 {
755         struct cache_file_mtime *cfmtime;
756         struct cache_file_include *cfinclude;
757         struct stat statbuf = { 0, };
758
759         /* Find our cached entry for this configuration file */
760         AST_LIST_LOCK(&cfmtime_head);
761         AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
762                 if (!strcmp(cfmtime->filename, configfile))
763                         break;
764         }
765         if (!cfmtime) {
766                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(configfile) + 1);
767                 if (!cfmtime) {
768                         AST_LIST_UNLOCK(&cfmtime_head);
769                         return;
770                 }
771                 AST_LIST_HEAD_INIT(&cfmtime->includes);
772                 strcpy(cfmtime->filename, configfile);
773                 /* Note that the file mtime is initialized to 0, i.e. 1970 */
774                 AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
775         }
776
777         if (!stat(configfile, &statbuf))
778                 cfmtime->mtime = 0;
779         else
780                 cfmtime->mtime = statbuf.st_mtime;
781
782         switch (attrtype) {
783         case ATTRIBUTE_INCLUDE:
784                 cfinclude = ast_calloc(1, sizeof(*cfinclude) + strlen(filename) + 1);
785                 if (!cfinclude) {
786                         AST_LIST_UNLOCK(&cfmtime_head);
787                         return;
788                 }
789                 strcpy(cfinclude->include, filename);
790                 AST_LIST_INSERT_TAIL(&cfmtime->includes, cfinclude, list);
791                 break;
792         case ATTRIBUTE_EXEC:
793                 cfmtime->has_exec = 1;
794                 break;
795         }
796         AST_LIST_UNLOCK(&cfmtime_head);
797 }
798
799 static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, struct ast_flags flags,
800                                                          char **comment_buffer, int *comment_buffer_size, char **lline_buffer, int *lline_buffer_size, const char *suggested_include_file)
801 {
802         char *c;
803         char *cur = buf;
804         struct ast_variable *v;
805         char cmd[512], exec_file[512];
806         int object, do_exec, do_include;
807
808         /* Actually parse the entry */
809         if (cur[0] == '[') {
810                 struct ast_category *newcat = NULL;
811                 char *catname;
812
813                 /* A category header */
814                 c = strchr(cur, ']');
815                 if (!c) {
816                         ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
817                         return -1;
818                 }
819                 *c++ = '\0';
820                 cur++;
821                 if (*c++ != '(')
822                         c = NULL;
823                 catname = cur;
824                 if (!(*cat = newcat = ast_category_new(catname, ast_strlen_zero(suggested_include_file)?configfile:suggested_include_file, lineno))) {
825                         return -1;
826                 }
827                 (*cat)->lineno = lineno;
828                 
829                 /* add comments */
830                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
831                         newcat->precomments = ALLOC_COMMENT(*comment_buffer);
832                 }
833                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
834                         newcat->sameline = ALLOC_COMMENT(*lline_buffer);
835                 }
836                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
837                         CB_RESET(comment_buffer, lline_buffer);
838                 
839                 /* If there are options or categories to inherit from, process them now */
840                 if (c) {
841                         if (!(cur = strchr(c, ')'))) {
842                                 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
843                                 return -1;
844                         }
845                         *cur = '\0';
846                         while ((cur = strsep(&c, ","))) {
847                                 if (!strcasecmp(cur, "!")) {
848                                         (*cat)->ignored = 1;
849                                 } else if (!strcasecmp(cur, "+")) {
850                                         *cat = category_get(cfg, catname, 1);
851                                         if (!(*cat)) {
852                                                 if (newcat)
853                                                         ast_category_destroy(newcat);
854                                                 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
855                                                 return -1;
856                                         }
857                                         if (newcat) {
858                                                 move_variables(newcat, *cat);
859                                                 ast_category_destroy(newcat);
860                                                 newcat = NULL;
861                                         }
862                                 } else {
863                                         struct ast_category *base;
864                                 
865                                         base = category_get(cfg, cur, 1);
866                                         if (!base) {
867                                                 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
868                                                 return -1;
869                                         }
870                                         inherit_category(*cat, base);
871                                 }
872                         }
873                 }
874                 if (newcat)
875                         ast_category_append(cfg, *cat);
876         } else if (cur[0] == '#') {
877                 /* A directive */
878                 cur++;
879                 c = cur;
880                 while (*c && (*c > 32)) c++;
881                 if (*c) {
882                         *c = '\0';
883                         /* Find real argument */
884                         c = ast_skip_blanks(c + 1);
885                         if (!(*c))
886                                 c = NULL;
887                 } else 
888                         c = NULL;
889                 do_include = !strcasecmp(cur, "include");
890                 if (!do_include)
891                         do_exec = !strcasecmp(cur, "exec");
892                 else
893                         do_exec = 0;
894                 if (do_exec && !ast_opt_exec_includes) {
895                         ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
896                         do_exec = 0;
897                 }
898                 if (do_include || do_exec) {
899                         if (c) {
900                                 char *cur2;
901                                 char real_inclusion_name[256];
902                                 struct ast_config_include *inclu;
903                                 
904                                 /* Strip off leading and trailing "'s and <>'s */
905                                 while ((*c == '<') || (*c == '>') || (*c == '\"')) c++;
906                                 /* Get rid of leading mess */
907                                 cur = c;
908                                 cur2 = cur;
909                                 while (!ast_strlen_zero(cur)) {
910                                         c = cur + strlen(cur) - 1;
911                                         if ((*c == '>') || (*c == '<') || (*c == '\"'))
912                                                 *c = '\0';
913                                         else
914                                                 break;
915                                 }
916                                 /* #exec </path/to/executable>
917                                    We create a tmp file, then we #include it, then we delete it. */
918                                 if (do_exec) {
919                                         if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
920                                                 config_cache_attribute(configfile, ATTRIBUTE_EXEC, NULL);
921                                         snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self());
922                                         snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
923                                         ast_safe_system(cmd);
924                                         cur = exec_file;
925                                 } else {
926                                         if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
927                                                 config_cache_attribute(configfile, ATTRIBUTE_INCLUDE, cur);
928                                         exec_file[0] = '\0';
929                                 }
930                                 /* A #include */
931                                 /* record this inclusion */
932                                 inclu = ast_include_new(cfg, configfile, cur, do_exec, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
933
934                                 do_include = ast_config_internal_load(cur, cfg, flags, real_inclusion_name) ? 1 : 0;
935                                 if (!ast_strlen_zero(exec_file))
936                                         unlink(exec_file);
937                                 if (!do_include)
938                                         return 0;
939
940                         } else {
941                                 ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n", 
942                                                 do_exec ? "exec" : "include",
943                                                 do_exec ? "/path/to/executable" : "filename",
944                                                 lineno,
945                                                 configfile);
946                         }
947                 }
948                 else 
949                         ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
950         } else {
951                 /* Just a line (variable = value) */
952                 if (!(*cat)) {
953                         ast_log(LOG_WARNING,
954                                 "parse error: No category context for line %d of %s\n", lineno, configfile);
955                         return -1;
956                 }
957                 c = strchr(cur, '=');
958                 if (c) {
959                         *c = 0;
960                         c++;
961                         /* Ignore > in => */
962                         if (*c== '>') {
963                                 object = 1;
964                                 c++;
965                         } else
966                                 object = 0;
967                         if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), *suggested_include_file ? suggested_include_file : configfile))) {
968                                 v->lineno = lineno;
969                                 v->object = object;
970                                 /* Put and reset comments */
971                                 v->blanklines = 0;
972                                 ast_variable_append(*cat, v);
973                                 /* add comments */
974                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *comment_buffer && (*comment_buffer)[0] ) {
975                                         v->precomments = ALLOC_COMMENT(*comment_buffer);
976                                 }
977                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && *lline_buffer && (*lline_buffer)[0] ) {
978                                         v->sameline = ALLOC_COMMENT(*lline_buffer);
979                                 }
980                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS))
981                                         CB_RESET(comment_buffer, lline_buffer);
982                                 
983                         } else {
984                                 return -1;
985                         }
986                 } else {
987                         ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
988                 }
989         }
990         return 0;
991 }
992
993 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)
994 {
995         char fn[256];
996         char buf[8192];
997         char *new_buf, *comment_p, *process_buf;
998         FILE *f;
999         int lineno=0;
1000         int comment = 0, nest[MAX_NESTED_COMMENTS];
1001         struct ast_category *cat = NULL;
1002         int count = 0;
1003         struct stat statbuf;
1004         struct cache_file_mtime *cfmtime = NULL;
1005         struct cache_file_include *cfinclude;
1006         /*! Growable string buffer */
1007         char *comment_buffer=0;   /*!< this will be a comment collector.*/
1008         int   comment_buffer_size=0;  /*!< the amount of storage so far alloc'd for the comment_buffer */
1009
1010         char *lline_buffer=0;    /*!< A buffer for stuff behind the ; */
1011         int  lline_buffer_size=0;
1012
1013         if (cfg)
1014                 cat = ast_config_get_current_category(cfg);
1015
1016         if (filename[0] == '/') {
1017                 ast_copy_string(fn, filename, sizeof(fn));
1018         } else {
1019                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename);
1020         }
1021
1022         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1023                 CB_INIT(&comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size);
1024                 if (!lline_buffer || !comment_buffer) {
1025                         ast_log(LOG_ERROR, "Failed to initialize the comment buffer!\n");
1026                         return NULL;
1027                 }
1028         }
1029 #ifdef AST_INCLUDE_GLOB
1030         {
1031                 int glob_ret;
1032                 glob_t globbuf;
1033                 globbuf.gl_offs = 0;    /* initialize it to silence gcc */
1034 #ifdef SOLARIS
1035                 glob_ret = glob(fn, GLOB_NOCHECK, NULL, &globbuf);
1036 #else
1037                 glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
1038 #endif
1039                 if (glob_ret == GLOB_NOSPACE)
1040                         ast_log(LOG_WARNING,
1041                                 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
1042                 else if (glob_ret  == GLOB_ABORTED)
1043                         ast_log(LOG_WARNING,
1044                                 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
1045                 else  {
1046                         /* loop over expanded files */
1047                         int i;
1048                         for (i=0; i<globbuf.gl_pathc; i++) {
1049                                 ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
1050 #endif
1051         do {
1052                 if (stat(fn, &statbuf))
1053                         continue;
1054
1055                 if (!S_ISREG(statbuf.st_mode)) {
1056                         ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
1057                         continue;
1058                 }
1059
1060                 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE)) {
1061                         /* Find our cached entry for this configuration file */
1062                         AST_LIST_LOCK(&cfmtime_head);
1063                         AST_LIST_TRAVERSE(&cfmtime_head, cfmtime, list) {
1064                                 if (!strcmp(cfmtime->filename, fn))
1065                                         break;
1066                         }
1067                         if (!cfmtime) {
1068                                 cfmtime = ast_calloc(1, sizeof(*cfmtime) + strlen(fn) + 1);
1069                                 if (!cfmtime)
1070                                         continue;
1071                                 AST_LIST_HEAD_INIT(&cfmtime->includes);
1072                                 strcpy(cfmtime->filename, fn);
1073                                 /* Note that the file mtime is initialized to 0, i.e. 1970 */
1074                                 AST_LIST_INSERT_TAIL(&cfmtime_head, cfmtime, list);
1075                         }
1076                 }
1077
1078                 if (cfmtime && (!cfmtime->has_exec) && (cfmtime->mtime == statbuf.st_mtime) && ast_test_flag(&flags, CONFIG_FLAG_FILEUNCHANGED)) {
1079                         /* File is unchanged, what about the (cached) includes (if any)? */
1080                         int unchanged = 1;
1081                         AST_LIST_TRAVERSE(&cfmtime->includes, cfinclude, list) {
1082                                 /* We must glob here, because if we did not, then adding a file to globbed directory would
1083                                  * incorrectly cause no reload to be necessary. */
1084                                 char fn2[256];
1085 #ifdef AST_INCLUDE_GLOB
1086                                 int glob_ret;
1087                                 glob_t globbuf = { .gl_offs = 0 };
1088 #ifdef SOLARIS
1089                                 glob_ret = glob(cfinclude->include, GLOB_NOCHECK, NULL, &globbuf);
1090 #else
1091                                 glob_ret = glob(cfinclude->include, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
1092 #endif
1093                                 /* On error, we reparse */
1094                                 if (glob_ret == GLOB_NOSPACE || glob_ret  == GLOB_ABORTED)
1095                                         unchanged = 0;
1096                                 else  {
1097                                         /* loop over expanded files */
1098                                         int j;
1099                                         for (j = 0; j < globbuf.gl_pathc; j++) {
1100                                                 ast_copy_string(fn2, globbuf.gl_pathv[j], sizeof(fn2));
1101 #else
1102                                                 ast_copy_string(fn2, cfinclude->include);
1103 #endif
1104                                                 if (config_text_file_load(NULL, NULL, fn2, NULL, flags, "") == NULL) { /* that last field needs to be looked at in this case... TODO */
1105                                                         unchanged = 0;
1106                                                         /* One change is enough to short-circuit and reload the whole shebang */
1107                                                         break;
1108                                                 }
1109 #ifdef AST_INCLUDE_GLOB
1110                                         }
1111                                 }
1112 #endif
1113                         }
1114
1115                         if (unchanged) {
1116                                 AST_LIST_UNLOCK(&cfmtime_head);
1117                                 return CONFIG_STATUS_FILEUNCHANGED;
1118                         }
1119                 }
1120                 if (!ast_test_flag(&flags, CONFIG_FLAG_NOCACHE))
1121                         AST_LIST_UNLOCK(&cfmtime_head);
1122
1123                 /* If cfg is NULL, then we just want an answer */
1124                 if (cfg == NULL)
1125                         return NULL;
1126
1127                 if (cfmtime)
1128                         cfmtime->mtime = statbuf.st_mtime;
1129
1130                 ast_verb(2, "Parsing '%s': ", fn);
1131                         fflush(stdout);
1132                 if (!(f = fopen(fn, "r"))) {
1133                         ast_debug(1, "No file to parse: %s\n", fn);
1134                         ast_verb(2, "Not found (%s)\n", strerror(errno));
1135                         continue;
1136                 }
1137                 count++;
1138                 /* If we get to this point, then we're loading regardless */
1139                 ast_clear_flag(&flags, CONFIG_FLAG_FILEUNCHANGED);
1140                 ast_debug(1, "Parsing %s\n", fn);
1141                 ast_verb(2, "Found\n");
1142                 while (!feof(f)) {
1143                         lineno++;
1144                         if (fgets(buf, sizeof(buf), f)) {
1145                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1146                                         CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
1147                                         lline_buffer[0] = 0;        /* erase the lline buffer */
1148                                 }
1149                                 
1150                                 new_buf = buf;
1151                                 if (comment) 
1152                                         process_buf = NULL;
1153                                 else
1154                                         process_buf = buf;
1155                                 
1156                                 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1157                                         if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
1158                                                 /* Escaped semicolons aren't comments. */
1159                                                 new_buf = comment_p + 1;
1160                                         } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1161                                                 /* Meta-Comment start detected ";--" */
1162                                                 if (comment < MAX_NESTED_COMMENTS) {
1163                                                         *comment_p = '\0';
1164                                                         new_buf = comment_p + 3;
1165                                                         comment++;
1166                                                         nest[comment-1] = lineno;
1167                                                 } else {
1168                                                         ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1169                                                 }
1170                                         } else if ((comment_p >= new_buf + 2) &&
1171                                                    (*(comment_p - 1) == COMMENT_TAG) &&
1172                                                    (*(comment_p - 2) == COMMENT_TAG)) {
1173                                                 /* Meta-Comment end detected */
1174                                                 comment--;
1175                                                 new_buf = comment_p + 1;
1176                                                 if (!comment) {
1177                                                         /* Back to non-comment now */
1178                                                         if (process_buf) {
1179                                                                 /* Actually have to move what's left over the top, then continue */
1180                                                                 char *oldptr;
1181                                                                 oldptr = process_buf + strlen(process_buf);
1182                                                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1183                                                                         CB_ADD(&comment_buffer, &comment_buffer_size, ";");
1184                                                                         CB_ADD_LEN(&comment_buffer, &comment_buffer_size, oldptr+1, new_buf-oldptr-1);
1185                                                                 }
1186                                                                 
1187                                                                 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1188                                                                 new_buf = oldptr;
1189                                                         } else
1190                                                                 process_buf = new_buf;
1191                                                 }
1192                                         } else {
1193                                                 if (!comment) {
1194                                                         /* If ; is found, and we are not nested in a comment, 
1195                                                            we immediately stop all comment processing */
1196                                                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1197                                                                 LLB_ADD(&lline_buffer, &lline_buffer_size, comment_p);
1198                                                         }
1199                                                         *comment_p = '\0'; 
1200                                                         new_buf = comment_p;
1201                                                 } else
1202                                                         new_buf = comment_p + 1;
1203                                         }
1204                                 }
1205                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf )
1206                                 {
1207                                         CB_ADD(&comment_buffer, &comment_buffer_size, buf);  /* the whole line is a comment, store it */
1208                                 }
1209                                 
1210                                 if (process_buf) {
1211                                         char *buf = ast_strip(process_buf);
1212                                         if (!ast_strlen_zero(buf)) {
1213                                                 if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file)) {
1214                                                         cfg = NULL;
1215                                                         break;
1216                                                 }
1217                                         }
1218                                 }
1219                         }
1220                 }
1221                 fclose(f);              
1222         } while (0);
1223         if (comment) {
1224                 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1225         }
1226 #ifdef AST_INCLUDE_GLOB
1227                                         if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
1228                                                 break;
1229                                 }
1230                                 globfree(&globbuf);
1231                         }
1232                 }
1233 #endif
1234
1235         if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer) {
1236                 ast_free(comment_buffer);
1237                 ast_free(lline_buffer);
1238                 comment_buffer = NULL;
1239                 lline_buffer = NULL;
1240                 comment_buffer_size = 0;
1241                 lline_buffer_size = 0;
1242         }
1243         
1244         if (count == 0)
1245                 return NULL;
1246
1247         return cfg;
1248 }
1249
1250
1251 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1252    which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1253    recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1254    be shocked and mystified as to why things are not showing up in the files! 
1255
1256    Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1257    and line number are stored for each include, plus the name of the file included, so that these statements may be
1258    included in the output files on a file_save operation. 
1259
1260    The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1261    are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1262    the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1263    and a header gets added.
1264
1265    vars and category heads are output in the order they are stored in the config file. So, if the software
1266    shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1267    file/lineno data probably won't get changed.
1268
1269 */
1270
1271 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1272 {
1273         char date[256]="";
1274         time_t t;
1275         time(&t);
1276         ast_copy_string(date, ctime(&t), sizeof(date));
1277
1278         fprintf(f1, ";!\n");
1279         fprintf(f1, ";! Automatically generated configuration file\n");
1280         if (strcmp(configfile, fn))
1281                 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1282         else
1283                 fprintf(f1, ";! Filename: %s\n", configfile);
1284         fprintf(f1, ";! Generator: %s\n", generator);
1285         fprintf(f1, ";! Creation Date: %s", date);
1286         fprintf(f1, ";!\n");
1287 }
1288
1289 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile)
1290 {
1291         if (!file || file[0] == 0) {
1292                 if (configfile[0] == '/')
1293                         ast_copy_string(fn, configfile, fn_size);
1294                 else
1295                         snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1296         } else if (file[0] == '/') 
1297                 ast_copy_string(fn, file, fn_size);
1298         else
1299                 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1300 }
1301
1302 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1303 {
1304         FILE *f;
1305         char fn[256];
1306         struct ast_variable *var;
1307         struct ast_category *cat;
1308         struct ast_comment *cmt;
1309         struct ast_config_include *incl;
1310         int blanklines = 0;
1311
1312         /* reset all the output flags, in case this isn't our first time saving this data */
1313
1314         for (incl=cfg->includes; incl; incl = incl->next)
1315                 incl->output = 0;
1316
1317         /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1318            are all truncated to zero bytes and have that nice header*/
1319
1320         for (incl=cfg->includes; incl; incl = incl->next)
1321         {
1322                 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*/
1323                         FILE *f1;
1324
1325                         set_fn(fn, sizeof(fn), incl->included_file, configfile); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
1326                         f1 = fopen(fn,"w");
1327                         if (f1) {
1328                                 gen_header(f1, configfile, fn, generator);
1329                                 fclose(f1); /* this should zero out the file */
1330                         } else {
1331                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1332                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1333                         }
1334                 }
1335         }
1336
1337         set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
1338 #ifdef __CYGWIN__       
1339         if ((f = fopen(fn, "w+"))) {
1340 #else
1341         if ((f = fopen(fn, "w"))) {
1342 #endif      
1343                 ast_verb(2, "Saving '%s': ", fn);
1344                 gen_header(f, configfile, fn, generator);
1345                 cat = cfg->root;
1346                 fclose(f);
1347                 
1348                 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1349                 /* since each var, cat, and associated comments can come from any file, we have to be 
1350                    mobile, and open each file, print, and close it on an entry-by-entry basis */
1351
1352                 while (cat) {
1353                         set_fn(fn, sizeof(fn), cat->file, configfile);
1354                         f = fopen(fn, "a");
1355                         if (!f)
1356                         {
1357                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1358                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1359                                 return -1;
1360                         }
1361
1362                         /* dump any includes that happen before this category header */
1363                         for (incl=cfg->includes; incl; incl = incl->next) {
1364                                 if (strcmp(incl->include_location_file, cat->file) == 0){
1365                                         if (cat->lineno > incl->include_location_lineno && !incl->output) {
1366                                                 if (incl->exec)
1367                                                         fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1368                                                 else
1369                                                         fprintf(f,"#include \"%s\"\n", incl->included_file);
1370                                                 incl->output = 1;
1371                                         }
1372                                 }
1373                         }
1374                         
1375                         /* Dump section with any appropriate comment */
1376                         for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1377                                 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1378                                         fprintf(f,"%s", cmt->cmt);
1379                         }
1380                         if (!cat->precomments)
1381                                 fprintf(f,"\n");
1382                         fprintf(f, "[%s]", cat->name);
1383                         for (cmt = cat->sameline; cmt; cmt=cmt->next) {
1384                                 fprintf(f,"%s", cmt->cmt);
1385                         }
1386                         if (!cat->sameline)
1387                                 fprintf(f,"\n");
1388                         fclose(f);
1389                         
1390                         var = cat->root;
1391                         while (var) {
1392                                 set_fn(fn, sizeof(fn), var->file, configfile);
1393                                 f = fopen(fn, "a");
1394                                 if (!f)
1395                                 {
1396                                         ast_debug(1, "Unable to open for writing: %s\n", fn);
1397                                         ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1398                                         return -1;
1399                                 }
1400                                 
1401                                 /* dump any includes that happen before this category header */
1402                                 for (incl=cfg->includes; incl; incl = incl->next) {
1403                                         if (strcmp(incl->include_location_file, var->file) == 0){
1404                                                 if (var->lineno > incl->include_location_lineno && !incl->output) {
1405                                                         if (incl->exec)
1406                                                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1407                                                         else
1408                                                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1409                                                         incl->output = 1;
1410                                                 }
1411                                         }
1412                                 }
1413                                 
1414                                 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1415                                         if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1416                                                 fprintf(f,"%s", cmt->cmt);
1417                                 }
1418                                 if (var->sameline) 
1419                                         fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1420                                 else    
1421                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1422                                 if (var->blanklines) {
1423                                         blanklines = var->blanklines;
1424                                         while (blanklines--)
1425                                                 fprintf(f, "\n");
1426                                 }
1427                                 
1428                                 fclose(f);
1429                                 
1430                                 var = var->next;
1431                         }
1432                         cat = cat->next;
1433                 }
1434                 if (!option_debug)
1435                         ast_verb(2, "Saved\n");
1436         } else {
1437                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1438                 ast_verb(2, "Unable to write (%s)", strerror(errno));
1439                 return -1;
1440         }
1441
1442         /* Now, for files with trailing #include/#exec statements,
1443            we have to make sure every entry is output */
1444
1445         for (incl=cfg->includes; incl; incl = incl->next) {
1446                 if (!incl->output) {
1447                         /* open the respective file */
1448                         set_fn(fn, sizeof(fn), incl->include_location_file, configfile);
1449                         f = fopen(fn, "a");
1450                         if (!f)
1451                         {
1452                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1453                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1454                                 return -1;
1455                         }
1456                         
1457                         /* output the respective include */
1458                         if (incl->exec)
1459                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1460                         else
1461                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1462                         fclose(f);
1463                         incl->output = 1;
1464                 }
1465         }
1466                                 
1467         return 0;
1468 }
1469
1470 static void clear_config_maps(void) 
1471 {
1472         struct ast_config_map *map;
1473
1474         ast_mutex_lock(&config_lock);
1475
1476         while (config_maps) {
1477                 map = config_maps;
1478                 config_maps = config_maps->next;
1479                 ast_free(map);
1480         }
1481                 
1482         ast_mutex_unlock(&config_lock);
1483 }
1484
1485 static int append_mapping(char *name, char *driver, char *database, char *table)
1486 {
1487         struct ast_config_map *map;
1488         int length;
1489
1490         length = sizeof(*map);
1491         length += strlen(name) + 1;
1492         length += strlen(driver) + 1;
1493         length += strlen(database) + 1;
1494         if (table)
1495                 length += strlen(table) + 1;
1496
1497         if (!(map = ast_calloc(1, length)))
1498                 return -1;
1499
1500         map->name = map->stuff;
1501         strcpy(map->name, name);
1502         map->driver = map->name + strlen(map->name) + 1;
1503         strcpy(map->driver, driver);
1504         map->database = map->driver + strlen(map->driver) + 1;
1505         strcpy(map->database, database);
1506         if (table) {
1507                 map->table = map->database + strlen(map->database) + 1;
1508                 strcpy(map->table, table);
1509         }
1510         map->next = config_maps;
1511
1512         ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1513
1514         config_maps = map;
1515         return 0;
1516 }
1517
1518 int read_config_maps(void) 
1519 {
1520         struct ast_config *config, *configtmp;
1521         struct ast_variable *v;
1522         char *driver, *table, *database, *stringp, *tmp;
1523         struct ast_flags flags = { 0 };
1524
1525         clear_config_maps();
1526
1527         configtmp = ast_config_new();
1528         configtmp->max_include_level = 1;
1529         config = ast_config_internal_load(extconfig_conf, configtmp, flags, "");
1530         if (!config) {
1531                 ast_config_destroy(configtmp);
1532                 return 0;
1533         }
1534
1535         for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1536                 stringp = v->value;
1537                 driver = strsep(&stringp, ",");
1538
1539                 if ((tmp = strchr(stringp, '\"')))
1540                         stringp = tmp;
1541
1542                 /* check if the database text starts with a double quote */
1543                 if (*stringp == '"') {
1544                         stringp++;
1545                         database = strsep(&stringp, "\"");
1546                         strsep(&stringp, ",");
1547                 } else {
1548                         /* apparently this text has no quotes */
1549                         database = strsep(&stringp, ",");
1550                 }
1551
1552                 table = strsep(&stringp, ",");
1553
1554                 if (!strcmp(v->name, extconfig_conf)) {
1555                         ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1556                         continue;
1557                 }
1558
1559                 if (!strcmp(v->name, "asterisk.conf")) {
1560                         ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1561                         continue;
1562                 }
1563
1564                 if (!strcmp(v->name, "logger.conf")) {
1565                         ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1566                         continue;
1567                 }
1568
1569                 if (!driver || !database)
1570                         continue;
1571                 if (!strcasecmp(v->name, "sipfriends")) {
1572                         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");
1573                         append_mapping("sipusers", driver, database, table ? table : "sipfriends");
1574                         append_mapping("sippeers", driver, database, table ? table : "sipfriends");
1575                 } else if (!strcasecmp(v->name, "iaxfriends")) {
1576                         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");
1577                         append_mapping("iaxusers", driver, database, table ? table : "iaxfriends");
1578                         append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends");
1579                 } else 
1580                         append_mapping(v->name, driver, database, table);
1581         }
1582                 
1583         ast_config_destroy(config);
1584         return 0;
1585 }
1586
1587 int ast_config_engine_register(struct ast_config_engine *new) 
1588 {
1589         struct ast_config_engine *ptr;
1590
1591         ast_mutex_lock(&config_lock);
1592
1593         if (!config_engine_list) {
1594                 config_engine_list = new;
1595         } else {
1596                 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1597                 ptr->next = new;
1598         }
1599
1600         ast_mutex_unlock(&config_lock);
1601         ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1602
1603         return 1;
1604 }
1605
1606 int ast_config_engine_deregister(struct ast_config_engine *del) 
1607 {
1608         struct ast_config_engine *ptr, *last=NULL;
1609
1610         ast_mutex_lock(&config_lock);
1611
1612         for (ptr = config_engine_list; ptr; ptr=ptr->next) {
1613                 if (ptr == del) {
1614                         if (last)
1615                                 last->next = ptr->next;
1616                         else
1617                                 config_engine_list = ptr->next;
1618                         break;
1619                 }
1620                 last = ptr;
1621         }
1622
1623         ast_mutex_unlock(&config_lock);
1624
1625         return 0;
1626 }
1627
1628 /*! \brief Find realtime engine for realtime family */
1629 static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) 
1630 {
1631         struct ast_config_engine *eng, *ret = NULL;
1632         struct ast_config_map *map;
1633
1634         ast_mutex_lock(&config_lock);
1635
1636         for (map = config_maps; map; map = map->next) {
1637                 if (!strcasecmp(family, map->name)) {
1638                         if (database)
1639                                 ast_copy_string(database, map->database, dbsiz);
1640                         if (table)
1641                                 ast_copy_string(table, map->table ? map->table : family, tabsiz);
1642                         break;
1643                 }
1644         }
1645
1646         /* Check if the required driver (engine) exist */
1647         if (map) {
1648                 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
1649                         if (!strcasecmp(eng->name, map->driver))
1650                                 ret = eng;
1651                 }
1652         }
1653
1654         ast_mutex_unlock(&config_lock);
1655         
1656         /* if we found a mapping, but the engine is not available, then issue a warning */
1657         if (map && !ret)
1658                 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
1659
1660         return ret;
1661 }
1662
1663 static struct ast_config_engine text_file_engine = {
1664         .name = "text",
1665         .load_func = config_text_file_load,
1666 };
1667
1668 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
1669 {
1670         char db[256];
1671         char table[256];
1672         struct ast_config_engine *loader = &text_file_engine;
1673         struct ast_config *result; 
1674
1675         if (cfg->include_level == cfg->max_include_level) {
1676                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
1677                 return NULL;
1678         }
1679
1680         cfg->include_level++;
1681
1682         if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
1683                 struct ast_config_engine *eng;
1684
1685                 eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
1686
1687
1688                 if (eng && eng->load_func) {
1689                         loader = eng;
1690                 } else {
1691                         eng = find_engine("global", db, sizeof(db), table, sizeof(table));
1692                         if (eng && eng->load_func)
1693                                 loader = eng;
1694                 }
1695         }
1696
1697         result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file);
1698
1699         if (result && result != CONFIG_STATUS_FILEUNCHANGED)
1700                 result->include_level--;
1701         else
1702                 cfg->include_level--;
1703
1704         return result;
1705 }
1706
1707 struct ast_config *ast_config_load(const char *filename, struct ast_flags flags)
1708 {
1709         struct ast_config *cfg;
1710         struct ast_config *result;
1711
1712         cfg = ast_config_new();
1713         if (!cfg)
1714                 return NULL;
1715
1716         result = ast_config_internal_load(filename, cfg, flags, "");
1717         if (!result || result == CONFIG_STATUS_FILEUNCHANGED)
1718                 ast_config_destroy(cfg);
1719
1720         return result;
1721 }
1722
1723 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
1724 {
1725         struct ast_config_engine *eng;
1726         char db[256]="";
1727         char table[256]="";
1728         struct ast_variable *res=NULL;
1729
1730         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1731         if (eng && eng->realtime_func) 
1732                 res = eng->realtime_func(db, table, ap);
1733
1734         return res;
1735 }
1736
1737 struct ast_variable *ast_load_realtime_all(const char *family, ...)
1738 {
1739         struct ast_variable *res;
1740         va_list ap;
1741
1742         va_start(ap, family);
1743         res = ast_load_realtime_helper(family, ap);
1744         va_end(ap);
1745
1746         return res;
1747 }
1748
1749 struct ast_variable *ast_load_realtime(const char *family, ...)
1750 {
1751         struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
1752         va_list ap;
1753
1754         va_start(ap, family);
1755         res = ast_load_realtime_helper(family, ap);
1756         va_end(ap);
1757
1758         /* Eliminate blank entries */
1759         for (cur = res; cur; cur = cur->next) {
1760                 if (freeme) {
1761                         ast_free(freeme);
1762                         freeme = NULL;
1763                 }
1764
1765                 if (ast_strlen_zero(cur->value)) {
1766                         if (prev)
1767                                 prev->next = cur->next;
1768                         else
1769                                 res = cur->next;
1770                         freeme = cur;
1771                 } else {
1772                         prev = cur;
1773                 }
1774         }
1775         return res;
1776 }
1777
1778 /*! \brief Check if realtime engine is configured for family */
1779 int ast_check_realtime(const char *family)
1780 {
1781         struct ast_config_engine *eng;
1782
1783         eng = find_engine(family, NULL, 0, NULL, 0);
1784         if (eng)
1785                 return 1;
1786         return 0;
1787
1788 }
1789
1790 /*! \brief Check if there's any realtime engines loaded */
1791 int ast_realtime_enabled()
1792 {
1793         return config_maps ? 1 : 0;
1794 }
1795
1796 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
1797 {
1798         struct ast_config_engine *eng;
1799         char db[256]="";
1800         char table[256]="";
1801         struct ast_config *res=NULL;
1802         va_list ap;
1803
1804         va_start(ap, family);
1805         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1806         if (eng && eng->realtime_multi_func) 
1807                 res = eng->realtime_multi_func(db, table, ap);
1808         va_end(ap);
1809
1810         return res;
1811 }
1812
1813 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
1814 {
1815         struct ast_config_engine *eng;
1816         int res = -1;
1817         char db[256]="";
1818         char table[256]="";
1819         va_list ap;
1820
1821         va_start(ap, lookup);
1822         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1823         if (eng && eng->update_func) 
1824                 res = eng->update_func(db, table, keyfield, lookup, ap);
1825         va_end(ap);
1826
1827         return res;
1828 }
1829
1830 int ast_store_realtime(const char *family, ...) {
1831         struct ast_config_engine *eng;
1832         int res = -1;
1833         char db[256]="";
1834         char table[256]="";
1835         va_list ap;
1836
1837         va_start(ap, family);
1838         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1839         if (eng && eng->store_func) 
1840                 res = eng->store_func(db, table, ap);
1841         va_end(ap);
1842
1843         return res;
1844 }
1845
1846 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) {
1847         struct ast_config_engine *eng;
1848         int res = -1;
1849         char db[256]="";
1850         char table[256]="";
1851         va_list ap;
1852
1853         va_start(ap, lookup);
1854         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1855         if (eng && eng->destroy_func) 
1856                 res = eng->destroy_func(db, table, keyfield, lookup, ap);
1857         va_end(ap);
1858
1859         return res;
1860 }
1861
1862 /*! \brief Helper function to parse arguments
1863  * See documentation in config.h
1864  */
1865 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
1866         void *p_result, ...)
1867 {
1868         va_list ap;
1869         int error = 0;
1870
1871         va_start(ap, p_result);
1872         switch (flags & PARSE_TYPE) {
1873         case PARSE_INT32:
1874             {
1875                 int32_t *result = p_result;
1876                 int32_t x, def = result ? *result : 0,
1877                         high = (int32_t)0x7fffffff,
1878                         low  = (int32_t)0x80000000;
1879                 /* optional argument: first default value, then range */
1880                 if (flags & PARSE_DEFAULT)
1881                         def = va_arg(ap, int32_t);
1882                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1883                         /* range requested, update bounds */
1884                         low = va_arg(ap, int32_t);
1885                         high = va_arg(ap, int32_t);
1886                 }
1887                 x = strtol(arg, NULL, 0);
1888                 error = (x < low) || (x > high);
1889                 if (flags & PARSE_OUT_RANGE)
1890                         error = !error;
1891                 if (result)
1892                         *result  = error ? def : x;
1893                 ast_debug(3,
1894                         "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
1895                         arg, low, high,
1896                         result ? *result : x, error);
1897                 break;
1898             }
1899
1900         case PARSE_UINT32:
1901             {
1902                 uint32_t *result = p_result;
1903                 uint32_t x, def = result ? *result : 0,
1904                         low = 0, high = (uint32_t)~0;
1905                 /* optional argument: first default value, then range */
1906                 if (flags & PARSE_DEFAULT)
1907                         def = va_arg(ap, uint32_t);
1908                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1909                         /* range requested, update bounds */
1910                         low = va_arg(ap, uint32_t);
1911                         high = va_arg(ap, uint32_t);
1912                 }
1913                 x = strtoul(arg, NULL, 0);
1914                 error = (x < low) || (x > high);
1915                 if (flags & PARSE_OUT_RANGE)
1916                         error = !error;
1917                 if (result)
1918                         *result  = error ? def : x;
1919                 ast_debug(3,
1920                         "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
1921                         arg, low, high,
1922                         result ? *result : x, error);
1923                 break;
1924             }
1925
1926         case PARSE_INADDR:
1927             {
1928                 char *port, *buf;
1929                 struct sockaddr_in _sa_buf;     /* buffer for the result */
1930                 struct sockaddr_in *sa = p_result ?
1931                         (struct sockaddr_in *)p_result : &_sa_buf;
1932                 /* default is either the supplied value or the result itself */
1933                 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
1934                         va_arg(ap, struct sockaddr_in *) : sa;
1935                 struct hostent *hp;
1936                 struct ast_hostent ahp;
1937
1938                 bzero(&_sa_buf, sizeof(_sa_buf)); /* clear buffer */
1939                 /* duplicate the string to strip away the :port */
1940                 port = ast_strdupa(arg);
1941                 buf = strsep(&port, ":");
1942                 sa->sin_family = AF_INET;       /* assign family */
1943                 /*
1944                  * honor the ports flag setting, assign default value
1945                  * in case of errors or field unset.
1946                  */
1947                 flags &= PARSE_PORT_MASK; /* the only flags left to process */
1948                 if (port) {
1949                         if (flags == PARSE_PORT_FORBID) {
1950                                 error = 1;      /* port was forbidden */
1951                                 sa->sin_port = def->sin_port;
1952                         } else if (flags == PARSE_PORT_IGNORE)
1953                                 sa->sin_port = def->sin_port;
1954                         else /* accept or require */
1955                                 sa->sin_port = htons(strtol(port, NULL, 0));
1956                 } else {
1957                         sa->sin_port = def->sin_port;
1958                         if (flags == PARSE_PORT_REQUIRE)
1959                                 error = 1;
1960                 }
1961                 /* Now deal with host part, even if we have errors before. */
1962                 hp = ast_gethostbyname(buf, &ahp);
1963                 if (hp) /* resolved successfully */
1964                         memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
1965                 else {
1966                         error = 1;
1967                         sa->sin_addr = def->sin_addr;
1968                 }
1969                 ast_debug(3,
1970                         "extract inaddr from [%s] gives [%s:%d](%d)\n",
1971                         arg, ast_inet_ntoa(sa->sin_addr),
1972                         ntohs(sa->sin_port), error);
1973                 break;
1974             }
1975         }
1976         va_end(ap);
1977         return error;
1978 }
1979
1980 static int config_command(int fd, int argc, char **argv) 
1981 {
1982         struct ast_config_engine *eng;
1983         struct ast_config_map *map;
1984         
1985         ast_mutex_lock(&config_lock);
1986
1987         ast_cli(fd, "\n\n");
1988         for (eng = config_engine_list; eng; eng = eng->next) {
1989                 ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
1990                 for (map = config_maps; map; map = map->next)
1991                         if (!strcasecmp(map->driver, eng->name)) {
1992                                 ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
1993                                         map->table ? map->table : map->name);
1994                         }
1995         }
1996         ast_cli(fd,"\n\n");
1997         
1998         ast_mutex_unlock(&config_lock);
1999
2000         return 0;
2001 }
2002
2003 static char show_config_help[] =
2004         "Usage: core show config mappings\n"
2005         "       Shows the filenames to config engines.\n";
2006
2007 static struct ast_cli_entry cli_config[] = {
2008         { { "core", "show", "config", "mappings", NULL },
2009         config_command, "Display config mappings (file names to config engines)",
2010         show_config_help },
2011 };
2012
2013 int register_config_cli() 
2014 {
2015         ast_cli_register_multiple(cli_config, sizeof(cli_config) / sizeof(struct ast_cli_entry));
2016         return 0;
2017 }