2 * Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
30 #include <sys/param.h>
43 struct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL};
46 * Search for config option (struct figpar_config) in the array of config
47 * options, returning the struct whose directive matches the given parameter.
48 * If no match is found, a pointer to the static dummy array (above) is
51 * This is to eliminate dependency on the index position of an item in the
52 * array, since the index position is more apt to be changed as code grows.
54 struct figpar_config *
55 get_config_option(struct figpar_config options[], const char *directive)
60 if (options == NULL || directive == NULL)
61 return (&figpar_dummy_config);
63 /* Loop through the array, return the index of the first match */
64 for (n = 0; options[n].directive != NULL; n++)
65 if (strcmp(options[n].directive, directive) == 0)
66 return (&(options[n]));
68 /* Re-initialize the dummy variable in case it was written to */
69 figpar_dummy_config.directive = NULL;
70 figpar_dummy_config.type = 0;
71 figpar_dummy_config.action = NULL;
72 figpar_dummy_config.value.u_num = 0;
74 return (&figpar_dummy_config);
78 * Parse the configuration file at `path' and execute the `action' call-back
79 * functions for any directives defined by the array of config options (first
82 * For unknown directives that are encountered, you can optionally pass a
83 * call-back function for the third argument to be called for unknowns.
85 * Returns zero on success; otherwise returns -1 and errno should be consulted.
88 parse_config(struct figpar_config options[], const char *path,
89 int (*unknown)(struct figpar_config *option, uint32_t line,
90 char *directive, char *value), uint16_t processing_options)
94 uint8_t case_sensitive;
98 uint8_t have_equals = 0;
100 uint8_t require_equals;
101 uint8_t strict_equals;
116 char rpath[PATH_MAX];
118 /* Sanity check: if no options and no unknown function, return */
119 if (options == NULL && unknown == NULL)
122 /* Processing options */
123 bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1;
125 (processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
127 (processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1;
129 (processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1;
131 (processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1;
133 /* Initialize strings */
134 directive = value = 0;
137 /* Resolve the file path */
138 if (realpath(path, rpath) == 0)
142 if ((fd = open(rpath, O_RDONLY)) < 0)
145 /* Read the file until EOF */
149 /* skip to the beginning of a directive */
150 while (r != 0 && (isspace(*p) || *p == '#' || comment ||
151 (bsemicolon && *p == ';'))) {
154 else if (*p == '\n') {
160 /* Test for EOF; if EOF then no directive was found */
166 /* Get the current offset */
167 curpos = lseek(fd, 0, SEEK_CUR) - 1;
173 /* Find the length of the directive */
174 for (n = 0; r != 0; n++) {
177 if (bequals && *p == '=') {
181 if (bsemicolon && *p == ';')
186 /* Test for EOF, if EOF then no directive was found */
187 if (n == 0 && r == 0) {
192 /* Go back to the beginning of the directive */
193 error = (int)lseek(fd, curpos, SEEK_SET);
194 if (error == (curpos - 1)) {
199 /* Allocate and read the directive into memory */
201 if ((directive = realloc(directive, n + 1)) == NULL) {
207 r = read(fd, directive, n);
209 /* Advance beyond the equals sign if appropriate/desired */
210 if (bequals && *p == '=') {
211 if (lseek(fd, 1, SEEK_CUR) != -1)
213 if (strict_equals && isspace(*p))
217 /* Terminate the string */
220 /* Convert directive to lower case before comparison */
222 strtolower(directive);
224 /* Move to what may be the start of the value */
225 if (!(bsemicolon && *p == ';') &&
226 !(strict_equals && *p == '=')) {
227 while (r != 0 && isspace(*p) && *p != '\n')
231 /* An equals sign may have stopped us, should we eat it? */
232 if (r != 0 && bequals && *p == '=' && !strict_equals) {
235 while (r != 0 && isspace(*p) && *p != '\n')
239 /* If no value, allocate a dummy value and jump to action */
240 if (r == 0 || *p == '\n' || *p == '#' ||
241 (bsemicolon && *p == ';')) {
242 /* Initialize the value if not already done */
243 if (value == NULL && (value = malloc(1)) == NULL) {
251 /* Get the current offset */
252 curpos = lseek(fd, 0, SEEK_CUR) - 1;
258 /* Find the end of the value */
261 while (r != 0 && end == 0) {
262 /* Advance to the next character if we know we can */
263 if (*p != '\"' && *p != '#' && *p != '\n' &&
264 (!bsemicolon || *p != ';')) {
270 * If we get this far, we've hit an end-key
273 /* Get the current offset */
274 charpos = lseek(fd, 0, SEEK_CUR) - 1;
281 * Go back so we can read the character before the key
282 * to check if the character is escaped (which means we
285 error = (int)lseek(fd, -2, SEEK_CUR);
293 * Count how many backslashes there are (an odd number
294 * means the key is escaped, even means otherwise).
296 for (n = 1; *p == '\\'; n++) {
297 /* Move back another offset to read */
298 error = (int)lseek(fd, -2, SEEK_CUR);
306 /* Move offset back to the key and read it */
307 error = (int)lseek(fd, charpos, SEEK_SET);
308 if (error == (charpos - 1)) {
315 * If an even number of backslashes was counted meaning
316 * key is not escaped, we should evaluate what to do.
322 * Flag current sequence of characters
323 * to follow as being quoted (hashes
324 * are not considered comments).
330 * If we aren't in a quoted series, we
331 * just hit an inline comment and have
332 * found the end of the value.
339 * Newline characters must always be
340 * escaped, whether inside a quoted
341 * series or not, otherwise they
342 * terminate the value.
346 if (!quote && bsemicolon)
350 } else if (*p == '\n')
351 /* Escaped newline character. increment */
354 /* Advance to the next character */
358 /* Get the current offset */
359 charpos = lseek(fd, 0, SEEK_CUR) - 1;
365 /* Get the length of the value */
366 n = (uint32_t)(charpos - curpos);
367 if (r != 0) /* more to read, but don't read ending key */
370 /* Move offset back to the beginning of the value */
371 error = (int)lseek(fd, curpos, SEEK_SET);
372 if (error == (curpos - 1)) {
377 /* Allocate and read the value into memory */
379 if ((value = realloc(value, n + 1)) == NULL) {
385 r = read(fd, value, n);
387 /* Terminate the string */
390 /* Cut trailing whitespace off by termination */
392 while (isspace(*--t))
395 /* Escape the escaped quotes (replaceall is in string_m.c) */
396 x = strcount(value, "\\\""); /* in string_m.c */
397 if (x != 0 && (n + x) > vsize) {
398 if ((value = realloc(value, n + x + 1)) == NULL) {
404 if (replaceall(value, "\\\"", "\\\\\"") < 0) {
405 /* Replace operation failed for some unknown reason */
410 /* Remove all new line characters */
411 if (replaceall(value, "\\\n", "") < 0) {
412 /* Replace operation failed for some unknown reason */
417 /* Resolve escape sequences */
418 strexpand(value); /* in string_m.c */
421 /* Abort if we're seeking only assignments */
422 if (require_equals && !have_equals)
425 found = have_equals = 0; /* reset */
427 /* If there are no options defined, call unknown and loop */
428 if (options == NULL && unknown != NULL) {
429 error = unknown(NULL, line, directive, value);
437 /* Loop through the array looking for a match for the value */
438 for (n = 0; options[n].directive != NULL; n++) {
439 error = fnmatch(options[n].directive, directive,
443 /* Call function for array index item */
444 if (options[n].action != NULL) {
445 error = options[n].action(
447 line, directive, value);
453 } else if (error != FNM_NOMATCH) {
454 /* An error has occurred */
459 if (!found && unknown != NULL) {
461 * No match was found for the value we read from the
462 * file; call function designated for unknown values.
464 error = unknown(NULL, line, directive, value);