xref: /freebsd/lib/libbsdconf/bsdconf.c (revision 90ad63b540317ce7c349b5ebab065b40ba748c27)
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