cfg_parser.c
92060a0e
 /*
  * Standalone Configuration File Parser
  *
  * Copyright (C) 2008 iptelorg GmbH
  * Written by Jan Janak <jan@iptel.org>
  *
88693a29
  * This file is part of Kamailio, a free SIP server.
92060a0e
  *
88693a29
  * Kamailio is free software; you can redistribute it and/or modify it under the
92060a0e
  * terms of the GNU General Public License as published by the Free Software
  * Foundation; either version 2 of the License, or (at your option) any later
  * version.
  *
88693a29
  * Kamailio is distributed in the hope that it will be useful, but WITHOUT ANY
92060a0e
  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  * details.
  *
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc., 
9e1ff448
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
92060a0e
  */
1d0661db
 /*!
  * \file
88693a29
  * \brief Kamailio core :: Standalone Configuration File Parser
  * \author Jan Janak <jan@iptel.org>
1d0661db
  * \ingroup core
5a03489e
  *
1d0661db
  * Module: \ref core
5a03489e
  *
  * See \ref ConfigEngine
  *
  * \page ConfigEngine
  * In file \ref cfg_parser.c 
  * Configuration examples
  * - \ref ConfigExample1
  * - \ref ConfigExample2
  * - \ref ConfigExample3
  * - \ref ConfigExample4
  * - \ref ConfigExample5
  * - \ref ConfigExample6
  * - \ref ConfigExample7
  * - \ref ConfigExample8
  *
  * <b>Run-time Allocated Destination Variables</b>
  * - the destination variable pointers in arrays are assigned at compile time
  * - The address of variables allocated on the heap (typically in dynamically allocated
  *   structures) is not know and thus we need to assign NULL initially and change the pointer
  *   at runtime.
  *   (provide an example).
  *
  * <b>Built-in parsing functions</b>
  *
  * *_val functions parse the whole option body (including =)
  */
 
92060a0e
 
5a03489e
 /*! \page ConfigExample1  Configuration engine Example 1: Options without values
92060a0e
  *
5a03489e
 \verbatim
92060a0e
  *	str file = STR_STATIC_INIT("test.cfg");
  *	cfg_parser_t* parser;
  *	int feature_a = 0, feature_b = 0;
  *
  *	cfg_option_t options[] = {
  *		{"enable_feature_a",  .param = &feature_a, .val = 1},
  *		{"disable_feature_a", .param = &feature_a, .val = 0},
  *		{"feature_b",         .param = &feature_b, .val = 1},
  *      {0}
  *	};
  *
  *	if ((parser = cfg_parser_init(&file)) == NULL) {
  *		ERR("Error while creating parser\n");
  *		return -1;
  *	}
  *
  *	cfg_set_options(parser, options);
  *
50ca02e5
  *	if (sr_cfg_parse(parser) < 0) {
92060a0e
  *		ERR("Error while parsing configuration file\n");
  *      cfg_parser_close(parser);
  *		return -1;
  *	}
  *
  *	cfg_parser_close(parser);
5a03489e
 \endverbatim
92060a0e
  */
 
5a03489e
 /*! \page ConfigExample2  Configuration engine Example 2: Options with integer values
 \verbatim
92060a0e
  * 	cfg_option_t options[] = {
  *		{"max_number",   .param = &max_number,   .f = cfg_parse_int_val },
  *		{"extra_checks", .param = &extra_checks, .f = cfg_parse_bool_val},
  *		{0}
  *	};
5a03489e
 \endverbatim
92060a0e
  */
 
5a03489e
 /*! \page ConfigExample3 Configuration engine Example 3: Enum options
 \verbatim
92060a0e
  * int scope;
  *
  *	cfg_option_t scopes[] = {
  *	    {"base",     .param = &scope, .val = 1},
  *	    {"onelevel", .param = &scope, .val = 2},
  *	    {"one",      .param = &scope, .val = 3},
  *	    {"subtree",  .param = &scope, .val = 4},
  *	    {"sub",      .param = &scope, .val = 5},
  *	    {"children", .param = &scope, .val = 6},
  *	    {0}
  *  };
  *
  *	cfg_option_t options[] = {
  *		{"scope", .param = scopes, .f = cfg_parse_enum_val},
  *		{0}
  *	};
5a03489e
 \endverbatim
92060a0e
  */
 
