1 /*
2 **  Parse a standard block-structured configuration file syntax.
3 **
4 **  Herein are all the parsing and access functions for the configuration
5 **  syntax used by INN.  See doc/config-* for additional documentation.
6 **
7 **  All entry point functions begin with config_*.  config_parse_file is
8 **  the entry point for most of the work done in this file; all other
9 **  functions access the parse tree that config_parse_file generates.
10 **
11 **  Functions are named by the structure or basic task they work on:
12 **
13 **      parameter_*     config_parameter structs.
14 **      group_*         config_group structs.
15 **      file_*          config_file structs (including all I/O).
16 **      token_*         The guts of the lexer.
17 **      parse_*         The guts of the parser.
18 **      error_*         Error reporting functions.
19 **      convert_*       Converting raw parameter values.
20 **
21 **  Each currently open file is represented by a config_file struct, which
22 **  contains the current parse state for that file, including the internal
23 **  buffer, a pointer to where in the buffer the next token starts, and the
24 **  current token.  Inclusion of additional files is handled by maintaining a
25 **  stack of config_file structs, so when one file is finished, the top struct
26 **  popped off the stack and parsing continues where it left off.
27 **
28 **  Since config_file structs contain the parse state, they're passed as an
29 **  argument to most functions.
30 **
31 **  A config_file struct contains a token struct, representing the current
32 **  token.  The configuration file syntax is specifically designed to never
33 **  require lookahead to parse; all parse decisions can be made on the basis
34 **  of the current state and a single token.  A token consists of a type and
35 **  an optional attached string.  Note that strings are allocated by the lexer
36 **  but are never freed by the lexer!  Any token with an associated string
37 **  should have that string copied into permanent storage (like the params
38 **  hash of a config_group) or freed.  error_unexpected_token will do the
39 **  latter.
40 **
41 **  Errors in the lexer are indicated by setting the token to TOKEN_ERROR.
42 **  All parsing errors are indicated by setting the error flag in the current
43 **  config_file struct.  Error recovery is *not* implemented by the current
44 **  algorithm; it would add a lot of complexity to the parsing algorithm and
45 **  the results still probably shouldn't be used by the calling program, so it
46 **  would only be useful to catch more than one syntax error per invocation
47 **  and it isn't expected that syntax errors will be that common.  Instead, if
48 **  something fails to parse, the whole parser unwinds and returns failure.
49 **
50 **  The config_param_* functions are used to retrieve the values of
51 **  parameters; each use a convert_* function to convert the raw parameter
52 **  value to the type specified by the user.  group_parameter_get can
53 **  therefore be the same for all parameter types, with all of the variations
54 **  encapsulated in the convert_* functions.
55 */
56 
57 #include "portable/system.h"
58 
59 #include <errno.h>
60 #include <fcntl.h>
61 
62 #include "inn/confparse.h"
63 #include "inn/hashtab.h"
64 #include "inn/libinn.h"
65 #include "inn/messages.h"
66 #include "inn/vector.h"
67 
68 
69 /* The types of tokens seen in configuration files. */
70 enum token_type
71 {
72     TOKEN_CRLF,
73     TOKEN_STRING,
74     TOKEN_QSTRING,
75     TOKEN_PARAM,
76     TOKEN_LBRACE,
77     TOKEN_RBRACE,
78     TOKEN_LANGLE,
79     TOKEN_RANGLE,
80     TOKEN_LBRACKET,
81     TOKEN_RBRACKET,
82     TOKEN_SEMICOLON,
83     TOKEN_EOF,
84     TOKEN_ERROR
85 };
86 
87 /* The parse status of a file.  Variables marked internal are only used by
88    file_* functions; other functions don't need to look at them.  Other
89    variables are marked by what functions are responsible for maintaining
90    them. */
91 struct config_file {
92     int fd;               /* Internal */
93     char *buffer;         /* Internal */
94     size_t bufsize;       /* Internal */
95     const char *filename; /* file_open */
96     unsigned int line;    /* token_newline and token_quoted_string */
97     bool error;           /* Everyone */
98 
99     /* Set by file_* and token_*.  current == NULL indicates we've not yet
100        read from the file. */
101     char *current;
102 
103     /* Normally set by token_*, but file_read and file_read_more may set token
104        to TOKEN_ERROR or TOKEN_EOF when those conditions are encountered.  In
105        that situation, they also return false. */
106     struct {
107         enum token_type type;
108         char *string;
109     } token;
110 };
111 
112 /* The types of parameters, used to distinguish the values of the union in the
113    config_parameter_s struct. */
114 enum value_type
115 {
116     VALUE_UNKNOWN,
117     VALUE_BOOL,
118     VALUE_NUMBER,
119     VALUE_UNUMBER,
120     VALUE_REAL,
121     VALUE_STRING,
122     VALUE_LIST,
123     VALUE_INVALID
124 };
125 
126 /* Each setting is represented by one of these structs, stored in the params
127    hash of a config group.  Since all of a config_group must be in the same
128    file (either group->file for regular groups or group->included for groups
129    whose definition is in an included file), we don't have to stash a file
130    name here for error reporting but can instead get that from the enclosing
131    group. */
132 struct config_parameter {
133     char *key;
134     char *raw_value;
135     unsigned int line; /* For error reporting. */
136     enum value_type type;
137     union {
138         bool boolean;
139         long signed_number;
140         unsigned long unsigned_number;
141         double real;
142         char *string;
143         struct vector *list;
144     } value;
145 };
146 
147 /* The type of a function that converts a raw parameter value to some other
148    data type, storing the result in its second argument and returning true on
149    success or false on failure. */
150 typedef bool (*convert_func)(struct config_parameter *, const char *, void *);
151 
152 /* The basic element of configuration data, a group of parameters.  This is
153    the only struct that is exposed to callers, and then only as an opaque
154    data structure. */
155 struct config_group {
156     char *type;
157     char *tag;
158     char *file;        /* File in which the group starts. */
159     unsigned int line; /* Line number where the group starts. */
160     char *included;    /* For group <file>, the included file. */
161     struct hash *params;
162 
163     struct config_group *parent;
164     struct config_group *child;
165     struct config_group *next;
166 };
167 
168 
169 /* Parameter handling, used by the hash table stored in a config_group. */
170 static const void *parameter_key(const void *p);
171 static bool parameter_equal(const void *k, const void *p);
172 static void parameter_free(void *p);
173 
174 /* Hash traversal function to collect parameters into a vector. */
175 static void parameter_collect(void *, void *);
176 
177 /* Group handling. */
178 static struct config_group *group_new(const char *file, unsigned int line,
179                                       char *type, char *tag);
180 static void group_free(struct config_group *);
181 static bool group_parameter_get(struct config_group *group, const char *key,
182                                 void *result, convert_func convert);
183 
184 /* Parameter type conversion functions.  All take the parameter, the file, and
185    a pointer to where the result can be placed. */
186 static bool convert_boolean(struct config_parameter *, const char *, void *);
187 static bool convert_signed_number(struct config_parameter *, const char *,
188                                   void *);
189 static bool convert_unsigned_number(struct config_parameter *, const char *,
190                                     void *);
191 static bool convert_real(struct config_parameter *, const char *, void *);
192 static bool convert_string(struct config_parameter *, const char *, void *);
193 static bool convert_list(struct config_parameter *, const char *, void *);
194 
195 /* File I/O.  Many other functions also manipulate config_file structs; see
196    the struct definition for notes on who's responsible for what. */
197 static struct config_file *file_open(const char *filename);
198 static bool file_read(struct config_file *);
199 static bool file_read_more(struct config_file *, ptrdiff_t offset);
200 static void file_close(struct config_file *);
201 
202 /* The basic lexer function.  The token is stashed in file; the return value
203    is just for convenience and duplicates that information. */
204 static enum token_type token_next(struct config_file *);
205 
206 /* Handler functions for specific types of tokens.  These should only be
207    called by token_next. */
208 static void token_simple(struct config_file *, enum token_type type);
209 static void token_newline(struct config_file *);
210 static void token_string(struct config_file *);
211 static void token_quoted_string(struct config_file *);
212 
213 /* Handles whitespace for the rest of the lexer. */
214 static bool token_skip_whitespace(struct config_file *);
215 
216 /* Handles comments for the rest of the lexer. */
217 static bool token_skip_comment(struct config_file *);
218 
219 /* Convert a quoted string to the string value it represents. */
220 static char *token_unquote_string(const char *, const char *file,
221                                   unsigned int line);
222 
223 /* Parser functions to parse the named syntactic element. */
224 static enum token_type parse_list(struct config_group *, struct config_file *,
225                                   char *key);
226 static enum token_type parse_parameter(struct config_group *,
227                                        struct config_file *, char *key);
228 static bool parse_group_contents(struct config_group *, struct config_file *);
229 static bool parse_include(struct config_group *, struct config_file *);
230 static bool parse_group(struct config_file *, struct config_group *parent);
231 
232 /* Error reporting functions. */
233 static void error_bad_unquoted_char(struct config_file *, char bad);
234 static void error_unexpected_token(struct config_file *,
235                                    const char *expecting);
236 
237 
238 /*
239 **  Return the key from a parameter struct, used by the hash table.
240 */
241 static const void *
parameter_key(const void * p)242 parameter_key(const void *p)
243 {
244     const struct config_parameter *param = p;
245 
246     return param->key;
247 }
248 
249 
250 /*
251 **  Check to see if a provided key matches the key of a parameter struct,
252 **  used by the hash table.
253 */
254 static bool
parameter_equal(const void * k,const void * p)255 parameter_equal(const void *k, const void *p)
256 {
257     const char *key = k;
258     const struct config_parameter *param = p;
259 
260     return strcmp(key, param->key) == 0;
261 }
262 
263 
264 /*
265 **  Free a parameter, used by the hash table.
266 */
267 static void
parameter_free(void * p)268 parameter_free(void *p)
269 {
270     struct config_parameter *param = p;
271 
272     free(param->key);
273     if (param->raw_value != NULL)
274         free(param->raw_value);
275     if (param->type == VALUE_STRING)
276         free(param->value.string);
277     else if (param->type == VALUE_LIST)
278         vector_free(param->value.list);
279     free(param);
280 }
281 
282 
283 /*
284 **  Report an unexpected character while parsing a regular string and set the
285 **  current token type to TOKEN_ERROR.
286 */
287 static void
error_bad_unquoted_char(struct config_file * file,char bad)288 error_bad_unquoted_char(struct config_file *file, char bad)
289 {
290     warn("%s:%u: invalid character '%c' in unquoted string", file->filename,
291          file->line, bad);
292     file->token.type = TOKEN_ERROR;
293     file->error = true;
294 }
295 
296 
297 /*
298 **  Report an unexpected token.  If the token is TOKEN_ERROR, don't print an
299 **  additional error message.  Takes a string saying what token was expected.
300 **  Sets the token to TOKEN_ERROR and frees the associated string if the
301 **  current token type is TOKEN_STRING, TOKEN_QSTRING, or TOKEN_PARAM.
302 */
303 static void
error_unexpected_token(struct config_file * file,const char * expecting)304 error_unexpected_token(struct config_file *file, const char *expecting)
305 {
306     const char *name;
307     bool string = false;
308 
309     /* If the bad token type is a string, param, or quoted string, free the
310        string associated with the token to avoid a memory leak. */
311     if (file->token.type != TOKEN_ERROR) {
312         switch (file->token.type) {
313         /* clang-format off */
314         case TOKEN_STRING:      name = "string";        string = true; break;
315         case TOKEN_QSTRING:     name = "quoted string"; string = true; break;
316         case TOKEN_PARAM:       name = "parameter";     string = true; break;
317         case TOKEN_CRLF:        name = "end of line";   break;
318         case TOKEN_LBRACE:      name = "'{'";           break;
319         case TOKEN_RBRACE:      name = "'}'";           break;
320         case TOKEN_LANGLE:      name = "'<'";           break;
321         case TOKEN_RANGLE:      name = "'>'";           break;
322         case TOKEN_LBRACKET:    name = "'['";           break;
323         case TOKEN_RBRACKET:    name = "']'";           break;
324         case TOKEN_SEMICOLON:   name = "';'";           break;
325         case TOKEN_EOF:         name = "end of file";   break;
326         default:                name = "unknown token"; break;
327         }
328         /* clang-format on */
329 
330         warn("%s:%u: parse error: saw %s, expecting %s", file->filename,
331              file->line, name, expecting);
332     }
333     if (string) {
334         free(file->token.string);
335         file->token.string = NULL;
336     }
337     file->token.type = TOKEN_ERROR;
338     file->error = true;
339 }
340 
341 
342 /*
343 **  Handle a simple token (a single character), advancing the file->current
344 **  pointer past it and setting file->token as appropriate.
345 */
346 static void
token_simple(struct config_file * file,enum token_type type)347 token_simple(struct config_file *file, enum token_type type)
348 {
349     file->current++;
350     file->token.type = type;
351     file->token.string = NULL;
352 }
353 
354 
355 /*
356 **  Handle a newline.  Skip any number of comments after the newline,
357 **  including reading more data from the file if necessary, and update
358 **  file->line as needed.
359 */
360 static void
token_newline(struct config_file * file)361 token_newline(struct config_file *file)
362 {
363     /* If we're actually positioned on a newline, update file->line and skip
364        over it.  Try to handle CRLF correctly, as a single line terminator
365        that only increments the line count once, while still treating either
366        CR or LF alone as line terminators in their own regard. */
367     if (*file->current == '\n') {
368         file->current++;
369         file->line++;
370     } else if (*file->current == '\r') {
371         if (file->current[1] == '\n')
372             file->current += 2;
373         else if (file->current[1] != '\0')
374             file->current++;
375         else {
376             if (!file_read(file)) {
377                 file->current++;
378                 return;
379             }
380             if (*file->current == '\n')
381                 file->current++;
382         }
383         file->line++;
384     }
385 
386     if (!token_skip_whitespace(file))
387         return;
388     while (*file->current == '#') {
389         if (!token_skip_comment(file))
390             return;
391         if (!token_skip_whitespace(file))
392             return;
393     }
394     file->token.type = TOKEN_CRLF;
395     file->token.string = NULL;
396 }
397 
398 
399 /*
400 **  Handle a string.  Only some characters are allowed in an unquoted string;
401 **  check that, since otherwise it could hide syntax errors.  Any whitespace
402 **  ends the token.  We have to distinguish between TOKEN_PARAM and
403 **  TOKEN_STRING; the former ends in a colon, unlike the latter.
404 */
405 static void
token_string(struct config_file * file)406 token_string(struct config_file *file)
407 {
408     int i;
409     bool status;
410     ptrdiff_t offset;
411     bool done = false;
412     bool colon = false;
413 
414     /* Use an offset from file->current rather than a pointer that moves
415        through the buffer, since the base of file->current can change during a
416        file_read_more() call and we don't want to have to readjust a
417        pointer.  If we have to read more, adjust our counter back one
418        character, since the nul was replaced by a new, valid character. */
419     i = 0;
420     while (!done) {
421         switch (file->current[i]) {
422         case '\t':
423         case '\r':
424         case '\n':
425         case ' ':
426         case ';':
427         case '>':
428         case '}':
429             done = true;
430             break;
431         case '"':
432         case '<':
433         case '[':
434         case '\\':
435         case ']':
436         case '{':
437             error_bad_unquoted_char(file, file->current[i]);
438             return;
439         case ':':
440             if (colon) {
441                 error_bad_unquoted_char(file, file->current[i]);
442                 return;
443             }
444             colon = true;
445             break;
446         case '\0':
447             offset = file->current - file->buffer;
448             status = file_read_more(file, offset);
449             if (status)
450                 i--;
451             else
452                 done = true;
453             break;
454         default:
455             if (colon) {
456                 error_bad_unquoted_char(file, ':');
457                 return;
458             }
459         }
460         if (!done)
461             i++;
462     }
463     file->token.type = colon ? TOKEN_PARAM : TOKEN_STRING;
464     file->token.string = xstrndup(file->current, i - colon);
465     file->current += i;
466 }
467 
468 
469 /*
470 **  Handle a quoted string.  This token is unique as the only token that can
471 **  contain whitespace, even newlines if they're escaped, so we also have to
472 **  update file->line as we go.  Note that the quotes *are* included in the
473 **  string we stash in file->token, since they should be part of the raw_value
474 **  of a parameter.
475 */
476 static void
token_quoted_string(struct config_file * file)477 token_quoted_string(struct config_file *file)
478 {
479     int i;
480     ptrdiff_t offset;
481     bool status;
482     bool done = false;
483 
484     /* Use an offset from file->current rather than a pointer that moves
485        through the buffer, since the base of file->current can change during a
486        file_read_more() call and we don't want to have to readjust a pointer.
487        If we have to read more, adjust our counter back one character, since
488        the nul was replaced by a new, valid character. */
489     for (i = 1; !done; i++) {
490         switch (file->current[i]) {
491         case '"':
492             done = true;
493             break;
494         case '\r':
495         case '\n':
496             warn("%s:%u: no close quote seen for quoted string",
497                  file->filename, file->line);
498             file->token.type = TOKEN_ERROR;
499             file->error = true;
500             return;
501         case '\\':
502             i++;
503             if (file->current[i] == '\n')
504                 file->line++;
505 
506             /* CRLF should count as one line terminator.  Handle most cases of
507                that here, but the case where CR is at the end of one buffer
508                and LF at the beginning of the next has to be handled in the \0
509                case below. */
510             if (file->current[i] == '\r') {
511                 file->line++;
512                 if (file->current[i + 1] == '\n')
513                     i++;
514             }
515             break;
516         case '\0':
517             offset = file->current - file->buffer;
518             status = file_read_more(file, offset);
519             if (status)
520                 i--;
521             else {
522                 warn("%s:%u: end of file encountered while parsing quoted"
523                      " string",
524                      file->filename, file->line);
525                 file->token.type = TOKEN_ERROR;
526                 file->error = true;
527                 return;
528             }
529 
530             /* If the last character of the previous buffer was CR and the
531                first character that we just read was LF, the CR must have been
532                escaped which means that the LF is part of it, forming a CRLF
533                line terminator.  Skip over the LF. */
534             if (file->current[i] == '\r' && file->current[i + 1] == '\n')
535                 i++;
536 
537             break;
538         default:
539             break;
540         }
541     }
542     file->token.type = TOKEN_QSTRING;
543     file->token.string = xstrndup(file->current, i);
544     file->current += i;
545 }
546 
547 
548 /*
549 **  Convert a quoted string to a string and returns the newly allocated string
550 **  or NULL on failure.  Takes the quoted string and the file and line number
551 **  for error reporting.
552 */
553 static char *
token_unquote_string(const char * raw,const char * file,unsigned int line)554 token_unquote_string(const char *raw, const char *file, unsigned int line)
555 {
556     size_t length;
557     char *string, *dest;
558     const char *src;
559 
560     length = strlen(raw) - 2;
561     string = xmalloc(length + 1);
562     src = raw + 1;
563     dest = string;
564     for (; *src != '"' && *src != '\0'; src++) {
565         if (*src != '\\') {
566             *dest++ = *src;
567         } else {
568             src++;
569 
570             /* This should implement precisely the semantics of backslash
571                escapes in quoted strings in C. */
572             /* clang-format off */
573             switch (*src) {
574             case 'a':   *dest++ = '\a'; break;
575             case 'b':   *dest++ = '\b'; break;
576             case 'f':   *dest++ = '\f'; break;
577             case 'n':   *dest++ = '\n'; break;
578             case 'r':   *dest++ = '\r'; break;
579             case 't':   *dest++ = '\t'; break;
580             case 'v':   *dest++ = '\v'; break;
581 
582             case '\n':  break;  /* Escaped newlines disappear. */
583 
584             case '\\':
585             case '\'':
586             case '"':
587             case '?':
588                 *dest++ = *src;
589                 break;
590 
591             case '\0':
592                 /* Should never happen; the tokenizer should catch this. */
593                 warn("%s:%u: unterminated string", file, line);
594                 goto fail;
595 
596             default:
597                 /* FIXME: \<octal>, \x, \u, and \U not yet implemented; the
598                    last three could use the same basic code.  Think about
599                    whether the escape should generate a single 8-bit character
600                    or a UTF-8 encoded character; maybe the first two generate
601                    the former and \u and \U generate the latter? */
602                 warn("%s:%u: unrecognized escape '\\%c'", file, line, *src);
603                 goto fail;
604             }
605             /* clang-format on */
606         }
607     }
608     *dest = '\0';
609 
610     /* Should never happen; the tokenizer should catch this. */
611     if (*src != '"') {
612         warn("%s:%u: unterminated string (no closing quote)", file, line);
613         goto fail;
614     }
615     return string;
616 
617 fail:
618     free(string);
619     return NULL;
620 }
621 
622 
623 /*
624 **  Skip over a comment line at file->current, reading more data as necessary.
625 **  Stop when an end of line is encountered, positioning file->current
626 **  directly after the end of line.  Returns false on end of file or a read
627 **  error, true otherwise.
628 */
629 static bool
token_skip_comment(struct config_file * file)630 token_skip_comment(struct config_file *file)
631 {
632     char *p = file->current;
633 
634     while (*p != '\0' && *p != '\n' && *p != '\r')
635         p++;
636     while (*p == '\0') {
637         if (!file_read(file))
638             return false;
639         p = file->current;
640         while (*p != '\0' && *p != '\n' && *p != '\r')
641             p++;
642     }
643 
644     /* CRLF should count as a single line terminator, but it may be split
645        across a read boundary.  Try to handle that case correctly. */
646     if (*p == '\n')
647         p++;
648     else if (*p == '\r') {
649         p++;
650         if (*p == '\n')
651             p++;
652         else if (*p == '\0') {
653             if (!file_read(file))
654                 return false;
655             p = file->current;
656             if (*p == '\n')
657                 p++;
658         }
659     }
660     file->current = p;
661     file->line++;
662     return true;
663 }
664 
665 /*
666 **  Skip over all whitespace at file->current, reading more data as
667 **  necessary.  Stop when the first non-whitespace character is encountered or
668 **  at end of file, leaving file->current pointing appropriately.  Returns
669 **  true if non-whitespace is found and false on end of file or a read error.
670 */
671 static bool
token_skip_whitespace(struct config_file * file)672 token_skip_whitespace(struct config_file *file)
673 {
674     char *p = file->current;
675 
676     while (*p == ' ' || *p == '\t')
677         p++;
678     while (*p == '\0') {
679         if (!file_read(file))
680             return false;
681         p = file->current;
682         while (*p == ' ' || *p == '\t')
683             p++;
684     }
685     file->current = p;
686     return true;
687 }
688 
689 
690 /*
691 **  The basic lexer function.  Read the next token from a configuration file.
692 **  Returns the token, which is also stored in file.  Lexer failures set the
693 **  token to TOKEN_ERROR.
694 */
695 static enum token_type
token_next(struct config_file * file)696 token_next(struct config_file *file)
697 {
698     /* If file->current is NULL, we've never read from the file.  There is
699        special handling for a comment at the very beginning of a file, since
700        normally we only look for comments after newline tokens.
701 
702        If we do see a # at the beginning of the first line, let token_newline
703        deal with it.  That function can cope with file->current not pointing
704        at a newline.  We then return the newline token as the first token in
705        the file. */
706     if (file->current == NULL) {
707         if (!file_read(file))
708             return file->token.type;
709         if (!token_skip_whitespace(file))
710             return file->token.type;
711         if (*file->current == '#') {
712             token_newline(file);
713             return file->token.type;
714         }
715     } else {
716         if (!token_skip_whitespace(file))
717             return file->token.type;
718     }
719 
720     /* Almost all of our tokens can be recognized by the first character; the
721        only exception is telling strings from parameters.  token_string
722        handles both of those and sets file->token.type appropriately.
723        Comments are handled by token_newline. */
724     /* clang-format off */
725     switch (*file->current) {
726     case '{':   token_simple(file, TOKEN_LBRACE);       break;
727     case '}':   token_simple(file, TOKEN_RBRACE);       break;
728     case '<':   token_simple(file, TOKEN_LANGLE);       break;
729     case '>':   token_simple(file, TOKEN_RANGLE);       break;
730     case '[':   token_simple(file, TOKEN_LBRACKET);     break;
731     case ']':   token_simple(file, TOKEN_RBRACKET);     break;
732     case ';':   token_simple(file, TOKEN_SEMICOLON);    break;
733     case '\r':  token_newline(file);                    break;
734     case '\n':  token_newline(file);                    break;
735     case '"':   token_quoted_string(file);              break;
736     default:    token_string(file);                     break;
737     }
738     /* clang-format on */
739 
740     return file->token.type;
741 }
742 
743 
744 /*
745 **  Open a new configuration file and return config_file representing the
746 **  parse state of that file.  We assume that we don't have to make a copy of
747 **  the filename argument.  Default to stdio BUFSIZ for our buffer size, since
748 **  it's generally reasonably chosen with respect to disk block sizes, memory
749 **  consumption, and the like.
750 */
751 static struct config_file *
file_open(const char * filename)752 file_open(const char *filename)
753 {
754     struct config_file *file;
755     int oerrno;
756 
757     file = xmalloc(sizeof(*file));
758     file->filename = filename;
759     file->fd = open(filename, O_RDONLY);
760     if (file->fd < 0) {
761         oerrno = errno;
762         free(file);
763         errno = oerrno;
764         return NULL;
765     }
766     file->buffer = xmalloc(BUFSIZ);
767     file->bufsize = BUFSIZ;
768     file->current = NULL;
769     file->line = 1;
770     file->token.type = TOKEN_ERROR;
771     file->error = false;
772     return file;
773 }
774 
775 
776 /*
777 **  Read some data from a configuration file, handling errors (by reporting
778 **  them with warn) and returning true if there's data left and false on EOF
779 **  or a read error.
780 */
781 static bool
file_read(struct config_file * file)782 file_read(struct config_file *file)
783 {
784     ssize_t status;
785 
786     status = read(file->fd, file->buffer, file->bufsize - 1);
787     if (status < 0) {
788         syswarn("%s: read error", file->filename);
789         file->token.type = TOKEN_ERROR;
790         file->error = true;
791     } else if (status == 0) {
792         file->token.type = TOKEN_EOF;
793     }
794     if (status <= 0)
795         return false;
796     file->buffer[status] = '\0';
797     file->current = file->buffer;
798 
799     /* Reject nuls, since otherwise they would cause strange problems. */
800     if (strlen(file->buffer) != (size_t) status) {
801         warn("%s: invalid NUL character found in file", file->filename);
802         return false;
803     }
804     return true;
805 }
806 
807 
808 /*
809 **  Read additional data from a configuration file when there's some partial
810 **  data in the buffer already that we want to save.  Takes the config_file
811 **  struct and an offset from file->buffer specifying the start of the data
812 **  that we want to preserve.  Resizes the buffer if offset is 0.  Returns
813 **  false on EOF or a read error, true otherwise.
814 */
815 static bool
file_read_more(struct config_file * file,ptrdiff_t offset)816 file_read_more(struct config_file *file, ptrdiff_t offset)
817 {
818     char *start;
819     size_t amount;
820     ssize_t status;
821 
822     if (offset > 0) {
823         size_t left;
824 
825         left = file->bufsize - offset - 1;
826         memmove(file->buffer, file->buffer + offset, left);
827         file->current -= offset;
828         start = file->buffer + left;
829         amount = offset;
830     } else {
831         file->buffer = xrealloc(file->buffer, file->bufsize + BUFSIZ);
832         file->current = file->buffer;
833         start = file->buffer + file->bufsize - 1;
834         amount = BUFSIZ;
835         file->bufsize += BUFSIZ;
836     }
837     status = read(file->fd, start, amount);
838     if (status < 0)
839         syswarn("%s: read error", file->filename);
840     if (status <= 0)
841         return false;
842     start[status] = '\0';
843 
844     /* Reject nuls, since otherwise they would cause strange problems. */
845     if (strlen(start) != (size_t) status) {
846         warn("%s: invalid NUL character found in file", file->filename);
847         return false;
848     }
849     return true;
850 }
851 
852 
853 /*
854 **  Close a file and free the resources associated with it.
855 */
856 static void
file_close(struct config_file * file)857 file_close(struct config_file *file)
858 {
859     close(file->fd);
860     free(file->buffer);
861     free(file);
862 }
863 
864 
865 /*
866 **  Parse a vector.  Takes the group that we're currently inside, the
867 **  config_file parse state, and the key of the parameter.  Returns the next
868 **  token after the vector.
869 */
870 static enum token_type
parse_list(struct config_group * group,struct config_file * file,char * key)871 parse_list(struct config_group *group, struct config_file *file, char *key)
872 {
873     enum token_type token;
874     struct vector *vector;
875     struct config_parameter *param;
876     char *string;
877 
878     vector = vector_new();
879     token = token_next(file);
880     while (token != TOKEN_RBRACKET) {
881         if (token == TOKEN_CRLF) {
882             token = token_next(file);
883             continue;
884         }
885         if (token != TOKEN_STRING && token != TOKEN_QSTRING)
886             break;
887         vector_resize(vector, vector->allocated + 1);
888         if (token == TOKEN_QSTRING) {
889             string = token_unquote_string(file->token.string, file->filename,
890                                           file->line);
891             free(file->token.string);
892             if (string == NULL) {
893                 vector_free(vector);
894                 free(key);
895                 return TOKEN_ERROR;
896             }
897             vector->strings[vector->count] = string;
898         } else {
899             vector->strings[vector->count] = file->token.string;
900         }
901         vector->count++;
902         token = token_next(file);
903     }
904     if (token == TOKEN_RBRACKET) {
905         param = xmalloc(sizeof(*param));
906         param->key = key;
907         param->raw_value = NULL;
908         param->type = VALUE_LIST;
909         param->value.list = vector;
910         param->line = file->line;
911         if (!hash_insert(group->params, key, param)) {
912             warn("%s:%u: duplicate parameter %s", file->filename, file->line,
913                  key);
914             vector_free(vector);
915             free(param->key);
916             free(param);
917         }
918         token = token_next(file);
919         return token;
920     } else {
921         error_unexpected_token(file, "string or ']'");
922         vector_free(vector);
923         free(key);
924         return TOKEN_ERROR;
925     }
926 }
927 
928 
929 /*
930 **  Parse a parameter.  Takes the group we're currently inside, the
931 **  config_file parse state, and the key of the parameter.  Returns the next
932 **  token after the parameter, and also checks to make sure that it's
933 **  something legal (end of line, end of file, or a semicolon).
934 */
935 static enum token_type
parse_parameter(struct config_group * group,struct config_file * file,char * key)936 parse_parameter(struct config_group *group, struct config_file *file,
937                 char *key)
938 {
939     enum token_type token;
940 
941     token = token_next(file);
942     if (token == TOKEN_LBRACKET) {
943         return parse_list(group, file, key);
944     } else if (token == TOKEN_STRING || token == TOKEN_QSTRING) {
945         struct config_parameter *param;
946         unsigned int line;
947         char *value;
948 
949         /* Before storing the parameter, check to make sure that the next
950            token is valid.  If it isn't, chances are high that the user has
951            tried to set a parameter to a value containing spaces without
952            quoting the value. */
953         value = file->token.string;
954         line = file->line;
955         token = token_next(file);
956         switch (token) {
957         default:
958             error_unexpected_token(file, "semicolon or newline");
959             free(value);
960             break;
961         case TOKEN_CRLF:
962         case TOKEN_SEMICOLON:
963         case TOKEN_EOF:
964         case TOKEN_RBRACE:
965             param = xmalloc(sizeof(*param));
966             param->key = key;
967             param->raw_value = value;
968             param->type = VALUE_UNKNOWN;
969             param->line = line;
970             if (!hash_insert(group->params, key, param)) {
971                 warn("%s:%u: duplicate parameter %s", file->filename, line,
972                      key);
973                 free(param->raw_value);
974                 free(param->key);
975                 free(param);
976             }
977             return token;
978         }
979     } else {
980         error_unexpected_token(file, "parameter value");
981     }
982 
983     /* If we fell through, we encountered some sort of error.  Free allocated
984        memory and return an error token. */
985     free(key);
986     return TOKEN_ERROR;
987 }
988 
989 
990 /*
991 **  Given a config_group with the type and tag already filled in and a
992 **  config_file with the buffer positioned after the opening brace of the
993 **  group, read and add parameters to the group until encountering a close
994 **  brace.  Returns true on a successful parse, false on an error that
995 **  indicates the group should be discarded.
996 */
997 static bool
parse_group_contents(struct config_group * group,struct config_file * file)998 parse_group_contents(struct config_group *group, struct config_file *file)
999 {
1000     enum token_type token;
1001     bool semicolon = false;
1002 
1003     token = token_next(file);
1004     while (!file->error) {
1005         switch (token) {
1006         case TOKEN_PARAM:
1007             if (group->child != NULL) {
1008                 warn("%s:%u: parameter specified after nested group",
1009                      file->filename, file->line);
1010                 free(file->token.string);
1011                 return false;
1012             }
1013             token = parse_parameter(group, file, file->token.string);
1014             while (token == TOKEN_CRLF || token == TOKEN_SEMICOLON) {
1015                 semicolon = (token == TOKEN_SEMICOLON);
1016                 token = token_next(file);
1017             }
1018             break;
1019         case TOKEN_CRLF:
1020             token = token_next(file);
1021             break;
1022         case TOKEN_STRING:
1023             if (semicolon) {
1024                 error_unexpected_token(file, "parameter");
1025                 break;
1026             }
1027             if (!parse_group(file, group))
1028                 return false;
1029             token = token_next(file);
1030             break;
1031         case TOKEN_RBRACE:
1032         case TOKEN_EOF:
1033             return true;
1034         case TOKEN_ERROR:
1035             return false;
1036         default:
1037             error_unexpected_token(file, "parameter");
1038             break;
1039         }
1040     }
1041     return false;
1042 }
1043 
1044 
1045 /*
1046 **  Given a newly allocated group and with config_file positioned on the left
1047 **  angle bracket, parse an included group.  Return true on a successful
1048 **  parse, false on an error that indicates the group should be discarded.
1049 */
1050 static bool
parse_include(struct config_group * group,struct config_file * file)1051 parse_include(struct config_group *group, struct config_file *file)
1052 {
1053     char *filename, *path, *slash;
1054     enum token_type token;
1055     bool status;
1056     struct config_group *parent;
1057     int levels;
1058     struct config_file *included;
1059 
1060     /* Make sure that the syntax is fine. */
1061     token = token_next(file);
1062     if (token != TOKEN_STRING) {
1063         error_unexpected_token(file, "file name");
1064         return false;
1065     }
1066     filename = file->token.string;
1067     token = token_next(file);
1068     if (token != TOKEN_RANGLE) {
1069         error_unexpected_token(file, "'>'");
1070         return false;
1071     }
1072 
1073     /* Build the file name that we want to include.  If the file name is
1074        relative, it's relative to the file of its enclosing group line. */
1075     if (*filename == '/')
1076         path = filename;
1077     else {
1078         slash = strrchr(group->file, '/');
1079         if (slash == NULL)
1080             path = filename;
1081         else {
1082             *slash = '\0';
1083             path = concat(group->file, "/", filename, (char *) 0);
1084             *slash = '/';
1085             free(filename);
1086         }
1087     }
1088 
1089     /* Make sure we're not including ourselves recursively and put an
1090        arbitrary limit of 20 levels in case of something like a recursive
1091        symlink. */
1092     levels = 1;
1093     for (parent = group; parent != NULL; parent = parent->parent) {
1094         if (strcmp(path, parent->file) == 0) {
1095             warn("%s:%u: file %s recursively included", group->file,
1096                  group->line, path);
1097             goto fail;
1098         }
1099         if (levels > 20) {
1100             warn("%s:%u: file inclusions limited to 20 deep", group->file,
1101                  group->line);
1102             goto fail;
1103         }
1104         levels++;
1105     }
1106 
1107     /* Now, attempt to include the file. */
1108     included = file_open(path);
1109     if (included == NULL) {
1110         syswarn("%s:%u: open of %s failed", group->file, group->line, path);
1111         goto fail;
1112     }
1113     group->included = path;
1114     status = parse_group_contents(group, included);
1115     file_close(included);
1116     return status;
1117 
1118 fail:
1119     free(path);
1120     return false;
1121 }
1122 
1123 
1124 /*
1125 **  With config_file positioned at the beginning of the group declaration,
1126 **  Parse a group declaration and its nested contents.  Return true on a
1127 **  successful parse, false on an error that indicates the group should be
1128 **  discarded.
1129 */
1130 static bool
parse_group(struct config_file * file,struct config_group * parent)1131 parse_group(struct config_file *file, struct config_group *parent)
1132 {
1133     char *type, *tag;
1134     const char *expected;
1135     struct config_group *group, *last;
1136     enum token_type token;
1137     bool status;
1138 
1139     tag = NULL;
1140     type = file->token.string;
1141     token = token_next(file);
1142     if (token == TOKEN_STRING) {
1143         tag = file->token.string;
1144         token = token_next(file);
1145     }
1146     if (token != TOKEN_LBRACE && token != TOKEN_LANGLE) {
1147         free(type);
1148         expected = tag != NULL ? "'{' or '<'" : "group tag, '{', or '<'";
1149         if (tag != NULL)
1150             free(tag);
1151         error_unexpected_token(file, expected);
1152         return false;
1153     }
1154     group = group_new(file->filename, file->line, type, tag);
1155     group->parent = parent;
1156     if (parent->child == NULL)
1157         parent->child = group;
1158     else {
1159         for (last = parent->child; last->next != NULL; last = last->next)
1160             ;
1161         last->next = group;
1162     }
1163     if (token == TOKEN_LANGLE)
1164         status = parse_include(group, file);
1165     else
1166         status = parse_group_contents(group, file);
1167     if (!status)
1168         return false;
1169     if (token != TOKEN_LANGLE && file->token.type != TOKEN_RBRACE) {
1170         error_unexpected_token(file, "'}'");
1171         return false;
1172     }
1173     return true;
1174 }
1175 
1176 
1177 /*
1178 **  Allocate a new config_group and set the initial values of all of the
1179 **  struct members.  The group type and tag should be allocated memory that
1180 **  can later be freed, although the tag may be NULL.
1181 */
1182 static struct config_group *
group_new(const char * file,unsigned int line,char * type,char * tag)1183 group_new(const char *file, unsigned int line, char *type, char *tag)
1184 {
1185     struct config_group *group;
1186 
1187     group = xmalloc(sizeof(*group));
1188     group->type = type;
1189     group->tag = tag;
1190     group->file = xstrdup(file);
1191     group->included = NULL;
1192     group->line = line;
1193     group->params = hash_create(4, hash_string, parameter_key, parameter_equal,
1194                                 parameter_free);
1195     group->parent = NULL;
1196     group->child = NULL;
1197     group->next = NULL;
1198     return group;
1199 }
1200 
1201 
1202 /*
1203 **  Free a config_group and all associated storage.
1204 */
1205 static void
group_free(struct config_group * group)1206 group_free(struct config_group *group)
1207 {
1208     free(group->type);
1209     if (group->tag != NULL)
1210         free(group->tag);
1211     free(group->file);
1212     if (group->included != NULL)
1213         free(group->included);
1214     hash_free(group->params);
1215     free(group);
1216 }
1217 
1218 
1219 /*
1220 **  Accessor function for the group type.
1221 */
1222 const char *
config_group_type(struct config_group * group)1223 config_group_type(struct config_group *group)
1224 {
1225     return group->type;
1226 }
1227 
1228 
1229 /*
1230 **  Accessor function for the group tag.
1231 */
1232 const char *
config_group_tag(struct config_group * group)1233 config_group_tag(struct config_group *group)
1234 {
1235     return group->tag;
1236 }
1237 
1238 
1239 /*
1240 **  Parse a configuration file, returning the config_group that's the root of
1241 **  the tree represented by that file (and any other files that it includes).
1242 **  Returns NULL on a parse failure.
1243 */
1244 struct config_group *
config_parse_file(const char * filename,...)1245 config_parse_file(const char *filename, ...)
1246 {
1247     struct config_group *group;
1248     struct config_file *file;
1249     bool success;
1250 
1251     file = file_open(filename);
1252     if (file == NULL) {
1253         syswarn("open of %s failed", filename);
1254         return NULL;
1255     }
1256     group = group_new(filename, 1, xstrdup("GLOBAL"), NULL);
1257     success = parse_group_contents(group, file);
1258     file_close(file);
1259     if (success)
1260         return group;
1261     else {
1262         config_free(group);
1263         return NULL;
1264     }
1265 }
1266 
1267 
1268 /*
1269 **  Given a config_group representing the root of a configuration structure,
1270 **  recursively free the entire structure.
1271 */
1272 void
config_free(struct config_group * group)1273 config_free(struct config_group *group)
1274 {
1275     struct config_group *child, *last;
1276 
1277     child = group->child;
1278     while (child != NULL) {
1279         last = child;
1280         child = child->next;
1281         config_free(last);
1282     }
1283     group_free(group);
1284 }
1285 
1286 
1287 /*
1288 **  Convert a given parameter value to a boolean, returning true if successful
1289 **  and false otherwise.
1290 */
1291 static bool
convert_boolean(struct config_parameter * param,const char * file,void * result)1292 convert_boolean(struct config_parameter *param, const char *file, void *result)
1293 {
1294     static const char *const truevals[] = {"yes", "on", "true", NULL};
1295     static const char *const falsevals[] = {"no", "off", "false", NULL};
1296     bool *value = result;
1297     int i;
1298 
1299     if (param->type == VALUE_BOOL) {
1300         *value = param->value.boolean;
1301         return true;
1302     } else if (param->type != VALUE_UNKNOWN) {
1303         warn("%s:%u: %s is not a boolean", file, param->line, param->key);
1304         return false;
1305     }
1306     param->type = VALUE_BOOL;
1307     for (i = 0; truevals[i] != NULL; i++)
1308         if (strcmp(param->raw_value, truevals[i]) == 0) {
1309             param->value.boolean = true;
1310             *value = true;
1311             return true;
1312         }
1313     for (i = 0; falsevals[i] != NULL; i++)
1314         if (strcmp(param->raw_value, falsevals[i]) == 0) {
1315             param->value.boolean = false;
1316             *value = false;
1317             return true;
1318         }
1319     param->type = VALUE_INVALID;
1320     warn("%s:%u: %s is not a boolean", file, param->line, param->key);
1321     return false;
1322 }
1323 
1324 
1325 /*
1326 **  Convert a given parameter value to an integer, returning true if
1327 **  successful and false otherwise.
1328 */
1329 static bool
convert_signed_number(struct config_parameter * param,const char * file,void * result)1330 convert_signed_number(struct config_parameter *param, const char *file,
1331                       void *result)
1332 {
1333     long *value = result;
1334     char *p;
1335 
1336     if (param->type == VALUE_NUMBER) {
1337         *value = param->value.signed_number;
1338         return true;
1339     } else if (param->type != VALUE_UNKNOWN) {
1340         warn("%s:%u: %s is not an integer", file, param->line, param->key);
1341         return false;
1342     }
1343 
1344     /* Do a syntax check even though strtol would do some of this for us,
1345        since otherwise some syntax errors may go silently undetected. */
1346     p = param->raw_value;
1347     if (*p == '-')
1348         p++;
1349     for (; *p != '\0'; p++)
1350         if (*p < '0' || *p > '9')
1351             break;
1352     if (*p != '\0') {
1353         warn("%s:%u: %s is not an integer", file, param->line, param->key);
1354         return false;
1355     }
1356 
1357     /* Do the actual conversion with strtol. */
1358     errno = 0;
1359     param->value.signed_number = strtol(param->raw_value, NULL, 10);
1360     if (errno != 0) {
1361         warn("%s:%u: %s doesn't convert to an integer", file, param->line,
1362              param->key);
1363         return false;
1364     }
1365     *value = param->value.signed_number;
1366     param->type = VALUE_NUMBER;
1367     return true;
1368 }
1369 
1370 
1371 /*
1372 **  Convert a given parameter value to an unsigned integer, returning true
1373 **  if successful and false otherwise.
1374 */
1375 static bool
convert_unsigned_number(struct config_parameter * param,const char * file,void * result)1376 convert_unsigned_number(struct config_parameter *param, const char *file,
1377                         void *result)
1378 {
1379     unsigned long *value = result;
1380     char *p;
1381 
1382     if (param->type == VALUE_UNUMBER) {
1383         *value = param->value.unsigned_number;
1384         return true;
1385     } else if (param->type != VALUE_UNKNOWN) {
1386         warn("%s:%u: %s is not an integer", file, param->line, param->key);
1387         return false;
1388     }
1389 
1390     /* Do a syntax check even though strtoul would do some of this for us,
1391      * since otherwise some syntax errors may go silently undetected. */
1392     p = param->raw_value;
1393     if (*p == '-') {
1394         warn("%s:%u: %s is not a positive integer", file, param->line,
1395              param->key);
1396         return false;
1397     }
1398     for (; *p != '\0'; p++)
1399         if (*p < '0' || *p > '9')
1400             break;
1401     if (*p != '\0') {
1402         warn("%s:%u: %s is not an integer", file, param->line, param->key);
1403         return false;
1404     }
1405 
1406     /* Do the actual conversion with strtoul. */
1407     errno = 0;
1408     param->value.unsigned_number = strtoul(param->raw_value, NULL, 10);
1409     if (errno != 0) {
1410         warn("%s:%u: %s doesn't convert to a positive integer", file,
1411              param->line, param->key);
1412         return false;
1413     }
1414     *value = param->value.unsigned_number;
1415     param->type = VALUE_UNUMBER;
1416     return true;
1417 }
1418 
1419 
1420 /*
1421 **  Convert a given parameter value to a real number, returning true if
1422 **  successful and false otherwise.
1423 */
1424 static bool
convert_real(struct config_parameter * param,const char * file,void * result)1425 convert_real(struct config_parameter *param, const char *file, void *result)
1426 {
1427     double *value = result;
1428     char *p;
1429 
1430     if (param->type == VALUE_REAL) {
1431         *value = param->value.real;
1432         return true;
1433     } else if (param->type != VALUE_UNKNOWN) {
1434         warn("%s:%u: %s is not a real number", file, param->line, param->key);
1435         return false;
1436     }
1437 
1438     /* Do a syntax check even though strtod would do some of this for us,
1439        since otherwise some syntax errors may go silently undetected.  We have
1440        a somewhat stricter syntax. */
1441     p = param->raw_value;
1442     if (*p == '-')
1443         p++;
1444     if (*p < '0' || *p > '9')
1445         goto fail;
1446     while (*p != '\0' && *p >= '0' && *p <= '9')
1447         p++;
1448     if (*p == '.') {
1449         p++;
1450         if (*p < '0' || *p > '9')
1451             goto fail;
1452         while (*p != '\0' && *p >= '0' && *p <= '9')
1453             p++;
1454     }
1455     if (*p == 'e') {
1456         p++;
1457         if (*p == '-')
1458             p++;
1459         if (*p < '0' || *p > '9')
1460             goto fail;
1461         while (*p != '\0' && *p >= '0' && *p <= '9')
1462             p++;
1463     }
1464     if (*p != '\0')
1465         goto fail;
1466 
1467     /* Do the actual conversion with strtod. */
1468     errno = 0;
1469     param->value.real = strtod(param->raw_value, NULL);
1470     if (errno != 0) {
1471         warn("%s:%u: %s doesn't convert to a real number", file, param->line,
1472              param->key);
1473         return false;
1474     }
1475     *value = param->value.real;
1476     param->type = VALUE_REAL;
1477     return true;
1478 
1479 fail:
1480     warn("%s:%u: %s is not a real number", file, param->line, param->key);
1481     return false;
1482 }
1483 
1484 
1485 /*
1486 **  Convert a given parameter value to a string, returning true if successful
1487 **  and false otherwise.
1488 */
1489 static bool
convert_string(struct config_parameter * param,const char * file,void * result)1490 convert_string(struct config_parameter *param, const char *file, void *result)
1491 {
1492     const char **value = result;
1493     char *string;
1494 
1495     if (param->type == VALUE_STRING) {
1496         *value = param->value.string;
1497         return true;
1498     } else if (param->type != VALUE_UNKNOWN) {
1499         warn("%s:%u: %s is not a string", file, param->line, param->key);
1500         return false;
1501     }
1502 
1503     if (*param->raw_value == '"')
1504         string = token_unquote_string(param->raw_value, file, param->line);
1505     else
1506         string = xstrdup(param->raw_value);
1507     if (string == NULL)
1508         return false;
1509     param->value.string = string;
1510     param->type = VALUE_STRING;
1511     *value = param->value.string;
1512     return true;
1513 }
1514 
1515 
1516 /*
1517 **  Convert a given parameter value to a list, returning true if succcessful
1518 **  and false otherwise.
1519 */
1520 static bool
convert_list(struct config_parameter * param,const char * file,void * result)1521 convert_list(struct config_parameter *param, const char *file, void *result)
1522 {
1523     const struct vector **value = result;
1524     struct vector *vector;
1525     char *string;
1526 
1527     if (param->type == VALUE_LIST) {
1528         *value = param->value.list;
1529         return true;
1530     } else if (param->type != VALUE_UNKNOWN) {
1531         warn("%s:%u: %s is not a list", file, param->line, param->key);
1532         return false;
1533     }
1534 
1535     /* If the value type is unknown, the value was actually a string.  We
1536        support returning string values as lists with one element, since that
1537        way config_param_list can be used to read any value. */
1538     if (*param->raw_value == '"') {
1539         string = token_unquote_string(param->raw_value, file, param->line);
1540         if (string == NULL)
1541             return false;
1542         vector = vector_new();
1543         vector_resize(vector, 1);
1544         vector->strings[0] = string;
1545         vector->count++;
1546     } else {
1547         vector = vector_new();
1548         vector_add(vector, param->raw_value);
1549     }
1550     param->type = VALUE_LIST;
1551     param->value.list = vector;
1552     *value = vector;
1553     return true;
1554 }
1555 
1556 
1557 /*
1558 **  Given a group, query it for the given parameter and then when the
1559 **  parameter is found, check to see if it's already marked invalid.  If so,
1560 **  fail quietly; otherwise, hand it off to the conversion function to do
1561 **  type-specific work, returning the result.  Returns true if the parameter
1562 **  is found in the group or one of its parents and convert can successfully
1563 **  convert the raw value and put it in result, false otherwise (either for
1564 **  the parameter not being found or for it being the wrong type).
1565 */
1566 static bool
group_parameter_get(struct config_group * group,const char * key,void * result,convert_func convert)1567 group_parameter_get(struct config_group *group, const char *key, void *result,
1568                     convert_func convert)
1569 {
1570     struct config_group *current = group;
1571 
1572     while (current != NULL) {
1573         struct config_parameter *param;
1574 
1575         param = hash_lookup(current->params, key);
1576         if (param != NULL) {
1577             if (param->type == VALUE_INVALID)
1578                 return false;
1579             else
1580                 return (*convert)(param, current->file, result);
1581         }
1582         current = current->parent;
1583     }
1584     return false;
1585 }
1586 
1587 
1588 /*
1589 **  All of the config_param_* functions do the following:
1590 **
1591 **  Given a group, query it for the given parameter, interpreting its value as
1592 **  the appropriate type and returning it in the third argument.  Returns true
1593 **  on success, false on failure (such as the parameter not being set or an
1594 **  error), and report errors via warn.
1595 */
1596 bool
config_param_boolean(struct config_group * group,const char * key,bool * result)1597 config_param_boolean(struct config_group *group, const char *key, bool *result)
1598 {
1599     return group_parameter_get(group, key, result, convert_boolean);
1600 }
1601 
1602 bool
config_param_signed_number(struct config_group * group,const char * key,long * result)1603 config_param_signed_number(struct config_group *group, const char *key,
1604                            long *result)
1605 {
1606     return group_parameter_get(group, key, result, convert_signed_number);
1607 }
1608 
1609 bool
config_param_unsigned_number(struct config_group * group,const char * key,unsigned long * result)1610 config_param_unsigned_number(struct config_group *group, const char *key,
1611                              unsigned long *result)
1612 {
1613     return group_parameter_get(group, key, result, convert_unsigned_number);
1614 }
1615 
1616 bool
config_param_real(struct config_group * group,const char * key,double * result)1617 config_param_real(struct config_group *group, const char *key, double *result)
1618 {
1619     return group_parameter_get(group, key, result, convert_real);
1620 }
1621 
1622 bool
config_param_string(struct config_group * group,const char * key,const char ** result)1623 config_param_string(struct config_group *group, const char *key,
1624                     const char **result)
1625 {
1626     return group_parameter_get(group, key, result, convert_string);
1627 }
1628 
1629 bool
config_param_list(struct config_group * group,const char * key,const struct vector ** result)1630 config_param_list(struct config_group *group, const char *key,
1631                   const struct vector **result)
1632 {
1633     return group_parameter_get(group, key, result, convert_list);
1634 }
1635 
1636 
1637 /*
1638 **  A hash traversal function to add all parameter keys to the vector provided
1639 **  as the second argument.  Currently this does a simple linear search to see
1640 **  if this parameter was already set, which makes the config_params function
1641 **  overall O(n^2).  So far, this isn't a problem.
1642 */
1643 static void
parameter_collect(void * element,void * cookie)1644 parameter_collect(void *element, void *cookie)
1645 {
1646     struct config_parameter *param = element;
1647     struct vector *params = cookie;
1648     size_t i;
1649 
1650     for (i = 0; i < params->count; i++)
1651         if (strcmp(params->strings[i], param->key) == 0)
1652             return;
1653     vector_add(params, param->key);
1654 }
1655 
1656 
1657 /*
1658 **  Returns a newly allocated vector of all of the config parameters in a
1659 **  group, including the inherited ones (not implemented yet).
1660 */
1661 struct vector *
config_params(struct config_group * group)1662 config_params(struct config_group *group)
1663 {
1664     struct vector *params;
1665     size_t size;
1666 
1667     params = vector_new();
1668     for (; group != NULL; group = group->parent) {
1669         size = hash_count(group->params);
1670         vector_resize(params, params->allocated + size);
1671         hash_traverse(group->params, parameter_collect, params);
1672     }
1673     return params;
1674 }
1675 
1676 
1677 /*
1678 **  Given a config_group and a group type, find the next group in a
1679 **  depth-first traversal of the configuration tree of the given type and
1680 **  return it.  Returns NULL if no further groups of that type are found.
1681 */
1682 struct config_group *
config_find_group(struct config_group * group,const char * type)1683 config_find_group(struct config_group *group, const char *type)
1684 {
1685     struct config_group *sib;
1686 
1687     if (group->child != NULL) {
1688         if (strcmp(group->child->type, type) == 0)
1689             return group->child;
1690         else
1691             return config_find_group(group->child, type);
1692     }
1693     for (; group != NULL; group = group->parent)
1694         for (sib = group->next; sib != NULL; sib = sib->next) {
1695             if (strcmp(sib->type, type) == 0)
1696                 return sib;
1697             if (sib->child != NULL) {
1698                 if (strcmp(sib->child->type, type) == 0)
1699                     return sib->child;
1700                 else
1701                     return config_find_group(sib->child, type);
1702             }
1703         }
1704     return NULL;
1705 }
1706 
1707 
1708 /*
1709 **  Find the next group with the same type as the current group.  This is just
1710 **  a simple wrapper around config_find_group for convenience.
1711 */
1712 struct config_group *
config_next_group(struct config_group * group)1713 config_next_group(struct config_group *group)
1714 {
1715     return config_find_group(group, group->type);
1716 }
1717 
1718 
1719 /*
1720 **  Report an error in a given parameter.  Used so that the file and line
1721 **  number can be included in the error message.
1722 */
1723 void
config_error_param(struct config_group * group,const char * key,const char * fmt,...)1724 config_error_param(struct config_group *group, const char *key,
1725                    const char *fmt, ...)
1726 {
1727     va_list args;
1728     char *message, *file;
1729     struct config_parameter *param;
1730 
1731     va_start(args, fmt);
1732     xvasprintf(&message, fmt, args);
1733     va_end(args);
1734 
1735     param = hash_lookup(group->params, key);
1736     if (param == NULL)
1737         warn("%s", message);
1738     else {
1739         file = (group->included != NULL ? group->included : group->file);
1740         warn("%s:%u: %s", file, param->line, message);
1741     }
1742 
1743     free(message);
1744 }
1745 
1746 
1747 /*
1748 **  Report an error in a given group.  Used so that the file and line number
1749 **  can be included in the error message.  Largely duplicates
1750 **  config_error_param (which we could fix if we were sure we had va_copy).
1751 */
1752 void
config_error_group(struct config_group * group,const char * fmt,...)1753 config_error_group(struct config_group *group, const char *fmt, ...)
1754 {
1755     va_list args;
1756     char *message;
1757 
1758     va_start(args, fmt);
1759     xvasprintf(&message, fmt, args);
1760     va_end(args);
1761     warn("%s:%u: %s", group->file, group->line, message);
1762     free(message);
1763 }
1764