13fe5961aSDevin Teske /*
23fe5961aSDevin Teske * Copyright (c) 2002-2026 Devin Teske <dteske@FreeBSD.org>
33fe5961aSDevin Teske * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
43fe5961aSDevin Teske *
53fe5961aSDevin Teske * SPDX-License-Identifier: BSD-2-Clause
63fe5961aSDevin Teske */
73fe5961aSDevin Teske
83fe5961aSDevin Teske #include <ctype.h>
93fe5961aSDevin Teske #include <errno.h>
103fe5961aSDevin Teske #include <fcntl.h>
113fe5961aSDevin Teske #include <fnmatch.h>
123fe5961aSDevin Teske #include <stdio.h>
133fe5961aSDevin Teske #include <stdlib.h>
143fe5961aSDevin Teske #include <string.h>
153fe5961aSDevin Teske #include <unistd.h>
163fe5961aSDevin Teske
173fe5961aSDevin Teske #include "bsdconf.h"
183fe5961aSDevin Teske #include "bsdconf_internal.h"
193fe5961aSDevin Teske
203fe5961aSDevin Teske /*
213fe5961aSDevin Teske * Search for a config option (struct bsdconf_option) in the array of config
223fe5961aSDevin Teske * options, returning the struct whose directive matches the given parameter.
233fe5961aSDevin Teske * If no match is found, NULL is returned.
243fe5961aSDevin Teske *
253fe5961aSDevin Teske * This is to eliminate dependency on the index position of an item in the
263fe5961aSDevin Teske * array, since the index position is more apt to be changed as code grows.
273fe5961aSDevin Teske */
283fe5961aSDevin Teske struct bsdconf_option *
bsdconf_get_option(struct bsdconf_option options[],const char * directive)293fe5961aSDevin Teske bsdconf_get_option(struct bsdconf_option options[], const char *directive)
303fe5961aSDevin Teske {
313fe5961aSDevin Teske uint32_t n;
323fe5961aSDevin Teske
333fe5961aSDevin Teske if (options == NULL || directive == NULL)
343fe5961aSDevin Teske return (NULL);
353fe5961aSDevin Teske
363fe5961aSDevin Teske for (n = 0; options[n].directive != NULL; n++)
373fe5961aSDevin Teske if (strcmp(options[n].directive, directive) == 0)
383fe5961aSDevin Teske return (&options[n]);
393fe5961aSDevin Teske
403fe5961aSDevin Teske return (NULL);
413fe5961aSDevin Teske }
423fe5961aSDevin Teske
433fe5961aSDevin Teske /*
443fe5961aSDevin Teske * Strip one layer of surrounding double-quotes from `value' in place (the
453fe5961aSDevin Teske * parser preserves quotes so that values round-trip losslessly; see
463fe5961aSDevin Teske * bsdconf_fparse() below). Returns `value' for convenience. If the value is
473fe5961aSDevin Teske * not a quoted string, it is returned unmodified.
483fe5961aSDevin Teske */
493fe5961aSDevin Teske char *
bsdconf_unquote(char * value)503fe5961aSDevin Teske bsdconf_unquote(char *value)
513fe5961aSDevin Teske {
523fe5961aSDevin Teske size_t len;
533fe5961aSDevin Teske
543fe5961aSDevin Teske if (value == NULL || (len = strlen(value)) < 2)
553fe5961aSDevin Teske return (value);
563fe5961aSDevin Teske if (value[0] != '"' || value[len - 1] != '"')
573fe5961aSDevin Teske return (value);
583fe5961aSDevin Teske
593fe5961aSDevin Teske memmove(value, value + 1, len - 2);
603fe5961aSDevin Teske value[len - 2] = '\0';
613fe5961aSDevin Teske
623fe5961aSDevin Teske return (value);
633fe5961aSDevin Teske }
643fe5961aSDevin Teske
653fe5961aSDevin Teske /*
663fe5961aSDevin Teske * Copy the remaining contents of the open file descriptor `fd' to an
673fe5961aSDevin Teske * unlinked temporary file and return a seekable descriptor referencing it
683fe5961aSDevin Teske * (which the caller must close(2); the backing storage is reclaimed then).
693fe5961aSDevin Teske * This adapts input that cannot seek -- a pipe or socket, standard input
703fe5961aSDevin Teske * included -- for the scanner in bsdconf_fparse() below, which seeks
713fe5961aSDevin Teske * liberally. Returns the new descriptor on success; otherwise returns -1
723fe5961aSDevin Teske * and errno should be consulted.
733fe5961aSDevin Teske */
743fe5961aSDevin Teske int
bsdconf_spool(int fd)753fe5961aSDevin Teske bsdconf_spool(int fd)
763fe5961aSDevin Teske {
773fe5961aSDevin Teske FILE *tmp;
783fe5961aSDevin Teske int error;
793fe5961aSDevin Teske int newfd;
803fe5961aSDevin Teske int tfd;
813fe5961aSDevin Teske ssize_t r;
823fe5961aSDevin Teske char buf[8192];
833fe5961aSDevin Teske
843fe5961aSDevin Teske if ((tmp = tmpfile()) == NULL)
853fe5961aSDevin Teske return (-1);
863fe5961aSDevin Teske tfd = fileno(tmp);
873fe5961aSDevin Teske
883fe5961aSDevin Teske for (;;) {
893fe5961aSDevin Teske r = read(fd, buf, sizeof(buf));
903fe5961aSDevin Teske if (r < 0) {
913fe5961aSDevin Teske if (errno == EINTR)
923fe5961aSDevin Teske continue;
933fe5961aSDevin Teske goto fail;
943fe5961aSDevin Teske }
953fe5961aSDevin Teske if (r == 0)
963fe5961aSDevin Teske break;
973fe5961aSDevin Teske if (bsdconf_writeall(tfd, buf, (size_t)r) != 0)
983fe5961aSDevin Teske goto fail;
993fe5961aSDevin Teske }
1003fe5961aSDevin Teske if (lseek(tfd, 0, SEEK_SET) == -1)
1013fe5961aSDevin Teske goto fail;
1023fe5961aSDevin Teske
1033fe5961aSDevin Teske /* Detach the descriptor from the stream before closing it */
1043fe5961aSDevin Teske if ((newfd = dup(tfd)) == -1)
1053fe5961aSDevin Teske goto fail;
1063fe5961aSDevin Teske fclose(tmp);
1073fe5961aSDevin Teske return (newfd);
1083fe5961aSDevin Teske
1093fe5961aSDevin Teske fail:
1103fe5961aSDevin Teske error = errno; /* preserve errno across fclose(3) */
1113fe5961aSDevin Teske fclose(tmp);
1123fe5961aSDevin Teske errno = error;
1133fe5961aSDevin Teske return (-1);
1143fe5961aSDevin Teske }
1153fe5961aSDevin Teske
1163fe5961aSDevin Teske /*
1173fe5961aSDevin Teske * Read one byte into `*p', restarting on EINTR. Returns 1 on success, 0 on
1183fe5961aSDevin Teske * EOF, or -1 on error (with errno set). Callers must treat a negative return
1193fe5961aSDevin Teske * as failure: a loop conditioned only on `r != 0' spins forever on error
1203fe5961aSDevin Teske * because read(2) returns -1, and a length counter in such a loop can grow
1213fe5961aSDevin Teske * without bound (see the directive scan in bsdconf_fparse() below).
1223fe5961aSDevin Teske */
1233fe5961aSDevin Teske static ssize_t
bsdconf_read1(int fd,char * p)1243fe5961aSDevin Teske bsdconf_read1(int fd, char *p)
1253fe5961aSDevin Teske {
1263fe5961aSDevin Teske ssize_t r;
1273fe5961aSDevin Teske
1283fe5961aSDevin Teske do {
1293fe5961aSDevin Teske r = read(fd, p, 1);
1303fe5961aSDevin Teske } while (r < 0 && errno == EINTR);
1313fe5961aSDevin Teske return (r);
1323fe5961aSDevin Teske }
1333fe5961aSDevin Teske
1343fe5961aSDevin Teske /*
1353fe5961aSDevin Teske * Read exactly `n' bytes into `buf', restarting on EINTR. Returns 0 on
1363fe5961aSDevin Teske * success, or -1 on error / premature EOF (with errno set; EIO for a short
1373fe5961aSDevin Teske * read after the caller measured a length on a seekable descriptor).
1383fe5961aSDevin Teske */
1393fe5961aSDevin Teske static int
bsdconf_readn(int fd,void * buf,size_t n)1403fe5961aSDevin Teske bsdconf_readn(int fd, void *buf, size_t n)
1413fe5961aSDevin Teske {
1423fe5961aSDevin Teske char *p = buf;
1433fe5961aSDevin Teske size_t off = 0;
1443fe5961aSDevin Teske ssize_t r;
1453fe5961aSDevin Teske
1463fe5961aSDevin Teske while (off < n) {
1473fe5961aSDevin Teske r = read(fd, p + off, n - off);
1483fe5961aSDevin Teske if (r < 0) {
1493fe5961aSDevin Teske if (errno == EINTR)
1503fe5961aSDevin Teske continue;
1513fe5961aSDevin Teske return (-1);
1523fe5961aSDevin Teske }
1533fe5961aSDevin Teske if (r == 0) {
1543fe5961aSDevin Teske errno = EIO;
1553fe5961aSDevin Teske return (-1);
1563fe5961aSDevin Teske }
1573fe5961aSDevin Teske off += (size_t)r;
1583fe5961aSDevin Teske }
1593fe5961aSDevin Teske return (0);
1603fe5961aSDevin Teske }
1613fe5961aSDevin Teske
1623fe5961aSDevin Teske /*
1633fe5961aSDevin Teske * Advance past horizontal whitespace (spaces and tabs, not newline).
1643fe5961aSDevin Teske * Updates `*r' and the byte in `*p'. Returns 0 on success, or -1 on
1653fe5961aSDevin Teske * read error (errno set).
1663fe5961aSDevin Teske */
1673fe5961aSDevin Teske static int
bsdconf_skip_hspace(int fd,char * p,ssize_t * r)1683fe5961aSDevin Teske bsdconf_skip_hspace(int fd, char *p, ssize_t *r)
1693fe5961aSDevin Teske {
1703fe5961aSDevin Teske
1713fe5961aSDevin Teske while (*r > 0 && isspace((unsigned char)*p) && *p != '\n') {
1723fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
1733fe5961aSDevin Teske if (*r < 0)
1743fe5961aSDevin Teske return (-1);
1753fe5961aSDevin Teske }
1763fe5961aSDevin Teske return (0);
1773fe5961aSDevin Teske }
1783fe5961aSDevin Teske
1793fe5961aSDevin Teske /*
1803fe5961aSDevin Teske * Truncate trailing whitespace from a NUL-terminated string whose end
1813fe5961aSDevin Teske * (the NUL) is at `end'. Returns a pointer to the last remaining
1823fe5961aSDevin Teske * character, or to `value' when the string is empty.
1833fe5961aSDevin Teske */
1843fe5961aSDevin Teske static char *
bsdconf_rtrim_ws(char * value,char * end)1853fe5961aSDevin Teske bsdconf_rtrim_ws(char *value, char *end)
1863fe5961aSDevin Teske {
1873fe5961aSDevin Teske char *t = end;
1883fe5961aSDevin Teske
1893fe5961aSDevin Teske while (t > value && isspace((unsigned char)*--t))
1903fe5961aSDevin Teske *t = '\0';
1913fe5961aSDevin Teske return (t);
1923fe5961aSDevin Teske }
1933fe5961aSDevin Teske
1943fe5961aSDevin Teske /*
1953fe5961aSDevin Teske * Drop a trailing inline `#' or unescaped `;' that rode along with the
1963fe5961aSDevin Teske * value (historic figpar behavior), then trim again. `ecomment' is set
1973fe5961aSDevin Teske * when the end-key scan stopped on an unquoted `#'.
1983fe5961aSDevin Teske */
1993fe5961aSDevin Teske static char *
bsdconf_trim_value_key(char * value,char * t,bool ecomment,bool bsemicolon)2003fe5961aSDevin Teske bsdconf_trim_value_key(char *value, char *t, bool ecomment, bool bsemicolon)
2013fe5961aSDevin Teske {
2023fe5961aSDevin Teske uint32_t x;
2033fe5961aSDevin Teske
2043fe5961aSDevin Teske if (ecomment && t > value && *t == '#') {
2053fe5961aSDevin Teske *t = '\0';
2063fe5961aSDevin Teske return (bsdconf_rtrim_ws(value, t));
2073fe5961aSDevin Teske }
2083fe5961aSDevin Teske if (bsemicolon && t > value && *t == ';') {
2093fe5961aSDevin Teske for (x = 0; t - x > value && *(t - x - 1) == '\\'; x++)
2103fe5961aSDevin Teske ;
2113fe5961aSDevin Teske if ((x & 1) == 0) {
2123fe5961aSDevin Teske *t = '\0';
2133fe5961aSDevin Teske return (bsdconf_rtrim_ws(value, t));
2143fe5961aSDevin Teske }
2153fe5961aSDevin Teske }
2163fe5961aSDevin Teske return (t);
2173fe5961aSDevin Teske }
2183fe5961aSDevin Teske
2193fe5961aSDevin Teske /*
2203fe5961aSDevin Teske * Invoke the unknown-directive call-back with a stack-local option that
2213fe5961aSDevin Teske * carries the statement's assignment operator (there is no matched
2223fe5961aSDevin Teske * options[] slot to hang it on). Returns the call-back's result.
2233fe5961aSDevin Teske */
2243fe5961aSDevin Teske static int
bsdconf_call_unknown(int (* unknown)(struct bsdconf_option * option,uint32_t line,char * directive,char * value),enum bsdconf_op op,uint32_t dline,char * directive,char * value)2253fe5961aSDevin Teske bsdconf_call_unknown(int (*unknown)(struct bsdconf_option *option,
2263fe5961aSDevin Teske uint32_t line, char *directive, char *value), enum bsdconf_op op,
2273fe5961aSDevin Teske uint32_t dline, char *directive, char *value)
2283fe5961aSDevin Teske {
2293fe5961aSDevin Teske struct bsdconf_option unk;
2303fe5961aSDevin Teske
2313fe5961aSDevin Teske memset(&unk, 0, sizeof(unk));
2323fe5961aSDevin Teske unk.op = op;
2333fe5961aSDevin Teske return (unknown(&unk, dline, directive, value));
2343fe5961aSDevin Teske }
2353fe5961aSDevin Teske
2363fe5961aSDevin Teske /*
2373fe5961aSDevin Teske * Scan from the current byte in `*p' to the end of the value. Handles
2383fe5961aSDevin Teske * quotes, escaped end-keys, inline comments, and semicolon terminators.
2393fe5961aSDevin Teske * On return, `*p' holds the terminating key (or is at EOF), and `*r',
2403fe5961aSDevin Teske * `*line', `*comment', and `*ecomment' are updated. Returns 0 on
2413fe5961aSDevin Teske * success, or -1 on seek/read error (errno set).
2423fe5961aSDevin Teske */
2433fe5961aSDevin Teske static int
bsdconf_scan_value_end(int fd,char * p,ssize_t * r,uint32_t * line,uint8_t * comment,uint8_t * ecomment,bool bsemicolon)2443fe5961aSDevin Teske bsdconf_scan_value_end(int fd, char *p, ssize_t *r, uint32_t *line,
2453fe5961aSDevin Teske uint8_t *comment, uint8_t *ecomment, bool bsemicolon)
2463fe5961aSDevin Teske {
2473fe5961aSDevin Teske uint8_t end = 0;
2483fe5961aSDevin Teske uint8_t quote = 0;
2493fe5961aSDevin Teske uint32_t n;
2503fe5961aSDevin Teske off_t charpos;
2513fe5961aSDevin Teske
2523fe5961aSDevin Teske *ecomment = 0;
2533fe5961aSDevin Teske while (*r > 0 && end == 0) {
2543fe5961aSDevin Teske /* Advance to the next character if we know we can */
2553fe5961aSDevin Teske if (*p != '\"' && *p != '#' && *p != '\n' &&
2563fe5961aSDevin Teske (!bsemicolon || *p != ';')) {
2573fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
2583fe5961aSDevin Teske if (*r < 0)
2593fe5961aSDevin Teske return (-1);
2603fe5961aSDevin Teske continue;
2613fe5961aSDevin Teske }
2623fe5961aSDevin Teske
2633fe5961aSDevin Teske /*
2643fe5961aSDevin Teske * If we get this far, we've hit an end-key
2653fe5961aSDevin Teske */
2663fe5961aSDevin Teske
2673fe5961aSDevin Teske /* Get the current offset */
2683fe5961aSDevin Teske if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1)
2693fe5961aSDevin Teske return (-1);
2703fe5961aSDevin Teske charpos--;
2713fe5961aSDevin Teske
2723fe5961aSDevin Teske /*
2733fe5961aSDevin Teske * Go back so we can read the character before the key to
2743fe5961aSDevin Teske * check if the character is escaped (which means we should
2753fe5961aSDevin Teske * continue).
2763fe5961aSDevin Teske */
2773fe5961aSDevin Teske if (lseek(fd, -2, SEEK_CUR) == -1)
2783fe5961aSDevin Teske return (-1);
2793fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
2803fe5961aSDevin Teske if (*r < 0)
2813fe5961aSDevin Teske return (-1);
2823fe5961aSDevin Teske
2833fe5961aSDevin Teske /*
2843fe5961aSDevin Teske * Count how many backslashes there are (an odd number means
2853fe5961aSDevin Teske * the key is escaped, even means otherwise).
2863fe5961aSDevin Teske */
2873fe5961aSDevin Teske for (n = 1; *r > 0 && *p == '\\'; n++) {
2883fe5961aSDevin Teske /* Move back another offset to read */
2893fe5961aSDevin Teske if (lseek(fd, -2, SEEK_CUR) == -1)
2903fe5961aSDevin Teske return (-1);
2913fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
2923fe5961aSDevin Teske if (*r < 0)
2933fe5961aSDevin Teske return (-1);
2943fe5961aSDevin Teske }
2953fe5961aSDevin Teske
2963fe5961aSDevin Teske /* Move offset back to the key and read it */
2973fe5961aSDevin Teske if (lseek(fd, charpos, SEEK_SET) == -1)
2983fe5961aSDevin Teske return (-1);
2993fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
3003fe5961aSDevin Teske if (*r < 0)
3013fe5961aSDevin Teske return (-1);
3023fe5961aSDevin Teske
3033fe5961aSDevin Teske /*
3043fe5961aSDevin Teske * If an even number of backslashes was counted meaning key
3053fe5961aSDevin Teske * is not escaped, we should evaluate what to do.
3063fe5961aSDevin Teske */
3073fe5961aSDevin Teske if ((n & 1) == 1) {
3083fe5961aSDevin Teske switch (*p) {
3093fe5961aSDevin Teske case '\"':
3103fe5961aSDevin Teske /*
3113fe5961aSDevin Teske * Flag current sequence of characters to
3123fe5961aSDevin Teske * follow as being quoted (hashes are not
3133fe5961aSDevin Teske * considered comments).
3143fe5961aSDevin Teske */
3153fe5961aSDevin Teske quote = !quote;
3163fe5961aSDevin Teske break;
3173fe5961aSDevin Teske case '#':
3183fe5961aSDevin Teske /*
3193fe5961aSDevin Teske * If we aren't in a quoted series, we just
3203fe5961aSDevin Teske * hit an inline comment and have found the
3213fe5961aSDevin Teske * end of the value. Flag the remainder of
3223fe5961aSDevin Teske * the line as a comment so it is not
3233fe5961aSDevin Teske * mistaken for a new directive.
3243fe5961aSDevin Teske */
3253fe5961aSDevin Teske if (!quote) {
3263fe5961aSDevin Teske *ecomment = *comment = 1;
3273fe5961aSDevin Teske end = 1;
3283fe5961aSDevin Teske }
3293fe5961aSDevin Teske break;
3303fe5961aSDevin Teske case '\n':
3313fe5961aSDevin Teske /*
3323fe5961aSDevin Teske * Newline characters must always be escaped,
3333fe5961aSDevin Teske * whether inside a quoted series or not,
3343fe5961aSDevin Teske * otherwise they terminate the value.
3353fe5961aSDevin Teske */
3363fe5961aSDevin Teske (*line)++;
3373fe5961aSDevin Teske end = 1;
3383fe5961aSDevin Teske /* FALLTHROUGH */
3393fe5961aSDevin Teske case ';':
3403fe5961aSDevin Teske if (!quote && bsemicolon)
3413fe5961aSDevin Teske end = 1;
3423fe5961aSDevin Teske break;
3433fe5961aSDevin Teske }
3443fe5961aSDevin Teske } else if (*p == '\n')
3453fe5961aSDevin Teske /* Escaped newline character. increment */
3463fe5961aSDevin Teske (*line)++;
3473fe5961aSDevin Teske
3483fe5961aSDevin Teske /* Advance to the next character */
3493fe5961aSDevin Teske *r = bsdconf_read1(fd, p);
3503fe5961aSDevin Teske if (*r < 0)
3513fe5961aSDevin Teske return (-1);
3523fe5961aSDevin Teske }
3533fe5961aSDevin Teske return (0);
3543fe5961aSDevin Teske }
3553fe5961aSDevin Teske
3563fe5961aSDevin Teske /*
3573fe5961aSDevin Teske * Parse the configuration data on the open file descriptor `fd' and execute
3583fe5961aSDevin Teske * the `parse' call-back functions for any directives defined by the array of
3593fe5961aSDevin Teske * config options (first argument).
3603fe5961aSDevin Teske *
3613fe5961aSDevin Teske * For unknown directives that are encountered, you can optionally pass a
3623fe5961aSDevin Teske * call-back function for the third argument to be called for unknowns.
3633fe5961aSDevin Teske *
3643fe5961aSDevin Teske * The scanner requires a seekable descriptor; input that cannot seek (a
3653fe5961aSDevin Teske * pipe or socket, standard input included) is detected up front and spooled
3663fe5961aSDevin Teske * through bsdconf_spool() above, parsed from the temporary, and costs one
3673fe5961aSDevin Teske * transient copy of the data. The descriptor is left positioned at
3683fe5961aSDevin Teske * end-of-file (non-seekable input is left drained) and remains open (the
3693fe5961aSDevin Teske * caller retains ownership).
3703fe5961aSDevin Teske *
3713fe5961aSDevin Teske * Returns zero on success; otherwise returns -1 (or the non-zero result of a
3723fe5961aSDevin Teske * call-back) and errno should be consulted.
3733fe5961aSDevin Teske */
3743fe5961aSDevin Teske int
bsdconf_fparse(struct bsdconf_option options[],int fd,int (* unknown)(struct bsdconf_option * option,uint32_t line,char * directive,char * value),uint16_t processing_options)3753fe5961aSDevin Teske bsdconf_fparse(struct bsdconf_option options[], int fd,
3763fe5961aSDevin Teske int (*unknown)(struct bsdconf_option *option, uint32_t line,
3773fe5961aSDevin Teske char *directive, char *value), uint16_t processing_options)
3783fe5961aSDevin Teske {
3793fe5961aSDevin Teske bool bequals;
3803fe5961aSDevin Teske bool bsemicolon;
3813fe5961aSDevin Teske bool case_sensitive;
3823fe5961aSDevin Teske bool operator_equals;
3833fe5961aSDevin Teske bool require_equals;
3843fe5961aSDevin Teske bool strict_equals;
3853fe5961aSDevin Teske uint8_t comment = 0;
3863fe5961aSDevin Teske uint8_t ecomment;
3873fe5961aSDevin Teske uint8_t found;
3883fe5961aSDevin Teske uint8_t have_equals = 0;
3893fe5961aSDevin Teske char p[2];
3903fe5961aSDevin Teske char *directive = NULL;
3913fe5961aSDevin Teske char *t;
3923fe5961aSDevin Teske char *value = NULL;
3933fe5961aSDevin Teske enum bsdconf_op op;
3943fe5961aSDevin Teske int error;
3953fe5961aSDevin Teske int rv = 0;
3963fe5961aSDevin Teske int spoolfd = -1;
3973fe5961aSDevin Teske ssize_t r = 1;
3983fe5961aSDevin Teske uint32_t dline;
3993fe5961aSDevin Teske uint32_t dsize = 0;
4003fe5961aSDevin Teske uint32_t line = 1;
4013fe5961aSDevin Teske uint32_t n;
4023fe5961aSDevin Teske uint32_t vsize = 0;
4033fe5961aSDevin Teske uint32_t x;
4043fe5961aSDevin Teske off_t charpos;
4053fe5961aSDevin Teske off_t curpos;
4063fe5961aSDevin Teske
4073fe5961aSDevin Teske /* Sanity check: if no options and no unknown function, return */
4083fe5961aSDevin Teske if (options == NULL && unknown == NULL) {
4093fe5961aSDevin Teske errno = EINVAL;
4103fe5961aSDevin Teske return (-1);
4113fe5961aSDevin Teske }
4123fe5961aSDevin Teske
4133fe5961aSDevin Teske /* Spool input that cannot seek (see bsdconf_spool() above) */
4143fe5961aSDevin Teske if (lseek(fd, 0, SEEK_CUR) == -1) {
4153fe5961aSDevin Teske if (errno != ESPIPE)
4163fe5961aSDevin Teske return (-1);
4173fe5961aSDevin Teske if ((spoolfd = bsdconf_spool(fd)) == -1)
4183fe5961aSDevin Teske return (-1);
4193fe5961aSDevin Teske fd = spoolfd;
4203fe5961aSDevin Teske }
4213fe5961aSDevin Teske
4223fe5961aSDevin Teske /* Processing options */
423*90ad63b5SDevin Teske bequals = processing_options & BSDCONF_BREAK_ON_EQUALS;
424*90ad63b5SDevin Teske bsemicolon = processing_options & BSDCONF_BREAK_ON_SEMICOLON;
425*90ad63b5SDevin Teske case_sensitive = processing_options & BSDCONF_CASE_SENSITIVE;
426*90ad63b5SDevin Teske operator_equals = processing_options & BSDCONF_OPERATOR_EQUALS;
427*90ad63b5SDevin Teske require_equals = processing_options & BSDCONF_REQUIRE_EQUALS;
428*90ad63b5SDevin Teske strict_equals = processing_options & BSDCONF_STRICT_EQUALS;
4293fe5961aSDevin Teske
4303fe5961aSDevin Teske /* Read the file until EOF */
4313fe5961aSDevin Teske while (r > 0) {
4323fe5961aSDevin Teske r = bsdconf_read1(fd, p);
4333fe5961aSDevin Teske if (r < 0)
4343fe5961aSDevin Teske goto fail;
4353fe5961aSDevin Teske
4363fe5961aSDevin Teske /* Skip to the beginning of a directive */
4373fe5961aSDevin Teske while (r > 0 && (isspace((unsigned char)*p) || *p == '#' ||
4383fe5961aSDevin Teske comment || (bsemicolon && *p == ';'))) {
4393fe5961aSDevin Teske if (*p == '#')
4403fe5961aSDevin Teske comment = 1;
4413fe5961aSDevin Teske else if (*p == '\n') {
4423fe5961aSDevin Teske comment = 0;
4433fe5961aSDevin Teske line++;
4443fe5961aSDevin Teske }
4453fe5961aSDevin Teske r = bsdconf_read1(fd, p);
4463fe5961aSDevin Teske if (r < 0)
4473fe5961aSDevin Teske goto fail;
4483fe5961aSDevin Teske }
4493fe5961aSDevin Teske /* Test for EOF; if EOF then no directive was found */
4503fe5961aSDevin Teske if (r == 0)
4513fe5961aSDevin Teske goto cleanup;
4523fe5961aSDevin Teske
4533fe5961aSDevin Teske /* Record the line number the directive appears on */
4543fe5961aSDevin Teske dline = line;
4553fe5961aSDevin Teske
4563fe5961aSDevin Teske /* Get the current offset */
4573fe5961aSDevin Teske if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1)
4583fe5961aSDevin Teske goto fail;
4593fe5961aSDevin Teske curpos--;
4603fe5961aSDevin Teske
4613fe5961aSDevin Teske /* Find the length of the directive */
4623fe5961aSDevin Teske for (n = 0; r > 0; n++) {
4633fe5961aSDevin Teske if (isspace((unsigned char)*p))
4643fe5961aSDevin Teske break;
4653fe5961aSDevin Teske if (bequals && *p == '=') {
4663fe5961aSDevin Teske have_equals = 1;
4673fe5961aSDevin Teske break;
4683fe5961aSDevin Teske }
4693fe5961aSDevin Teske if (bsemicolon && *p == ';')
4703fe5961aSDevin Teske break;
4713fe5961aSDevin Teske r = bsdconf_read1(fd, p);
4723fe5961aSDevin Teske if (r < 0)
4733fe5961aSDevin Teske goto fail;
4743fe5961aSDevin Teske }
4753fe5961aSDevin Teske
4763fe5961aSDevin Teske /* Test for EOF, if EOF then no directive was found */
4773fe5961aSDevin Teske if (n == 0 && r == 0)
4783fe5961aSDevin Teske goto cleanup;
4793fe5961aSDevin Teske
4803fe5961aSDevin Teske /* Go back to the beginning of the directive */
4813fe5961aSDevin Teske if (lseek(fd, curpos, SEEK_SET) == -1)
4823fe5961aSDevin Teske goto fail;
4833fe5961aSDevin Teske
4843fe5961aSDevin Teske /*
4853fe5961aSDevin Teske * Allocate and read the directive into memory. The buffer
4863fe5961aSDevin Teske * must be grown on the first pass (directive == NULL) even
4873fe5961aSDevin Teske * when the name is empty (a line beginning with `='), lest
4883fe5961aSDevin Teske * the string terminator below store through a NULL pointer.
4893fe5961aSDevin Teske */
4903fe5961aSDevin Teske if (directive == NULL || n > dsize) {
4913fe5961aSDevin Teske if ((t = realloc(directive, n + 1)) == NULL)
4923fe5961aSDevin Teske goto fail;
4933fe5961aSDevin Teske directive = t;
4943fe5961aSDevin Teske dsize = n;
4953fe5961aSDevin Teske }
4963fe5961aSDevin Teske if (bsdconf_readn(fd, directive, n) != 0)
4973fe5961aSDevin Teske goto fail;
4983fe5961aSDevin Teske
4993fe5961aSDevin Teske /* Advance beyond the equals sign if appropriate/desired */
5003fe5961aSDevin Teske if (bequals && *p == '=') {
5013fe5961aSDevin Teske if (lseek(fd, 1, SEEK_CUR) != -1) {
5023fe5961aSDevin Teske r = bsdconf_read1(fd, p);
5033fe5961aSDevin Teske if (r < 0)
5043fe5961aSDevin Teske goto fail;
5053fe5961aSDevin Teske }
5063fe5961aSDevin Teske if (strict_equals && isspace((unsigned char)*p))
5073fe5961aSDevin Teske *p = '\n';
5083fe5961aSDevin Teske }
5093fe5961aSDevin Teske
5103fe5961aSDevin Teske /* Terminate the string */
5113fe5961aSDevin Teske directive[n] = '\0';
5123fe5961aSDevin Teske
5133fe5961aSDevin Teske /*
5143fe5961aSDevin Teske * Split a make(1)-style operator (`+=' `?=' `:=' `!=') off
5153fe5961aSDevin Teske * the tail of the directive if requested. The operator
5163fe5961aSDevin Teske * character rode along with the directive because only the
5173fe5961aSDevin Teske * `=' terminates the directive scan (above).
5183fe5961aSDevin Teske */
5193fe5961aSDevin Teske op = have_equals ? BSDCONF_OP_ASSIGN : BSDCONF_OP_DEFAULT;
5203fe5961aSDevin Teske if (operator_equals && have_equals && n > 1) {
5213fe5961aSDevin Teske switch (directive[n - 1]) {
5223fe5961aSDevin Teske case '+': op = BSDCONF_OP_APPEND; break;
5233fe5961aSDevin Teske case '?': op = BSDCONF_OP_COND; break;
5243fe5961aSDevin Teske case ':': op = BSDCONF_OP_EXPAND; break;
5253fe5961aSDevin Teske case '!': op = BSDCONF_OP_SHELL; break;
5263fe5961aSDevin Teske }
5273fe5961aSDevin Teske if (op != BSDCONF_OP_ASSIGN)
5283fe5961aSDevin Teske directive[--n] = '\0';
5293fe5961aSDevin Teske }
5303fe5961aSDevin Teske
5313fe5961aSDevin Teske /* Convert directive to lower case before comparison */
5323fe5961aSDevin Teske if (!case_sensitive)
5333fe5961aSDevin Teske bsdconf_strtolower(directive);
5343fe5961aSDevin Teske
5353fe5961aSDevin Teske /* Move to what may be the start of the value */
5363fe5961aSDevin Teske if (!(bsemicolon && *p == ';') &&
5373fe5961aSDevin Teske !(strict_equals && *p == '=')) {
5383fe5961aSDevin Teske if (bsdconf_skip_hspace(fd, p, &r) != 0)
5393fe5961aSDevin Teske goto fail;
5403fe5961aSDevin Teske }
5413fe5961aSDevin Teske
5423fe5961aSDevin Teske /* An equals sign may have stopped us, should we eat it? */
5433fe5961aSDevin Teske if (r > 0 && bequals && *p == '=' && !strict_equals) {
5443fe5961aSDevin Teske have_equals = 1;
5453fe5961aSDevin Teske r = bsdconf_read1(fd, p);
5463fe5961aSDevin Teske if (r < 0)
5473fe5961aSDevin Teske goto fail;
5483fe5961aSDevin Teske if (bsdconf_skip_hspace(fd, p, &r) != 0)
5493fe5961aSDevin Teske goto fail;
5503fe5961aSDevin Teske }
5513fe5961aSDevin Teske
5523fe5961aSDevin Teske /* If no value, allocate a dummy value and jump to action */
5533fe5961aSDevin Teske if (r == 0 || *p == '\n' || *p == '#' ||
5543fe5961aSDevin Teske (bsemicolon && *p == ';')) {
5553fe5961aSDevin Teske /* Count the consumed terminator if a newline */
5563fe5961aSDevin Teske if (r > 0 && *p == '\n')
5573fe5961aSDevin Teske line++;
5583fe5961aSDevin Teske /* Flag a trailing comment so it is skipped */
5593fe5961aSDevin Teske if (r > 0 && *p == '#')
5603fe5961aSDevin Teske comment = 1;
5613fe5961aSDevin Teske /* Initialize the value if not already done */
5623fe5961aSDevin Teske if (value == NULL && (value = malloc(1)) == NULL)
5633fe5961aSDevin Teske goto fail;
5643fe5961aSDevin Teske value[0] = '\0';
5653fe5961aSDevin Teske goto call_function;
5663fe5961aSDevin Teske }
5673fe5961aSDevin Teske
5683fe5961aSDevin Teske /* Get the current offset */
5693fe5961aSDevin Teske if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1)
5703fe5961aSDevin Teske goto fail;
5713fe5961aSDevin Teske curpos--;
5723fe5961aSDevin Teske
5733fe5961aSDevin Teske /* Find the end of the value */
5743fe5961aSDevin Teske if (bsdconf_scan_value_end(fd, p, &r, &line, &comment,
5753fe5961aSDevin Teske &ecomment, bsemicolon) != 0)
5763fe5961aSDevin Teske goto fail;
5773fe5961aSDevin Teske
5783fe5961aSDevin Teske /* Get the current offset */
5793fe5961aSDevin Teske if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1)
5803fe5961aSDevin Teske goto fail;
5813fe5961aSDevin Teske
5823fe5961aSDevin Teske /* Get the length of the value */
5833fe5961aSDevin Teske n = (uint32_t)(charpos - curpos);
5843fe5961aSDevin Teske if (r > 0) /* more to read, but don't read ending key */
5853fe5961aSDevin Teske n--;
5863fe5961aSDevin Teske
5873fe5961aSDevin Teske /* Move offset back to the beginning of the value */
5883fe5961aSDevin Teske if (lseek(fd, curpos, SEEK_SET) == -1)
5893fe5961aSDevin Teske goto fail;
5903fe5961aSDevin Teske
5913fe5961aSDevin Teske /* Allocate and read the value into memory */
5923fe5961aSDevin Teske if (n > vsize) {
5933fe5961aSDevin Teske if ((t = realloc(value, n + 1)) == NULL)
5943fe5961aSDevin Teske goto fail;
5953fe5961aSDevin Teske value = t;
5963fe5961aSDevin Teske vsize = n;
5973fe5961aSDevin Teske }
5983fe5961aSDevin Teske if (bsdconf_readn(fd, value, n) != 0)
5993fe5961aSDevin Teske goto fail;
6003fe5961aSDevin Teske
6013fe5961aSDevin Teske /* Terminate the string */
6023fe5961aSDevin Teske value[n] = '\0';
6033fe5961aSDevin Teske
6043fe5961aSDevin Teske /* Cut trailing whitespace and a trailing `#' / `;' key */
6053fe5961aSDevin Teske t = bsdconf_rtrim_ws(value, value + n);
6063fe5961aSDevin Teske t = bsdconf_trim_value_key(value, t, ecomment != 0,
6073fe5961aSDevin Teske bsemicolon);
6083fe5961aSDevin Teske
6093fe5961aSDevin Teske /* Escape the escaped quotes (see bsdconf_string.c) */
6103fe5961aSDevin Teske x = bsdconf_strcount(value, "\\\"");
6113fe5961aSDevin Teske if (x != 0 && (n + x) > vsize) {
6123fe5961aSDevin Teske if ((t = realloc(value, n + x + 1)) == NULL)
6133fe5961aSDevin Teske goto fail;
6143fe5961aSDevin Teske value = t;
6153fe5961aSDevin Teske vsize = n + x;
6163fe5961aSDevin Teske }
6173fe5961aSDevin Teske if (bsdconf_replaceall(value, vsize + 1, "\\\"", "\\\\\"") < 0)
6183fe5961aSDevin Teske goto fail;
6193fe5961aSDevin Teske
6203fe5961aSDevin Teske /* Remove all escaped newline characters */
6213fe5961aSDevin Teske if (bsdconf_replaceall(value, vsize + 1, "\\\n", "") < 0)
6223fe5961aSDevin Teske goto fail;
6233fe5961aSDevin Teske
6243fe5961aSDevin Teske /* Resolve escape sequences */
6253fe5961aSDevin Teske bsdconf_strunexpand(value, value);
6263fe5961aSDevin Teske
6273fe5961aSDevin Teske call_function:
6283fe5961aSDevin Teske /* Abort if we're seeking only assignments */
6293fe5961aSDevin Teske if (require_equals && !have_equals) {
6303fe5961aSDevin Teske errno = EINVAL;
6313fe5961aSDevin Teske goto fail;
6323fe5961aSDevin Teske }
6333fe5961aSDevin Teske
6343fe5961aSDevin Teske found = have_equals = 0; /* reset */
6353fe5961aSDevin Teske
6363fe5961aSDevin Teske /*
6373fe5961aSDevin Teske * Report the statement's assignment operator through a
6383fe5961aSDevin Teske * stack-local option when invoking the unknown call-back
6393fe5961aSDevin Teske * (there is no matched options[] slot to hang it on).
6403fe5961aSDevin Teske */
6413fe5961aSDevin Teske if (options == NULL && unknown != NULL) {
6423fe5961aSDevin Teske error = bsdconf_call_unknown(unknown, op, dline,
6433fe5961aSDevin Teske directive, value);
6443fe5961aSDevin Teske if (error != 0) {
6453fe5961aSDevin Teske rv = error;
6463fe5961aSDevin Teske goto cleanup;
6473fe5961aSDevin Teske }
6483fe5961aSDevin Teske continue;
6493fe5961aSDevin Teske }
6503fe5961aSDevin Teske
6513fe5961aSDevin Teske /* Loop through the array looking for a match */
6523fe5961aSDevin Teske for (n = 0; options[n].directive != NULL; n++) {
6533fe5961aSDevin Teske error = fnmatch(options[n].directive, directive,
6543fe5961aSDevin Teske FNM_NOESCAPE);
6553fe5961aSDevin Teske if (error == 0) {
6563fe5961aSDevin Teske found = 1;
6573fe5961aSDevin Teske /* Call function for array index item */
6583fe5961aSDevin Teske options[n].op = op;
6593fe5961aSDevin Teske if (options[n].parse != NULL) {
6603fe5961aSDevin Teske error = options[n].parse(&options[n],
6613fe5961aSDevin Teske dline, directive, value);
6623fe5961aSDevin Teske if (error != 0) {
6633fe5961aSDevin Teske rv = error;
6643fe5961aSDevin Teske goto cleanup;
6653fe5961aSDevin Teske }
6663fe5961aSDevin Teske }
6673fe5961aSDevin Teske } else if (error != FNM_NOMATCH) {
6683fe5961aSDevin Teske /* An error has occurred */
6693fe5961aSDevin Teske errno = EINVAL;
6703fe5961aSDevin Teske goto fail;
6713fe5961aSDevin Teske }
6723fe5961aSDevin Teske }
6733fe5961aSDevin Teske if (!found && unknown != NULL) {
6743fe5961aSDevin Teske /*
6753fe5961aSDevin Teske * No match was found for the value we read from the
6763fe5961aSDevin Teske * file; call function designated for unknown values.
6773fe5961aSDevin Teske */
6783fe5961aSDevin Teske error = bsdconf_call_unknown(unknown, op, dline,
6793fe5961aSDevin Teske directive, value);
6803fe5961aSDevin Teske if (error != 0) {
6813fe5961aSDevin Teske rv = error;
6823fe5961aSDevin Teske goto cleanup;
6833fe5961aSDevin Teske }
6843fe5961aSDevin Teske }
6853fe5961aSDevin Teske }
6863fe5961aSDevin Teske
6873fe5961aSDevin Teske goto cleanup;
6883fe5961aSDevin Teske
6893fe5961aSDevin Teske fail:
6903fe5961aSDevin Teske rv = -1;
6913fe5961aSDevin Teske
6923fe5961aSDevin Teske cleanup:
6933fe5961aSDevin Teske x = errno; /* preserve errno across free(3) and close(2) */
6943fe5961aSDevin Teske if (spoolfd != -1)
6953fe5961aSDevin Teske close(spoolfd);
6963fe5961aSDevin Teske free(directive);
6973fe5961aSDevin Teske free(value);
6983fe5961aSDevin Teske errno = x;
6993fe5961aSDevin Teske
7003fe5961aSDevin Teske return (rv);
7013fe5961aSDevin Teske }
7023fe5961aSDevin Teske
7033fe5961aSDevin Teske /*
7043fe5961aSDevin Teske * Parse the configuration file at `path' and execute the `parse' call-back
7053fe5961aSDevin Teske * functions for any directives defined by the array of config options (first
7063fe5961aSDevin Teske * argument). This is a convenience wrapper around bsdconf_fparse() above.
7073fe5961aSDevin Teske *
7083fe5961aSDevin Teske * Returns zero on success; otherwise returns -1 (or the non-zero result of a
7093fe5961aSDevin Teske * call-back) and errno should be consulted.
7103fe5961aSDevin Teske */
7113fe5961aSDevin Teske int
bsdconf_parse(struct bsdconf_option options[],const char * path,int (* unknown)(struct bsdconf_option * option,uint32_t line,char * directive,char * value),uint16_t processing_options)7123fe5961aSDevin Teske bsdconf_parse(struct bsdconf_option options[], const char *path,
7133fe5961aSDevin Teske int (*unknown)(struct bsdconf_option *option, uint32_t line,
7143fe5961aSDevin Teske char *directive, char *value), uint16_t processing_options)
7153fe5961aSDevin Teske {
7163fe5961aSDevin Teske int error;
7173fe5961aSDevin Teske int fd;
7183fe5961aSDevin Teske
7193fe5961aSDevin Teske /* Sanity check: if no options and no unknown function, return */
7203fe5961aSDevin Teske if (path == NULL || (options == NULL && unknown == NULL)) {
7213fe5961aSDevin Teske errno = EINVAL;
7223fe5961aSDevin Teske return (-1);
7233fe5961aSDevin Teske }
7243fe5961aSDevin Teske
7253fe5961aSDevin Teske /* Open the file */
7263fe5961aSDevin Teske if ((fd = open(path, O_RDONLY)) < 0)
7273fe5961aSDevin Teske return (-1);
7283fe5961aSDevin Teske
7293fe5961aSDevin Teske error = bsdconf_fparse(options, fd, unknown, processing_options);
7303fe5961aSDevin Teske
7313fe5961aSDevin Teske close(fd);
7323fe5961aSDevin Teske return (error);
7333fe5961aSDevin Teske }
734