소스 검색

- standalone configuration file parser, this is an extended version of the
parser from the tls module. This version can be used from other modules too,
currently used by tls and ldap modules, in the future to be used by bdb,
mysql, postgres, flatstore, and others.

Jan Janak 17 년 전
부모
커밋
92060a0ea0
2개의 변경된 파일1142개의 추가작업 그리고 0개의 파일을 삭제
  1. 964 0
      cfg_parser.c
  2. 178 0
      cfg_parser.h

+ 964 - 0
cfg_parser.c

@@ -0,0 +1,964 @@
+/*
+ * $Id$
+ * Standalone Configuration File Parser
+ *
+ * Copyright (C) 2008 iptelorg GmbH
+ * Written by Jan Janak <[email protected]>
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER is free software; you can redistribute it and/or modify it under the
+ * 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.
+ *
+ * SER is distributed in the hope that it will be useful, but WITHOUT ANY
+ * 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., 
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** Example 1: Options without values
+ *
+ *	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);
+ *
+ *	if (cfg_parse(parser) < 0) {
+ *		ERR("Error while parsing configuration file\n");
+ *      cfg_parser_close(parser);
+ *		return -1;
+ *	}
+ *
+ *	cfg_parser_close(parser);
+ */
+
+/** Example 2: Options with integer values
+ * 	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}
+ *	};
+ */
+
+/** Example 3: Enum options
+ * 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}
+ *	};
+ */
+
+/** Example 4: Options with string values
+ * 	str filename = STR_NULL;
+ *
+ *	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
+ */
+
+/** Example 5: Custom value parsing
+ * TBD
+ */
+
+/** Example 6: Parsing Sections
+ * TBD
+ */
+
+/** Example 7: Default Options
+ * TBD
+ */
+
+/** Example 8: Memory management of strings
+ * - Data types with fixed size are easy, they can be copied into a pre-allocated memory
+ * buffer, strings cannot because their length is unknown.
+ */
+
+/** Run-time Allocated Destination Variables
+ * - 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).
+ */
+
+/** Built-in parsing functions
+ * *_val functions parse the whole option body (including =)
+ */
+
+#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>
+
+
+/* The states of the lexical scanner */
+enum st {
+	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 */
+};
+
+
+/* Test for alphanumeric characters */
+#define IS_ALPHA(c) \
+    (((c) >= 'a' && (c) <= 'z') || \
+     ((c) >= 'A' && (c) <= 'Z') || \
+     ((c) >= '0' && (c) <= '9') || \
+     (c) == '_')
+
+
+/* Test for delimiter characters */
+#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) == '\'')
+
+
+/* Whitespace characters */
+#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 */
+
+
+/*
+ * Append character to the value of current
+ * token
+ */
+#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);
+
+
+/*
+ * 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);         \
+    return 1;
+
+
+/*
+ * 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)
+{
+	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);
+	}
+}
+
+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:
+		return 0;
+
+	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;
+	str* name;
+
+	name = (str*)param;
+	if (name == NULL) {
+		BUG("cfg_parser: Invalid parameter value to cfg_parse_section\n");
+		return -1;
+	}
+
+	ret = cfg_get_token(&t, st, 0);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Section name missing.\n", st->file, st->line, st->col);
+		return -1;
+	}
+
+	if (t.type != CFG_TOKEN_ALPHA) {
+		ERR("%s:%d:%d: Invalid table name %d:'%.*s'\n", 
+		    st->file, t.start.line, t.start.col,
+		    t.type, STR_FMT(&t.val));
+		return -1;
+	}
+	
+	if ((name->s = as_asciiz(&t.val)) == NULL) {
+		ERR("%s:%d:%d: Out of memory\n", st->file, t.start.line, t.start.col);
+		return -1;
+	}
+	name->len = t.val.len;
+
+	ret = cfg_get_token(&t, st, 0);
+	if (ret < 0) goto error;
+	if (ret == 0) {
+		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;
+	}
+	return 0;
+
+ error:
+	if (name->s) pkg_free(name->s);
+	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;
+}
+
+
+cfg_parser_t* cfg_parser_init(str* filename)
+{
+	cfg_parser_t* st;
+	char* pathname, *base;
+
+	pathname = NULL;
+	st = NULL;
+	base = NULL;
+	
+	if ((pathname = get_abs_pathname(NULL, filename)) == NULL) {
+		ERR("cfg_parser: Error while converting %.*s to absolute pathname\n", 
+			STR_FMT(filename));
+		goto error;
+	}
+
+	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;
+	}
+
+	free(pathname);
+
+	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);
+	if (pathname) free(pathname);
+	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;
+}
+
+
+int cfg_parse(cfg_parser_t* st)
+{
+	int ret;
+	cfg_token_t t;
+	cfg_option_t* opt;
+
+	while(1) {
+		ret = cfg_get_token(&t, st, 0);
+		if (ret <= 0) return ret;
+
+		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;
+			if (process_option(st, opt) < 0) return -1;
+			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;
+		}
+
+		     /* Skip EOL */
+		ret = cfg_get_token(&t, st, 0);
+		if (ret <= 0) return ret;
+		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;
+}
+
+
+cfg_option_t* cfg_lookup_token(cfg_option_t* table, str* token)
+{
+	int len, i;
+	int (*cmp)(const char* s12, const char* s2, size_t n) = NULL;
+
+
+	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;
+}
+
+
+int cfg_eat_equal(cfg_parser_t* st)
+{
+	cfg_token_t t;
+	int ret;
+
+	ret = cfg_get_token(&t, st, 0);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n", 
+		    st->file, st->line, st->col);
+		return -1;
+	}
+
+	if (t.type != '=') {
+		ERR("%s:%d:%d: Syntax error, '=' expected\n", 
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+	return 0;
+}
+
+
+int cfg_parse_enum_val(void* param, cfg_parser_t* st, unsigned int flags)
+{
+	int ret;
+    cfg_token_t t;
+	cfg_option_t* values, *val;
+	
+	values = (cfg_option_t*)param;
+
+	if (cfg_eat_equal(st)) return -1;
+
+	ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n",
+		    st->file, st->line, st->col);
+		return -1;
+	}
+	
+	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
+		ERR("%s:%d:%d: Invalid option value '%.*s'\n",
+		    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) {
+			ERR("%s:%d:%d Unsupported option value '%.*s'\n", 
+				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
+			return -1;
+		}
+		return process_option(st, val);
+	} else {
+		return 0;
+	}
+}
+
+
+int cfg_parse_str_val(void* param, cfg_parser_t* st, unsigned int flags)
+{
+	str* val;
+	int ret;
+	char* buf;
+    cfg_token_t t;
+	
+	if (cfg_eat_equal(st)) return -1;
+
+	ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n",
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+	
+	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
+		ERR("%s:%d:%d: Invalid option value '%.*s'\n",
+		    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;
+}
+
+
+
+int cfg_parse_int_val(void* param, cfg_parser_t* st, unsigned int flags)
+{
+	int* val;
+	int ret, tmp;
+	cfg_token_t t;
+
+	val = (int*)param;
+
+	if (cfg_eat_equal(st)) return -1;
+	
+	ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n", 
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+
+	if (t.type != CFG_TOKEN_ALPHA && t.type != CFG_TOKEN_STRING) {
+		ERR("%s:%d:%d: Invalid option value '%.*s', integer expected\n", 
+		    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;
+}
+
+
+int cfg_parse_bool_val(void* param, cfg_parser_t* st, unsigned int flags)
+{
+	int ret, *val;
+	cfg_token_t t;
+	cfg_option_t* map;
+	
+	val = (int*)param;
+
+	ret = cfg_get_token(&t, st, 0);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n", 
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+	
+	if (t.type != '=') {
+		ERR("%s:%d:%d: Syntax error, '=' expected\n", 
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+
+	ret = cfg_get_token(&t, st, CFG_EXTENDED_ALPHA);
+	if (ret < 0) return -1;
+	if (ret == 0) {
+		ERR("%s:%d:%d: Option value missing\n", 
+		    st->file, t.start.line, t.start.col);
+		return -1;
+	}
+
+	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;
+}

+ 178 - 0
cfg_parser.h

@@ -0,0 +1,178 @@
+/*
+ * $Id$
+ * Standalone Configuration File Parser
+ *
+ * Copyright (C) 2008 iptelorg GmbH
+ * Written by Jan Janak <[email protected]>
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER is free software; you can redistribute it and/or modify it under the
+ * 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.
+ *
+ * SER is distributed in the hope that it will be useful, but WITHOUT ANY
+ * 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., 
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _CFG_PARSER_H
+#define _CFG_PARSER_H
+
+#include "str.h"
+#include <stdio.h>
+
+#define MAX_TOKEN_LEN 512 /**< Token names cannot be longer than this value */
+
+
+typedef enum cfg_flags {
+	/** Extended tokens can contain also delimiters, in addition to
+	 * alpha-numeric characters, this is used on the righ side of assignments
+	 * where no quotes are used.
+	 */
+	CFG_EXTENDED_ALPHA = (1 << 0),
+
+	/** The parser performs case-insensitive comparisons of token strings by
+	 * default. The parser will use case-sensitive comparison instead if this
+	 * flag is set.
+	 */
+	CFG_CASE_SENSITIVE = (1 << 1), 
+
+	/** This is a flag that can be set in the last element of cfg_option
+	 * arrays (this is the one with 0 as token name). When this flag is set
+	 * then the value or parsing function of the element will be used for
+	 * options that do not match any other element in the array.
+	 */
+	CFG_DEFAULT = (1 << 2),
+
+
+	/** When this flag is set then the name of the options is a prefix and all
+	 * options that have the same prefix will be matched by this entry.
+	 */
+	CFG_PREFIX = (1 << 3),
+
+	/** The result of cfg_parse_str_val will be in a buffer allocated by
+	 * pkg_malloc, if the destination varaiable contains a pointer to a buffer
+	 * already then it will be freed with pkg_free first.
+	 */
+	CFG_STR_PKGMEM = (1 << 4),
+
+	/** The result of cfg_parse_str_val will be in a buffer allocated by
+	 * shm_malloc, if the destination variable contains a pointer to a buffer
+	 * already then it will be freed with shm_free first.
+	 */
+	CFG_STR_SHMMEM = (1 << 5),
+
+	/** The result of cfg_parse_str_val will be in a buffer allocated by
+	 * malloc, if the destination variable contains a pointer to a buffer
+	 * already then it will be freed with free first.
+	 */
+	CFG_STR_MALLOC = (1 << 6),
+
+	/** The result of cfg_parse_str_val will be copied into a pre-allocated
+	 * buffer with a fixed size, a pointer to str variable which contains the
+	 * buffer and its size is passed to the function in parameter 'param'.
+	 */
+	CFG_STR_STATIC = (1 << 7),
+
+} cfg_flags_t;
+
+
+enum cfg_token_type {
+	CFG_TOKEN_EOF    = -1,
+	CFG_TOKEN_ALPHA  = -2,
+	CFG_TOKEN_STRING = -3
+};
+
+
+/** Structure representing a lexical token */
+typedef struct cfg_token {
+	char buf [MAX_TOKEN_LEN];
+	int type;  /**< Token type */
+	str val;   /**< Token value */
+	struct {   /**< Position of first and last character of token in file */
+		int line; /**< The starting/ending line of the token */
+		int col;  /**< The starting/ending column of the token */
+	} start, end;
+} cfg_token_t;
+
+
+struct cfg_parser;
+
+typedef int (*cfg_func_f)(void* param, struct cfg_parser* st,
+						  unsigned int flags);
+
+
+/** Token mapping structure.
+ * This structure is used to map tokens to values or function calls. Arrays of
+ * such structures are typically provided by the caller of the parser.
+ */
+typedef struct cfg_option {
+	char* name;    /**< Token name */
+	unsigned int flags;
+	void* param;   /**< Pointer to the destination variable */
+	int val;       /**< Value */
+	cfg_func_f f;  /**< Parser function to be called */
+} cfg_option_t;
+
+
+/* Parser state */
+typedef struct cfg_parser {
+	FILE* f;                 /**< Handle of the currently open file */
+	char* file;              /**< Current file name */
+	int line;                /**< Current line */
+	int col;                 /**< Column index */
+	struct cfg_option* options; /**< Array of supported options */
+	struct {
+		cfg_func_f parser;   /**< Section parser function */
+		void* param;         /**< Parameter value for the parser function */
+	} section;
+	struct cfg_token* cur_opt; /**< Current option */
+} cfg_parser_t;
+
+
+extern struct cfg_option cfg_bool_values[];
+
+struct cfg_parser* cfg_parser_init(str* filename);
+
+void cfg_section_parser(struct cfg_parser* st, cfg_func_f parser, void* param);
+
+void cfg_set_options(struct cfg_parser* st, struct cfg_option* options);
+
+int cfg_parse(struct cfg_parser* st);
+
+void cfg_parser_close(struct cfg_parser* st);
+
+struct cfg_option* cfg_lookup_token(struct cfg_option* options, str* token);
+
+/** Interface to the lexical scanner */
+int cfg_get_token(struct cfg_token* token, struct cfg_parser* st, unsigned int flags);
+
+/* Commonly needed parser functions */
+
+int cfg_eat_equal(struct cfg_parser* st);
+
+/* Parse section identifier of form [section]. The function expects parameter
+ * param to be of type (str*). The result string is allocated using pkg_malloc
+ * and is zero terminated. To free the memory use pkg_free(((str*)param)->s)
+ */
+int cfg_parse_section(void* param, struct cfg_parser* st, unsigned int flags);
+
+/* Parse string parameter value, either quoted or unquoted */
+int cfg_parse_str_val(void* param, struct cfg_parser* st, unsigned int flags);
+
+int cfg_parse_enum_val(void* param, struct cfg_parser* st, unsigned int flags);
+
+/* Parser integer parameter value */
+int cfg_parse_int_val(void* param, struct cfg_parser* st, unsigned int flags);
+
+/* Parse boolean parameter value */
+int cfg_parse_bool_val(void* param, struct cfg_parser* st, unsigned int flags);
+
+#endif /* _CFG_PARSER_H */