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