Mercurial > hg > nnchat
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 |