Add function ast_false, like ast_true
[asterisk/asterisk.git] / config.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Configuration File Parser
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
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 #include <asterisk/config.h>
21 #include <asterisk/options.h>
22 #include <asterisk/logger.h>
23 #include "asterisk.h"
24 #include "astconf.h"
25
26 #define MAX_INCLUDE_LEVEL 10
27
28 struct ast_category {
29         char name[80];
30         struct ast_variable *root;
31         struct ast_category *next;
32 #ifdef PRESERVE_COMMENTS
33         struct ast_comment *precomments;
34         struct ast_comment *sameline;
35 #endif  
36 };
37
38 struct ast_config {
39         /* Maybe this structure isn't necessary but we'll keep it
40            for now */
41         struct ast_category *root;
42         struct ast_category *prev;
43 #ifdef PRESERVE_COMMENTS
44         struct ast_comment *trailingcomments;
45 #endif  
46 };
47
48 #ifdef PRESERVE_COMMENTS
49 struct ast_comment_struct
50 {
51         struct ast_comment *root;
52         struct ast_comment *prev;
53 };
54 #endif
55
56 static char *strip(char *buf)
57 {
58         char *start;
59         /* Strip off trailing whitespace, returns, etc */
60         while(strlen(buf) && (buf[strlen(buf)-1]<33))
61                 buf[strlen(buf)-1] = '\0';
62         start = buf;
63         /* Strip off leading whitespace, returns, etc */
64         while(*start && (*start < 33))
65                 *start++ = '\0';
66         return start;
67 }
68
69 #ifdef PRESERVE_COMMENTS
70 static void free_comments(struct ast_comment *com)
71 {
72         struct ast_comment *l;
73         while (com) {
74                 l = com;
75                 com = com->next;
76                 free(l);
77         }
78 }
79 #endif
80
81 void ast_destroy(struct ast_config *ast)
82 {
83         struct ast_category *cat, *catn;
84         struct ast_variable *v, *vn;
85
86         if (!ast)
87                 return;
88
89         cat = ast->root;
90         while(cat) {
91                 v = cat->root;
92                 while(v) {
93                         vn = v;
94                         free(v->name);
95                         free(v->value);
96 #ifdef PRESERVE_COMMENTS
97                         free_comments(v->precomments);
98                         free_comments(v->sameline);
99 #endif                  
100                         v = v->next;
101                         free(vn);
102                 }
103                 catn = cat;
104 #ifdef PRESERVE_COMMENTS
105                 free_comments(cat->precomments);
106                 free_comments(cat->sameline);
107 #endif          
108                 cat = cat->next;
109                 free(catn);
110         }
111 #ifdef PRESERVE_COMMENTS
112         free_comments(ast->trailingcomments);
113 #endif  
114         free(ast);
115 }
116
117 int ast_true(char *s)
118 {
119         if (!s)
120                 return 0;
121         /* Determine if this is a true value */
122         if (!strcasecmp(s, "yes") ||
123             !strcasecmp(s, "true") ||
124                 !strcasecmp(s, "y") ||
125                 !strcasecmp(s, "t") ||
126                 !strcasecmp(s, "1"))
127                         return -1;
128         return 0;
129 }
130
131 int ast_false(char *s)
132 {
133         if (!s)
134                 return 0;
135         /* Determine if this is a false value */
136         if (!strcasecmp(s, "no") ||
137             !strcasecmp(s, "false") ||
138                 !strcasecmp(s, "n") ||
139                 !strcasecmp(s, "f") ||
140                 !strcasecmp(s, "0"))
141                         return -1;
142         return 0;
143 }
144
145 struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
146 {
147         struct ast_category *cat;
148         cat = config->root;
149         while(cat) {
150                 if (cat->name == category)
151                         return cat->root;
152                 cat = cat->next;
153         }
154         cat = config->root;
155         while(cat) {
156                 if (!strcasecmp(cat->name, category))
157                         return cat->root;
158                 cat = cat->next;
159         }
160         return NULL;
161 }
162
163 char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
164 {
165         struct ast_variable *v;
166         if (category) {
167                 v = ast_variable_browse(config, category);
168                 while (v) {
169                         if (value == v->name)
170                                 return v->value;
171                         v=v->next;
172                 }
173                 v = ast_variable_browse(config, category);
174                 while (v) {
175                         if (!strcasecmp(value, v->name))
176                                 return v->value;
177                         v=v->next;
178                 }
179         } else {
180                 struct ast_category *cat;
181                 cat = config->root;
182                 while(cat) {
183                         v = cat->root;
184                         while (v) {
185                                 if (!strcasecmp(value, v->name))
186                                         return v->value;
187                                 v=v->next;
188                         }
189                         cat = cat->next;
190                 }
191         }
192         return NULL;
193 }
194
195 #ifdef PRESERVE_COMMENTS
196 int ast_variable_delete(struct ast_config *cfg, char *category, char *variable, char *value)
197 {
198         struct ast_variable *v, *pv, *bv, *bpv;
199         struct ast_category *cat;
200         cat = cfg->root;
201         while(cat) {
202                 if (cat->name == category) {
203                         break;
204                 }
205                 cat = cat->next;
206         }
207         if (!cat) {
208                 cat = cfg->root;
209                 while(cat) {
210                         if (!strcasecmp(cat->name, category)) {
211                                 break;
212                         }
213                         cat = cat->next;
214                 }
215         }
216         if (!cat)
217                 return -1;
218         v = cat->root;
219         pv = NULL;
220         while (v) {
221                 if ((variable == v->name) && (!value || !strcmp(v->value, value)))
222                         break;
223                 pv = v;
224                 v=v->next;
225         }
226         if (!v) {
227                 /* Get the last one that looks like it */
228                 bv = NULL;
229                 bpv = NULL;
230                 v = cat->root;
231                 pv = NULL;
232                 while (v) {
233                         if (!strcasecmp(variable, v->name) && (!value || !strcmp(v->value, value))) {
234                                 bv = v;
235                                 bpv = pv;
236                         }
237                         pv = v;
238                         v=v->next;
239                 }
240                 v = bv;
241         }
242
243         if (v) {
244                 /* Unlink from original position */
245                 if (pv) 
246                         pv->next = v->next;
247                 else
248                         cat->root = v->next;
249                 v->next = NULL;
250                 free(v->name);
251                 if (v->value)
252                         free(v->value);
253                 free_comments(v->sameline);
254                 free_comments(v->precomments);
255                 return 0;
256         }
257         return -1;
258 }
259
260 int ast_category_delete(struct ast_config *cfg, char *category)
261 {
262         struct ast_variable *v, *pv;
263         struct ast_category *cat, *cprev;
264         cat = cfg->root;
265         cprev = NULL;
266         while(cat) {
267                 if (cat->name == category) {
268                         break;
269                 }
270                 cprev = cat;
271                 cat = cat->next;
272         }
273         if (!cat) {
274                 cat = cfg->root;
275                 cprev = NULL;
276                 while(cat) {
277                         if (!strcasecmp(cat->name, category)) {
278                                 break;
279                         }
280                         cprev = cat;
281                         cat = cat->next;
282                 }
283         }
284         if (!cat)
285                 return -1;
286         /* Unlink it */
287         if (cprev)
288                 cprev->next = cat->next;
289         else
290                 cfg->root = cat->next;
291         v = cat->root;
292         while (v) {
293                 pv = v;
294                 v=v->next;
295                 if (pv->value)
296                         free(pv->value);
297                 if (pv->name)
298                         free(pv->name);
299                 free_comments(pv->sameline);
300                 free_comments(pv->precomments);
301                 free(pv);
302         }
303         free_comments(cat->sameline);
304         free_comments(cat->precomments);
305         free(cat);
306         return 0;
307 }
308
309 struct ast_variable *ast_variable_append_modify(struct ast_config *config, char *category, char *variable, char *value, int newcat, int newvar, int move)
310 {
311         struct ast_variable *v, *pv=NULL, *bv, *bpv;
312         struct ast_category *cat, *pcat;
313         cat = config->root;
314         if (!newcat) {
315                 while(cat) {
316                         if (cat->name == category) {
317                                 break;
318                         }
319                         cat = cat->next;
320                 }
321                 if (!cat) {
322                         cat = config->root;
323                         while(cat) {
324                                 if (!strcasecmp(cat->name, category)) {
325                                         break;
326                                 }
327                                 cat = cat->next;
328                         }
329                 }
330         }
331         if (!cat) {
332                 cat = malloc(sizeof(struct ast_category));
333                 if (!cat)
334                         return NULL;
335                 memset(cat, 0, sizeof(struct ast_category));
336                 strncpy(cat->name, category, sizeof(cat->name));
337                 if (config->root) {
338                         /* Put us at the end */
339                         pcat = config->root;
340                         while(pcat->next)
341                                 pcat = pcat->next;
342                         pcat->next = cat;
343                 } else {
344                         /* We're the first one */
345                         config->root = cat;
346                 }
347                         
348         }
349         if (!newvar) {
350                 v = cat->root;
351                 pv = NULL;
352                 while (v) {
353                         if (variable == v->name)
354                                 break;
355                         pv = v;
356                         v=v->next;
357                 }
358                 if (!v) {
359                         /* Get the last one that looks like it */
360                         bv = NULL;
361                         bpv = NULL;
362                         v = cat->root;
363                         pv = NULL;
364                         while (v) {
365                                 if (!strcasecmp(variable, v->name)) {
366                                         bv = v;
367                                         bpv = pv;
368                                 }
369                                 pv = v;
370                                 v=v->next;
371                         }
372                         v = bv;
373                 }
374         } else v = NULL;
375         if (v && move) {
376                 /* Unlink from original position */
377                 if (pv) 
378                         pv->next = v->next;
379                 else
380                         cat->root = v->next;
381                 v->next = NULL;
382         }
383         if (!v) {
384                 v = malloc(sizeof(struct ast_variable));
385                 if (!v)
386                         return NULL;
387                 memset(v, 0, sizeof(struct ast_variable));
388                 v->name = strdup(variable);
389                 move = 1;
390         }
391         if (v->value)
392                 free(v->value);
393         if (value)
394                 v->value = strdup(value);
395         else
396                 v->value = strdup("");
397         if (move) {
398                 if (cat->root) {
399                         pv = cat->root;
400                         while (pv->next) 
401                                 pv = pv->next;
402                         pv->next = v;
403                 } else {
404                         cat->root = v;
405                 }
406         }
407         return v;
408 }
409 #endif          
410
411 int ast_category_exist(struct ast_config *config, char *category_name)
412 {
413         struct ast_category *category = NULL;
414
415         category = config->root;
416
417         while(category) {
418                 if (!strcasecmp(category->name,category_name)) 
419                         return 1;
420                 category = category->next;
421         } 
422
423         return 0;
424 }
425
426 #ifdef PRESERVE_COMMENTS
427 static struct ast_comment *build_comment(char *cmt)
428 {
429         struct ast_comment *c;
430         int len = strlen(cmt) + 1;
431         c = malloc(sizeof(struct ast_comment) + len);
432         if (c) {
433                 /* Memset the header */
434                 memset(c, 0, sizeof(struct ast_comment));
435                 /* Copy the rest */
436                 strcpy(c->cmt, cmt);
437         }
438         return c;
439 }
440 #endif
441
442 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
443 #ifdef PRESERVE_COMMENTS
444 , struct ast_comment_struct *acs
445 #endif
446 );
447
448 static int cfg_process(struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, char *buf, int lineno, char *configfile, int includelevel 
449 #ifdef PRESERVE_COMMENTS
450 ,struct ast_comment_struct *acs
451 #endif
452 )
453 {
454         char *c;
455         char *cur;
456         struct ast_variable *v;
457 #ifdef PRESERVE_COMMENTS
458         struct ast_comment *com = NULL;
459 #endif  
460         int object;
461         /* Strip off lines using ; as comment */
462         c = strchr(buf, ';');
463         if (c) {
464                 *c = '\0';
465 #ifdef PRESERVE_COMMENTS
466                 c++;
467                 if (*c != '!')
468                         com = build_comment(c);
469 #endif                  
470         }
471         cur = strip(buf);
472         if (strlen(cur)) {
473                 /* Actually parse the entry */
474                 if (cur[0] == '[') {
475                         /* A category header */
476                         c = strchr(cur, ']');
477                         if (c) {
478                                 *c = 0;
479                                 *_tmpc = malloc(sizeof(struct ast_category));
480                                 if (!*_tmpc) {
481                                         ast_destroy(tmp);
482                                         ast_log(LOG_WARNING,
483                                                 "Out of memory, line %d\n", lineno);
484                                         return -1;
485                                 }
486                                 memset(*_tmpc, 0, sizeof(struct ast_category));
487                                 strncpy((*_tmpc)->name, cur+1, sizeof((*_tmpc)->name) - 1);
488                                 (*_tmpc)->root =  NULL;
489 #ifdef PRESERVE_COMMENTS
490                                 (*_tmpc)->precomments = acs->root;
491                                 (*_tmpc)->sameline = com;
492 #endif                          
493                                 if (!tmp->prev)
494                                         tmp->root = *_tmpc;
495                                 else
496                                         tmp->prev->next = *_tmpc;
497
498                                 tmp->prev = *_tmpc;
499 #ifdef PRESERVE_COMMENTS
500                                 acs->root = NULL;
501                                 acs->prev = NULL;
502 #endif                          
503                                 *_last =  NULL;
504                         } else {
505                                 ast_log(LOG_WARNING, 
506                                         "parse error: no closing ']', line %d of %s\n", lineno, configfile);
507                         }
508                 } else if (cur[0] == '#') {
509                         /* A directive */
510                         cur++;
511                         c = cur;
512                         while(*c && (*c > 32)) c++;
513                         if (*c) {
514                                 *c = '\0';
515                                 c++;
516                                 /* Find real argument */
517                                 while(*c  && (*c < 33)) c++;
518                                 if (!*c)
519                                         c = NULL;
520                         } else 
521                                 c = NULL;
522                         if (!strcasecmp(cur, "include")) {
523                                 /* A #include */
524                                 if (c) {
525                                         while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
526                                         /* Get rid of leading mess */
527                                         cur = c;
528                                         while(strlen(cur)) {
529                                                 c = cur + strlen(cur) - 1;
530                                                 if ((*c == '>') || (*c == '<') || (*c == '\"'))
531                                                         *c = '\0';
532                                                 else
533                                                         break;
534                                         }
535                                         if (includelevel < MAX_INCLUDE_LEVEL) {
536                                                 __ast_load(cur, tmp, _tmpc, _last, includelevel + 1
537 #ifdef PRESERVE_COMMENTS
538                                                 ,acs
539 #endif
540                                                 );
541                                         } else 
542                                                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", includelevel);
543                                 } else
544                                         ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
545                                 /* Strip off leading and trailing "'s and <>'s */
546                         } else 
547                                 ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
548                 } else {
549                         /* Just a line (variable = value) */
550                         if (!*_tmpc) {
551                                 ast_log(LOG_WARNING,
552                                         "parse error: No category context for line %d of %s\n", lineno, configfile);
553                                 ast_destroy(tmp);
554                                 return -1;
555                         }
556                         c = strchr(cur, '=');
557                         if (c) {
558                                 *c = 0;
559                                 c++;
560                                 /* Ignore > in => */
561                                 if (*c== '>') {
562                                         object = 1;
563                                         c++;
564                                 } else
565                                         object = 0;
566                                 v = malloc(sizeof(struct ast_variable));
567                                 if (v) {
568                                         memset(v, 0, sizeof(struct ast_variable));
569                                         v->next = NULL;
570                                         v->name = strdup(strip(cur));
571                                         v->value = strdup(strip(c));
572                                         v->lineno = lineno;
573                                         v->object = object;
574                                         /* Put and reset comments */
575 #ifdef PRESERVE_COMMENTS
576                                         v->precomments = acs->root;
577                                         v->sameline = com;
578                                         acs->prev = NULL;
579                                         acs->root = NULL;
580 #endif                                  
581                                         v->blanklines = 0;
582                                         if (*_last)
583                                                 (*_last)->next = v;
584                                         else
585                                                 (*_tmpc)->root = v;
586                                         *_last = v;
587                                 } else {
588                                         ast_destroy(tmp);
589                                         ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
590                                         return -1;
591                                 }
592                         } else {
593                                 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
594                         }
595                                                                                                                 
596                 }
597         } else {
598                 /* store any comments if there are any */
599 #ifdef PRESERVE_COMMENTS
600                 if (com) {
601                         if (acs->prev)
602                                 acs->prev->next = com;
603                         else
604                                 acs->root = com;
605                         acs->prev = com;
606                 } else {
607                 if (*_last) 
608                         (*_last)->blanklines++;
609
610                 }
611 #endif
612         }
613         return 0;
614 }
615
616 #ifdef PRESERVE_COMMENTS
617 static void dump_comments(FILE *f, struct ast_comment *comment)
618 {
619         while (comment) {
620                 fprintf(f, ";%s", comment->cmt);
621                 comment = comment->next;
622         }
623 }
624 #endif
625
626 int ast_save(char *configfile, struct ast_config *cfg, char *generator)
627 {
628         FILE *f;
629         char fn[256];
630         char date[256];
631         time_t t;
632         struct ast_variable *var;
633         struct ast_category *cat;
634         int blanklines = 0;
635         if (configfile[0] == '/') {
636                 strncpy(fn, configfile, sizeof(fn)-1);
637         } else {
638                 snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
639         }
640         time(&t);
641         strncpy(date, ctime(&t), sizeof(date));
642         if ((f = fopen(fn, "w"))) {
643                 if ((option_verbose > 1) && !option_debug)
644                         ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
645                 fprintf(f, ";!\n");
646                 fprintf(f, ";! Automatically generated configuration file\n");
647                 fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
648                 fprintf(f, ";! Generator: %s\n", generator);
649                 fprintf(f, ";! Creation Date: %s", date);
650                 fprintf(f, ";!\n");
651                 cat = cfg->root;
652                 while(cat) {
653 #ifdef PRESERVE_COMMENTS
654                         /* Dump any precomments */
655                         dump_comments(f, cat->precomments);
656 #endif
657                         /* Dump section with any appropriate comment */
658 #ifdef PRESERVE_COMMENTS
659                         if (cat->sameline) 
660                                 fprintf(f, "[%s]  ; %s\n", cat->name, cat->sameline->cmt);
661                         else
662 #endif
663                                 fprintf(f, "[%s]\n", cat->name);
664                         var = cat->root;
665                         while(var) {
666 #ifdef PRESERVE_COMMENTS
667                                 dump_comments(f, var->precomments);
668 #endif                          
669                                 if (var->sameline) 
670                                         fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
671                                 else    
672                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
673                                 if (var->blanklines) {
674                                         blanklines = var->blanklines;
675                                         while (blanklines) {
676                                                 fprintf(f, "\n");
677                                                 blanklines--;
678                                         }
679                                 }
680                                         
681                                 var = var->next;
682                         }
683 #if 0
684                         /* Put an empty line */
685                         fprintf(f, "\n");
686 #endif
687                         cat = cat->next;
688                 }
689 #ifdef PRESERVE_COMMENTS
690                 dump_comments(f, cfg->trailingcomments);
691 #endif          
692         } else {
693                 if (option_debug)
694                         printf("Unable to open for writing: %s\n", fn);
695                 else if (option_verbose > 1)
696                         printf( "Unable to write (%s)", strerror(errno));
697                 return -1;
698         }
699         fclose(f);
700         return 0;
701 }
702
703 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
704 #ifdef PRESERVE_COMMENTS
705 , struct ast_comment_struct *acs
706 #endif
707 )
708 {
709         char fn[256];
710         char buf[512];
711         FILE *f;
712         int lineno=0;
713         int master=0;
714
715         if (configfile[0] == '/') {
716                 strncpy(fn, configfile, sizeof(fn)-1);
717         } else {
718                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, configfile);
719         }
720         if ((option_verbose > 1) && !option_debug) {
721                 ast_verbose(  VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
722                 fflush(stdout);
723         }
724         if ((f = fopen(fn, "r"))) {
725                 if (option_debug)
726                         ast_log(LOG_DEBUG, "Parsing %s\n", fn);
727                 else if (option_verbose > 1)
728                         ast_verbose( "Found\n");
729                 if (!tmp) {
730                         tmp = malloc(sizeof(struct ast_config));
731                         if (tmp)
732                                 memset(tmp, 0, sizeof(struct ast_config));
733
734                         master = 1;
735                 }
736                 if (!tmp) {
737                         ast_log(LOG_WARNING, "Out of memory\n");
738                         fclose(f);
739                         return NULL;
740                 }
741                 while(!feof(f)) {
742                         fgets(buf, sizeof(buf), f);
743                         lineno++;
744                         if (!feof(f)) {
745                                 if (cfg_process(tmp, _tmpc, _last, buf, lineno, configfile, includelevel
746 #ifdef PRESERVE_COMMENTS
747                                 , acs
748 #endif
749                                 )) {
750                                         fclose(f);
751                                         return NULL;
752                                 }
753                         }
754                 }
755                 fclose(f);              
756         } else {
757                 if (option_debug)
758                         ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
759                 else if (option_verbose > 1)
760                         ast_verbose( "Not found (%s)\n", strerror(errno));
761         }
762 #ifdef PRESERVE_COMMENTS
763         if (master) {
764                 /* Keep trailing comments */
765                 tmp->trailingcomments = acs->root;
766                 acs->root = NULL;
767                 acs->prev = NULL;
768         }
769 #endif
770         return tmp;
771 }
772
773 struct ast_config *ast_load(char *configfile)
774 {
775         struct ast_category *tmpc=NULL;
776         struct ast_variable *last = NULL;
777 #ifdef PRESERVE_COMMENTS
778         struct ast_comment_struct acs = { NULL, NULL };
779 #endif  
780         return __ast_load(configfile, NULL, &tmpc, &last, 0 
781 #ifdef PRESERVE_COMMENTS
782         ,&acs
783 #endif
784         );
785 }
786
787 char *ast_category_browse(struct ast_config *config, char *prev)
788 {       
789         struct ast_category *cat;
790         if (!prev) {
791                 if (config->root)
792                         return config->root->name;
793                 else
794                         return NULL;
795         }
796         cat = config->root;
797         while(cat) {
798                 if (cat->name == prev) {
799                         if (cat->next)
800                                 return cat->next->name;
801                         else
802                                 return NULL;
803                 }
804                 cat = cat->next;
805         }
806         cat = config->root;
807         while(cat) {
808                 if (!strcasecmp(cat->name, prev)) {
809                         if (cat->next)
810                                 return cat->next->name;
811                         else
812                                 return NULL;
813                 }
814                 cat = cat->next;
815         }
816         return NULL;
817 }