diff options
Diffstat (limited to 'lib/libfigpar/figpar.c')
-rw-r--r-- | lib/libfigpar/figpar.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/lib/libfigpar/figpar.c b/lib/libfigpar/figpar.c new file mode 100644 index 0000000..a97fc85 --- /dev/null +++ b/lib/libfigpar/figpar.c @@ -0,0 +1,469 @@ +/*- + * Copyright (c) 2002-2014 Devin Teske <dteske@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "figpar.h" +#include "string_m.h" + +struct fp_config fp_dummy_config = {0, NULL, {0}, NULL}; + +/* + * Search for config option (struct fp_config) in the array of config options, + * returning the struct whose directive matches the given parameter. If no + * match is found, a pointer to the static dummy array (above) is returned. + * + * This is to eliminate dependency on the index position of an item in the + * array, since the index position is more apt to be changed as code grows. + */ +struct fp_config * +get_config_option(struct fp_config options[], const char *directive) +{ + uint32_t n; + + /* Check arguments */ + if (options == NULL || directive == NULL) + return (&fp_dummy_config); + + /* Loop through the array, return the index of the first match */ + for (n = 0; options[n].directive != NULL; n++) + if (strcmp(options[n].directive, directive) == 0) + return (&(options[n])); + + /* Re-initialize the dummy variable in case it was written to */ + fp_dummy_config.directive = NULL; + fp_dummy_config.type = 0; + fp_dummy_config.action = NULL; + fp_dummy_config.value.u_num = 0; + + return (&fp_dummy_config); +} + +/* + * Parse the configuration file at `path' and execute the `action' call-back + * functions for any directives defined by the array of config options (first + * argument). + * + * For unknown directives that are encountered, you can optionally pass a + * call-back function for the third argument to be called for unknowns. + * + * Returns zero on success; otherwise returns -1 and errno should be consulted. +*/ +int +parse_config(struct fp_config options[], const char *path, + int (*unknown)(struct fp_config *option, uint32_t line, char *directive, + char *value), uint16_t processing_options) +{ + uint8_t bequals; + uint8_t bsemicolon; + uint8_t case_sensitive; + uint8_t comment = 0; + uint8_t end; + uint8_t found; + uint8_t have_equals = 0; + uint8_t quote; + uint8_t require_equals; + uint8_t strict_equals; + char p[2]; + char *directive; + char *t; + char *value; + int error; + int fd; + ssize_t r = 1; + uint32_t dsize; + uint32_t line = 1; + uint32_t n; + uint32_t vsize; + uint32_t x; + off_t charpos; + off_t curpos; + char rpath[PATH_MAX]; + + /* Sanity check: if no options and no unknown function, return */ + if (options == NULL && unknown == NULL) + return (-1); + + /* Processing options */ + bequals = (processing_options & FP_BREAK_ON_EQUALS) == 0 ? 0 : 1; + bsemicolon = (processing_options & FP_BREAK_ON_SEMICOLON) == 0 ? 0 : 1; + case_sensitive = (processing_options & FP_CASE_SENSITIVE) == 0 ? 0 : 1; + require_equals = (processing_options & FP_REQUIRE_EQUALS) == 0 ? 0 : 1; + strict_equals = (processing_options & FP_STRICT_EQUALS) == 0 ? 0 : 1; + + /* Initialize strings */ + directive = value = 0; + vsize = dsize = 0; + + /* Resolve the file path */ + if (realpath(path, rpath) == 0) + return (-1); + + /* Open the file */ + if ((fd = open(rpath, O_RDONLY)) < 0) + return (-1); + + /* Read the file until EOF */ + while (r != 0) { + r = read(fd, p, 1); + + /* skip to the beginning of a directive */ + while (r != 0 && (isspace(*p) || *p == '#' || comment || + (bsemicolon && *p == ';'))) { + if (*p == '#') + comment = 1; + else if (*p == '\n') { + comment = 0; + line++; + } + r = read(fd, p, 1); + } + /* Test for EOF; if EOF then no directive was found */ + if (r == 0) { + close(fd); + return (0); + } + + /* Get the current offset */ + curpos = lseek(fd, 0, SEEK_CUR) - 1; + if (curpos == -1) { + close(fd); + return (-1); + } + + /* Find the length of the directive */ + for (n = 0; r != 0; n++) { + if (isspace(*p)) + break; + if (bequals && *p == '=') { + have_equals = 1; + break; + } + if (bsemicolon && *p == ';') + break; + r = read(fd, p, 1); + } + + /* Test for EOF, if EOF then no directive was found */ + if (n == 0 && r == 0) { + close(fd); + return (0); + } + + /* Go back to the beginning of the directive */ + error = (int)lseek(fd, curpos, SEEK_SET); + if (error == (curpos - 1)) { + close(fd); + return (-1); + } + + /* Allocate and read the directive into memory */ + if (n > dsize) { + if ((directive = realloc(directive, n + 1)) == NULL) { + close(fd); + return (-1); + } + dsize = n; + } + r = read(fd, directive, n); + + /* Advance beyond the equals sign if appropriate/desired */ + if (bequals && *p == '=') { + if (lseek(fd, 1, SEEK_CUR) != -1) + r = read(fd, p, 1); + if (strict_equals && isspace(*p)) + *p = '\n'; + } + + /* Terminate the string */ + directive[n] = '\0'; + + /* Convert directive to lower case before comparison */ + if (!case_sensitive) + strtolower(directive); + + /* Move to what may be the start of the value */ + if (!(bsemicolon && *p == ';') && + !(strict_equals && *p == '=')) { + while (r != 0 && isspace(*p) && *p != '\n') + r = read(fd, p, 1); + } + + /* An equals sign may have stopped us, should we eat it? */ + if (r != 0 && bequals && *p == '=' && !strict_equals) { + have_equals = 1; + r = read(fd, p, 1); + while (r != 0 && isspace(*p) && *p != '\n') + r = read(fd, p, 1); + } + + /* If no value, allocate a dummy value and jump to action */ + if (r == 0 || *p == '\n' || *p == '#' || + (bsemicolon && *p == ';')) { + /* Initialize the value if not already done */ + if (value == NULL && (value = malloc(1)) == NULL) { + close(fd); + return (-1); + } + value[0] = '\0'; + goto call_function; + } + + /* Get the current offset */ + curpos = lseek(fd, 0, SEEK_CUR) - 1; + if (curpos == -1) { + close(fd); + return (-1); + } + + /* Find the end of the value */ + quote = 0; + end = 0; + while (r != 0 && end == 0) { + /* Advance to the next character if we know we can */ + if (*p != '\"' && *p != '#' && *p != '\n' && + (!bsemicolon || *p != ';')) { + r = read(fd, p, 1); + continue; + } + + /* + * If we get this far, we've hit an end-key + */ + + /* Get the current offset */ + charpos = lseek(fd, 0, SEEK_CUR) - 1; + if (charpos == -1) { + close(fd); + return (-1); + } + + /* + * Go back so we can read the character before the key + * to check if the character is escaped (which means we + * should continue). + */ + error = (int)lseek(fd, -2, SEEK_CUR); + if (error == -3) { + close(fd); + return (-1); + } + r = read(fd, p, 1); + + /* + * Count how many backslashes there are (an odd number + * means the key is escaped, even means otherwise). + */ + for (n = 1; *p == '\\'; n++) { + /* Move back another offset to read */ + error = (int)lseek(fd, -2, SEEK_CUR); + if (error == -3) { + close(fd); + return (-1); + } + r = read(fd, p, 1); + } + + /* Move offset back to the key and read it */ + error = (int)lseek(fd, charpos, SEEK_SET); + if (error == (charpos - 1)) { + close(fd); + return (-1); + } + r = read(fd, p, 1); + + /* + * If an even number of backslashes was counted meaning + * key is not escaped, we should evaluate what to do. + */ + if ((n & 1) == 1) { + switch (*p) { + case '\"': + /* + * Flag current sequence of characters + * to follow as being quoted (hashes + * are not considered comments). + */ + quote = !quote; + break; + case '#': + /* + * If we aren't in a quoted series, we + * just hit an inline comment and have + * found the end of the value. + */ + if (!quote) + end = 1; + break; + case '\n': + /* + * Newline characters must always be + * escaped, whether inside a quoted + * series or not, otherwise they + * terminate the value. + */ + end = 1; + case ';': + if (!quote && bsemicolon) + end = 1; + break; + } + } else if (*p == '\n') + /* Escaped newline character. increment */ + line++; + + /* Advance to the next character */ + r = read(fd, p, 1); + } + + /* Get the current offset */ + charpos = lseek(fd, 0, SEEK_CUR) - 1; + if (charpos == -1) { + close(fd); + return (-1); + } + + /* Get the length of the value */ + n = (uint32_t)(charpos - curpos); + if (r != 0) /* more to read, but don't read ending key */ + n--; + + /* Move offset back to the beginning of the value */ + error = (int)lseek(fd, curpos, SEEK_SET); + if (error == (curpos - 1)) { + close(fd); + return (-1); + } + + /* Allocate and read the value into memory */ + if (n > vsize) { + if ((value = realloc(value, n + 1)) == NULL) { + close(fd); + return (-1); + } + vsize = n; + } + r = read(fd, value, n); + + /* Terminate the string */ + value[n] = '\0'; + + /* Cut trailing whitespace off by termination */ + t = value + n; + while (isspace(*--t)) + *t = '\0'; + + /* Escape the escaped quotes (replaceall is in string_m.c) */ + x = strcount(value, "\\\""); /* in string_m.c */ + if (x != 0 && (n + x) > vsize) { + if ((value = realloc(value, n + x + 1)) == NULL) { + close(fd); + return (-1); + } + vsize = n + x; + } + if (replaceall(value, "\\\"", "\\\\\"") < 0) { + /* Replace operation failed for some unknown reason */ + close(fd); + return (-1); + } + + /* Remove all new line characters */ + if (replaceall(value, "\\\n", "") < 0) { + /* Replace operation failed for some unknown reason */ + close(fd); + return (-1); + } + + /* Resolve escape sequences */ + strexpand(value); /* in string_m.c */ + +call_function: + /* Abort if we're seeking only assignments */ + if (require_equals && !have_equals) + return (-1); + + found = have_equals = 0; /* reset */ + + /* If there are no options defined, call unknown and loop */ + if (options == NULL && unknown != NULL) { + error = unknown(NULL, line, directive, value); + if (error != 0) { + close(fd); + return (error); + } + continue; + } + + /* Loop through the array looking for a match for the value */ + for (n = 0; options[n].directive != NULL; n++) { + error = fnmatch(options[n].directive, directive, + FNM_NOESCAPE); + if (error == 0) { + found = 1; + /* Call function for array index item */ + if (options[n].action != NULL) { + error = options[n].action( + &options[n], + line, directive, value); + if (error != 0) { + close(fd); + return (error); + } + } + } else if (error != FNM_NOMATCH) { + /* An error has occurred */ + close(fd); + return (-1); + } + } + if (!found && unknown != NULL) { + /* + * No match was found for the value we read from the + * file; call function designated for unknown values. + */ + error = unknown(NULL, line, directive, value); + if (error != 0) { + close(fd); + return (error); + } + } + } + + close(fd); + return (0); +} |