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