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