5a03489e
 /*! \page ConfigExample4 Configuration engine Example 4: Options with string values
 \verbatim
 * 	str filename = STR_NULL;
92060a0e
  *
  *	cfg_option_t options[] = {
  *		{"filename", .param = &filename, .f = cfg_parse_str_val},
  *		{0}
  *	};
  *
  * - By default the function returns a pointer to an internal buffer which will be destroyed
  *   by a subsequent call
  * - There are flags to tell the function to copy the resuting string in a pkg, shm, glibc,
  *   or static buffers
5a03489e
 \endverbatim
92060a0e
  */
 
5a03489e
 /*! \page ConfigExample5 Configuration engine Example 5: Custom value parsing
92060a0e
  * TBD
  */
 
5a03489e
 /*! \page ConfigExample6 Configuration engine Example 6: Parsing Sections
92060a0e
  * TBD
  */
 
5a03489e
 /*! \page ConfigExample7 Configuration engine Example 7: Default Options
92060a0e
  * TBD
  */
 
5a03489e
 /*! \page ConfigExample8 Configuration engine Example 8: Memory management of strings
  *
  * Data types with fixed size are easy, they can be copied into a pre-allocated memory
92060a0e
  * buffer, strings cannot because their length is unknown.
  */
 
 #include "cfg_parser.h"
 
 #include "mem/mem.h"
 #include "mem/shm_mem.h"
 #include "dprint.h"
 #include "trim.h"
 #include "ut.h"
 
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <libgen.h>
 
 
5a03489e
 /*! \brief The states of the lexical scanner */
92060a0e
 enum st {
5a03489e
 	ST_S,  /*!< Begin */
 	ST_A,  /*!< Alphanumeric */
 	ST_AE, /*!< Alphanumeric escaped */
 	ST_Q,  /*!< Quoted */
 	ST_QE, /*!< Quoted escaped */
 	ST_C,  /*!< Comment */
 	ST_CE, /*!< Comment escaped */
 	ST_E,  /*!< Escaped */
92060a0e
 };
 
 
5a03489e
 /*! \brief Test for alphanumeric characters */
92060a0e
 #define IS_ALPHA(c) \
     (((c) >= 'a' && (c) <= 'z') || \
      ((c) >= 'A' && (c) <= 'Z') || \
      ((c) >= '0' && (c) <= '9') || \
      (c) == '_')
 
 
5a03489e
 /*! \brief Test for delimiter characters */
92060a0e
 #define IS_DELIM(c) \
     ((c) == '=' || \
      (c) == ':' || \
      (c) == ';' || \
      (c) == '.' || \
      (c) == ',' || \
      (c) == '?' || \
      (c) == '[' || \
      (c) == ']' || \
      (c) == '/' || \
      (c) == '@' || \
      (c) == '!' || \
      (c) == '$' || \
      (c) == '%' || \
      (c) == '&' || \
      (c) == '*' || \
      (c) == '(' || \
      (c) == ')' || \
      (c) == '-' || \
      (c) == '+' || \
      (c) == '|' || \
      (c) == '\'')
 
 
5a03489e
 /*! \brief Whitespace characters */
92060a0e
 #define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r') 
 #define IS_QUOTE(c)      ((c) == '\"')  /* Quote characters */
 #define IS_COMMENT(c)    ((c) == '#')   /* Characters that start comments */
 #define IS_ESCAPE(c)     ((c) == '\\')  /* Escape characters */
 #define IS_EOL(c)        ((c) == '\n')  /* End of line */
 
 
5a03489e
 /*! \brief
  * Append character to the value of current token
92060a0e
  */
 #define PUSH(c)                            \
     if (token->val.len >= MAX_TOKEN_LEN) { \
         ERR("%s:%d:%d: Token too long\n",  \
         st->file, st->line, st->col);      \
         return -1;                         \
     }                                      \
     if (token->val.len == 0) {             \
          token->start.line = st->line;     \
          token->start.col = st->col;       \
     }                                      \
     token->val.s[token->val.len++] = (c);
 
 
