Add category inheritance (bug #3099)
[asterisk/asterisk.git] / config.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Configuration File Parser
5  * 
6  * Copyright (C) 1999 - 2005, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <time.h>
20 #define AST_INCLUDE_GLOB 1
21 #ifdef AST_INCLUDE_GLOB
22 #ifdef __OSX__
23 #define GLOB_ABORTED GLOB_ABEND
24 #endif
25 # include <glob.h>
26 #endif
27 #include <asterisk/config.h>
28 #include <asterisk/cli.h>
29 #include <asterisk/lock.h>
30 #include <asterisk/options.h>
31 #include <asterisk/logger.h>
32 #include <asterisk/utils.h>
33 #include "asterisk.h"
34 #include "astconf.h"
35
36 #define MAX_NESTED_COMMENTS 128
37 #define COMMENT_START ";--"
38 #define COMMENT_END "--;"
39 #define COMMENT_META ';'
40 #define COMMENT_TAG '-'
41
42 static char *extconfig_conf = "extconfig.conf";
43
44 static struct ast_config_map {
45         struct ast_config_map *next;
46         char *name;
47         char *driver;
48         char *database;
49         char *table;
50         char stuff[0];
51 } *config_maps = NULL;
52
53 AST_MUTEX_DEFINE_STATIC(config_lock);
54 static struct ast_config_engine *config_engine_list;
55
56 #define MAX_INCLUDE_LEVEL 10
57
58 struct ast_comment {
59         struct ast_comment *next;
60         char cmt[0];
61 };
62
63 struct ast_category {
64         char name[80];
65         int ignored;                    /* do not let user of the config see this category */
66         struct ast_variable *root;
67         struct ast_variable *last;
68         struct ast_category *next;
69 };
70
71 struct ast_config {
72         struct ast_category *root;
73         struct ast_category *last;
74         struct ast_category *current;
75         struct ast_category *last_browse;               /* used to cache the last category supplied via category_browse */
76         int include_level;
77         int max_include_level;
78 };
79
80 int ast_true(const char *s)
81 {
82         if (!s)
83                 return 0;
84         /* Determine if this is a true value */
85         if (!strcasecmp(s, "yes") ||
86             !strcasecmp(s, "true") ||
87             !strcasecmp(s, "y") ||
88             !strcasecmp(s, "t") ||
89             !strcasecmp(s, "1") ||
90             !strcasecmp(s, "on"))
91                 return -1;
92         return 0;
93 }
94
95 int ast_false(const char *s)
96 {
97         if (!s)
98                 return 0;
99         /* Determine if this is a false value */
100         if (!strcasecmp(s, "no") ||
101             !strcasecmp(s, "false") ||
102             !strcasecmp(s, "n") ||
103             !strcasecmp(s, "f") ||
104             !strcasecmp(s, "0") ||
105             !strcasecmp(s, "off"))
106                 return -1;
107         return 0;
108 }
109
110 struct ast_variable *ast_variable_new(const char *name, const char *value) 
111 {
112         struct ast_variable *variable;
113
114         int length = strlen(name) + strlen(value) + 2 + sizeof(struct ast_variable);
115         variable = malloc(length);
116         if (variable) {
117                 memset(variable, 0, length);
118                 variable->name = variable->stuff;
119                 variable->value = variable->stuff + strlen(name) + 1;           
120                 strcpy(variable->name,name);
121                 strcpy(variable->value,value);
122         }
123
124         return variable;
125 }
126
127 static struct ast_variable *variable_get(const struct ast_category *category, const char *name)
128 {
129         struct ast_variable *variable;
130
131         for (variable = category->root; variable; variable = variable->next)
132                 if (!strcasecmp(variable->name, name))
133                         return variable;
134
135         return NULL;
136 }
137
138 static void variable_remove(struct ast_category *category, const struct ast_variable *variable)
139 {
140         struct ast_variable *prev = category->root;
141
142         if (!prev)
143                 return;
144
145         if (prev == variable) {
146                 category->root = prev->next;
147                 if (category->last == variable)
148                         category->last = NULL;
149         } else {
150                 while (prev->next && (prev->next != variable)) prev = prev->next;
151                 if (prev->next) {
152                         prev->next = variable->next;
153                         if (category->last == variable)
154                                 category->last = prev;
155                 }
156         }
157 }
158
159 void ast_variable_append(struct ast_category *category, struct ast_variable *variable)
160 {
161         /* Note: this function also implements "variable replacement"... if the
162            new variable's value is empty, then existing variables of the same
163            name in the category are removed (and the new variable is destroyed)
164         */
165         if (variable->value && !ast_strlen_zero(variable->value)) {
166                 if (category->last)
167                         category->last->next = variable;
168                 else
169                         category->root = variable;
170                 category->last = variable;
171         } else {
172                 struct ast_variable *v;
173
174                 while ((v = variable_get(category, variable->name))) {
175                         variable_remove(category, v);
176                         ast_variables_destroy(v);
177                 }
178                 ast_variables_destroy(variable);
179         }
180 }
181
182 void ast_variables_destroy(struct ast_variable *v)
183 {
184         struct ast_variable *vn;
185
186         while(v) {
187                 vn = v;
188                 v = v->next;
189                 free(vn);
190         }
191 }
192
193 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
194 {
195         struct ast_category *cat = NULL;
196
197         if (category && config->last_browse && (config->last_browse->name == category))
198                 cat = config->last_browse;
199         else
200                 cat = ast_category_get(config, category);
201
202         if (cat)
203                 return cat->root;
204         else
205                 return NULL;
206 }
207
208 char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
209 {
210         struct ast_variable *v;
211
212         if (category) {
213                 for (v = ast_variable_browse(config, category); v; v = v->next)
214                         if (variable == v->name)
215                                 return v->value;
216                 for (v = ast_variable_browse(config, category); v; v = v->next)
217                         if (!strcasecmp(variable, v->name))
218                                 return v->value;
219         } else {
220                 struct ast_category *cat;
221
222                 for (cat = config->root; cat; cat = cat->next)
223                         for (v = cat->root; v; v = v->next)
224                                 if (!strcasecmp(variable, v->name))
225                                         return v->value;
226         }
227
228         return NULL;
229 }
230
231 static struct ast_variable *variable_clone(const struct ast_variable *old)
232 {
233         struct ast_variable *new = ast_variable_new(old->name, old->value);
234
235         if (new) {
236                 new->lineno = old->lineno;
237                 new->object = old->object;
238                 new->blanklines = old->blanklines;
239                 /* TODO: clone comments? */
240         }
241
242         return new;
243 }
244  
245 static void move_variables(struct ast_category *old, struct ast_category *new)
246 {
247         struct ast_variable *var;
248         struct ast_variable *next;
249
250         next = old->root;
251         old->root = NULL;
252         for (var = next; var; var = next) {
253                 next = var->next;
254                 var->next = NULL;
255                 ast_variable_append(new, var);
256         }
257 }
258
259 struct ast_category *ast_category_new(const char *name) 
260 {
261         struct ast_category *category;
262
263         category = malloc(sizeof(struct ast_category));
264         if (category) {
265                 memset(category, 0, sizeof(struct ast_category));
266                 strncpy(category->name, name, sizeof(category->name) - 1);
267         }
268
269         return category;
270 }
271
272 static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
273 {
274         struct ast_category *cat;
275
276         for (cat = config->root; cat; cat = cat->next) {
277                 if (cat->name == category_name && (ignored || !cat->ignored))
278                         return cat;
279         }
280
281         for (cat = config->root; cat; cat = cat->next) {
282                 if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
283                         return cat;
284         }
285
286         return NULL;
287 }
288
289 struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
290 {
291         return category_get(config, category_name, 0);
292 }
293
294 int ast_category_exist(const struct ast_config *config, const char *category_name)
295 {
296         return !!ast_category_get(config, category_name);
297 }
298
299 void ast_category_append(struct ast_config *config, struct ast_category *category)
300 {
301         if (config->last)
302                 config->last->next = category;
303         else
304                 config->root = category;
305         config->last = category;
306         config->current = category;
307 }
308
309 void ast_category_destroy(struct ast_category *cat)
310 {
311         ast_variables_destroy(cat->root);
312         free(cat);
313 }
314
315 static struct ast_category *next_available_category(struct ast_category *cat)
316 {
317         for (; cat && cat->ignored; cat = cat->next);
318
319         return cat;
320 }
321
322 char *ast_category_browse(struct ast_config *config, const char *prev)
323 {       
324         struct ast_category *cat = NULL;
325
326         if (prev && config->last_browse && (config->last_browse->name == prev))
327                 cat = config->last_browse->next;
328         else if (!prev && config->root)
329                         cat = config->root;
330         else if (prev) {
331                 for (cat = config->root; cat; cat = cat->next) {
332                         if (cat->name == prev) {
333                                 cat = cat->next;
334                                 break;
335                         }
336                 }
337                 if (!cat) {
338                         for (cat = config->root; cat; cat = cat->next) {
339                                 if (!strcasecmp(cat->name, prev)) {
340                                         cat = cat->next;
341                                         break;
342                                 }
343                         }
344                 }
345         }
346         
347         if (cat)
348                 cat = next_available_category(cat);
349
350         config->last_browse = cat;
351         if (cat)
352                 return cat->name;
353         else
354                 return NULL;
355 }
356
357 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
358 {
359         struct ast_variable *v;
360
361         v = cat->root;
362         cat->root = NULL;
363
364         return v;
365 }
366
367 void ast_category_rename(struct ast_category *cat, const char *name)
368 {
369         strncpy(cat->name, name, sizeof(cat->name) - 1);
370 }
371
372 static void inherit_category(struct ast_category *new, const struct ast_category *base)
373 {
374         struct ast_variable *var;
375
376         for (var = base->root; var; var = var->next) {
377                 struct ast_variable *v;
378                 
379                 v = variable_clone(var);
380                 if (v)
381                         ast_variable_append(new, v);
382         }
383 }
384
385 struct ast_config *ast_config_new(void) 
386 {
387         struct ast_config *config;
388
389         config = malloc(sizeof(*config));
390         if (config) {
391                 memset(config, 0, sizeof(*config));
392                 config->max_include_level = MAX_INCLUDE_LEVEL;
393         }
394
395         return config;
396 }
397
398 void ast_config_destroy(struct ast_config *cfg)
399 {
400         struct ast_category *cat, *catn;
401
402         if (!cfg)
403                 return;
404
405         cat = cfg->root;
406         while(cat) {
407                 ast_variables_destroy(cat->root);
408                 catn = cat;
409                 cat = cat->next;
410                 free(catn);
411         }
412         free(cfg);
413 }
414
415 struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
416 {
417         return cfg->current;
418 }
419
420 void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
421 {
422         /* cast below is just to silence compiler warning about dropping "const" */
423         cfg->current = (struct ast_category *) cat;
424 }
425
426 static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile)
427 {
428         char *c;
429         char *cur = buf;
430         struct ast_variable *v;
431         int object;
432
433         /* Actually parse the entry */
434         if (cur[0] == '[') {
435                 struct ast_category *newcat = NULL;
436                 char *catname;
437
438                 /* A category header */
439                 c = strchr(cur, ']');
440                 if (!c) {
441                         ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
442                         return -1;
443                 }
444                 *c++ = '\0';
445                 cur++;
446                 if (*c++ != '(')
447                         c = NULL;
448                 catname = cur;
449                 *cat = newcat = ast_category_new(catname);
450                 if (!newcat) {
451                         ast_log(LOG_WARNING, "Out of memory, line %d of %s\n", lineno, configfile);
452                         return -1;
453                 }
454                 /* If there are options or categories to inherit from, process them now */
455                 if (c) {
456                         if (!(cur = strchr(c, ')'))) {
457                                 ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
458                                 return -1;
459                         }
460                         *cur = '\0';
461                         while ((cur = strsep(&c, ","))) {
462                                 if (!strcasecmp(cur, "!")) {
463                                         (*cat)->ignored = 1;
464                                 } else if (!strcasecmp(cur, "+")) {
465                                         *cat = category_get(cfg, catname, 1);
466                                         if (!*cat) {
467                                                 ast_destroy(cfg);
468                                                 if (newcat)
469                                                         ast_category_destroy(newcat);
470                                                 ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
471                                                 return -1;
472                                         }
473                                         if (newcat) {
474                                                 move_variables(newcat, *cat);
475                                                 ast_category_destroy(newcat);
476                                                 newcat = NULL;
477                                         }
478                                 } else {
479                                         struct ast_category *base;
480                                 
481                                         base = category_get(cfg, cur, 1);
482                                         if (!base) {
483                                                 ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
484                                                 return -1;
485                                         }
486                                         inherit_category(*cat, base);
487                                 }
488                         }
489                 }
490                 if (newcat)
491                         ast_category_append(cfg, *cat);
492         } else if (cur[0] == '#') {
493                 /* A directive */
494                 cur++;
495                 c = cur;
496                 while(*c && (*c > 32)) c++;
497                 if (*c) {
498                         *c = '\0';
499                         c++;
500                         /* Find real argument */
501                         while(*c  && (*c < 33)) c++;
502                         if (!*c)
503                                 c = NULL;
504                 } else 
505                         c = NULL;
506                 if (!strcasecmp(cur, "include")) {
507                         /* A #include */
508                         if (c) {
509                                 /* Strip off leading and trailing "'s and <>'s */
510                                 while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
511                                 /* Get rid of leading mess */
512                                 cur = c;
513                                 while (!ast_strlen_zero(cur)) {
514                                         c = cur + strlen(cur) - 1;
515                                         if ((*c == '>') || (*c == '<') || (*c == '\"'))
516                                                 *c = '\0';
517                                         else
518                                                 break;
519                                 }
520
521                                 if (!ast_config_internal_load(cur, cfg))
522                                         return -1;
523                         } else
524                                 ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
525                 }
526                 else 
527                         ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
528         } else {
529                 /* Just a line (variable = value) */
530                 if (!*cat) {
531                         ast_log(LOG_WARNING,
532                                 "parse error: No category context for line %d of %s\n", lineno, configfile);
533                         return -1;
534                 }
535                 c = strchr(cur, '=');
536                 if (c) {
537                         *c = 0;
538                         c++;
539                         /* Ignore > in => */
540                         if (*c== '>') {
541                                 object = 1;
542                                 c++;
543                         } else
544                                 object = 0;
545                         v = ast_variable_new(ast_strip(cur), ast_strip(c));
546                         if (v) {
547                                 v->lineno = lineno;
548                                 v->object = object;
549                                 /* Put and reset comments */
550                                 v->blanklines = 0;
551                                 ast_variable_append(*cat, v);
552                         } else {
553                                 ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
554                                 return -1;
555                         }
556                 } else {
557                         ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
558                 }
559
560         }
561         return 0;
562 }
563
564 static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg)
565 {
566         char fn[256];
567         char buf[8192];
568         char *new_buf, *comment_p, *process_buf;
569         FILE *f;
570         int lineno=0;
571         int comment = 0, nest[MAX_NESTED_COMMENTS];
572         struct ast_category *cat = NULL;
573         
574         cat = ast_config_get_current_category(cfg);
575
576         if (filename[0] == '/') {
577                 strncpy(fn, filename, sizeof(fn)-1);
578         } else {
579                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename);
580         }
581
582 #ifdef AST_INCLUDE_GLOB
583         {
584                 int glob_ret;
585                 glob_t globbuf;
586                 globbuf.gl_offs = 0;    /* initialize it to silence gcc */
587 #ifdef SOLARIS
588                 glob_ret = glob(fn, GLOB_NOCHECK, NULL, &globbuf);
589 #else
590                 glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
591 #endif
592                 if (glob_ret == GLOB_NOSPACE)
593                         ast_log(LOG_WARNING,
594                                 "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
595                 else if (glob_ret  == GLOB_ABORTED)
596                         ast_log(LOG_WARNING,
597                                 "Glob Expansion of pattern '%s' failed: Read error\n", fn);
598                 else  {
599                         /* loop over expanded files */
600                         int i;
601                         for (i=0; i<globbuf.gl_pathc; i++) {
602                                 strncpy(fn, globbuf.gl_pathv[i], sizeof(fn)-1);
603 #endif
604         if ((option_verbose > 1) && !option_debug) {
605                 ast_verbose(  VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
606                 fflush(stdout);
607         }
608         if ((f = fopen(fn, "r"))) {
609                 if (option_debug)
610                         ast_log(LOG_DEBUG, "Parsing %s\n", fn);
611                 else if (option_verbose > 1)
612                         ast_verbose("Found\n");
613                 while(!feof(f)) {
614                         lineno++;
615                         if (fgets(buf, sizeof(buf), f)) {
616                                 new_buf = buf;
617                                 if (comment)
618                                         process_buf = NULL;
619                                 else
620                                         process_buf = buf;
621                                 while ((comment_p = strchr(new_buf, COMMENT_META))) {
622                                         if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
623                                                 /* Yuck, gotta memmove */
624                                                 memmove(comment_p - 1, comment_p, strlen(comment_p) + 1);
625                                                 new_buf = comment_p;
626                                         } else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
627                                                 /* Meta-Comment start detected ";--" */
628                                                 if (comment < MAX_NESTED_COMMENTS) {
629                                                         *comment_p = '\0';
630                                                         new_buf = comment_p + 3;
631                                                         comment++;
632                                                         nest[comment-1] = lineno;
633                                                 } else {
634                                                         ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
635                                                 }
636                                         } else if ((comment_p >= new_buf + 2) &&
637                                                    (*(comment_p - 1) == COMMENT_TAG) &&
638                                                    (*(comment_p - 2) == COMMENT_TAG)) {
639                                                 /* Meta-Comment end detected */
640                                                 comment--;
641                                                 new_buf = comment_p + 1;
642                                                 if (!comment) {
643                                                         /* Back to non-comment now */
644                                                         if (process_buf) {
645                                                                 /* Actually have to move what's left over the top, then continue */
646                                                                 char *oldptr;
647                                                                 oldptr = process_buf + strlen(process_buf);
648                                                                 memmove(oldptr, new_buf, strlen(new_buf) + 1);
649                                                                 new_buf = oldptr;
650                                                         } else
651                                                                 process_buf = new_buf;
652                                                 }
653                                         } else {
654                                                 if (!comment) {
655                                                         /* If ; is found, and we are not nested in a comment, 
656                                                            we immediately stop all comment processing */
657                                                         *comment_p = '\0'; 
658                                                         new_buf = comment_p;
659                                                 } else
660                                                         new_buf = comment_p + 1;
661                                         }
662                                 }
663                                 if (process_buf) {
664                                         char *buf = ast_strip(process_buf);
665                                         if (!ast_strlen_zero(buf))
666                                                 if (process_text_line(cfg, &cat, buf, lineno, filename)) {
667                                                         cfg = NULL;
668                                                         break;
669                                                 }
670                                 }
671                         }
672                 }
673                 fclose(f);              
674         } else { /* can't open file */
675                 if (option_debug)
676                         ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
677                 else if (option_verbose > 2)
678                         ast_verbose( "Not found (%s)\n", strerror(errno));
679         }
680         if (comment) {
681                 ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]);
682         }
683 #ifdef AST_INCLUDE_GLOB
684                                         if (!cfg)
685                                                 break;
686                                 }
687                                 globfree(&globbuf);
688                         }
689                 }
690 #endif
691
692         return cfg;
693 }
694
695 int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
696 {
697         FILE *f;
698         char fn[256];
699         char date[256]="";
700         time_t t;
701         struct ast_variable *var;
702         struct ast_category *cat;
703         int blanklines = 0;
704
705         if (configfile[0] == '/') {
706                 strncpy(fn, configfile, sizeof(fn)-1);
707         } else {
708                 snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
709         }
710         time(&t);
711         strncpy(date, ctime(&t), sizeof(date) - 1);
712         if ((f = fopen(fn, "w"))) {
713                 if ((option_verbose > 1) && !option_debug)
714                         ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
715                 fprintf(f, ";!\n");
716                 fprintf(f, ";! Automatically generated configuration file\n");
717                 fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
718                 fprintf(f, ";! Generator: %s\n", generator);
719                 fprintf(f, ";! Creation Date: %s", date);
720                 fprintf(f, ";!\n");
721                 cat = cfg->root;
722                 while(cat) {
723                         /* Dump section with any appropriate comment */
724                         fprintf(f, "[%s]\n", cat->name);
725                         var = cat->root;
726                         while(var) {
727                                 if (var->sameline) 
728                                         fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
729                                 else    
730                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
731                                 if (var->blanklines) {
732                                         blanklines = var->blanklines;
733                                         while (blanklines--)
734                                                 fprintf(f, "\n");
735                                 }
736                                         
737                                 var = var->next;
738                         }
739 #if 0
740                         /* Put an empty line */
741                         fprintf(f, "\n");
742 #endif
743                         cat = cat->next;
744                 }
745         } else {
746                 if (option_debug)
747                         printf("Unable to open for writing: %s\n", fn);
748                 else if (option_verbose > 1)
749                         printf( "Unable to write (%s)", strerror(errno));
750                 return -1;
751         }
752         fclose(f);
753         return 0;
754 }
755
756 static void clear_config_maps(void) 
757 {
758         struct ast_config_map *map;
759
760         ast_mutex_lock(&config_lock);
761
762         while (config_maps) {
763                 map = config_maps;
764                 config_maps = config_maps->next;
765                 free(map);
766         }
767                 
768         ast_mutex_unlock(&config_lock);
769 }
770
771 void read_config_maps(void) 
772 {
773         struct ast_config *config;
774         struct ast_variable *v;
775         struct ast_config_map *map;
776         int length;
777         char *driver, *table, *database, *stringp;
778
779         clear_config_maps();
780
781         config = ast_config_new();
782         config->max_include_level = 1;
783         config = ast_config_internal_load(extconfig_conf, config);
784         if (!config)
785                 return;
786
787         for (v = ast_variable_browse(config, "settings"); v; v = v->next) {
788                 stringp = v->value;
789                 driver = strsep(&stringp, ",");
790                 database = strsep(&stringp, ",");
791                 table = strsep(&stringp, ",");
792                         
793                 if (!strcmp(v->name, extconfig_conf) || !strcmp(v->name, "asterisk.conf")) {
794                         ast_log(LOG_WARNING, "Cannot bind asterisk.conf or extconfig.conf!\n");
795                         continue;
796                 }
797
798                 if (!driver || !database)
799                         continue;
800
801                 length = sizeof(*map);
802                 length += strlen(v->name) + 1;
803                 length += strlen(driver) + 1;
804                 length += strlen(database) + 1;
805                 if (table)
806                         length += strlen(table) + 1;
807                 map = malloc(length);
808
809                 if (!map)
810                         continue;
811
812                 memset(map, 0, length);
813                 map->name = map->stuff;
814                 strcpy(map->name, v->name);
815                 map->driver = map->name + strlen(map->name) + 1;
816                 strcpy(map->driver, driver);
817                 map->database = map->driver + strlen(map->driver) + 1;
818                 strcpy(map->database, database);
819                 if (table) {
820                         map->table = map->database + strlen(map->database) + 1;
821                         strcpy(map->table, table);
822                 }
823                 map->next = config_maps;
824
825                 if (option_verbose > 1)
826                         ast_verbose(VERBOSE_PREFIX_2 "Binding %s to %s/%s/%s\n",
827                                     map->name, map->driver, map->database, map->table ? map->table : map->name);
828
829                 config_maps = map;
830         }
831                 
832         ast_config_destroy(config);
833 }
834
835 int ast_config_engine_register(struct ast_config_engine *new) 
836 {
837         struct ast_config_engine *ptr;
838
839         ast_mutex_lock(&config_lock);
840
841         if (!config_engine_list) {
842                 config_engine_list = new;
843         } else {
844                 for (ptr = config_engine_list; ptr->next; ptr=ptr->next);
845                 ptr->next = new;
846         }
847
848         ast_mutex_unlock(&config_lock);
849         ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name);
850
851         return 1;
852 }
853
854 int ast_config_engine_deregister(struct ast_config_engine *del) 
855 {
856         struct ast_config_engine *ptr, *last=NULL;
857
858         ast_mutex_lock(&config_lock);
859
860         for (ptr = config_engine_list; ptr; ptr=ptr->next) {
861                 if (ptr == del) {
862                         if (last)
863                                 last->next = ptr->next;
864                         else
865                                 config_engine_list = ptr->next;
866                         break;
867                 }
868                 last = ptr;
869         }
870
871         ast_mutex_unlock(&config_lock);
872
873         return 0;
874 }
875
876 static struct ast_config_engine *find_engine(const char *filename, char *database, int dbsiz, char *table, int tabsiz) 
877 {
878         struct ast_config_engine *eng, *ret=NULL;
879         struct ast_config_map *map;
880
881         ast_mutex_lock(&config_lock);
882
883         map = config_maps;
884         while (map) {
885                 if (!strcasecmp(filename, map->name)) {
886                         strncpy(database, map->database, dbsiz-1);
887                         if (map->table)
888                                 strncpy(table, map->table, tabsiz-1);
889                         else
890                                 strncpy(table, filename, tabsiz-1);
891                         break;
892                 }
893                 map = map->next;
894         }
895         if (map) {
896                 for (eng = config_engine_list; eng; eng = eng->next) {
897                         if (!strcmp(eng->name, map->driver)) {
898                                 ret = eng;
899                                 break;
900                         }
901                 }
902         }
903         ast_mutex_unlock(&config_lock);
904
905         return ret;
906 }
907
908 static struct ast_config_engine text_file_engine = {
909         .name = "text",
910         .load_func = config_text_file_load,
911 };
912
913 struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg)
914 {
915         char db[256];
916         char table[256];
917         struct ast_config_engine *loader = &text_file_engine;
918         struct ast_config *result;
919
920         if (cfg->include_level == cfg->max_include_level) {
921                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
922                 return NULL;
923         }
924
925         cfg->include_level++;
926
927         if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
928                 struct ast_config_engine *eng;
929
930                 eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
931                 if (eng && eng->load_func) {
932                         loader = eng;
933                 } else {
934                         eng = find_engine("global", db, sizeof(db), table, sizeof(table));
935                         if (eng && eng->load_func)
936                                 loader = eng;
937                 }
938         }
939
940         result = loader->load_func(db, table, filename, cfg);
941         if (result)
942                 result->include_level--;
943
944         return result;
945 }
946
947 struct ast_config *ast_config_load(const char *filename)
948 {
949         struct ast_config *cfg;
950         struct ast_config *result;
951
952         cfg = ast_config_new();
953         if (!cfg)
954                 return NULL;
955
956         result = ast_config_internal_load(filename, cfg);
957         if (!result)
958                 ast_config_destroy(cfg);
959
960         return result;
961 }
962
963 struct ast_variable *ast_load_realtime(const char *family, ...)
964 {
965         struct ast_config_engine *eng;
966         char db[256]="";
967         char table[256]="";
968         struct ast_variable *res=NULL;
969         va_list ap;
970
971         va_start(ap, family);
972         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
973         if (eng && eng->realtime_func) 
974                 res = eng->realtime_func(db, table, ap);
975         va_end(ap);
976
977         return res;
978 }
979
980 struct ast_config *ast_load_realtime_multientry(const char *family, ...)
981 {
982         struct ast_config_engine *eng;
983         char db[256]="";
984         char table[256]="";
985         struct ast_config *res=NULL;
986         va_list ap;
987
988         va_start(ap, family);
989         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
990         if (eng && eng->realtime_multi_func) 
991                 res = eng->realtime_multi_func(db, table, ap);
992         va_end(ap);
993
994         return res;
995 }
996
997 int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...)
998 {
999         struct ast_config_engine *eng;
1000         int res = -1;
1001         char db[256]="";
1002         char table[256]="";
1003         va_list ap;
1004
1005         va_start(ap, lookup);
1006         eng = find_engine(family, db, sizeof(db), table, sizeof(table));
1007         if (eng && eng->update_func) 
1008                 res = eng->update_func(db, table, keyfield, lookup, ap);
1009         va_end(ap);
1010
1011         return res;
1012 }
1013
1014 static int config_command(int fd, int argc, char **argv) 
1015 {
1016         struct ast_config_engine *eng;
1017         struct ast_config_map *map;
1018         
1019         ast_mutex_lock(&config_lock);
1020
1021         ast_cli(fd, "\n\n");
1022         for (eng = config_engine_list; eng; eng = eng->next) {
1023                 ast_cli(fd, "\nConfig Engine: %s\n", eng->name);
1024                 for (map = config_maps; map; map = map->next)
1025                         if (!strcasecmp(map->driver, eng->name)) {
1026                                 ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database,
1027                                         map->table ? map->table : map->name);
1028                                 break;
1029                         }
1030         }
1031         ast_cli(fd,"\n\n");
1032         
1033         ast_mutex_unlock(&config_lock);
1034
1035         return 0;
1036 }
1037
1038 static struct ast_cli_entry config_command_struct = {
1039         { "show", "config", "mappings" }, config_command, "Show Config mappings (file names to config engines)"
1040 };
1041
1042 int register_config_cli() 
1043 {
1044         return ast_cli_register(&config_command_struct);
1045 }