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