parser/sdp: more suggestive debug message
[sip-router] / cfg_parser.c
1 /*
2  * $Id$
3  * Standalone Configuration File Parser
4  *
5  * Copyright (C) 2008 iptelorg GmbH
6  * Written by Jan Janak <jan@iptel.org>
7  *
8  * This file is part of SER, a free SIP server.
9  *
10  * SER is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free Software
12  * Foundation; either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * SER is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc., 
22  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23  */
24 /*!
25  * \file
26  * \brief SIP-router core :: 
27  * \ingroup core
28  *
29  * Module: \ref core
30  *
31  * See \ref ConfigEngine
32  *
33  * \page ConfigEngine
34  * In file \ref cfg_parser.c 
35  * Configuration examples
36  * - \ref ConfigExample1
37  * - \ref ConfigExample2
38  * - \ref ConfigExample3
39  * - \ref ConfigExample4
40  * - \ref ConfigExample5
41  * - \ref ConfigExample6
42  * - \ref ConfigExample7
43  * - \ref ConfigExample8
44  *
45  * <b>Run-time Allocated Destination Variables</b>
46  * - the destination variable pointers in arrays are assigned at compile time
47  * - The address of variables allocated on the heap (typically in dynamically allocated
48  *   structures) is not know and thus we need to assign NULL initially and change the pointer
49  *   at runtime.
50  *   (provide an example).
51  *
52  * <b>Built-in parsing functions</b>
53  *
54  * *_val functions parse the whole option body (including =)
55  */
56
57
58 /*! \page ConfigExample1  Configuration engine Example 1: Options without values
59  *
60 \verbatim
61  *      str file = STR_STATIC_INIT("test.cfg");
62  *      cfg_parser_t* parser;
63  *      int feature_a = 0, feature_b = 0;
64  *
65  *      cfg_option_t options[] = {
66  *              {"enable_feature_a",  .param = &feature_a, .val = 1},
67  *              {"disable_feature_a", .param = &feature_a, .val = 0},
68  *              {"feature_b",         .param = &feature_b, .val = 1},
69  *      {0}
70  *      };
71  *
72  *      if ((parser = cfg_parser_init(&file)) == NULL) {
73  *              ERR("Error while creating parser\n");
74  *              return -1;
75  *      }
76  *
77  *      cfg_set_options(parser, options);
78  *
79  *      if (sr_cfg_parse(parser) < 0) {
80  *              ERR("Error while parsing configuration file\n");
81  *      cfg_parser_close(parser);
82  *              return -1;
83  *      }
84  *
85  *      cfg_parser_close(parser);
86 \endverbatim
87  */
88
89 /*! \page ConfigExample2  Configuration engine Example 2: Options with integer values
90 \verbatim
91  *      cfg_option_t options[] = {
92  *              {"max_number",   .param = &max_number,   .f = cfg_parse_int_val },
93  *              {"extra_checks", .param = &extra_checks, .f = cfg_parse_bool_val},
94  *              {0}
95  *      };
96 \endverbatim
97  */
98
99 /*! \page ConfigExample3 Configuration engine Example 3: Enum options
100 \verbatim
101  * int scope;
102  *
103  *      cfg_option_t scopes[] = {
104  *          {"base",     .param = &scope, .val = 1},
105  *          {"onelevel", .param = &scope, .val = 2},
106  *          {"one",      .param = &scope, .val = 3},
107  *          {"subtree",  .param = &scope, .val = 4},
108  *          {"sub",      .param = &scope, .val = 5},
109  *          {"children", .param = &scope, .val = 6},
110  *          {0}
111  *  };
112  *
113  *      cfg_option_t options[] = {
114  *              {"scope", .param = scopes, .f = cfg_parse_enum_val},
115  *              {0}
116  *      };
117 \endverbatim
118  */
119
120 /*! \page ConfigExample4 Configuration engine Example 4: Options with string values
121 \verbatim
122 *       str filename = STR_NULL;
123  *
124  *      cfg_option_t options[] = {
125  *              {"filename", .param = &filename, .f = cfg_parse_str_val},
126  *              {0}
127  *      };
128  *
129  * - By default the function returns a pointer to an internal buffer which will be destroyed
130  *   by a subsequent call
131  * - There are flags to tell the function to copy the resuting string in a pkg, shm, glibc,
132  *   or static buffers
133 \endverbatim
134  */
135
136 /*! \page ConfigExample5 Configuration engine Example 5: Custom value parsing
137  * TBD
138  */
139
140 /*! \page ConfigExample6 Configuration engine Example 6: Parsing Sections
141  * TBD
142  */
143
144 /*! \page ConfigExample7 Configuration engine Example 7: Default Options
145  * TBD
146  */
147
148 /*! \page ConfigExample8 Configuration engine Example 8: Memory management of strings
149  *
150  * Data types with fixed size are easy, they can be copied into a pre-allocated memory
151  * buffer, strings cannot because their length is unknown.
152  */
153
154 #include "cfg_parser.h"
155
156 #include "mem/mem.h"
157 #include "mem/shm_mem.h"
158 #include "dprint.h"
159 #include "trim.h"
160 #include "ut.h"
161
162 #include <string.h>
163 #include <stdlib.h>
164 #include <stdio.h>
165 #include <libgen.h>
166
167
168 /*! \brief The states of the lexical scanner */
169 enum st {
170         ST_S,  /*!< Begin */
171         ST_A,  /*!< Alphanumeric */
172         ST_AE, /*!< Alphanumeric escaped */
173         ST_Q,  /*!< Quoted */
174         ST_QE, /*!< Quoted escaped */
175         ST_C,  /*!< Comment */
176         ST_CE, /*!< Comment escaped */
177         ST_E,  /*!< Escaped */
178 };
179
180
181 /*! \brief Test for alphanumeric characters */
182 #define IS_ALPHA(c) \
183     (((c) >= 'a' && (c) <= 'z') || \
184      ((c) >= 'A' && (c) <= 'Z') || \
185      ((c) >= '0' && (c) <= '9') || \
186      (c) == '_')
187
188
189 /*! \brief Test for delimiter characters */
190 #define IS_DELIM(c) \
191     ((c) == '=' || \
192      (c) == ':' || \
193      (c) == ';' || \
194      (c) == '.' || \
195      (c) == ',' || \
196      (c) == '?' || \
197      (c) == '[' || \
198      (c) == ']' || \
199      (c) == '/' || \
200      (c) == '@' || \
201      (c) == '!' || \
202      (c) == '$' || \
203      (c) == '%' || \
204      (c) == '&' || \
205      (c) == '*' || \
206      (c) == '(' || \
207      (c) == ')' || \
208      (c) == '-' || \
209      (c) == '+' || \
210      (c) == '|' || \
211      (c) == '\'')
212
213
214 /*! \brief Whitespace characters */
215 #define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r') 
216 #define IS_QUOTE(c)      ((c) == '\"')  /* Quote characters */
217 #define IS_COMMENT(c)    ((c) == '#')   /* Characters that start comments */
218 #define IS_ESCAPE(c)     ((c) == '\\')  /* Escape characters */
219 #define IS_EOL(c)        ((c) == '\n')  /* End of line */
220
221
222 /*! \brief
223  * Append character to the value of current token
224  */
225 #define PUSH(c)                            \
226     if (token->val.len >= MAX_TOKEN_LEN) { \
227         ERR("%s:%d:%d: Token too long\n",  \
228         st->file, st->line, st->col);      \
229         return -1;                         \
230     }                                      \
231     if (token->val.len == 0) {             \
232          token->start.line = st->line;     \
233          token->start.col = st->col;       \
234     }                                      \
235     token->val.s[token->val.len++] = (c);
236
237
238 /*! \brief
239  * Return current token from the lexical analyzer
240  */
241 #define RETURN(c)               \
242     token->end.line = st->line; \
243     token->end.col = st->col;   \
244     token->type = (c);          \
245     print_token(token);         \
246     return 0;
247
248
249 /*! \brief
250  * Get next character and update counters
251  */
252 #define READ_CHAR      \
253      c = fgetc(st->f); \
254      if (IS_EOL(c)) {  \
255          st->line++;   \
256          st->col = 0;  \
257      } else {          \
258          st->col++;    \
259      }
260
261
262 cfg_option_t cfg_bool_values[] = {
263         {"yes",      .val = 1},
264         {"true",     .val = 1},
265         {"enable",   .val = 1},
266         {"enabled",  .val = 1},
267         {"1",        .val = 1},
268         {"on",       .val = 1},
269         {"no",       .val = 0},
270         {"false",    .val = 0},
271         {"disable",  .val = 0},
272         {"disabled", .val = 0},
273         {"0",        .val = 0},
274         {"off",      .val = 0},
275         {0}
276 };
277
278
279 static void print_token(cfg_token_t* token)
280 {
281 #ifdef EXTRA_DEBUG
282         int i, j;
283         char* buf;
284
285         if ((buf = pkg_malloc(token->val.len * 2)) == NULL) {
286                 DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", 
287                         token->type, STR_FMT(&token->val),
288                         token->start.line, token->start.col, 
289                         token->end.line, token->end.col);
290         } else {
291                 for(i = 0, j = 0; i < token->val.len; i++) {
292                         switch(token->val.s[i]) {
293                         case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break;
294                         case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break;
295                         case '\t': buf[j++] = '\\'; buf[j++] = 't'; break;
296                         case '\0': buf[j++] = '\\'; buf[j++] = '0'; break;
297                         case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break;
298                         default: buf[j++] = token->val.s[i];
299                         }
300                 }
301                 DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", 
302                         token->type, j, buf,
303                         token->start.line, token->start.col, 
304                         token->end.line, token->end.col);
305                 pkg_free(buf);
306         }
307 #endif /* EXTRA_DEBUG */
308 }
309
310
311 int cfg_get_token(cfg_token_t* token, cfg_parser_t* st, unsigned int flags)
312 {
313         static int look_ahead = EOF;
314         int c;
315         enum st state;
316
317         state = ST_S;
318         
319         token->val.s = token->buf;
320         token->val.len = 0;
321
322         if (look_ahead != EOF) {
323                 c = look_ahead;
324                 look_ahead = EOF;
325         } else {
326                 READ_CHAR;
327         }
328
329         while(c != EOF) {
330                 switch(state) {
331                 case ST_S:
332                         if (flags & CFG_EXTENDED_ALPHA) {
333                                 if (IS_WHITESPACE(c)) {
334                                              /* Do nothing */
335                                 } else if (IS_ALPHA(c) ||
336                                            IS_ESCAPE(c) ||
337                                            IS_DELIM(c)) {
338                                         PUSH(c);
339                                         state = ST_A;
340                                 } else if (IS_QUOTE(c)) {
341                                         state = ST_Q;
342                                 } else if (IS_COMMENT(c)) {
343                                         state = ST_C;
344                                 } else if (IS_EOL(c)) {
345                                         PUSH(c);
346                                         RETURN(c);
347                                 } else {
348                                         ERR("%s:%d:%d: Invalid character 0x%x\n", 
349                                             st->file, st->line, st->col, c);
350                                         return -1;
351                                 }
352                         } else {
353                                 if (IS_WHITESPACE(c)) {
354                                              /* Do nothing */
355                                 } else if (IS_ALPHA(c)) {
356                                         PUSH(c);
357                                         state = ST_A;
358                                 } else if (IS_QUOTE(c)) {
359                                         state = ST_Q;
360                                 } else if (IS_COMMENT(c)) {
361                                         state = ST_C;
362                                 } else if (IS_ESCAPE(c)) {
363                                         state = ST_E;
364                                 } else if (IS_DELIM(c) || IS_EOL(c)) {
365                                         PUSH(c);
366                                         RETURN(c);
367                                 } else {
368                                         ERR("%s:%d:%d: Invalid character 0x%x\n", 
369                                             st->file, st->line, st->col, c);
370                                         return -1;
371                                 }
372                         }
373                         break;
374
375                 case ST_A:
376                         if (flags & CFG_EXTENDED_ALPHA) {
377                                 if (IS_ALPHA(c) ||
378                                     IS_DELIM(c) ||
379                                     IS_QUOTE(c)) {
380                                         PUSH(c);
381                                 } else if (IS_ESCAPE(c)) {
382                                         state = ST_AE;
383                                 } else if (IS_COMMENT(c) || IS_EOL(c) || IS_WHITESPACE(c)) {
384                                         look_ahead = c;
385                                         RETURN(CFG_TOKEN_ALPHA);
386                                 } else {
387                                         ERR("%s:%d:%d: Invalid character 0x%x\n", 
388                                             st->file, st->line, st->col, c);
389                                         return -1;
390                                 }
391                         } else {
392                                 if (IS_ALPHA(c)) {
393                                         PUSH(c);
394                                 } else if (IS_ESCAPE(c)) {
395                                         state = ST_AE;
396                                 } else if (IS_WHITESPACE(c) ||
397                                            IS_DELIM(c) ||
398                                            IS_QUOTE(c) ||
399                                            IS_COMMENT(c) ||
400                                            IS_EOL(c)) {
401                                         look_ahead = c;
402                                         RETURN(CFG_TOKEN_ALPHA);
403                                 } else {
404                                         ERR("%s:%d:%d: Invalid character 0x%x\n", 
405                                             st->file, st->line, st->col, c);
406                                         return -1;
407                                 }
408                         }
409                         break;
410
411                 case ST_AE:
412                         if (IS_COMMENT(c) ||
413                             IS_QUOTE(c) ||
414                             IS_ESCAPE(c)) {
415                                 PUSH(c);
416                         } else if (c == 'r') {
417                                 PUSH('\r');
418                         } else if (c == 'n') {
419                                 PUSH('\n');
420                         } else if (c == 't') {
421                                 PUSH('\t');
422                         } else if (c == ' ') {
423                                 PUSH(' ');
424                         } else if (IS_EOL(c)) {
425                                      /* Do nothing */
426                         } else {
427                                 ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
428                                     st->file, st->line, st->col, c);
429                                 return -1;
430                         }
431                         state = ST_A;
432                         break;
433
434                 case ST_Q:
435                         if (IS_QUOTE(c)) {
436                                 RETURN(CFG_TOKEN_STRING);
437                         } else if (IS_ESCAPE(c)) {
438                                 state = ST_QE;
439                                 break;
440                         } else {
441                                 PUSH(c);
442                         }
443                         break;
444
445                 case ST_QE:
446                         if (IS_ESCAPE(c) ||
447                             IS_QUOTE(c)) {
448                                 PUSH(c);
449                         } else if (c == 'n') {
450                                 PUSH('\n');
451                         } else if (c == 'r') {
452                                 PUSH('\r');
453                         } else if (c == 't') {
454                                 PUSH('\t');
455                         } else if (IS_EOL(c)) {
456                                      /* Do nothing */
457                         } else {
458                                 ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
459                                     st->file, st->line, st->col, c);
460                                 return -1;
461                         }
462                         state = ST_Q;
463                         break;
464
465                 case ST_C:
466                         if (IS_ESCAPE(c)) {
467                                 state = ST_CE;
468                         } else if (IS_EOL(c)) {
469                                 state = ST_S;
470                                 continue; /* Do not read a new char, return EOL */
471                         } else {
472                                      /* Do nothing */
473                         }
474                         break;
475
476                 case ST_CE:
477                         state = ST_C;
478                         break;
479
480                 case ST_E:
481                         if (IS_COMMENT(c) ||
482                             IS_QUOTE(c) ||
483                             IS_ESCAPE(c)) {
484                                 PUSH(c);
485                                 RETURN(c);
486                         } else if (c == 'r') {
487                                 PUSH('\r');
488                                 RETURN('\r');
489                         } else if (c == 'n') {
490                                 PUSH('\n');
491                                 RETURN('\n');
492                         } else if (c == 't') {
493                                 PUSH('\t');
494                                 RETURN('\t');
495                         } else if (c == ' ') {
496                                 PUSH(' ');
497                                 RETURN(' ');
498                         } else if (IS_EOL(c)) {
499                                      /* Escped eol means no eol */
500                                 state = ST_S;
501                         } else {
502                                 ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
503                                     st->file, st->line, st->col, c);
504                                 return -1;
505                         }
506                         break;
507                 }
508
509                 READ_CHAR;
510         };
511
512         switch(state) {
513         case ST_S: 
514         case ST_C:
515         case ST_CE:
516                 return 1;
517
518         case ST_A:
519                 RETURN(CFG_TOKEN_ALPHA);
520
521         case ST_Q:
522                 ERR("%s:%d:%d: Premature end of file, missing closing quote in"
523                         " string constant\n", st->file, st->line, st->col);
524                 return -1;
525
526         case ST_QE:
527         case ST_E:
528         case ST_AE:
529                 ERR("%s:%d:%d: Premature end of file, missing escaped character\n", 
530                     st->file, st->line, st->col);
531                 return -1;
532         }
533         BUG("%s:%d:%d: Invalid state %d\n",
534                 st->file, st->line, st->col, state);
535         return -1;
536 }
537
538
539 int cfg_parse_section(void* param, cfg_parser_t* st, unsigned int flags)
540 {
541         cfg_token_t t;
542         int ret;
543
544         ret = cfg_parse_str(param, st, flags);
545         if (ret < 0) return ret;
546         if (ret > 0) {
547                 ERR("%s:%d:%d: Section name missing.\n",
548                         st->file, st->line, st->col);
549                 return ret;
550         }
551
552         ret = cfg_get_token(&t, st, flags);
553         if (ret < 0) goto error;
554         if (ret > 0) {
555                 ERR("%s:%d:%d: Closing ']' missing\n", st->file, st->line, st->col);
556                 goto error;
557         }
558         if (t.type != ']') {
559                 ERR("%s:%d:%d: Syntax error, ']' expected\n", 
560                     st->file, t.start.line, t.start.col);
561                 goto error;
562         }
563
564         if (cfg_eat_eol(st, flags)) goto error;
565         return 0;
566
567  error:
568         if (param && ((str*)param)->s) {
569                 if (flags & CFG_STR_PKGMEM) {
570                         pkg_free(((str*)param)->s);
571                         ((str*)param)->s = NULL;
572                 } else if (flags & CFG_STR_SHMMEM) {
573                         shm_free(((str*)param)->s);
574                         ((str*)param)->s = NULL;
575                 } else if (flags & CFG_STR_MALLOC) {
576                         free(((str*)param)->s);
577                         ((str*)param)->s = NULL;
578                 }               
579         }
580         return -1;
581 }
582
583
584 static char* get_base_name(str* filename)
585 {
586         char* tmp1, *tmp2, *res;
587         int len;
588
589         res = NULL;
590         if ((tmp1 = as_asciiz(filename)) == NULL) {
591                 ERR("cfg_parser: No memory left\n");
592                 goto error;
593         }
594         
595         if ((tmp2 = basename(tmp1)) == NULL) {
596                 ERR("cfg_parser: Error in basename\n");
597                 goto error;
598         }
599         len = strlen(tmp2);
600
601         if ((res = pkg_malloc(len + 1)) == NULL) {
602                 ERR("cfg_parser: No memory left");
603                 goto error;
604         }
605         memcpy(res, tmp2, len + 1);
606         pkg_free(tmp1);
607         return res;
608
609  error:
610         if (tmp1) pkg_free(tmp1);
611         return NULL;
612 }
613
614
615
616 /** intialize the config parser.
617  * @param basedir - path to the config file name. If 0 the path
618  *               (base directory) of the main ser.cfg file will be used, else
619  *               basedir will be concatenated to the filename. It will be
620  *               used only if filename is not an absolute path.
621  * @param filename - config filename (can include path elements).
622  * @return 0 on error, !=0 on success.
623  */
624 cfg_parser_t* cfg_parser_init(str* basedir, str* filename)
625 {
626         cfg_parser_t* st;
627         char* pathname, *base, *abs_pathname;
628
629         abs_pathname = NULL;
630         pathname = filename->s;
631         st = NULL;
632         base = NULL;
633         
634         /* if basedir == 0 or != "" get_abs_pathname */
635         if (basedir == 0  || basedir->len != 0) {
636                 if ((abs_pathname = get_abs_pathname(basedir, filename)) == NULL) {
637                         ERR("cfg_parser: Error while converting %.*s to absolute"
638                                         " pathname\n", STR_FMT(filename));
639                         goto error;
640                 }
641                 pathname = abs_pathname;
642         }
643
644         if ((base = get_base_name(filename)) == NULL) goto error;
645
646         if ((st = (cfg_parser_t*)pkg_malloc(sizeof(*st))) == NULL) {
647                 ERR("cfg_parser: No memory left\n");
648                 goto error;
649         }
650         memset(st, '\0', sizeof(*st));
651
652         if ((st->f = fopen(pathname, "r")) == NULL) {
653                 ERR("cfg_parser: Unable to open file '%s'\n", pathname);
654                 goto error;
655         }
656
657         if (abs_pathname) pkg_free(abs_pathname);
658
659         st->file = base;
660         st->line = 1;
661         st->col = 0;
662         return st;
663
664  error:
665         if (st) {
666                 if (st->f) fclose(st->f);
667                 pkg_free(st);
668         }
669         if (base) pkg_free(base);
670         if (abs_pathname) pkg_free(abs_pathname);
671         return NULL;
672 }
673
674
675 void cfg_parser_close(cfg_parser_t* st)
676 {
677         if (!st) return;
678         if (st->f) fclose(st->f);
679         if (st->file) pkg_free(st->file);
680         pkg_free(st);
681 }
682
683
684 void cfg_section_parser(cfg_parser_t* st, cfg_func_f parser, void* param)
685 {
686         if (st == NULL) return;
687         st->section.parser = parser;
688         st->section.param = param;
689 }
690
691
692 void cfg_set_options(cfg_parser_t* st, cfg_option_t* options)
693 {
694         if (st) st->options = options;
695 }
696
697
698 static int process_option(cfg_parser_t* st, cfg_option_t* opt)
699 {
700         if (opt->f) {
701                 /* We have a function so store it and pass opt->dst to it */
702                 if (opt->f(opt->param, st, opt->flags) < 0) return -1;
703         } else {
704                 /* We have no function, so if we have a pointer to some
705                  * variable in opt->param then store the value of opt->i
706                  * there, the variable is assumed to be of type i
707                  */
708                 if (opt->param) *(int*)opt->param = opt->val;
709         }
710         return 0;
711 }
712
713
714 int sr_cfg_parse(cfg_parser_t* st)
715 {
716         int ret;
717         cfg_token_t t;
718         cfg_option_t* opt;
719
720         while(1) {
721                 ret = cfg_get_token(&t, st, 0);
722                 if (ret < 0) return ret;
723                 if (ret > 0) break;
724
725                 switch(t.type) {
726                 case CFG_TOKEN_ALPHA:
727                         /* Lookup the option name */
728                         if ((opt = cfg_lookup_token(st->options, &t.val)) == NULL) {
729                                 ERR("%s:%d:%d: Unsupported option '%.*s'\n", 
730                                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
731                                 return -1;
732                         }
733
734                         st->cur_opt = &t;
735                         if (process_option(st, opt) < 0) return -1;
736                         break;
737
738                 case '[': 
739                         if (st->section.parser == NULL) {
740                                 ERR("%s:%d:%d: Syntax error\n", st->file,
741                                         t.start.line, t.start.col);
742                                 return -1;
743                         }
744                         if (st->section.parser(st->section.param, st, 0) < 0) return -1;
745                         break;
746
747                              /* Empty line */
748                 case '\n': continue;
749
750                 default:
751                         ERR("%s:%d:%d: Syntax error\n", 
752                             st->file, t.start.line, t.start.col);
753                         return -1;
754                 }
755         }
756         return 0;
757 }
758
759
760 cfg_option_t* cfg_lookup_token(cfg_option_t* table, str* token)
761 {
762         int len, i;
763         int (*cmp)(const char* s1, const char* s2, size_t n) = NULL;
764
765
766         if (table == NULL) return NULL;
767
768         for(i = 0; table[i].name; i++) {
769                 len = strlen(table[i].name);
770
771                 if (table[i].flags & CFG_PREFIX) {
772                         if (token->len < len) continue;
773                 } else {
774                         if (token->len != len) continue;
775                 }
776
777                 if (table[i].flags & CFG_CASE_SENSITIVE) cmp = strncmp;
778                 else cmp = strncasecmp;
779
780                 if (cmp(token->s, table[i].name, len)) continue;
781                 return table + i;
782         }
783         if (table[i].flags & CFG_DEFAULT) {
784                 return table + i;
785         }
786         return NULL;
787 }
788
789
790 int cfg_eat_equal(cfg_parser_t* st, unsigned int flags)
791 {
792         cfg_token_t t;
793         int ret;
794
795         ret = cfg_get_token(&t, st, flags);
796         if (ret < 0) return ret;
797         if (ret > 0) {
798                 ERR("%s:%d:%d: Delimiter '=' missing\n", 
799                     st->file, st->line, st->col);
800                 return ret;
801         }
802
803         if (t.type != '=') {
804                 ERR("%s:%d:%d: Syntax error, '=' expected\n", 
805                     st->file, t.start.line, t.start.col);
806                 return -1;
807         }
808         return 0;
809 }
810
811
812 int cfg_eat_eol(cfg_parser_t* st, unsigned int flags)
813 {
814         cfg_token_t t;
815         int ret;
816
817         /* Skip EOL */
818         ret = cfg_get_token(&t, st, 0);
819         if (ret < 0) return ret;
820         if (ret > 0) return 0;
821         if (t.type != '\n') {
822                 ERR("%s:%d:%d: End of line expected\n", 
823                         st->file, t.start.line, t.start.col);
824                 return -1;
825         }
826         return 0;
827 }
828
829
830 int cfg_parse_enum(void* param, cfg_parser_t* st, unsigned int flags)
831 {
832         int ret;
833     cfg_token_t t;
834         cfg_option_t* values, *val;
835         
836         values = (cfg_option_t*)param;
837
838         ret = cfg_get_token(&t, st, flags);
839         if (ret != 0) return ret;
840
841         if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
842                 ERR("%s:%d:%d: Invalid enum value '%.*s'\n",
843                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
844                 return -1;
845         }
846
847         if (values) {
848                 if ((val = cfg_lookup_token(values, &t.val)) == NULL) {
849                         ERR("%s:%d:%d Unsupported enum value '%.*s'\n", 
850                                 st->file, t.start.line, t.start.col, STR_FMT(&t.val));
851                         return -1;
852                 }
853                 return process_option(st, val);
854         } else {
855                 return 0;
856         }
857 }
858
859
860 int cfg_parse_enum_opt(void* param, cfg_parser_t* st, unsigned int flags)
861 {
862         int ret;
863
864         if (cfg_eat_equal(st, flags)) return -1;
865
866         ret = cfg_parse_enum(param, st, CFG_EXTENDED_ALPHA | flags);
867         if (ret > 0) {
868                 ERR("%s:%d:%d: Option value missing\n",
869                     st->file, st->line, st->col);
870                 return ret;
871         } else if (ret < 0) return ret;
872
873         if (cfg_eat_eol(st, flags)) return -1;
874         return 0;
875 }
876
877
878 int cfg_parse_str(void* param, cfg_parser_t* st, unsigned int flags)
879 {
880         str* val;
881         int ret;
882         char* buf;
883     cfg_token_t t;
884         
885         ret = cfg_get_token(&t, st, flags);
886         if (ret != 0) return ret;
887         
888         if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
889                 ERR("%s:%d:%d: Invalid string value '%.*s', a string expected.\n",
890                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
891                 return -1;
892         }
893
894         if (!param) return 0;
895         val = (str*)param;
896
897         if (flags & CFG_STR_STATIC) {
898                 if (!val->s || val->len <= t.val.len) {
899                         ERR("%s:%d:%d: Destination string buffer too small\n",
900                                 st->file, t.start.line, t.start.col);
901                         return -1;
902                 }
903                 buf = val->s;
904         } else if (flags & CFG_STR_SHMMEM) {
905                 if ((buf = shm_malloc(t.val.len + 1)) == NULL) {
906                         ERR("%s:%d:%d: Out of shared memory\n", st->file,
907                                 t.start.line, t.start.col);
908                         return -1;
909                 }
910                 if (val->s) shm_free(val->s);
911         } else if (flags & CFG_STR_MALLOC) {
912                 if ((buf = malloc(t.val.len + 1)) == NULL) {
913                         ERR("%s:%d:%d: Out of malloc memory\n", st->file,
914                                 t.start.line, t.start.col);
915                         return -1;
916                 }
917                 if (val->s) free(val->s);
918         } else if (flags & CFG_STR_PKGMEM) {
919                 if ((buf = pkg_malloc(t.val.len + 1)) == NULL) {
920                         ERR("%s:%d:%d: Out of private memory\n", st->file,
921                                 t.start.line, t.start.col);
922                         return -1;
923                 }
924                 if (val->s) pkg_free(val->s);
925         } else {
926                 *val = t.val;
927                 return 0;
928         }
929         
930         memcpy(buf, t.val.s, t.val.len);
931         buf[t.val.len] = '\0';
932         val->s = buf;
933         val->len = t.val.len;
934         return 0;
935 }
936
937
938 int cfg_parse_str_opt(void* param, cfg_parser_t* st, unsigned int flags)
939 {
940         int ret;
941
942         if (cfg_eat_equal(st, flags)) return -1;
943
944         ret = cfg_parse_str(param, st, flags | CFG_EXTENDED_ALPHA);
945         if (ret > 0) {
946                 ERR("%s:%d:%d: Option value missing\n",
947                     st->file, st->line, st->col);
948         } else if (ret < 0) return ret;
949
950         if (cfg_eat_eol(st, flags)) return -1;
951         return 0;
952 }
953
954
955 int cfg_parse_int(void* param, cfg_parser_t* st, unsigned int flags)
956 {
957         int* val;
958         int ret, tmp;
959         cfg_token_t t;
960
961         val = (int*)param;
962
963         ret = cfg_get_token(&t, st, flags);
964         if (ret != 0) return ret;
965
966         if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
967                 ERR("%s:%d:%d: Invalid integer value '%.*s'\n", 
968                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
969                 return -1;
970         }
971
972         if (str2sint(&t.val, &tmp) < 0) {
973                 ERR("%s:%d:%d: Invalid integer value '%.*s'\n",
974                         st->file, t.start.line, t.start.col, STR_FMT(&t.val));
975                 return -1;
976         }
977
978         if (val) *val = tmp;
979         return 0;
980 }
981
982
983 int cfg_parse_int_opt(void* param, cfg_parser_t* st, unsigned int flags)
984 {
985         int ret;
986
987         if (cfg_eat_equal(st, flags)) return -1;
988
989         ret = cfg_parse_int(param, st, flags);
990         if (ret > 0) {
991                 ERR("%s:%d:%d: Option value missing\n", 
992                     st->file, st->line, st->col);
993         } else if (ret < 0) return ret;
994
995         if (cfg_eat_eol(st, flags)) return -1;
996         return 0;
997 }
998
999
1000 int cfg_parse_bool(void* param, cfg_parser_t* st, unsigned int flags)
1001 {
1002         int ret, *val;
1003         cfg_token_t t;
1004         cfg_option_t* map;
1005         
1006         val = (int*)param;
1007
1008         ret = cfg_get_token(&t, st, flags);
1009         if (ret != 0) return ret;
1010
1011         if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
1012                 ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n", 
1013                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
1014                 return -1;
1015         }
1016
1017         if ((map = cfg_lookup_token(cfg_bool_values, &t.val)) == NULL) {
1018                 ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n", 
1019                     st->file, t.start.line, t.start.col, STR_FMT(&t.val));
1020                 return -1;
1021         }
1022
1023         if (val) *val = map->val;
1024         return 0;
1025 }
1026
1027
1028 int cfg_parse_bool_opt(void* param, cfg_parser_t* st, unsigned int flags)
1029 {
1030         int ret;
1031         if (cfg_eat_equal(st, flags)) return -1;
1032
1033     ret = cfg_parse_bool(param, st, CFG_EXTENDED_ALPHA | flags);
1034         if (ret > 0) {
1035                 ERR("%s:%d:%d: Option value missing\n", 
1036                     st->file, st->line, st->col);
1037         } else if (ret < 0) return ret;
1038
1039         if (cfg_eat_eol(st, flags)) return -1;
1040         return 0;
1041 }