5a03489e
 /*! \brief
92060a0e
  * Return current token from the lexical analyzer
  */
 #define RETURN(c)               \
     token->end.line = st->line; \
     token->end.col = st->col;   \
     token->type = (c);          \
     print_token(token);         \
7ed7f2ae
     return 0;
92060a0e
 
 
5a03489e
 /*! \brief
92060a0e
  * Get next character and update counters
  */
 #define READ_CHAR      \
      c = fgetc(st->f); \
      if (IS_EOL(c)) {  \
          st->line++;   \
          st->col = 0;  \
      } else {          \
          st->col++;    \
      }
 
 
 cfg_option_t cfg_bool_values[] = {
 	{"yes",      .val = 1},
 	{"true",     .val = 1},
 	{"enable",   .val = 1},
 	{"enabled",  .val = 1},
 	{"1",        .val = 1},
 	{"on",       .val = 1},
 	{"no",       .val = 0},
 	{"false",    .val = 0},
 	{"disable",  .val = 0},
 	{"disabled", .val = 0},
 	{"0",        .val = 0},
 	{"off",      .val = 0},
 	{0}
 };
 
 
 static void print_token(cfg_token_t* token)
 {
38b2c094
 #ifdef EXTRA_DEBUG
92060a0e
 	int i, j;
 	char* buf;
 
 	if ((buf = pkg_malloc(token->val.len * 2)) == NULL) {
 		DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", 
 			token->type, STR_FMT(&token->val),
 			token->start.line, token->start.col, 
 			token->end.line, token->end.col);
 	} else {
 		for(i = 0, j = 0; i < token->val.len; i++) {
 			switch(token->val.s[i]) {
 			case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break;
 			case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break;
 			case '\t': buf[j++] = '\\'; buf[j++] = 't'; break;
 			case '\0': buf[j++] = '\\'; buf[j++] = '0'; break;
 			case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break;
 			default: buf[j++] = token->val.s[i];
 			}
 		}
 		DBG("token(%d, '%.*s', <%d,%d>-<%d,%d>)\n", 
 			token->type, j, buf,
 			token->start.line, token->start.col, 
 			token->end.line, token->end.col);
 		pkg_free(buf);
 	}
7ed7f2ae
 #endif /* EXTRA_DEBUG */
92060a0e
 }
 
7ed7f2ae
 
