comparison th_config.c @ 133:ffe8bbd429fa

Config file.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 30 Oct 2010 20:37:09 +0300
parents
children 26fe4dab7e78
comparison
equal deleted inserted replaced
132:10daf4660cae 133:ffe8bbd429fa
1 /*
2 * Very simple configuration handling functions
3 * Programmed and designed by Matti 'ccr' Hamalainen
4 * (C) Copyright 2004-2008 Tecnic Software productions (TNSP)
5 *
6 * Please read file 'COPYING' for information on license and distribution.
7 */
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include "th_config.h"
14 #include "th_util.h"
15 #include "th_string.h"
16
17 #define SET_MAX_BUF (8192)
18
19
20 /* Free a given configuration (the values are not free'd)
21 */
22 void th_cfg_free(cfgitem_t *cfg)
23 {
24 cfgitem_t *curr = cfg;
25
26 while (curr != NULL) {
27 cfgitem_t *next = curr->next;
28
29 if (curr->type == ITEM_SECTION)
30 th_cfg_free((cfgitem_t *) curr->data);
31
32 th_free(curr->name);
33 th_free(curr);
34 curr = next;
35 }
36 }
37
38
39 /* Allocate and add new item to configuration
40 */
41 static cfgitem_t *th_cfg_add(cfgitem_t **cfg, char *name, int type, void *data)
42 {
43 cfgitem_t *node;
44
45 if (cfg == NULL)
46 return NULL;
47
48 /* Allocate new item */
49 node = (cfgitem_t *) th_calloc(1, sizeof(cfgitem_t));
50 if (node == NULL)
51 return NULL;
52
53 /* Set values */
54 node->type = type;
55 node->data = data;
56 node->name = th_strdup(name);
57
58 /* Insert into linked list */
59 if (*cfg != NULL) {
60 node->prev = (*cfg)->prev;
61 (*cfg)->prev->next = node;
62 (*cfg)->prev = node;
63 } else {
64 *cfg = node;
65 node->prev = node;
66 }
67 node->next = NULL;
68
69 return node;
70 }
71
72
73 /* Add integer type setting into give configuration
74 */
75 int th_cfg_add_int(cfgitem_t ** cfg, char * name,
76 int *itemData, int itemDef)
77 {
78 cfgitem_t *node;
79
80 node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData);
81 if (node == NULL)
82 return -1;
83
84 *itemData = itemDef;
85
86 return 0;
87 }
88
89
90 int th_cfg_add_hexvalue(cfgitem_t ** cfg, char * name,
91 int *itemData, int itemDef)
92 {
93 cfgitem_t *node;
94
95 node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData);
96 if (node == NULL)
97 return -1;
98
99 *itemData = itemDef;
100
101 return 0;
102 }
103
104
105 /* Add unsigned integer type setting into give configuration
106 */
107 int th_cfg_add_uint(cfgitem_t ** cfg, char * name,
108 unsigned int * itemData, unsigned int itemDef)
109 {
110 cfgitem_t *node;
111
112 node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData);
113 if (node == NULL)
114 return -1;
115
116 *itemData = itemDef;
117
118 return 0;
119 }
120
121
122 /* Add strint type setting into given configuration
123 */
124 int th_cfg_add_string(cfgitem_t ** cfg, char * name,
125 char ** itemData, char * itemDef)
126 {
127 cfgitem_t *node;
128
129 node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData);
130 if (node == NULL)
131 return -1;
132
133 *itemData = th_strdup(itemDef);
134
135 return 0;
136 }
137
138
139 /* Add boolean type setting into given configuration
140 */
141 int th_cfg_add_bool(cfgitem_t ** cfg, char * name,
142 BOOL * itemData, BOOL itemDef)
143 {
144 cfgitem_t *node;
145
146 node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData);
147 if (node == NULL)
148 return -1;
149
150 *itemData = itemDef;
151
152 return 0;
153 }
154
155
156 /* Add implicit comment
157 */
158 int th_cfg_add_comment(cfgitem_t ** cfg, char * comment)
159 {
160 cfgitem_t *node;
161
162 node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL);
163 if (node == NULL)
164 return -1;
165
166 return 0;
167 }
168
169
170 /* Add new section
171 */
172 int th_cfg_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
173 {
174 cfgitem_t *node;
175
176 node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) data);
177 if (node == NULL)
178 return -1;
179
180 return 0;
181 }
182
183
184 /* Read a given file into configuration structure and variables
185 */
186 enum {
187 PM_EOF,
188 PM_ERROR,
189 PM_NORMAL,
190 PM_COMMENT,
191 PM_NEXT,
192 PM_KEYNAME,
193 PM_KEYSET,
194 PM_STRING,
195 PM_INT,
196 PM_BOOL,
197 PM_SECTION
198 };
199
200 #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
201 #define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#')
202
203 typedef struct {
204 FILE *file;
205 char *filename;
206 size_t line;
207 } conffile_t;
208
209
210 static void th_cfg_error(conffile_t *f, const char *fmt, ...)
211 {
212 va_list ap;
213 va_start(ap, fmt);
214 fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, f->filename, f->line);
215 vfprintf(stderr, fmt, ap);
216 va_end(ap);
217 }
218
219
220 static int th_cfg_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
221 {
222 cfgitem_t *item = NULL;
223 char tmpStr[SET_MAX_BUF + 1];
224 size_t strPos;
225 int c, parseMode, prevMode, nextMode, tmpCh;
226 BOOL isFound, isStart, isError, validError;
227
228 /* Initialize values */
229 tmpCh = 0;
230 strPos = 0;
231 c = -1;
232 isFound = isStart = isError = validError = FALSE;
233 nextMode = prevMode = parseMode = PM_NORMAL;
234
235 /* Parse the configuration */
236 while (parseMode != PM_EOF && parseMode != PM_ERROR) {
237 if (c == -1) {
238 /* Get next character */
239 switch (c = fgetc(f->file)) {
240 case EOF:
241 if (parseMode != PM_NORMAL) {
242 th_cfg_error(f,
243 "Unexpected end of file.\n");
244 parseMode = PM_ERROR;
245 } else
246 parseMode = PM_EOF;
247 break;
248
249 case '\n':
250 f->line++;
251 }
252 }
253
254 switch (parseMode) {
255 case PM_COMMENT:
256 /* Comment parsing mode */
257 if (c == '\n') {
258 /* End of line, end of comment */
259 parseMode = prevMode;
260 prevMode = PM_COMMENT;
261 }
262 c = -1;
263 break;
264
265 case PM_NORMAL:
266 /* Normal parsing mode */
267 if (c == '#') {
268 prevMode = parseMode;
269 parseMode = PM_COMMENT;
270 c = -1;
271 } else if (VISEND(c)) {
272 c = -1;
273 } else if (c == '}') {
274 if (nesting > 0) {
275 /* Check for validation errors */
276 return (validError) ? 1 : 0;
277 } else {
278 th_cfg_error(f, "Invalid nesting sequence encountered.\n");
279 parseMode = PM_ERROR;
280 }
281 } else if (th_isalpha(c)) {
282 /* Start of key name found */
283 prevMode = parseMode;
284 parseMode = PM_KEYNAME;
285 strPos = 0;
286 } else {
287 /* Error! Invalid character found */
288 th_cfg_error(f,
289 "Unexpected character '%c'.\n", c);
290 parseMode = PM_ERROR;
291 }
292 break;
293
294 case PM_KEYNAME:
295 /* Configuration KEY name parsing mode */
296 if (c == '#') {
297 /* Start of comment */
298 prevMode = parseMode;
299 parseMode = PM_COMMENT;
300 c = -1;
301 } else if (th_iscrlf(c) || th_isspace(c) || c == '=') {
302 /* End of key name */
303 prevMode = parseMode;
304 parseMode = PM_NEXT;
305 nextMode = PM_KEYSET;
306 } else if (th_isalnum(c) || c == '_') {
307 /* Add to key name string */
308 VADDCH(c)
309 else
310 {
311 /* Error! Key name string too long! */
312 th_cfg_error(f,
313 "Config key name too long!");
314 parseMode = PM_ERROR;
315 }
316 c = -1;
317 } else {
318 /* Error! Invalid character found */
319 tmpStr[strPos] = 0;
320 th_cfg_error(f,
321 "Unexpected character '%c' in key name '%s'.\n", c, tmpStr);
322 parseMode = PM_ERROR;
323 }
324 break;
325
326 case PM_KEYSET:
327 if (c == '=') {
328 /* Find key from configuration */
329 tmpStr[strPos] = 0;
330 isFound = FALSE;
331 item = cfg;
332 while (item != NULL && !isFound) {
333 if (item->name != NULL && strcmp(item->name, tmpStr) == 0)
334 isFound = TRUE;
335 else
336 item = item->next;
337 }
338
339 /* Check if key was found */
340 if (isFound) {
341 /* Okay, set next mode */
342 switch (item->type) {
343 case ITEM_HEX_TRIPLET:
344 case ITEM_STRING:
345 nextMode = PM_STRING;
346 break;
347
348 case ITEM_INT:
349 case ITEM_UINT:
350 nextMode = PM_INT;
351 break;
352
353 case ITEM_BOOL:
354 nextMode = PM_BOOL;
355 break;
356
357 case ITEM_SECTION:
358 nextMode = PM_SECTION;
359 break;
360 }
361
362 prevMode = parseMode;
363 parseMode = PM_NEXT;
364 isStart = TRUE;
365 strPos = 0;
366 } else {
367 /* Error! No configuration key by this name found */
368 th_cfg_error(f,
369 "No such configuration setting ('%s')\n", tmpStr);
370 parseMode = PM_ERROR;
371 }
372
373 c = -1;
374 } else {
375 /* Error! '=' expected! */
376 th_cfg_error(f,
377 "Unexpected character '%c', assignation '=' was expected.\n", c);
378 parseMode = PM_ERROR;
379 }
380 break;
381
382 case PM_NEXT:
383 /* Search next item parsing mode */
384 if (c == '#') {
385 /* Start of comment */
386 prevMode = parseMode;
387 parseMode = PM_COMMENT;
388 } else if (th_isspace(c) || th_iscrlf(c)) {
389 /* Ignore whitespaces and linechanges */
390 c = -1;
391 } else {
392 /* Next item found */
393 prevMode = parseMode;
394 parseMode = nextMode;
395 }
396 break;
397
398 case PM_SECTION:
399 /* Section parsing mode */
400 if (c != '{') {
401 /* Error! Section start '{' expected! */
402 th_cfg_error(f,
403 "Unexpected character '%c', section start '{' was expected.\n", c);
404 parseMode = PM_ERROR;
405 } else {
406 int res = th_cfg_read_sect(f, (cfgitem_t *) item->data, nesting + 1);
407 c = -1;
408 if (res > 0)
409 validError = TRUE;
410 else if (res < 0)
411 parseMode = PM_ERROR;
412 else {
413 prevMode = parseMode;
414 parseMode = PM_NORMAL;
415 }
416 }
417 break;
418
419 case PM_STRING:
420 /* String parsing mode */
421 if (isStart) {
422 /* Start of string, get delimiter */
423 tmpCh = c;
424 isStart = FALSE;
425 strPos = 0;
426 } else if (c == tmpCh) {
427 /* End of string, set the value */
428 tmpStr[strPos] = 0;
429
430 if (item->type == ITEM_HEX_TRIPLET) {
431 } else if (item->type == ITEM_STRING) {
432 th_pstrcpy((char **) item->data, tmpStr);
433 }
434
435 prevMode = parseMode;
436 parseMode = PM_NORMAL;
437 } else {
438 /* Add character to string */
439 VADDCH(c)
440 else
441 {
442 /* Error! String too long! */
443 th_cfg_error(f,
444 "String too long! Maximum is %d characters.",
445 SET_MAX_BUF);
446 parseMode = PM_ERROR;
447 }
448 }
449
450 c = -1;
451 break;
452
453 case PM_INT:
454 /* Integer parsing mode */
455 if (isStart && item->type == ITEM_UINT && c == '-') {
456 /* Error! Negative values not allowed for unsigned ints */
457 th_cfg_error(f,
458 "Negative value specified for %s, unsigned value expected.",
459 item->name);
460 parseMode = PM_ERROR;
461 } else if (isStart && (c == '-' || c == '+')) {
462 VADDCH(c)
463 else
464 isError = TRUE;
465 } else if (th_isdigit(c)) {
466 VADDCH(c)
467 else
468 isError = TRUE;
469 } else if (VISEND(c)) {
470 /* End of integer parsing mode */
471 tmpStr[strPos] = 0;
472 switch (item->type) {
473 case ITEM_INT:
474 *((int *) item->data) = atoi(tmpStr);
475 break;
476
477 case ITEM_UINT:
478 *((unsigned int *) item->data) = atol(tmpStr);
479 break;
480 }
481
482 prevMode = parseMode;
483 parseMode = PM_NORMAL;
484 } else {
485 /* Error! Unexpected character. */
486 th_cfg_error(f,
487 "Unexpected character '%c' for integer setting '%s'.",
488 c, item->name);
489 parseMode = PM_ERROR;
490 }
491
492 if (isError) {
493 /* Error! String too long! */
494 th_cfg_error(f, "String too long! Maximum is %d characters.",
495 SET_MAX_BUF);
496 parseMode = PM_ERROR;
497 }
498
499 isStart = FALSE;
500 c = -1;
501 break;
502
503 case PM_BOOL:
504 /* Boolean parsing mode */
505 if (isStart) {
506 tmpCh = c;
507 isStart = FALSE;
508 } else if (VISEND(c)) {
509 BOOL tmpBool;
510
511 /* End of boolean parsing */
512 switch (tmpCh) {
513 case 'Y': case 'y':
514 case 'T': case 't':
515 case '1':
516 tmpBool = TRUE;
517 break;
518
519 case 'N': case 'n':
520 case 'F': case 'f':
521 case '0':
522 tmpBool = FALSE;
523 break;
524
525 default:
526 isError = TRUE;
527 }
528
529 if (isError) {
530 th_cfg_error(f, "Invalid boolean value for '%s'.\n", item->name);
531 parseMode = PM_ERROR;
532 } else {
533 *((BOOL *) item->data) = tmpBool;
534
535 prevMode = parseMode;
536 parseMode = PM_NORMAL;
537 }
538 }
539 c = -1;
540 break;
541 }
542 }
543
544 /* Check for validation errors */
545 if (validError)
546 return 1;
547
548 /* Return result */
549 if (parseMode == PM_ERROR)
550 return -2;
551 else
552 return 0;
553 }
554
555
556 int th_cfg_read(FILE *inFile, char *filename, cfgitem_t * cfg)
557 {
558 conffile_t f;
559
560 f.file = inFile;
561 f.filename = filename;
562 f.line = 1;
563
564 return th_cfg_read_sect(&f, cfg, 0);
565 }
566
567
568 /* Write a configuration into file
569 */
570 static void th_print_indent(conffile_t *f, int nesting)
571 {
572 int i;
573 for (i = 0; i < nesting * 2; i++)
574 fputc(' ', f->file);
575 }
576
577
578 static int th_cfg_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
579 {
580 while (item != NULL) {
581 if (item->type == ITEM_COMMENT) {
582 th_print_indent(f, nesting);
583 if (fprintf(f->file, "# %s\n", (item->name != NULL) ? item->name : "" ) < 0)
584 return -1;
585 } else
586 if (item->name != NULL) {
587 th_print_indent(f, nesting);
588
589 switch (item->type) {
590 case ITEM_STRING:
591 if (*((char **) item->data) == NULL) {
592 if (fprintf(f->file, "#%s = \"\"\n", item->name) < 0)
593 return -3;
594 } else {
595 if (fprintf(f->file, "%s = \"%s\"\n",
596 item->name, *((char **) item->data)) < 0)
597 return -3;
598 }
599 break;
600
601 case ITEM_INT:
602 if (fprintf(f->file, "%s = %i\n",
603 item->name, *((int *) item->data)) < 0)
604 return -4;
605 break;
606
607 case ITEM_UINT:
608 if (fprintf(f->file, "%s = %d\n",
609 item->name, *((unsigned int *) item->data)) < 0)
610 return -5;
611 break;
612
613 case ITEM_BOOL:
614 if (fprintf(f->file, "%s = %s\n",
615 item->name, *((BOOL *) item->data) ? "yes" : "no") < 0)
616 return -6;
617 break;
618
619 case ITEM_SECTION:
620 {
621 int res;
622 if (fprintf(f->file, "\n%s = {\n", item->name) < 0)
623 return -7;
624 res = th_cfg_write_sect(f, (cfgitem_t *) item->data, nesting + 1);
625 if (res != 0) return res;
626 if (fprintf(f->file, "} # End of '%s'\n\n", item->name) < 0)
627 return -8;
628 }
629 break;
630
631 case ITEM_HEX_TRIPLET:
632 if (fprintf(f->file, "%s = \"%06x\"\n",
633 item->name, *((int *) item->data)) < 0)
634 return -6;
635 break;
636 }
637 }
638 item = item->next;
639 }
640
641 return 0;
642 }
643
644
645 int th_cfg_write(FILE *outFile, char *filename, cfgitem_t *cfg)
646 {
647 conffile_t f;
648
649 if (cfg == NULL)
650 return -1;
651
652 f.file = outFile;
653 f.filename = filename;
654 f.line = 1;
655
656 fprintf(outFile, "# Configuration written by %s %s\n\n",
657 th_prog_fullname, th_prog_version);
658
659 return th_cfg_write_sect(&f, cfg, 0);
660 }
661
662