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