92060a0e
 int cfg_get_token(cfg_token_t* token, cfg_parser_t* st, unsigned int flags)
 {
 	static int look_ahead = EOF;
 	int c;
 	enum st state;
 
 	state = ST_S;
 	
 	token->val.s = token->buf;
 	token->val.len = 0;
 
 	if (look_ahead != EOF) {
 		c = look_ahead;
 		look_ahead = EOF;
 	} else {
 		READ_CHAR;
 	}
 
 	while(c != EOF) {
 		switch(state) {
 		case ST_S:
 			if (flags & CFG_EXTENDED_ALPHA) {
 				if (IS_WHITESPACE(c)) {
 					     /* Do nothing */
 				} else if (IS_ALPHA(c) ||
 					   IS_ESCAPE(c) ||
 					   IS_DELIM(c)) {
 					PUSH(c);
 					state = ST_A;
 				} else if (IS_QUOTE(c)) {
 					state = ST_Q;
 				} else if (IS_COMMENT(c)) {
 					state = ST_C;
 				} else if (IS_EOL(c)) {
 					PUSH(c);
 					RETURN(c);
 				} else {
 					ERR("%s:%d:%d: Invalid character 0x%x\n", 
 					    st->file, st->line, st->col, c);
 					return -1;
 				}
 			} else {
 				if (IS_WHITESPACE(c)) {
 					     /* Do nothing */
 				} else if (IS_ALPHA(c)) {
 					PUSH(c);
 					state = ST_A;
 				} else if (IS_QUOTE(c)) {
 					state = ST_Q;
 				} else if (IS_COMMENT(c)) {
 					state = ST_C;
 				} else if (IS_ESCAPE(c)) {
 					state = ST_E;
 				} else if (IS_DELIM(c) || IS_EOL(c)) {
 					PUSH(c);
 					RETURN(c);
 				} else {
 					ERR("%s:%d:%d: Invalid character 0x%x\n", 
 					    st->file, st->line, st->col, c);
 					return -1;
 				}
 			}
 			break;
 
 		case ST_A:
 			if (flags & CFG_EXTENDED_ALPHA) {
 				if (IS_ALPHA(c) ||
 				    IS_DELIM(c) ||
 				    IS_QUOTE(c)) {
 					PUSH(c);
 				} else if (IS_ESCAPE(c)) {
 					state = ST_AE;
 				} else if (IS_COMMENT(c) || IS_EOL(c) || IS_WHITESPACE(c)) {
 					look_ahead = c;
 					RETURN(CFG_TOKEN_ALPHA);
 				} else {
 					ERR("%s:%d:%d: Invalid character 0x%x\n", 
 					    st->file, st->line, st->col, c);
 					return -1;
 				}
 			} else {
 				if (IS_ALPHA(c)) {
 					PUSH(c);
 				} else if (IS_ESCAPE(c)) {
 					state = ST_AE;
 				} else if (IS_WHITESPACE(c) ||
 					   IS_DELIM(c) ||
 					   IS_QUOTE(c) ||
 					   IS_COMMENT(c) ||
 					   IS_EOL(c)) {
 					look_ahead = c;
 					RETURN(CFG_TOKEN_ALPHA);
 				} else {
 					ERR("%s:%d:%d: Invalid character 0x%x\n", 
 					    st->file, st->line, st->col, c);
 					return -1;
 				}
 			}
 			break;
 
 		case ST_AE:
 			if (IS_COMMENT(c) ||
 			    IS_QUOTE(c) ||
 			    IS_ESCAPE(c)) {
 				PUSH(c);
 			} else if (c == 'r') {
 				PUSH('\r');
 			} else if (c == 'n') {
 				PUSH('\n');
 			} else if (c == 't') {
 				PUSH('\t');
 			} else if (c == ' ') {
 				PUSH(' ');
 			} else if (IS_EOL(c)) {
 				     /* Do nothing */
 			} else {
 				ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
 				    st->file, st->line, st->col, c);
 				return -1;
 			}
 			state = ST_A;
 			break;
 
 		case ST_Q:
 			if (IS_QUOTE(c)) {
 				RETURN(CFG_TOKEN_STRING);
 			} else if (IS_ESCAPE(c)) {
 				state = ST_QE;
 				break;
 			} else {
 				PUSH(c);
 			}
 			break;
 
 		case ST_QE:
 			if (IS_ESCAPE(c) ||
 			    IS_QUOTE(c)) {
 				PUSH(c);
 			} else if (c == 'n') {
 				PUSH('\n');
 			} else if (c == 'r') {
 				PUSH('\r');
 			} else if (c == 't') {
 				PUSH('\t');
 			} else if (IS_EOL(c)) {
 				     /* Do nothing */
 			} else {
 				ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
 				    st->file, st->line, st->col, c);
 				return -1;
 			}
 			state = ST_Q;
 			break;
 
 		case ST_C:
 			if (IS_ESCAPE(c)) {
 				state = ST_CE;
 			} else if (IS_EOL(c)) {
 				state = ST_S;
 				continue; /* Do not read a new char, return EOL */
 			} else {
 				     /* Do nothing */
 			}
 			break;
 
 		case ST_CE:
 			state = ST_C;
 			break;
 
 		case ST_E:
 			if (IS_COMMENT(c) ||
 			    IS_QUOTE(c) ||
 			    IS_ESCAPE(c)) {
 				PUSH(c);
 				RETURN(c);
 			} else if (c == 'r') {
 				PUSH('\r');
 				RETURN('\r');
 			} else if (c == 'n') {
 				PUSH('\n');
 				RETURN('\n');
 			} else if (c == 't') {
 				PUSH('\t');
 				RETURN('\t');
 			} else if (c == ' ') {
 				PUSH(' ');
 				RETURN(' ');
 			} else if (IS_EOL(c)) {
 				     /* Escped eol means no eol */
 				state = ST_S;
 			} else {
 				ERR("%s:%d:%d: Unsupported escape character 0x%x\n", 
 				    st->file, st->line, st->col, c);
 				return -1;
 			}
 			break;
 		}
 
 		READ_CHAR;
 	};
 
 	switch(state) {
 	case ST_S: 
 	case ST_C:
 	case ST_CE:
7ed7f2ae
 		return 1;
92060a0e
 
 	case ST_A:
 		RETURN(CFG_TOKEN_ALPHA);
 
 	case ST_Q:
 		ERR("%s:%d:%d: Premature end of file, missing closing quote in"
 			" string constant\n", st->file, st->line, st->col);
 		return -1;
 
 	case ST_QE:
 	case ST_E:
 	case ST_AE:
 		ERR("%s:%d:%d: Premature end of file, missing escaped character\n", 
 		    st->file, st->line, st->col);
 		return -1;
 	}
 	BUG("%s:%d:%d: Invalid state %d\n",
 		st->file, st->line, st->col, state);
 	return -1;
 }
 
 
 int cfg_parse_section(void* param, cfg_parser_t* st, unsigned int flags)
 {
 	cfg_token_t t;
 	int ret;
 
7ed7f2ae
 	ret = cfg_parse_str(param, st, flags);
 	if (ret < 0) return ret;
 	if (ret > 0) {
 		ERR("%s:%d:%d: Section name missing.\n",
 			st->file, st->line, st->col);
 		return ret;
92060a0e
 	}
 
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
92060a0e
 	if (ret < 0) goto error;
7ed7f2ae
 	if (ret > 0) {
92060a0e
 		ERR("%s:%d:%d: Closing ']' missing\n", st->file, st->line, st->col);
 		goto error;
 	}
 	if (t.type != ']') {
 		ERR("%s:%d:%d: Syntax error, ']' expected\n", 
 		    st->file, t.start.line, t.start.col);
 		goto error;
 	}
7ed7f2ae
 
 	if (cfg_eat_eol(st, flags)) goto error;
92060a0e
 	return 0;
 
  error:
7ed7f2ae
 	if (param && ((str*)param)->s) {
 		if (flags & CFG_STR_PKGMEM) {
 			pkg_free(((str*)param)->s);
 			((str*)param)->s = NULL;
 		} else if (flags & CFG_STR_SHMMEM) {
 			shm_free(((str*)param)->s);
 			((str*)param)->s = NULL;
 		} else if (flags & CFG_STR_MALLOC) {
 			free(((str*)param)->s);
 			((str*)param)->s = NULL;
 		}		
 	}
92060a0e
 	return -1;
 }
 
 
 static char* get_base_name(str* filename)
 {
 	char* tmp1, *tmp2, *res;
 	int len;
 
 	res = NULL;
 	if ((tmp1 = as_asciiz(filename)) == NULL) {
 		ERR("cfg_parser: No memory left\n");
 		goto error;
 	}
 	
 	if ((tmp2 = basename(tmp1)) == NULL) {
 		ERR("cfg_parser: Error in basename\n");
 		goto error;
 	}
 	len = strlen(tmp2);
 
 	if ((res = pkg_malloc(len + 1)) == NULL) {
 		ERR("cfg_parser: No memory left");
 		goto error;
 	}
 	memcpy(res, tmp2, len + 1);
 	pkg_free(tmp1);
 	return res;
 
  error:
 	if (tmp1) pkg_free(tmp1);
 	return NULL;
 }
 
 
