2 * Copyright (c) 2002-2014 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 fp_config fp_dummy_config = {0, NULL, {0}, NULL};
46 * Search for config option (struct fp_config) in the array of config options,
47 * returning the struct whose directive matches the given parameter. If no
48 * match is found, a pointer to the static dummy array (above) is returned.
50 * This is to eliminate dependency on the index position of an item in the
51 * array, since the index position is more apt to be changed as code grows.
54 get_config_option(struct fp_config options[], const char *directive)
59 if (options == NULL || directive == NULL)
60 return (&fp_dummy_config);
62 /* Loop through the array, return the index of the first match */
63 for (n = 0; options[n].directive != NULL; n++)
64 if (strcmp(options[n].directive, directive) == 0)
65 return (&(options[n]));
67 /* Re-initialize the dummy variable in case it was written to */
68 fp_dummy_config.directive = NULL;
69 fp_dummy_config.type = 0;
70 fp_dummy_config.action = NULL;
71 fp_dummy_config.value.u_num = 0;
73 return (&fp_dummy_config);
77 * Parse the configuration file at `path' and execute the `action' call-back
78 * functions for any directives defined by the array of config options (first
81 * For unknown directives that are encountered, you can optionally pass a
82 * call-back function for the third argument to be called for unknowns.
84 * Returns zero on success; otherwise returns -1 and errno should be consulted.
87 parse_config(struct fp_config options[], const char *path,
88 int (*unknown)(struct fp_config *option, uint32_t line, char *directive,
89 char *value), uint16_t processing_options)
93 uint8_t case_sensitive;
97 uint8_t have_equals = 0;
99 uint8_t require_equals;
100 uint8_t strict_equals;
115 char rpath[PATH_MAX];
117 /* Sanity check: if no options and no unknown function, return */
118 if (options == NULL && unknown == NULL)
121 /* Processing options */
122 bequals = (processing_options & FP_BREAK_ON_EQUALS) == 0 ? 0 : 1;
123 bsemicolon = (processing_options & FP_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
124 case_sensitive = (processing_options & FP_CASE_SENSITIVE) == 0 ? 0 : 1;
125 require_equals = (processing_options & FP_REQUIRE_EQUALS) == 0 ? 0 : 1;
126 strict_equals = (processing_options & FP_STRICT_EQUALS) == 0 ? 0 : 1;
128 /* Initialize strings */
129 directive = value = 0;
132 /* Resolve the file path */
133 if (realpath(path, rpath) == 0)
137 if ((fd = open(rpath, O_RDONLY)) < 0)
140 /* Read the file until EOF */
144 /* skip to the beginning of a directive */
145 while (r != 0 && (isspace(*p) || *p == '#' || comment ||
146 (bsemicolon && *p == ';'))) {
149 else if (*p == '\n') {
155 /* Test for EOF; if EOF then no directive was found */
161 /* Get the current offset */
162 curpos = lseek(fd, 0, SEEK_CUR) - 1;
168 /* Find the length of the directive */
169 for (n = 0; r != 0; n++) {
172 if (bequals && *p == '=') {
176 if (bsemicolon && *p == ';')
181 /* Test for EOF, if EOF then no directive was found */
182 if (n == 0 && r == 0) {
187 /* Go back to the beginning of the directive */
188 error = (int)lseek(fd, curpos, SEEK_SET);
189 if (error == (curpos - 1)) {
194 /* Allocate and read the directive into memory */
196 if ((directive = realloc(directive, n + 1)) == NULL) {
202 r = read(fd, directive, n);
204 /* Advance beyond the equals sign if appropriate/desired */
205 if (bequals && *p == '=') {
206 if (lseek(fd, 1, SEEK_CUR) != -1)
208 if (strict_equals && isspace(*p))
212 /* Terminate the string */
215 /* Convert directive to lower case before comparison */
217 strtolower(directive);
219 /* Move to what may be the start of the value */
220 if (!(bsemicolon && *p == ';') &&
221 !(strict_equals && *p == '=')) {
222 while (r != 0 && isspace(*p) && *p != '\n')
226 /* An equals sign may have stopped us, should we eat it? */
227 if (r != 0 && bequals && *p == '=' && !strict_equals) {
230 while (r != 0 && isspace(*p) && *p != '\n')
234 /* If no value, allocate a dummy value and jump to action */
235 if (r == 0 || *p == '\n' || *p == '#' ||
236 (bsemicolon && *p == ';')) {
237 /* Initialize the value if not already done */
238 if (value == NULL && (value = malloc(1)) == NULL) {
246 /* Get the current offset */
247 curpos = lseek(fd, 0, SEEK_CUR) - 1;
253 /* Find the end of the value */
256 while (r != 0 && end == 0) {
257 /* Advance to the next character if we know we can */
258 if (*p != '\"' && *p != '#' && *p != '\n' &&
259 (!bsemicolon || *p != ';')) {
265 * If we get this far, we've hit an end-key
268 /* Get the current offset */
269 charpos = lseek(fd, 0, SEEK_CUR) - 1;
276 * Go back so we can read the character before the key
277 * to check if the character is escaped (which means we
280 error = (int)lseek(fd, -2, SEEK_CUR);
288 * Count how many backslashes there are (an odd number
289 * means the key is escaped, even means otherwise).
291 for (n = 1; *p == '\\'; n++) {
292 /* Move back another offset to read */
293 error = (int)lseek(fd, -2, SEEK_CUR);
301 /* Move offset back to the key and read it */
302 error = (int)lseek(fd, charpos, SEEK_SET);
303 if (error == (charpos - 1)) {
310 * If an even number of backslashes was counted meaning
311 * key is not escaped, we should evaluate what to do.
317 * Flag current sequence of characters
318 * to follow as being quoted (hashes
319 * are not considered comments).
325 * If we aren't in a quoted series, we
326 * just hit an inline comment and have
327 * found the end of the value.
334 * Newline characters must always be
335 * escaped, whether inside a quoted
336 * series or not, otherwise they
337 * terminate the value.
341 if (!quote && bsemicolon)
345 } else if (*p == '\n')
346 /* Escaped newline character. increment */
349 /* Advance to the next character */
353 /* Get the current offset */
354 charpos = lseek(fd, 0, SEEK_CUR) - 1;
360 /* Get the length of the value */
361 n = (uint32_t)(charpos - curpos);
362 if (r != 0) /* more to read, but don't read ending key */
365 /* Move offset back to the beginning of the value */
366 error = (int)lseek(fd, curpos, SEEK_SET);
367 if (error == (curpos - 1)) {
372 /* Allocate and read the value into memory */
374 if ((value = realloc(value, n + 1)) == NULL) {
380 r = read(fd, value, n);
382 /* Terminate the string */
385 /* Cut trailing whitespace off by termination */
387 while (isspace(*--t))
390 /* Escape the escaped quotes (replaceall is in string_m.c) */
391 x = strcount(value, "\\\""); /* in string_m.c */
392 if (x != 0 && (n + x) > vsize) {
393 if ((value = realloc(value, n + x + 1)) == NULL) {
399 if (replaceall(value, "\\\"", "\\\\\"") < 0) {
400 /* Replace operation failed for some unknown reason */
405 /* Remove all new line characters */
406 if (replaceall(value, "\\\n", "") < 0) {
407 /* Replace operation failed for some unknown reason */
412 /* Resolve escape sequences */
413 strexpand(value); /* in string_m.c */
416 /* Abort if we're seeking only assignments */
417 if (require_equals && !have_equals)
420 found = have_equals = 0; /* reset */
422 /* If there are no options defined, call unknown and loop */
423 if (options == NULL && unknown != NULL) {
424 error = unknown(NULL, line, directive, value);
432 /* Loop through the array looking for a match for the value */
433 for (n = 0; options[n].directive != NULL; n++) {
434 error = fnmatch(options[n].directive, directive,
438 /* Call function for array index item */
439 if (options[n].action != NULL) {
440 error = options[n].action(
442 line, directive, value);
448 } else if (error != FNM_NOMATCH) {
449 /* An error has occurred */
454 if (!found && unknown != NULL) {
456 * No match was found for the value we read from the
457 * file; call function designated for unknown values.
459 error = unknown(NULL, line, directive, value);