5889a0358d2056df50bcdb7de78a996e12ce5607
[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                 ast_debug(1, "Parsing %s\n", fn);
1139                 ast_verb(2, "Found\n");
1140                 while (!feof(f)) {
1141                         lineno++;
1142                         if (fgets(buf, sizeof(buf), f)) {
1143                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1144                                         CB_ADD(&comment_buffer, &comment_buffer_size, lline_buffer);       /* add the current lline buffer to the comment buffer */
1145                                         lline_buffer[0] = 0;        /* erase the lline buffer */
1146                                 }
1147                                 
1148                                 new_buf = buf;
1149                                 if (comment) 
1150                                         process_buf = NULL;
1151                                 else
1152                                         process_buf = buf;
1153                                 
1154                                 while ((comment_p = strchr(new_buf, COMMENT_META))) {
1155                                         if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
1156                                                 /* Escaped semicolons aren't comments. */
1157                                                 new_buf = comment_p + 1;
1158                                         } else if (comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
1159                                                 /* Meta-Comment start detected ";--" */
1160                                                 if (comment < MAX_NESTED_COMMENTS) {
1161                                                         *comment_p = '\0';
1162                                                         new_buf = comment_p + 3;
1163                                                         comment++;
1164                                                         nest[comment-1] = lineno;
1165                                                 } else {
1166                                                         ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
1167                                                 }
1168                                         } else if ((comment_p >= new_buf + 2) &&
1169                                                    (*(comment_p - 1) == COMMENT_TAG) &&
1170                                                    (*(comment_p - 2) == COMMENT_TAG)) {
1171                                                 /* Meta-Comment end detected */
1172                                                 comment--;
1173                                                 new_buf = comment_p + 1;
1174                                                 if (!comment) {
1175                                                         /* Back to non-comment now */
1176                                                         if (process_buf) {
1177                                                                 /* Actually have to move what's left over the top, then continue */
1178                                                                 char *oldptr;
1179                                                                 oldptr = process_buf + strlen(process_buf);
1180                                                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1181                                                                         CB_ADD(&comment_buffer, &comment_buffer_size, ";");
1182                                                                         CB_ADD_LEN(&comment_buffer, &comment_buffer_size, oldptr+1, new_buf-oldptr-1);
1183                                                                 }
1184                                                                 
1185                                                                 memmove(oldptr, new_buf, strlen(new_buf) + 1);
1186                                                                 new_buf = oldptr;
1187                                                         } else
1188                                                                 process_buf = new_buf;
1189                                                 }
1190                                         } else {
1191                                                 if (!comment) {
1192                                                         /* If ; is found, and we are not nested in a comment, 
1193                                                            we immediately stop all comment processing */
1194                                                         if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS)) {
1195                                                                 LLB_ADD(&lline_buffer, &lline_buffer_size, comment_p);
1196                                                         }
1197                                                         *comment_p = '\0'; 
1198                                                         new_buf = comment_p;
1199                                                 } else
1200                                                         new_buf = comment_p + 1;
1201                                         }
1202                                 }
1203                                 if (ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment && !process_buf )
1204                                 {
1205                                         CB_ADD(&comment_buffer, &comment_buffer_size, buf);  /* the whole line is a comment, store it */
1206                                 }
1207                                 
1208                                 if (process_buf) {
1209                                         char *buf = ast_strip(process_buf);
1210                                         if (!ast_strlen_zero(buf)) {
1211                                                 if (process_text_line(cfg, &cat, buf, lineno, fn, flags, &comment_buffer, &comment_buffer_size, &lline_buffer, &lline_buffer_size, suggested_include_file)) {
1212                                                         cfg = NULL;
1213                                                         break;
1214                                                 }
1215                                         }
1216                                 }
1217                         }
1218                 }
1219                 fclose(f);              
1220         } while (0);
1221         if (comment) {
1222                 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment - 1]);
1223         }
1224 #ifdef AST_INCLUDE_GLOB
1225                                         if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
1226                                                 break;
1227                                 }
1228                                 globfree(&globbuf);
1229                         }
1230                 }
1231 #endif
1232
1233         if (cfg && cfg != CONFIG_STATUS_FILEUNCHANGED && cfg->include_level == 1 && ast_test_flag(&flags, CONFIG_FLAG_WITHCOMMENTS) && comment_buffer) {
1234                 ast_free(comment_buffer);
1235                 ast_free(lline_buffer);
1236                 comment_buffer = NULL;
1237                 lline_buffer = NULL;
1238                 comment_buffer_size = 0;
1239                 lline_buffer_size = 0;
1240         }
1241         
1242         if (count == 0)
1243                 return NULL;
1244
1245         return cfg;
1246 }
1247
1248
1249 /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
1250    which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
1251    recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
1252    be shocked and mystified as to why things are not showing up in the files! 
1253
1254    Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
1255    and line number are stored for each include, plus the name of the file included, so that these statements may be
1256    included in the output files on a file_save operation. 
1257
1258    The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
1259    are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
1260    the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
1261    and a header gets added.
1262
1263    vars and category heads are output in the order they are stored in the config file. So, if the software
1264    shuffles these at all, then the placement of #include directives might get a little mixed up, because the
1265    file/lineno data probably won't get changed.
1266
1267 */
1268
1269 static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
1270 {
1271         char date[256]="";
1272         time_t t;
1273         time(&t);
1274         ast_copy_string(date, ctime(&t), sizeof(date));
1275
1276         fprintf(f1, ";!\n");
1277         fprintf(f1, ";! Automatically generated configuration file\n");
1278         if (strcmp(configfile, fn))
1279                 fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
1280         else
1281                 fprintf(f1, ";! Filename: %s\n", configfile);
1282         fprintf(f1, ";! Generator: %s\n", generator);
1283         fprintf(f1, ";! Creation Date: %s", date);
1284         fprintf(f1, ";!\n");
1285 }
1286
1287 static void set_fn(char *fn, int fn_size, const char *file, const char *configfile)
1288 {
1289         if (!file || file[0] == 0) {
1290                 if (configfile[0] == '/')
1291                         ast_copy_string(fn, configfile, fn_size);
1292                 else
1293                         snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
1294         } else if (file[0] == '/') 
1295                 ast_copy_string(fn, file, fn_size);
1296         else
1297                 snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
1298 }
1299
1300 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
1301 {
1302         FILE *f;
1303         char fn[256];
1304         struct ast_variable *var;
1305         struct ast_category *cat;
1306         struct ast_comment *cmt;
1307         struct ast_config_include *incl;
1308         int blanklines = 0;
1309
1310         /* reset all the output flags, in case this isn't our first time saving this data */
1311
1312         for (incl=cfg->includes; incl; incl = incl->next)
1313                 incl->output = 0;
1314
1315         /* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
1316            are all truncated to zero bytes and have that nice header*/
1317
1318         for (incl=cfg->includes; incl; incl = incl->next)
1319         {
1320                 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*/
1321                         FILE *f1;
1322
1323                         set_fn(fn, sizeof(fn), incl->included_file, configfile); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
1324                         f1 = fopen(fn,"w");
1325                         if (f1) {
1326                                 gen_header(f1, configfile, fn, generator);
1327                                 fclose(f1); /* this should zero out the file */
1328                         } else {
1329                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1330                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1331                         }
1332                 }
1333         }
1334
1335         set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
1336 #ifdef __CYGWIN__       
1337         if ((f = fopen(fn, "w+"))) {
1338 #else
1339         if ((f = fopen(fn, "w"))) {
1340 #endif      
1341                 ast_verb(2, "Saving '%s': ", fn);
1342                 gen_header(f, configfile, fn, generator);
1343                 cat = cfg->root;
1344                 fclose(f);
1345                 
1346                 /* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
1347                 /* since each var, cat, and associated comments can come from any file, we have to be 
1348                    mobile, and open each file, print, and close it on an entry-by-entry basis */
1349
1350                 while (cat) {
1351                         set_fn(fn, sizeof(fn), cat->file, configfile);
1352                         f = fopen(fn, "a");
1353                         if (!f)
1354                         {
1355                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1356                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1357                                 return -1;
1358                         }
1359
1360                         /* dump any includes that happen before this category header */
1361                         for (incl=cfg->includes; incl; incl = incl->next) {
1362                                 if (strcmp(incl->include_location_file, cat->file) == 0){
1363                                         if (cat->lineno > incl->include_location_lineno && !incl->output) {
1364                                                 if (incl->exec)
1365                                                         fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1366                                                 else
1367                                                         fprintf(f,"#include \"%s\"\n", incl->included_file);
1368                                                 incl->output = 1;
1369                                         }
1370                                 }
1371                         }
1372                         
1373                         /* Dump section with any appropriate comment */
1374                         for (cmt = cat->precomments; cmt; cmt=cmt->next) {
1375                                 if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1376                                         fprintf(f,"%s", cmt->cmt);
1377                         }
1378                         if (!cat->precomments)
1379                                 fprintf(f,"\n");
1380                         fprintf(f, "[%s]", cat->name);
1381                         for (cmt = cat->sameline; cmt; cmt=cmt->next) {
1382                                 fprintf(f,"%s", cmt->cmt);
1383                         }
1384                         if (!cat->sameline)
1385                                 fprintf(f,"\n");
1386                         fclose(f);
1387                         
1388                         var = cat->root;
1389                         while (var) {
1390                                 set_fn(fn, sizeof(fn), var->file, configfile);
1391                                 f = fopen(fn, "a");
1392                                 if (!f)
1393                                 {
1394                                         ast_debug(1, "Unable to open for writing: %s\n", fn);
1395                                         ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1396                                         return -1;
1397                                 }
1398                                 
1399                                 /* dump any includes that happen before this category header */
1400                                 for (incl=cfg->includes; incl; incl = incl->next) {
1401                                         if (strcmp(incl->include_location_file, var->file) == 0){
1402                                                 if (var->lineno > incl->include_location_lineno && !incl->output) {
1403                                                         if (incl->exec)
1404                                                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1405                                                         else
1406                                                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1407                                                         incl->output = 1;
1408                                                 }
1409                                         }
1410                                 }
1411                                 
1412                                 for (cmt = var->precomments; cmt; cmt=cmt->next) {
1413                                         if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
1414                                                 fprintf(f,"%s", cmt->cmt);
1415                                 }
1416                                 if (var->sameline) 
1417                                         fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
1418                                 else    
1419                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
1420                                 if (var->blanklines) {
1421                                         blanklines = var->blanklines;
1422                                         while (blanklines--)
1423                                                 fprintf(f, "\n");
1424                                 }
1425                                 
1426                                 fclose(f);
1427                                 
1428                                 var = var->next;
1429                         }
1430                         cat = cat->next;
1431                 }
1432                 if (!option_debug)
1433                         ast_verb(2, "Saved\n");
1434         } else {
1435                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1436                 ast_verb(2, "Unable to write (%s)", strerror(errno));
1437                 return -1;
1438         }
1439
1440         /* Now, for files with trailing #include/#exec statements,
1441            we have to make sure every entry is output */
1442
1443         for (incl=cfg->includes; incl; incl = incl->next) {
1444                 if (!incl->output) {
1445                         /* open the respective file */
1446                         set_fn(fn, sizeof(fn), incl->include_location_file, configfile);
1447                         f = fopen(fn, "a");
1448                         if (!f)
1449                         {
1450                                 ast_debug(1, "Unable to open for writing: %s\n", fn);
1451                                 ast_verb(2, "Unable to write %s (%s)", fn, strerror(errno));
1452                                 return -1;
1453                         }
1454                         
1455                         /* output the respective include */
1456                         if (incl->exec)
1457                                 fprintf(f,"#exec \"%s\"\n", incl->exec_file);
1458                         else
1459                                 fprintf(f,"#include \"%s\"\n", incl->included_file);
1460                         fclose(f);
1461                         incl->output = 1;
1462                 }
1463         }
1464                                 
1465         return 0;
1466 }
1467
1468 static void clear_config_maps(void) 
1469 {
1470         struct ast_config_map *map;
1471
1472         ast_mutex_lock(&config_lock);
1473
1474         while (config_maps) {
1475                 map = config_maps;
1476                 config_maps = config_maps->next;
1477                 ast_free(map);
1478         }
1479                 
1480         ast_mutex_unlock(&config_lock);
1481 }
1482
1483 static int append_mapping(char *name, char *driver, char *database, char *table)
1484 {
1485         struct ast_config_map *map;
1486         int length;
1487
1488         length = sizeof(*map);
1489         length += strlen(name) + 1;
1490         length += strlen(driver) + 1;
1491         length += strlen(database) + 1;
1492         if (table)
1493                 length += strlen(table) + 1;
1494
1495         if (!(map = ast_calloc(1, length)))
1496                 return -1;
1497
1498         map->name = map->stuff;
1499         strcpy(map->name, name);
1500         map->driver = map->name + strlen(map->name) + 1;
1501         strcpy(map->driver, driver);
1502         map->database = map->driver + strlen(map->driver) + 1;
1503         strcpy(map->database, database);
1504         if (table) {
1505                 map->table = map->database + strlen(map->database) + 1;
1506                 strcpy(map->table, table);
1507         }
1508         map->next = config_maps;
1509
1510         ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name);
1511
1512         config_maps = map;
1513         return 0;
1514 }
1515
1516 int read_config_maps(void) 
1517 {
1518         struct ast_config *config, *configtmp;
1519         struct ast_variable *v;
1520         char *driver, *table, *database, *stringp, *tmp;
1521         struct ast_flags flags = { 0 };
1522
1523         clear_config_maps();
1524
1525         configtmp = ast_config_new();
1526         configtmp->max_include_level = 1;
1527         config = ast_config_internal_load(extconfig_conf, configtmp, flags, "");
1528         if (!config) {
1529                 ast_config_destroy(configtmp);
1530                 return 0;
1531         }
1532
1533         for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
1534                 stringp = v->value;
1535                 driver = strsep(&stringp, ",");
1536
1537                 if ((tmp = strchr(stringp, '\"')))
1538                         stringp = tmp;
1539
1540                 /* check if the database text starts with a double quote */
1541                 if (*stringp == '"') {
1542                         stringp++;
1543                         database = strsep(&stringp, "\"");
1544                         strsep(&stringp, ",");
1545                 } else {
1546                         /* apparently this text has no quotes */
1547                         database = strsep(&stringp, ",");
1548                 }
1549
1550                 table = strsep(&stringp, ",");
1551
1552                 if (!strcmp(v->name, extconfig_conf)) {
1553                         ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf);
1554                         continue;
1555                 }
1556
1557                 if (!strcmp(v->name, "asterisk.conf")) {
1558                         ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n");
1559                         continue;
1560                 }
1561
1562                 if (!strcmp(v->name, "logger.conf")) {
1563                         ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n");
1564                         continue;
1565                 }
1566
1567                 if (!driver || !database)
1568                         continue;
1569                 if (!strcasecmp(v->name, "sipfriends")) {
1570                         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");
1571                         append_mapping("sipusers", driver, database, table ? table : "sipfriends");
1572                         append_mapping("sippeers", driver, database, table ? table : "sipfriends");
1573                 } else if (!strcasecmp(v->name, "iaxfriends")) {
1574                         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");
1575                         append_mapping("iaxusers", driver, database, table ? table : "iaxfriends");
1576                         append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends");
1577                 } else 
1578                         append_mapping(v->name, driver, database, table);
1579         }
1580                 
1581         ast_config_destroy(config);
1582         return 0;
1583 }
1584
1585 int ast_config_engine_register(struct ast_config_engine *new) 
1586 {
1587         struct ast_config_engine *ptr;
1588
1589         ast_mutex_lock(&config_lock);
1590
1591         if (!config_engine_list) {
1592                 config_engine_list = new;
1593         } else {
1594                 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
1595                 ptr->next = new;
1596         }
1597
1598         ast_mutex_unlock(&config_lock);
1599         ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
1600
1601         return 1;
1602 }
1603
1604 int ast_config_engine_deregister(struct ast_config_engine *del) 
1605 {
1606         struct ast_config_engine *ptr, *last=NULL;
1607
1608         ast_mutex_lock(&config_lock);
1609
1610         for (ptr = config_engine_list; ptr; ptr=ptr->next) {
1611                 if (ptr == del) {
1612                         if (last)
1613                                 last->next = ptr->next;
1614                         else
1615                                 config_engine_list = ptr->next;
1616                         break;
1617                 }
1618                 last = ptr;
1619         }
1620
1621         ast_mutex_unlock(&config_lock);
1622
1623         return 0;
1624 }
1625
1626 /*! \brief Find realtime engine for realtime family */
1627 static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) 
1628 {
1629         struct ast_config_engine *eng, *ret = NULL;
1630         struct ast_config_map *map;
1631
1632         ast_mutex_lock(&config_lock);
1633
1634         for (map = config_maps; map; map = map->next) {
1635                 if (!strcasecmp(family, map->name)) {
1636                         if (database)
1637                                 ast_copy_string(database, map->database, dbsiz);
1638                         if (table)
1639                                 ast_copy_string(table, map->table ? map->table : family, tabsiz);
1640                         break;
1641                 }
1642         }
1643
1644         /* Check if the required driver (engine) exist */
1645         if (map) {
1646                 for (eng = config_engine_list; !ret && eng; eng = eng->next) {
1647                         if (!strcasecmp(eng->name, map->driver))
1648                                 ret = eng;
1649                 }
1650         }
1651
1652         ast_mutex_unlock(&config_lock);
1653         
1654         /* if we found a mapping, but the engine is not available, then issue a warning */
1655         if (map && !ret)
1656                 ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
1657
1658         return ret;
1659 }
1660
1661 static struct ast_config_engine text_file_engine = {
1662         .name = "text",
1663         .load_func = config_text_file_load,
1664 };
1665
1666 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, struct ast_flags flags, const char *suggested_include_file)
1667 {
1668         char db[256];
1669         char table[256];
1670         struct ast_config_engine *loader = &text_file_engine;
1671         struct ast_config *result; 
1672
1673         if (cfg->include_level == cfg->max_include_level) {
1674                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
1675                 return NULL;
1676         }
1677
1678         cfg->include_level++;
1679
1680         if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
1681                 struct ast_config_engine *eng;
1682
1683                 eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
1684
1685
1686                 if (eng && eng->load_func) {
1687                         loader = eng;
1688                 } else {
1689                         eng = find_engine("global", db, sizeof(db), table, sizeof(table));
1690                         if (eng && eng->load_func)
1691                                 loader = eng;
1692                 }
1693         }
1694
1695         result = loader->load_func(db, table, filename, cfg, flags, suggested_include_file);
1696
1697         if (result && result != CONFIG_STATUS_FILEUNCHANGED)
1698                 result->include_level--;
1699         else
1700                 cfg->include_level--;
1701
1702         return result;
1703 }
1704
1705 struct ast_config *ast_config_load(const char *filename, struct ast_flags flags)
1706 {
1707         struct ast_config *cfg;
1708         struct ast_config *result;
1709
1710         cfg = ast_config_new();
1711         if (!cfg)
1712                 return NULL;
1713
1714         result = ast_config_internal_load(filename, cfg, flags, "");
1715         if (!result || result == CONFIG_STATUS_FILEUNCHANGED)
1716                 ast_config_destroy(cfg);
1717
1718         return result;
1719 }
1720
1721 static struct ast_variable *ast_load_realtime_helper(const char *family, va_list ap)
1722 {
1723         struct ast_config_engine *eng;
1724         char db[256]="";
1725         char table[256]="";
1726         struct ast_variable *res=NULL;
1727
1728         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1729         if (eng && eng->realtime_func) 
1730                 res = eng->realtime_func(db, table, ap);
1731
1732         return res;
1733 }
1734
1735 struct ast_variable *ast_load_realtime_all(const char *family, ...)
1736 {
1737         struct ast_variable *res;
1738         va_list ap;
1739
1740         va_start(ap, family);
1741         res = ast_load_realtime_helper(family, ap);
1742         va_end(ap);
1743
1744         return res;
1745 }
1746
1747 struct ast_variable *ast_load_realtime(const char *family, ...)
1748 {
1749         struct ast_variable *res, *cur, *prev = NULL, *freeme = NULL;
1750         va_list ap;
1751
1752         va_start(ap, family);
1753         res = ast_load_realtime_helper(family, ap);
1754         va_end(ap);
1755
1756         /* Eliminate blank entries */
1757         for (cur = res; cur; cur = cur->next) {
1758                 if (freeme) {
1759                         ast_free(freeme);
1760                         freeme = NULL;
1761                 }
1762
1763                 if (ast_strlen_zero(cur->value)) {
1764                         if (prev)
1765                                 prev->next = cur->next;
1766                         else
1767                                 res = cur->next;
1768                         freeme = cur;
1769                 } else {
1770                         prev = cur;
1771                 }
1772         }
1773         return res;
1774 }
1775
1776 /*! \brief Check if realtime engine is configured for family */
1777 int ast_check_realtime(const char *family)
1778 {
1779         struct ast_config_engine *eng;
1780
1781         eng = find_engine(family, NULL, 0, NULL, 0);
1782         if (eng)
1783                 return 1;
1784         return 0;
1785
1786 }
1787
1788 /*! \brief Check if there's any realtime engines loaded */
1789 int ast_realtime_enabled()
1790 {
1791         return config_maps ? 1 : 0;
1792 }
1793
1794 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
1795 {
1796         struct ast_config_engine *eng;
1797         char db[256]="";
1798         char table[256]="";
1799         struct ast_config *res=NULL;
1800         va_list ap;
1801
1802         va_start(ap, family);
1803         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1804         if (eng && eng->realtime_multi_func) 
1805                 res = eng->realtime_multi_func(db, table, ap);
1806         va_end(ap);
1807
1808         return res;
1809 }
1810
1811 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
1812 {
1813         struct ast_config_engine *eng;
1814         int res = -1;
1815         char db[256]="";
1816         char table[256]="";
1817         va_list ap;
1818
1819         va_start(ap, lookup);
1820         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1821         if (eng && eng->update_func) 
1822                 res = eng->update_func(db, table, keyfield, lookup, ap);
1823         va_end(ap);
1824
1825         return res;
1826 }
1827
1828 int ast_store_realtime(const char *family, ...) {
1829         struct ast_config_engine *eng;
1830         int res = -1;
1831         char db[256]="";
1832         char table[256]="";
1833         va_list ap;
1834
1835         va_start(ap, family);
1836         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1837         if (eng && eng->store_func) 
1838                 res = eng->store_func(db, table, ap);
1839         va_end(ap);
1840
1841         return res;
1842 }
1843
1844 int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) {
1845         struct ast_config_engine *eng;
1846         int res = -1;
1847         char db[256]="";
1848         char table[256]="";
1849         va_list ap;
1850
1851         va_start(ap, lookup);
1852         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1853         if (eng && eng->destroy_func) 
1854                 res = eng->destroy_func(db, table, keyfield, lookup, ap);
1855         va_end(ap);
1856
1857         return res;
1858 }
1859
1860 /*! \brief Helper function to parse arguments
1861  * See documentation in config.h
1862  */
1863 int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
1864         void *p_result, ...)
1865 {
1866         va_list ap;
1867         int error = 0;
1868
1869         va_start(ap, p_result);
1870         switch (flags & PARSE_TYPE) {
1871         case PARSE_INT32:
1872             {
1873                 int32_t *result = p_result;
1874                 int32_t x, def = result ? *result : 0,
1875                         high = (int32_t)0x7fffffff,
1876                         low  = (int32_t)0x80000000;
1877                 /* optional argument: first default value, then range */
1878                 if (flags & PARSE_DEFAULT)
1879                         def = va_arg(ap, int32_t);
1880                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1881                         /* range requested, update bounds */
1882                         low = va_arg(ap, int32_t);
1883                         high = va_arg(ap, int32_t);
1884                 }
1885                 x = strtol(arg, NULL, 0);
1886                 error = (x < low) || (x > high);
1887                 if (flags & PARSE_OUT_RANGE)
1888                         error = !error;
1889                 if (result)
1890                         *result  = error ? def : x;
1891                 ast_debug(3,
1892                         "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
1893                         arg, low, high,
1894                         result ? *result : x, error);
1895                 break;
1896             }
1897
1898         case PARSE_UINT32:
1899             {
1900                 uint32_t *result = p_result;
1901                 uint32_t x, def = result ? *result : 0,
1902                         low = 0, high = (uint32_t)~0;
1903                 /* optional argument: first default value, then range */
1904                 if (flags & PARSE_DEFAULT)
1905                         def = va_arg(ap, uint32_t);
1906                 if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
1907                         /* range requested, update bounds */
1908                         low = va_arg(ap, uint32_t);
1909                         high = va_arg(ap, uint32_t);
1910                 }
1911                 x = strtoul(arg, NULL, 0);
1912                 error = (x < low) || (x > high);
1913                 if (flags & PARSE_OUT_RANGE)
1914                         error = !error;
1915                 if (result)
1916                         *result  = error ? def : x;
1917                 ast_debug(3,
1918                         "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
1919                         arg, low, high,
1920                         result ? *result : x, error);
1921                 break;
1922             }
1923
1924         case PARSE_INADDR:
1925             {
1926                 char *port, *buf;
1927                 struct sockaddr_in _sa_buf;     /* buffer for the result */
1928                 struct sockaddr_in *sa = p_result ?
1929                         (struct sockaddr_in *)p_result : &_sa_buf;
1930                 /* default is either the supplied value or the result itself */
1931                 struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
1932                         va_arg(ap, struct sockaddr_in *) : sa;
1933                 struct hostent *hp;
1934                 struct ast_hostent ahp;
1935
1936                 bzero(&_sa_buf, sizeof(_sa_buf)); /* clear buffer */
1937                 /* duplicate the string to strip away the :port */
1938                 port = ast_strdupa(arg);
1939                 buf = strsep(&port, ":");
1940                 sa->sin_family = AF_INET;       /* assign family */
1941                 /*
1942                  * honor the ports flag setting, assign default value
1943                  * in case of errors or field unset.
1944                  */
1945                 flags &= PARSE_PORT_MASK; /* the only flags left to process */
1946                 if (port) {
1947                         if (flags == PARSE_PORT_FORBID) {
1948                                 error = 1;      /* port was forbidden */
1949                                 sa->sin_port = def->sin_port;
1950                         } else if (flags == PARSE_PORT_IGNORE)
1951                                 sa->sin_port = def->sin_port;
1952                         else /* accept or require */
1953                                 sa->sin_port = htons(strtol(port, NULL, 0));
1954                 } else {
1955                         sa->sin_port = def->sin_port;
1956                         if (flags == PARSE_PORT_REQUIRE)
1957                                 error = 1;
1958                 }
1959                 /* Now deal with host part, even if we have errors before. */
1960                 hp = ast_gethostbyname(buf, &ahp);
1961                 if (hp) /* resolved successfully */
1962                         memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
1963                 else {
1964                         error = 1;
1965                         sa->sin_addr = def->sin_addr;
1966                 }
1967                 ast_debug(3,
1968                         "extract inaddr from [%s] gives [%s:%d](%d)\n",
1969                         arg, ast_inet_ntoa(sa->sin_addr),
1970                         ntohs(sa->sin_port), error);
1971                 break;
1972             }
1973         }
1974         va_end(ap);
1975         return error;
1976 }
1977
1978 static int config_command(int fd, int argc, char **argv) 
1979 {
1980         struct ast_config_engine *eng;
1981         struct ast_config_map *map;
1982         
1983         ast_mutex_lock(&config_lock);
1984
1985         ast_cli(fd, "\n\n");
1986         for (eng = config_engine_list; eng; eng = eng->next) {
1987                 ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
1988                 for (map = config_maps; map; map = map->next)
1989                         if (!strcasecmp(map->driver, eng->name)) {
1990                                 ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
1991                                         map->table ? map->table : map->name);
1992                         }
1993         }
1994         ast_cli(fd,"\n\n");
1995         
1996         ast_mutex_unlock(&config_lock);
1997
1998         return 0;
1999 }
2000
2001 static char show_config_help[] =
2002         "Usage: core show config mappings\n"
2003         "       Shows the filenames to config engines.\n";
2004
2005 static struct ast_cli_entry cli_config[] = {
2006         { { "core", "show", "config", "mappings", NULL },
2007         config_command, "Display config mappings (file names to config engines)",
2008         show_config_help },
2009 };
2010
2011 int register_config_cli() 
2012 {
2013         ast_cli_register_multiple(cli_config, sizeof(cli_config) / sizeof(struct ast_cli_entry));
2014         return 0;
2015 }