13991319
 
 /** intialize the config parser.
  * @param basedir - path to the config file name. If 0 the path
  *               (base directory) of the main ser.cfg file will be used, else
  *               basedir will be concatenated to the filename. It will be
  *               used only if filename is not an absolute path.
  * @param filename - config filename (can include path elements).
  * @return 0 on error, !=0 on success.
  */
 cfg_parser_t* cfg_parser_init(str* basedir, str* filename)
92060a0e
 {
 	cfg_parser_t* st;
13991319
 	char* pathname, *base, *abs_pathname;
92060a0e
 
13991319
 	abs_pathname = NULL;
 	pathname = filename->s;
92060a0e
 	st = NULL;
 	base = NULL;
 	
13991319
 	/* if basedir == 0 or != "" get_abs_pathname */
 	if (basedir == 0  || basedir->len != 0) {
 		if ((abs_pathname = get_abs_pathname(basedir, filename)) == NULL) {
 			ERR("cfg_parser: Error while converting %.*s to absolute"
 					" pathname\n", STR_FMT(filename));
 			goto error;
 		}
 		pathname = abs_pathname;
92060a0e
 	}
 
 	if ((base = get_base_name(filename)) == NULL) goto error;
 
 	if ((st = (cfg_parser_t*)pkg_malloc(sizeof(*st))) == NULL) {
 		ERR("cfg_parser: No memory left\n");
 		goto error;
 	}
 	memset(st, '\0', sizeof(*st));
 
 	if ((st->f = fopen(pathname, "r")) == NULL) {
 		ERR("cfg_parser: Unable to open file '%s'\n", pathname);
 		goto error;
 	}
 
13991319
 	if (abs_pathname) pkg_free(abs_pathname);
92060a0e
 
 	st->file = base;
 	st->line = 1;
 	st->col = 0;
 	return st;
 
  error:
 	if (st) {
 		if (st->f) fclose(st->f);
 		pkg_free(st);
 	}
 	if (base) pkg_free(base);
13991319
 	if (abs_pathname) pkg_free(abs_pathname);
92060a0e
 	return NULL;
 }
 
 
 void cfg_parser_close(cfg_parser_t* st)
 {
 	if (!st) return;
 	if (st->f) fclose(st->f);
 	if (st->file) pkg_free(st->file);
 	pkg_free(st);
 }
 
 
 void cfg_section_parser(cfg_parser_t* st, cfg_func_f parser, void* param)
 {
 	if (st == NULL) return;
 	st->section.parser = parser;
 	st->section.param = param;
 }
 
 
 void cfg_set_options(cfg_parser_t* st, cfg_option_t* options)
 {
 	if (st) st->options = options;
 }
 
 
 static int process_option(cfg_parser_t* st, cfg_option_t* opt)
 {
 	if (opt->f) {
 		/* We have a function so store it and pass opt->dst to it */
 		if (opt->f(opt->param, st, opt->flags) < 0) return -1;
 	} else {
 		/* We have no function, so if we have a pointer to some
 		 * variable in opt->param then store the value of opt->i
 		 * there, the variable is assumed to be of type i
 		 */
 		if (opt->param) *(int*)opt->param = opt->val;
 	}
 	return 0;
 }
 
 
