Browse code

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