source: branches/ithildin-1.1/source/conf.c @ 831

Revision 831, 21.0 KB checked in by wd, 3 years ago (diff)

Clean up unknown client counting (hopefully fixed) and add some helper
functions for protocol transition.

  • Property svn:keywords set to Id Rev
Line 
1/*
2 * conf.c: configuration file parser
3 *
4 * Copyright 2002 the Ithildin Project.
5 * See the COPYING file for more information on licensing and use.
6 *
7 * This file contains the routines responsible for extracting data from
8 * configuration files.  The file format is documented lightly in doc/conf.txt.
9 * This really ought to be written in lex/yacc.
10 */
11
12#include <ithildin/stand.h>
13
14IDSTRING(rcsid, "$Id$");
15
16/* these are parsing functions */
17static char *conf_preparse(char *, char *);
18static conf_list_t *conf_parse(char *, int, char *);
19static conf_entry_t *merge_into_conf(conf_list_t *, conf_entry_t *,
20        conf_list_t *);
21static char *conf_expand_text(char *);
22
23#define parse_err(x) log_error("%s:%d %s", file, line, x);
24
25/* this function reads the configuration data from 'file' and turns it into a
26 * tree headed at the conf_list structure it returns.  The rest of the
27 * functions in this file can then be used to look at this data.  */
28conf_list_t *read_conf(char *file) {
29    char *stuff = NULL;
30    conf_list_t *list = NULL;
31
32    /* mmap the data in */
33    if ((stuff = mmap_file(file)) == NULL)
34        return NULL;
35
36    /* weed out comments, and bad strings */
37    if ((stuff = conf_preparse(file, stuff)) == NULL)
38        return NULL;
39   
40    /* now parse */
41    list = conf_parse(file, 1, stuff);
42    /* 'stuff' is now not necessary one way or the other */
43    free(stuff);
44
45    if (list == NULL)
46        return NULL;
47
48    /* display_tree(0, list);*/
49    return list;
50}
51
52/* this is used to free the memory consumed in a conf_list.  it works
53 * recursively, clearing all data down the line. */
54void destroy_conf_branch(conf_list_t *list) {
55    conf_entry_t *ep1, *ep2 = NULL;
56
57    ep1 = LIST_FIRST(list);
58    while (ep1 != NULL) {
59        ep2 = LIST_NEXT(ep1, lp);
60        if (ep1->type == CONF_TYPE_LIST)
61            destroy_conf_branch(ep1->list);
62        if (ep1->string != NULL)
63            free(ep1->string);
64        free(ep1->name);
65        free(ep1);
66        ep1 = ep2;
67    }
68}
69
70/* this is a debugging function whicb prints the data in 'list' out in a
71 * tree-like format to stdout. */
72void conf_display_tree(int depth, conf_list_t *list) {
73    int i;
74    conf_entry_t *ep = NULL;
75
76#define print_branches(x) for (i = x;i;i--) printf("| ");
77    LIST_FOREACH(ep, list, lp) {
78        print_branches(depth);
79        if (ep->type == CONF_TYPE_DATA)
80            printf("%s-> %s = '%s'\n", (depth ? "\b" : ""), ep->name,
81                    ep->string);
82        else {
83            printf("%s->[%s]\n", (depth ? "\b" : ""), ep->name);
84            conf_display_tree(depth + 1, ep->list);
85        }
86    }
87#undef print_branches
88}
89
90/* this function finds the first occurence of the conf with the given name (and
91 * possibly containing data, if data is non-NULL) and of the given type (if
92 * specified, it may be 0 if either a list or entry is acceptable) from the
93 * given list, recursing at most maxdepth - 1 times. */
94conf_entry_t *conf_find(const char *name, const char *data, int type,
95    conf_list_t *list, int maxdepth) {
96    conf_entry_t *cep = NULL, *tmpcep;
97
98    if (maxdepth < 1 || list == NULL)
99        return NULL;
100
101    LIST_FOREACH(cep, list, lp) {
102        if (!strcasecmp(cep->name, name) && (cep->type == type || type == 0)) {
103            /* return the entry if the strings match, or if data is NULL */
104            if (data == NULL)
105                return cep;
106            else if (!strcasecmp(cep->string, data))
107                return cep;
108        } else if (cep->type == CONF_TYPE_LIST) {
109            if ((tmpcep = conf_find(name, data, type, cep->list, maxdepth - 1))
110                    != NULL)
111                return tmpcep;
112        }
113    }
114
115    return NULL;
116}
117
118/* this function behaves exactly like the above, except that it finds the
119 * specified entry *after* 'last'.  If last is NULL, it starts from the head of
120 * the list. */
121conf_entry_t *conf_find_next(const char *name, const char *data, int type,
122        conf_entry_t *last, conf_list_t *list, int maxdepth) {
123    conf_entry_t *cep = NULL, *tmpcep;
124
125    if (maxdepth < 1 || list == NULL)
126        return NULL;
127
128    /* with a conf entry, we get the list data too, so we know where
129     * to start from! */
130    if (last == NULL)
131        cep = LIST_FIRST(list);
132    else
133        cep = LIST_NEXT(last, lp);
134
135    while (cep != NULL) {
136        if (!strcasecmp(cep->name, name) &&
137                (cep->type == type || type == 0)) {
138            /* return the entry if the strings match, or if data is NULL */
139            if (data == NULL)
140                return cep;
141            else if (!strcasecmp(cep->string, data))
142                return cep;
143        } else if (cep->type == CONF_TYPE_LIST) {
144            if ((tmpcep = conf_find(name, data, type, cep->list, maxdepth - 1))
145                    != NULL)
146                return tmpcep;
147        }
148        cep = LIST_NEXT(cep, lp);
149    }
150    return NULL;
151}
152
153/* this is shorthand to find a list-type conf, it uses conf_find() above. */
154conf_list_t *conf_find_list(const char *name, conf_list_t *list,
155        int maxdepth) {
156    conf_entry_t *cep;
157   
158    if ((cep = conf_find(name, NULL, CONF_TYPE_LIST, list, maxdepth)) != NULL)
159        return cep->list;
160
161    return NULL;
162}
163
164/* this is shorthand to find an entry-type conf, it uses conf_find() above. */
165char *conf_find_entry(const char *name, conf_list_t *list, int maxdepth) {
166    conf_entry_t *cep;
167   
168    if ((cep = conf_find(name, NULL, CONF_TYPE_DATA, list, maxdepth)) != NULL)
169        return cep->string;
170
171    return NULL;
172}
173
174/* these two functions work like conf_find_next(), only they're not as
175 * efficient as they always have to start at the head of the conf.  If
176 * possible, it is recommended you use conf_find_next() instead. */
177conf_list_t *conf_find_list_next(const char *name, conf_list_t *last,
178        conf_list_t *list, int maxdepth) {
179    conf_entry_t *cep = NULL;
180    conf_list_t *clp = NULL;
181    int found = 0;
182
183    if (maxdepth < 1 || list == NULL)
184        return NULL;
185
186    if (last == NULL)
187        found = 1; /* if last is NULL, start at the beginning */
188
189    LIST_FOREACH(cep, list, lp) {
190        if (!found) {
191            if (cep->list == last)
192                found++; /* this is the matching entry */
193            continue;
194        }
195
196        if (cep->type == CONF_TYPE_LIST && !strcasecmp(cep->name, name))
197            return cep->list;
198        else if (cep->type == CONF_TYPE_LIST) {
199            if ((clp = conf_find_list(name, cep->list, maxdepth - 1)) != NULL)
200                return clp;
201        }
202    }
203
204    return NULL;
205}
206char *conf_find_entry_next(const char *name, char *last,
207        conf_list_t *list, int maxdepth) {
208    conf_entry_t *cep = NULL;
209    char *s = NULL;
210    int found = 0;
211
212    if (maxdepth < 1 || list == NULL)
213        return NULL;
214
215    if (last == NULL)
216        found = 1; /* if last is NULL, start at the beginning */
217
218    LIST_FOREACH(cep, list, lp) {
219        if (!found) {
220            if (cep->string == last)
221                found++; /* this is the matching entry */
222            continue;
223        }
224
225        if (cep->type == CONF_TYPE_DATA && !strcasecmp(cep->name, name))
226            return cep->string;
227        else if (cep->type == CONF_TYPE_LIST) {
228            if ((s = conf_find_entry(name, cep->list, maxdepth - 1)) != NULL)
229                return s;
230        }
231    }
232
233    return NULL;
234}
235
236/* this function prunes the data in 'oldstr', it clears out excess whitespace,
237 * and handles reading in included files.  It also does light sanity-checking
238 * to make sure comments and strings are properly terminated. */
239static char *conf_preparse(char *file, char *oldstr) {
240    char *s = oldstr;
241    char *newstr = malloc(strlen(oldstr) + 1);
242    char *str = newstr;
243    int instring = 0;
244    int incomment = 0;
245    int line = 1, begline = 0;
246
247    while (*s) {
248        switch (*s) {
249            case '"':
250                if (incomment) {
251                    s++;
252                    break;
253                }
254                if (!instring)
255                    begline=line;
256                instring ^= 1;
257                *str++ = *s++;
258                break;
259            case '\\': /* the \ just negates us from checking the next
260                          character, shrug. */
261                if (incomment) {
262                    s++;
263                    break;
264                }
265                *str++ = *s++;
266                if (*s == '\n') {
267#if 0
268                    parse_err("cannot backquote literal newline.");
269                    free(newstr);
270                    free(oldstr);
271                    return NULL;
272#endif
273                    line++;
274                }
275                *str++ = *s++;
276                break;
277            case '#':
278                if (incomment) {
279                    s++;
280                    break;
281                } else if (instring)
282                    *str++ = *s++;
283                else
284                    while (*s && *s != '\n')
285                        s++; /* a #-style comment.  whee. */
286                break;
287            case '/':
288                if (incomment) {
289                    s++;
290                    break;
291                }
292                if (*(s + 1) == '/' && !instring) {
293                    /* this being a comment of the C++ neature, eat until
294                       EOL */
295                    while (*s && *s != '\n') 
296                        s++;
297                } else if (*(s + 1) == '*' && !instring) {
298                    s += 2;
299                    incomment = 1;
300                    begline=line;
301                } else
302                    *str++ = *s++;
303                break;               
304            case '\n':
305                line++;
306#if 0
307                if (instring) {
308                    parse_err("unterminated string on previous line");
309                    free(newstr);
310                    free(oldstr);
311                    return NULL;
312                }
313#endif
314                *str++ = *s++;
315                break;
316            default:
317                if (!incomment)
318                    *str++ = *s++;
319                else if (incomment && *s == '*') {
320                    if (*(s + 1) == '/') { /* the comment is over */
321                        s += 2;
322                        incomment=0;
323                    } else
324                        s++;
325                } else
326                    s++;
327                break;
328        }
329    }
330
331    *str = 0;
332    free(oldstr);
333
334    if (instring || incomment) {
335        line = begline;
336        parse_err("unterminated string or comment beginning here");
337        free(newstr);
338        return NULL;
339    }
340   
341    return newstr;
342}
343
344/* do the final parsing of the data, extract name/value pairs and build our
345 * tree, and all with a spot of recursion.  yum! */
346static conf_list_t *conf_parse(char *file, int line, char *str) {
347    conf_list_t *list = malloc(sizeof(conf_list_t));
348    conf_entry_t *ent = NULL, *last = NULL;
349    char *s, save;
350    int i; /* use s as a place holder, and i as a length counter */
351    int startline = 0;
352
353#define swallow_whitespace() do {                                        \
354    while (*str) {                                                        \
355        if (*str == '\n')                                                \
356            line++;                                                        \
357        if (isspace(*str))                                                \
358            str++;                                                        \
359        else                                                                \
360            break;                                                        \
361    }                                                                        \
362} while (0)
363
364    LIST_INIT(list);
365
366    while (*str) {
367        swallow_whitespace();
368        if (!*str)
369            return list; /* that's it, we're done! */
370       
371        ent = malloc(sizeof(conf_entry_t));
372        memset(ent, 0, sizeof(conf_entry_t));
373        ent->parent = list;
374
375        /* here's some cute syntactic sugar.  we allow nameless entries if
376         * they are lists (start with/end with {/}) or if the are quoted (""),
377         * we set the name field to "" */
378        if (*str != '"' && *str != '{') {
379            i = 0;
380            s = str;
381            while (*str && !isspace(*str) && *str != '{' && *str != ';') {
382                str++;
383                i++;
384            }
385
386            if (!*str) {
387                destroy_conf_branch(list);
388                parse_err("file terminated prematurely");
389                return NULL; /* this is obviously eroneous */
390            }
391       
392            /* silly hack for nameless entries here, too.  We swallow
393             * whitespace, and if our next character is a ';', we craftily push
394             * our pointer back to where it was before and let the parser
395             * continue. */
396            swallow_whitespace();
397            if (*str == ';') {
398                str = s;
399                ent->name = malloc(1);
400                strcpy(ent->name, "");
401            } else {
402                ent->name = malloc((size_t)i + 1);
403                strncpy(ent->name, s, (size_t)i);
404                ent->name[i]=0;
405            }
406        } else {
407            ent->name = malloc(1);
408            strcpy(ent->name, "");
409        }
410   
411        swallow_whitespace();
412
413        s = str;
414        while (*str && *str != ';' && *str != '{') {
415            if (*str == '"') {
416                while (*str) {
417                    if (*str == '\\')
418                        str += 2;
419                    else if (*str == '"') {
420                        str++;
421                        break;
422                    } else
423                        str++;
424                }
425
426                if (!*str) {
427                    free(ent->name);
428                    free(ent);
429                    destroy_conf_branch(list);
430                    parse_err("syntax error");
431                    return NULL;
432                }
433            } else
434                str++;
435        }
436
437        if (!*str) {
438            free(ent->name);
439            free(ent);
440            destroy_conf_branch(list);
441            parse_err("syntax error");
442            return NULL;
443        }
444
445        /* copy in the string, if any.  if the string evaluates to empty, free
446         * the memory and set string to NULL. */
447        save = *str;
448        *str = 0;
449        ent->string = conf_expand_text(s);
450        if (*ent->string == '\0') {
451            free(ent->string);
452            ent->string = NULL;
453        }
454        *str = save;
455
456        /* if this really was a data-type entry, check to see if it was an
457         * include statement, and do the including.  also check to see if it
458         * boiled down to an empty string and complain if it did. */
459        if (*str == ';') {
460            ent->type = CONF_TYPE_DATA;
461            if (ent->string == NULL) {
462                /* actually.. let's try this.. if string came out NULL, make it
463                 * a nameless entry. */
464                ent->string = ent->name;
465                ent->name = strdup("");
466            }
467
468            if (!strcmp(ent->name, "$INCLUDE")) {
469                conf_list_t *inclist;
470
471                if ((inclist = read_conf(ent->string)) == NULL) {
472                    parse_err("error in included file:");
473                    parse_err(ent->string);
474                    destroy_conf_branch(list);
475                    return NULL;
476                } else {
477                    free(ent->string);
478                    free(ent);
479                    last = merge_into_conf(list, last, inclist);
480                }
481            } else {
482                if (last == NULL)
483                    LIST_INSERT_HEAD(list, ent, lp);
484                else
485                    LIST_INSERT_AFTER(last, ent, lp);
486                last = ent;
487            }
488            str++; /* skip the semicolon */
489        } else if (*str == '{') {
490            /* otherwise, it's a section, keep going .. */
491            int depth = 1;
492            startline = line;
493            s = ++str;
494            ent->type = CONF_TYPE_LIST;
495
496            while (*str) {
497                if (*str == '"') {
498                    str++;
499                    while (*str) {
500                        if (*str == '\\' && *(str + 1) != '\0')
501                            str += 2;
502                        else if (*str == '"')
503                            break;
504                        else
505                            str++;
506                    }
507                } else if (*str == '{')
508                    depth++;
509                else if (*str == '}') {
510                    depth--;
511
512                    if (depth == 0)
513                        break;
514                }
515                if (*str == '\n')
516                    line++;
517                str++;
518            }
519
520            if (!*str) {
521                free(ent->name);
522                free(ent);
523                destroy_conf_branch(list);
524                parse_err("syntax error (unclosed braces)");
525                return NULL; /* some kind of syntax error */
526            }
527           
528            *str++ = 0;
529            ent->list = conf_parse(file, startline, s);
530            if (ent->list == NULL) {
531                free(ent->name);
532                free(ent);
533                destroy_conf_branch(list);
534                return NULL;
535            }
536
537            if (last == NULL)
538                LIST_INSERT_HEAD(list, ent, lp);
539            else
540                LIST_INSERT_AFTER(last, ent, lp);
541            last = ent;
542
543            swallow_whitespace();
544
545            if (!*str) {
546                free(ent->name);
547                free(ent);
548                destroy_conf_branch(list);
549                parse_err("missing semicolon (';')");
550                return NULL; /* missing semi-colon */
551            } else
552                s = NULL;
553
554            if (*str != ';') {
555                free(ent->name);
556                destroy_conf_branch(ent->list);
557                free(ent);
558                parse_err("garbage between closing brace ('}') and "
559                       "semicolon (';')");
560                return NULL;
561            }
562           
563            *str++ = '\0';
564            continue;
565        } else {
566            destroy_conf_branch(list);
567            parse_err("conf parser barfed!");
568            return NULL; /* some kind of syntax error */
569        }
570    }
571
572    return list; /* finished! */
573}
574
575/* merge everything in the conf_list 'little' into the conf_list 'big',
576 * place entries starting after the 'after' point */
577static conf_entry_t *merge_into_conf(conf_list_t *big, conf_entry_t *after,
578        conf_list_t *little) {
579    conf_entry_t *last = after;
580    conf_entry_t *ep;
581
582    while (!LIST_EMPTY(little)) {
583        ep = LIST_FIRST(little);
584        LIST_REMOVE(ep, lp);
585        if (last == NULL)
586            LIST_INSERT_HEAD(big, ep, lp);
587        else
588            LIST_INSERT_AFTER(last, ep, lp);
589        ep->parent = big;
590        last = ep;
591    }
592
593    free(little);
594    return last;
595}
596
597/* this is a small function used to clean up strings read by the conf parser.
598 * basically, what we do is trim whitespaces down to a single space unless the
599 * entry is quoted,  It also trims unquoted whitespace from the end of the
600 * string.  Lastly, for certain backquoted sequences in quoted text the
601 * sequences are expanded to their C equivalents (\n, \r, et al) */
602static char *conf_expand_text(char *str) {
603    char *newstr = malloc(strlen(str) + 1);
604    char *s = newstr;
605   
606    while (*str) {
607        if (*str == '"') {
608            str++;
609            while (*str) {
610                if (*str == '\\') {
611                    str++;
612                    if (*str == '\0') {
613                        *s++ = '\\';
614                        break;
615                    }
616                    switch (*str) {
617                        case 'a':
618                            *s++ = '\007'; /* BEL character (^G) */
619                            break;
620                        case 'b':
621                            *s++ = '\010'; /* Backspace character (^H) */
622                            break;
623                        case 'f':
624                            *s++ = '\014'; /* Form-feed character (^L) */
625                            break;
626                        case 'n':
627                            *s++ = '\012'; /* Newline character (^J) */
628                            break;
629                        case 'r':
630                            *s++ = '\015'; /* Carriage return character (^M) */
631                            break;
632                        case 't':
633                            *s++ = '\011'; /* Tab character (^I) */
634                            break;
635                        case 'v':
636                            *s++ = '\013'; /* Vertical tab character (^K) */
637                            break;
638                        default:
639                            *s++ = *str;
640                    }
641                    str++;
642                } else if (*str == '"') {
643                    str++;
644                    break; /* break out of the inner loop, the outer one will
645                              pick up the slack. */
646                } else
647                    *s++ = *str++;
648            }
649        } else if (isspace(*str)) {
650            *s++ = ' ';
651            while (isspace(*str))
652                str++;
653            if (!*str)
654                s--; /* let that last space get trimmed down below. */
655        } else 
656            *s++ = *str++;
657    }
658   
659    *s = 0;
660    return newstr;
661}
662
663/* vi:set ts=8 sts=4 sw=4 tw=76 et: */
Note: See TracBrowser for help on using the repository browser.