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