50ca02e5
 int sr_cfg_parse(cfg_parser_t* st)
92060a0e
 {
 	int ret;
 	cfg_token_t t;
 	cfg_option_t* opt;
 
 	while(1) {
 		ret = cfg_get_token(&t, st, 0);
7ed7f2ae
 		if (ret < 0) return ret;
 		if (ret > 0) break;
92060a0e
 
 		switch(t.type) {
 		case CFG_TOKEN_ALPHA:
 			/* Lookup the option name */
 			if ((opt = cfg_lookup_token(st->options, &t.val)) == NULL) {
 				ERR("%s:%d:%d: Unsupported option '%.*s'\n", 
 				    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 				return -1;
 			}
 
 			st->cur_opt = &t;
1faf19fa
 			if (process_option(st, opt) < 0) { st->cur_opt = 0; return -1; }
 			st->cur_opt = 0;
92060a0e
 			break;
 
 		case '[': 
 			if (st->section.parser == NULL) {
 				ERR("%s:%d:%d: Syntax error\n", st->file,
 					t.start.line, t.start.col);
 				return -1;
 			}
 			if (st->section.parser(st->section.param, st, 0) < 0) return -1;
 			break;
 
 			     /* Empty line */
 		case '\n': continue;
 
 		default:
 			ERR("%s:%d:%d: Syntax error\n", 
 			    st->file, t.start.line, t.start.col);
 			return -1;
 		}
 	}
 	return 0;
 }
 
 
 cfg_option_t* cfg_lookup_token(cfg_option_t* table, str* token)
 {
 	int len, i;
7ed7f2ae
 	int (*cmp)(const char* s1, const char* s2, size_t n) = NULL;
92060a0e
 
 
 	if (table == NULL) return NULL;
 
 	for(i = 0; table[i].name; i++) {
 		len = strlen(table[i].name);
 
 		if (table[i].flags & CFG_PREFIX) {
 			if (token->len < len) continue;
 		} else {
 			if (token->len != len) continue;
 		}
 
 		if (table[i].flags & CFG_CASE_SENSITIVE) cmp = strncmp;
 		else cmp = strncasecmp;
 
 		if (cmp(token->s, table[i].name, len)) continue;
 		return table + i;
 	}
 	if (table[i].flags & CFG_DEFAULT) {
 		return table + i;
 	}
 	return NULL;
 }
 
 
7ed7f2ae
 int cfg_eat_equal(cfg_parser_t* st, unsigned int flags)
92060a0e
 {
 	cfg_token_t t;
 	int ret;
 
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
 	if (ret < 0) return ret;
 	if (ret > 0) {
 		ERR("%s:%d:%d: Delimiter '=' missing\n", 
92060a0e
 		    st->file, st->line, st->col);
7ed7f2ae
 		return ret;
92060a0e
 	}
 
 	if (t.type != '=') {
 		ERR("%s:%d:%d: Syntax error, '=' expected\n", 
 		    st->file, t.start.line, t.start.col);
 		return -1;
 	}
 	return 0;
 }
 
 
7ed7f2ae
 int cfg_eat_eol(cfg_parser_t* st, unsigned int flags)
 {
 	cfg_token_t t;
 	int ret;
 
 	/* Skip EOL */
 	ret = cfg_get_token(&t, st, 0);
 	if (ret < 0) return ret;
 	if (ret > 0) return 0;
 	if (t.type != '\n') {
 		ERR("%s:%d:%d: End of line expected\n", 
 			st->file, t.start.line, t.start.col);
 		return -1;
 	}
 	return 0;
 }
 
 
 int cfg_parse_enum(void* param, cfg_parser_t* st, unsigned int flags)
92060a0e
 {
 	int ret;
     cfg_token_t t;
 	cfg_option_t* values, *val;
 	
 	values = (cfg_option_t*)param;
 
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
 	if (ret != 0) return ret;
92060a0e
 
 	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
7ed7f2ae
 		ERR("%s:%d:%d: Invalid enum value '%.*s'\n",
92060a0e
 		    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if (values) {
 		if ((val = cfg_lookup_token(values, &t.val)) == NULL) {
7ed7f2ae
 			ERR("%s:%d:%d Unsupported enum value '%.*s'\n", 
92060a0e
 				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 			return -1;
 		}
 		return process_option(st, val);
 	} else {
 		return 0;
 	}
 }
 
 
7ed7f2ae
 int cfg_parse_enum_opt(void* param, cfg_parser_t* st, unsigned int flags)
 {
 	int ret;
 
 	if (cfg_eat_equal(st, flags)) return -1;
 
 	ret = cfg_parse_enum(param, st, CFG_EXTENDED_ALPHA | flags);
 	if (ret > 0) {
 		ERR("%s:%d:%d: Option value missing\n",
 		    st->file, st->line, st->col);
 		return ret;
 	} else if (ret < 0) return ret;
 
 	if (cfg_eat_eol(st, flags)) return -1;
 	return 0;
 }
 
 
 int cfg_parse_str(void* param, cfg_parser_t* st, unsigned int flags)
92060a0e
 {
 	str* val;
 	int ret;
 	char* buf;
     cfg_token_t t;
 	
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
 	if (ret != 0) return ret;
92060a0e
 	
 	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
7ed7f2ae
 		ERR("%s:%d:%d: Invalid string value '%.*s', a string expected.\n",
92060a0e
 		    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if (!param) return 0;
 	val = (str*)param;
 
 	if (flags & CFG_STR_STATIC) {
 		if (!val->s || val->len <= t.val.len) {
 			ERR("%s:%d:%d: Destination string buffer too small\n",
 				st->file, t.start.line, t.start.col);
 			return -1;
 		}
 		buf = val->s;
 	} else if (flags & CFG_STR_SHMMEM) {
 		if ((buf = shm_malloc(t.val.len + 1)) == NULL) {
 			ERR("%s:%d:%d: Out of shared memory\n", st->file,
 				t.start.line, t.start.col);
 			return -1;
 		}
 		if (val->s) shm_free(val->s);
 	} else if (flags & CFG_STR_MALLOC) {
 		if ((buf = malloc(t.val.len + 1)) == NULL) {
 			ERR("%s:%d:%d: Out of malloc memory\n", st->file,
 				t.start.line, t.start.col);
 			return -1;
 		}
 		if (val->s) free(val->s);
 	} else if (flags & CFG_STR_PKGMEM) {
 		if ((buf = pkg_malloc(t.val.len + 1)) == NULL) {
 			ERR("%s:%d:%d: Out of private memory\n", st->file,
 				t.start.line, t.start.col);
 			return -1;
 		}
 		if (val->s) pkg_free(val->s);
 	} else {
 		*val = t.val;
 		return 0;
 	}
 	
 	memcpy(buf, t.val.s, t.val.len);
 	buf[t.val.len] = '\0';
 	val->s = buf;
 	val->len = t.val.len;
 	return 0;
 }
 
 
7ed7f2ae
 int cfg_parse_str_opt(void* param, cfg_parser_t* st, unsigned int flags)
 {
 	int ret;
 
 	if (cfg_eat_equal(st, flags)) return -1;
 
 	ret = cfg_parse_str(param, st, flags | CFG_EXTENDED_ALPHA);
 	if (ret > 0) {
 		ERR("%s:%d:%d: Option value missing\n",
 		    st->file, st->line, st->col);
 	} else if (ret < 0) return ret;
 
 	if (cfg_eat_eol(st, flags)) return -1;
 	return 0;
 }
92060a0e
 
7ed7f2ae
 
 int cfg_parse_int(void* param, cfg_parser_t* st, unsigned int flags)
92060a0e
 {
 	int* val;
 	int ret, tmp;
 	cfg_token_t t;
 
 	val = (int*)param;
 
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
 	if (ret != 0) return ret;
92060a0e
 
 	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
7ed7f2ae
 		ERR("%s:%d:%d: Invalid integer value '%.*s'\n", 
92060a0e
 		    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if (str2sint(&t.val, &tmp) < 0) {
 		ERR("%s:%d:%d: Invalid integer value '%.*s'\n",
 			st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if (val) *val = tmp;
 	return 0;
 }
 
 
7ed7f2ae
 int cfg_parse_int_opt(void* param, cfg_parser_t* st, unsigned int flags)
 {
 	int ret;
 
 	if (cfg_eat_equal(st, flags)) return -1;
 
 	ret = cfg_parse_int(param, st, flags);
 	if (ret > 0) {
 		ERR("%s:%d:%d: Option value missing\n", 
 		    st->file, st->line, st->col);
 	} else if (ret < 0) return ret;
 
 	if (cfg_eat_eol(st, flags)) return -1;
 	return 0;
 }
 
 
 int cfg_parse_bool(void* param, cfg_parser_t* st, unsigned int flags)
92060a0e
 {
 	int ret, *val;
 	cfg_token_t t;
 	cfg_option_t* map;
 	
 	val = (int*)param;
 
7ed7f2ae
 	ret = cfg_get_token(&t, st, flags);
 	if (ret != 0) return ret;
92060a0e
 
 	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
 		ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n", 
 		    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if ((map = cfg_lookup_token(cfg_bool_values, &t.val)) == NULL) {
 		ERR("%s:%d:%d: Invalid option value '%.*s', boolean expected\n", 
 		    st->file, t.start.line, t.start.col, STR_FMT(&t.val));
 		return -1;
 	}
 
 	if (val) *val = map->val;
 	return 0;
 }
7ed7f2ae
 
 
 int cfg_parse_bool_opt(void* param, cfg_parser_t* st, unsigned int flags)
 {
 	int ret;
 	if (cfg_eat_equal(st, flags)) return -1;
 
     ret = cfg_parse_bool(param, st, CFG_EXTENDED_ALPHA | flags);
 	if (ret > 0) {
 		ERR("%s:%d:%d: Option value missing\n", 
 		    st->file, st->line, st->col);
 	} else if (ret < 0) return ret;
 
 	if (cfg_eat_eol(st, flags)) return -1;
 	return 0;
 }