xref: /freebsd/lib/libbsdconf/bsdconf_format.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
1*3fe5961aSDevin Teske /*
2*3fe5961aSDevin Teske  * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3*3fe5961aSDevin Teske  * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4*3fe5961aSDevin Teske  *
5*3fe5961aSDevin Teske  * SPDX-License-Identifier: BSD-2-Clause
6*3fe5961aSDevin Teske  */
7*3fe5961aSDevin Teske 
8*3fe5961aSDevin Teske #include <sys/stat.h>
9*3fe5961aSDevin Teske 
10*3fe5961aSDevin Teske #include <dirent.h>
11*3fe5961aSDevin Teske #include <errno.h>
12*3fe5961aSDevin Teske #include <libgen.h>
13*3fe5961aSDevin Teske #include <limits.h>
14*3fe5961aSDevin Teske #include <stdio.h>
15*3fe5961aSDevin Teske #include <stdlib.h>
16*3fe5961aSDevin Teske #include <string.h>
17*3fe5961aSDevin Teske 
18*3fe5961aSDevin Teske #include "bsdconf.h"
19*3fe5961aSDevin Teske #include "bsdconf_formats.h"
20*3fe5961aSDevin Teske 
21*3fe5961aSDevin Teske /*
22*3fe5961aSDevin Teske  * The format abstraction layer registry.
23*3fe5961aSDevin Teske  *
24*3fe5961aSDevin Teske  * Every configuration file format -- built-in or bolted on at run-time -- is
25*3fe5961aSDevin Teske  * described by the same small descriptor (struct bsdconf_format_def in
26*3fe5961aSDevin Teske  * bsdconf.h): a target keyword, a default write path, an ordered list of
27*3fe5961aSDevin Teske  * configuration sources, and the processing/put bitmasks that drive the
28*3fe5961aSDevin Teske  * shared parsing and writing cores. There is exactly one engine; a format
29*3fe5961aSDevin Teske  * merely parameterizes it.
30*3fe5961aSDevin Teske  *
31*3fe5961aSDevin Teske  * Each built-in format is a self-contained translation unit
32*3fe5961aSDevin Teske  * (bsdconf_format_<keyword>.c) documenting and defining its own descriptor;
33*3fe5961aSDevin Teske  * this file only collects the descriptors into the table indexed by enum
34*3fe5961aSDevin Teske  * bsdconf_format and resolves keywords, basenames, and source lists against
35*3fe5961aSDevin Teske  * it (see bsdconf_formats.h for the recipe to add a format).
36*3fe5961aSDevin Teske  *
37*3fe5961aSDevin Teske  * A format that is "mostly like" an existing one need not be added at all:
38*3fe5961aSDevin Teske  * an application calls bsdconf_format_derive() to inherit the nearest
39*3fe5961aSDevin Teske  * descriptor, adjusts only the members that differ, and registers the
40*3fe5961aSDevin Teske  * result with bsdconf_format_register(). The handle it gets back taps into
41*3fe5961aSDevin Teske  * the same core engine everywhere a built-in format works, including
42*3fe5961aSDevin Teske  * keyword, path, and source-list resolution in sysconf(8).
43*3fe5961aSDevin Teske  */
44*3fe5961aSDevin Teske 
45*3fe5961aSDevin Teske static const struct bsdconf_format_def *bsdconf_formats[] = {
46*3fe5961aSDevin Teske 	[BSDCONF_FORMAT_GENERIC] =	&bsdconf_format_generic_def,
47*3fe5961aSDevin Teske 	[BSDCONF_FORMAT_LOADER] =	&bsdconf_format_loader_def,
48*3fe5961aSDevin Teske 	[BSDCONF_FORMAT_SYSCTL] =	&bsdconf_format_sysctl_def,
49*3fe5961aSDevin Teske 	[BSDCONF_FORMAT_MAKE] =		&bsdconf_format_make_def,
50*3fe5961aSDevin Teske 	[BSDCONF_FORMAT_SRC] =		&bsdconf_format_src_def,
51*3fe5961aSDevin Teske };
52*3fe5961aSDevin Teske #define BSDCONF_NFORMATS \
53*3fe5961aSDevin Teske 	(sizeof(bsdconf_formats) / sizeof(*bsdconf_formats))
54*3fe5961aSDevin Teske 
55*3fe5961aSDevin Teske #define BSDCONF_CONF_SUFFIX		".conf"
56*3fe5961aSDevin Teske #define BSDCONF_CONF_SUFFIX_LEN		(sizeof(BSDCONF_CONF_SUFFIX) - 1)
57*3fe5961aSDevin Teske 
58*3fe5961aSDevin Teske /*
59*3fe5961aSDevin Teske  * Registered (bolt-on) formats. Descriptors are copied by value; the
60*3fe5961aSDevin Teske  * keyword and path strings they reference remain owned by the caller and
61*3fe5961aSDevin Teske  * must stay valid for the life of the registration.
62*3fe5961aSDevin Teske  */
63*3fe5961aSDevin Teske #define BSDCONF_MAXUSERFORMATS	32
64*3fe5961aSDevin Teske static struct bsdconf_format_def bsdconf_user_formats[BSDCONF_MAXUSERFORMATS];
65*3fe5961aSDevin Teske static unsigned int bsdconf_nuser_formats = 0;
66*3fe5961aSDevin Teske 
67*3fe5961aSDevin Teske /*
68*3fe5961aSDevin Teske  * Return the descriptor for `format' or NULL if the format is neither
69*3fe5961aSDevin Teske  * built-in nor registered.
70*3fe5961aSDevin Teske  */
71*3fe5961aSDevin Teske const struct bsdconf_format_def *
bsdconf_format_lookup(enum bsdconf_format format)72*3fe5961aSDevin Teske bsdconf_format_lookup(enum bsdconf_format format)
73*3fe5961aSDevin Teske {
74*3fe5961aSDevin Teske 	unsigned int n;
75*3fe5961aSDevin Teske 
76*3fe5961aSDevin Teske 	if ((unsigned int)format < BSDCONF_NFORMATS)
77*3fe5961aSDevin Teske 		return (bsdconf_formats[format]);
78*3fe5961aSDevin Teske 
79*3fe5961aSDevin Teske 	n = (unsigned int)format - BSDCONF_FORMAT_USER;
80*3fe5961aSDevin Teske 	if (format >= BSDCONF_FORMAT_USER && n < bsdconf_nuser_formats)
81*3fe5961aSDevin Teske 		return (&bsdconf_user_formats[n]);
82*3fe5961aSDevin Teske 
83*3fe5961aSDevin Teske 	return (NULL);
84*3fe5961aSDevin Teske }
85*3fe5961aSDevin Teske 
86*3fe5961aSDevin Teske /*
87*3fe5961aSDevin Teske  * Copy the descriptor of `base' into `def' so that a caller can adjust only
88*3fe5961aSDevin Teske  * the members that differ before registering the result as a new format.
89*3fe5961aSDevin Teske  * On success, returns zero; otherwise returns -1 (unknown base format) and
90*3fe5961aSDevin Teske  * errno is set to EINVAL.
91*3fe5961aSDevin Teske  */
92*3fe5961aSDevin Teske int
bsdconf_format_derive(enum bsdconf_format base,struct bsdconf_format_def * def)93*3fe5961aSDevin Teske bsdconf_format_derive(enum bsdconf_format base,
94*3fe5961aSDevin Teske     struct bsdconf_format_def *def)
95*3fe5961aSDevin Teske {
96*3fe5961aSDevin Teske 	const struct bsdconf_format_def *found;
97*3fe5961aSDevin Teske 
98*3fe5961aSDevin Teske 	/* Check arguments */
99*3fe5961aSDevin Teske 	if (def == NULL || (found = bsdconf_format_lookup(base)) == NULL) {
100*3fe5961aSDevin Teske 		errno = EINVAL;
101*3fe5961aSDevin Teske 		return (-1);
102*3fe5961aSDevin Teske 	}
103*3fe5961aSDevin Teske 
104*3fe5961aSDevin Teske 	*def = *found;
105*3fe5961aSDevin Teske 	return (0);
106*3fe5961aSDevin Teske }
107*3fe5961aSDevin Teske 
108*3fe5961aSDevin Teske /*
109*3fe5961aSDevin Teske  * Register a new configuration file format described by `def', storing its
110*3fe5961aSDevin Teske  * newly assigned handle through `format'. The descriptor is copied; the
111*3fe5961aSDevin Teske  * keyword and path strings it references are not (see above). On success,
112*3fe5961aSDevin Teske  * returns zero; otherwise returns -1 and errno is set (EINVAL for a bad
113*3fe5961aSDevin Teske  * argument, ENOSPC when the registration table is full).
114*3fe5961aSDevin Teske  */
115*3fe5961aSDevin Teske int
bsdconf_format_register(const struct bsdconf_format_def * def,enum bsdconf_format * format)116*3fe5961aSDevin Teske bsdconf_format_register(const struct bsdconf_format_def *def,
117*3fe5961aSDevin Teske     enum bsdconf_format *format)
118*3fe5961aSDevin Teske {
119*3fe5961aSDevin Teske 
120*3fe5961aSDevin Teske 	/* Check arguments */
121*3fe5961aSDevin Teske 	if (def == NULL || format == NULL) {
122*3fe5961aSDevin Teske 		errno = EINVAL;
123*3fe5961aSDevin Teske 		return (-1);
124*3fe5961aSDevin Teske 	}
125*3fe5961aSDevin Teske 	if (bsdconf_nuser_formats >= BSDCONF_MAXUSERFORMATS) {
126*3fe5961aSDevin Teske 		errno = ENOSPC;
127*3fe5961aSDevin Teske 		return (-1);
128*3fe5961aSDevin Teske 	}
129*3fe5961aSDevin Teske 
130*3fe5961aSDevin Teske 	bsdconf_user_formats[bsdconf_nuser_formats] = *def;
131*3fe5961aSDevin Teske 	*format = (enum bsdconf_format)
132*3fe5961aSDevin Teske 	    (BSDCONF_FORMAT_USER + bsdconf_nuser_formats);
133*3fe5961aSDevin Teske 	bsdconf_nuser_formats++;
134*3fe5961aSDevin Teske 
135*3fe5961aSDevin Teske 	return (0);
136*3fe5961aSDevin Teske }
137*3fe5961aSDevin Teske 
138*3fe5961aSDevin Teske /*
139*3fe5961aSDevin Teske  * Map a target keyword (e.g., "loader") to its format, storing the result
140*3fe5961aSDevin Teske  * through `format'. Registered formats are searched after the built-ins.
141*3fe5961aSDevin Teske  * On success, returns zero; otherwise returns -1 (unknown keyword; `format'
142*3fe5961aSDevin Teske  * is left untouched).
143*3fe5961aSDevin Teske  */
144*3fe5961aSDevin Teske int
bsdconf_format_find(const char * keyword,enum bsdconf_format * format)145*3fe5961aSDevin Teske bsdconf_format_find(const char *keyword, enum bsdconf_format *format)
146*3fe5961aSDevin Teske {
147*3fe5961aSDevin Teske 	unsigned int n;
148*3fe5961aSDevin Teske 
149*3fe5961aSDevin Teske 	/* Check arguments */
150*3fe5961aSDevin Teske 	if (keyword == NULL || format == NULL)
151*3fe5961aSDevin Teske 		return (-1);
152*3fe5961aSDevin Teske 
153*3fe5961aSDevin Teske 	for (n = 0; n < BSDCONF_NFORMATS; n++) {
154*3fe5961aSDevin Teske 		if (bsdconf_formats[n]->keyword != NULL &&
155*3fe5961aSDevin Teske 		    strcmp(bsdconf_formats[n]->keyword, keyword) == 0) {
156*3fe5961aSDevin Teske 			*format = (enum bsdconf_format)n;
157*3fe5961aSDevin Teske 			return (0);
158*3fe5961aSDevin Teske 		}
159*3fe5961aSDevin Teske 	}
160*3fe5961aSDevin Teske 	for (n = 0; n < bsdconf_nuser_formats; n++) {
161*3fe5961aSDevin Teske 		if (bsdconf_user_formats[n].keyword != NULL &&
162*3fe5961aSDevin Teske 		    strcmp(bsdconf_user_formats[n].keyword, keyword) == 0) {
163*3fe5961aSDevin Teske 			*format = (enum bsdconf_format)
164*3fe5961aSDevin Teske 			    (BSDCONF_FORMAT_USER + n);
165*3fe5961aSDevin Teske 			return (0);
166*3fe5961aSDevin Teske 		}
167*3fe5961aSDevin Teske 	}
168*3fe5961aSDevin Teske 
169*3fe5961aSDevin Teske 	return (-1);
170*3fe5961aSDevin Teske }
171*3fe5961aSDevin Teske 
172*3fe5961aSDevin Teske /*
173*3fe5961aSDevin Teske  * Test whether the basename of `path' matches `<keyword>.conf' with an
174*3fe5961aSDevin Teske  * optional trailing suffix (e.g., "sysctl.conf.local" matches "sysctl").
175*3fe5961aSDevin Teske  */
176*3fe5961aSDevin Teske static int
bsdconf_basename_matches(const char * base,const char * keyword)177*3fe5961aSDevin Teske bsdconf_basename_matches(const char *base, const char *keyword)
178*3fe5961aSDevin Teske {
179*3fe5961aSDevin Teske 	size_t klen;
180*3fe5961aSDevin Teske 
181*3fe5961aSDevin Teske 	if (keyword == NULL)
182*3fe5961aSDevin Teske 		return (0);
183*3fe5961aSDevin Teske 	klen = strlen(keyword);
184*3fe5961aSDevin Teske 	if (strncmp(base, keyword, klen) != 0)
185*3fe5961aSDevin Teske 		return (0);
186*3fe5961aSDevin Teske 	return (strncmp(base + klen, BSDCONF_CONF_SUFFIX,
187*3fe5961aSDevin Teske 	    BSDCONF_CONF_SUFFIX_LEN) == 0);
188*3fe5961aSDevin Teske }
189*3fe5961aSDevin Teske 
190*3fe5961aSDevin Teske /*
191*3fe5961aSDevin Teske  * Guess the format of an arbitrary configuration file from the basename of
192*3fe5961aSDevin Teske  * its `path' (e.g., "/etc/sysctl.conf.local" is BSDCONF_FORMAT_SYSCTL).
193*3fe5961aSDevin Teske  * Registered formats are searched after the built-ins. Returns
194*3fe5961aSDevin Teske  * BSDCONF_FORMAT_GENERIC when nothing matches.
195*3fe5961aSDevin Teske  */
196*3fe5961aSDevin Teske enum bsdconf_format
bsdconf_format_guess(const char * path)197*3fe5961aSDevin Teske bsdconf_format_guess(const char *path)
198*3fe5961aSDevin Teske {
199*3fe5961aSDevin Teske 	unsigned int n;
200*3fe5961aSDevin Teske 	char tmp[PATH_MAX];
201*3fe5961aSDevin Teske 	char *base;
202*3fe5961aSDevin Teske 
203*3fe5961aSDevin Teske 	if (path == NULL)
204*3fe5961aSDevin Teske 		return (BSDCONF_FORMAT_GENERIC);
205*3fe5961aSDevin Teske 
206*3fe5961aSDevin Teske 	if (strlen(path) >= sizeof(tmp))
207*3fe5961aSDevin Teske 		return (BSDCONF_FORMAT_GENERIC);
208*3fe5961aSDevin Teske 	memcpy(tmp, path, strlen(path) + 1);
209*3fe5961aSDevin Teske 	base = basename(tmp);
210*3fe5961aSDevin Teske 
211*3fe5961aSDevin Teske 	for (n = 0; n < BSDCONF_NFORMATS; n++)
212*3fe5961aSDevin Teske 		if (bsdconf_basename_matches(base,
213*3fe5961aSDevin Teske 		    bsdconf_formats[n]->keyword))
214*3fe5961aSDevin Teske 			return ((enum bsdconf_format)n);
215*3fe5961aSDevin Teske 	for (n = 0; n < bsdconf_nuser_formats; n++)
216*3fe5961aSDevin Teske 		if (bsdconf_basename_matches(base,
217*3fe5961aSDevin Teske 		    bsdconf_user_formats[n].keyword))
218*3fe5961aSDevin Teske 			return ((enum bsdconf_format)
219*3fe5961aSDevin Teske 			    (BSDCONF_FORMAT_USER + n));
220*3fe5961aSDevin Teske 
221*3fe5961aSDevin Teske 	return (BSDCONF_FORMAT_GENERIC);
222*3fe5961aSDevin Teske }
223*3fe5961aSDevin Teske 
224*3fe5961aSDevin Teske /*
225*3fe5961aSDevin Teske  * Return the default file path for `format' (e.g., "/boot/loader.conf") or
226*3fe5961aSDevin Teske  * NULL if the format has no fixed path (e.g., BSDCONF_FORMAT_GENERIC).
227*3fe5961aSDevin Teske  */
228*3fe5961aSDevin Teske const char *
bsdconf_format_path(enum bsdconf_format format)229*3fe5961aSDevin Teske bsdconf_format_path(enum bsdconf_format format)
230*3fe5961aSDevin Teske {
231*3fe5961aSDevin Teske 	const struct bsdconf_format_def *def;
232*3fe5961aSDevin Teske 
233*3fe5961aSDevin Teske 	if ((def = bsdconf_format_lookup(format)) == NULL)
234*3fe5961aSDevin Teske 		return (NULL);
235*3fe5961aSDevin Teske 	return (def->path);
236*3fe5961aSDevin Teske }
237*3fe5961aSDevin Teske 
238*3fe5961aSDevin Teske /*
239*3fe5961aSDevin Teske  * Return the processing_options bitmask suited to `format', for passing to
240*3fe5961aSDevin Teske  * bsdconf_parse(), bsdconf_fparse(), and bsdconf_put(). Unknown formats
241*3fe5961aSDevin Teske  * fall back to the generic descriptor.
242*3fe5961aSDevin Teske  */
243*3fe5961aSDevin Teske uint16_t
bsdconf_format_processing(enum bsdconf_format format)244*3fe5961aSDevin Teske bsdconf_format_processing(enum bsdconf_format format)
245*3fe5961aSDevin Teske {
246*3fe5961aSDevin Teske 	const struct bsdconf_format_def *def;
247*3fe5961aSDevin Teske 
248*3fe5961aSDevin Teske 	if ((def = bsdconf_format_lookup(format)) == NULL)
249*3fe5961aSDevin Teske 		def = bsdconf_formats[BSDCONF_FORMAT_GENERIC];
250*3fe5961aSDevin Teske 	return (def->processing);
251*3fe5961aSDevin Teske }
252*3fe5961aSDevin Teske 
253*3fe5961aSDevin Teske /*
254*3fe5961aSDevin Teske  * Return the put_options bitmask suited to `format', for passing to
255*3fe5961aSDevin Teske  * bsdconf_put(). Unknown formats fall back to the generic descriptor.
256*3fe5961aSDevin Teske  */
257*3fe5961aSDevin Teske uint16_t
bsdconf_format_put(enum bsdconf_format format)258*3fe5961aSDevin Teske bsdconf_format_put(enum bsdconf_format format)
259*3fe5961aSDevin Teske {
260*3fe5961aSDevin Teske 	const struct bsdconf_format_def *def;
261*3fe5961aSDevin Teske 
262*3fe5961aSDevin Teske 	if ((def = bsdconf_format_lookup(format)) == NULL)
263*3fe5961aSDevin Teske 		def = bsdconf_formats[BSDCONF_FORMAT_GENERIC];
264*3fe5961aSDevin Teske 	return (def->put);
265*3fe5961aSDevin Teske }
266*3fe5961aSDevin Teske 
267*3fe5961aSDevin Teske /*
268*3fe5961aSDevin Teske  * Append `path' (already allocated; ownership is taken) to the growing
269*3fe5961aSDevin Teske  * `files' array. On success, returns zero; otherwise returns -1 (errno set
270*3fe5961aSDevin Teske  * by realloc(3)) and `path' is freed.
271*3fe5961aSDevin Teske  */
272*3fe5961aSDevin Teske static int
bsdconf_files_add(char *** files,size_t * nfiles,size_t * size,char * path)273*3fe5961aSDevin Teske bsdconf_files_add(char ***files, size_t *nfiles, size_t *size, char *path)
274*3fe5961aSDevin Teske {
275*3fe5961aSDevin Teske 	char **tmp;
276*3fe5961aSDevin Teske 
277*3fe5961aSDevin Teske 	if (path == NULL)
278*3fe5961aSDevin Teske 		return (-1);
279*3fe5961aSDevin Teske 	if (*nfiles >= *size) {
280*3fe5961aSDevin Teske 		*size = (*size == 0) ? 8 : *size << 1;
281*3fe5961aSDevin Teske 		tmp = realloc(*files, *size * sizeof(**files));
282*3fe5961aSDevin Teske 		if (tmp == NULL) {
283*3fe5961aSDevin Teske 			free(path);
284*3fe5961aSDevin Teske 			return (-1);
285*3fe5961aSDevin Teske 		}
286*3fe5961aSDevin Teske 		*files = tmp;
287*3fe5961aSDevin Teske 	}
288*3fe5961aSDevin Teske 	(*files)[(*nfiles)++] = path;
289*3fe5961aSDevin Teske 	return (0);
290*3fe5961aSDevin Teske }
291*3fe5961aSDevin Teske 
292*3fe5961aSDevin Teske /*
293*3fe5961aSDevin Teske  * scandir(3) filter accepting non-hidden `*.conf' entries.
294*3fe5961aSDevin Teske  */
295*3fe5961aSDevin Teske static int
bsdconf_files_filter(const struct dirent * entry)296*3fe5961aSDevin Teske bsdconf_files_filter(const struct dirent *entry)
297*3fe5961aSDevin Teske {
298*3fe5961aSDevin Teske 	size_t len;
299*3fe5961aSDevin Teske 
300*3fe5961aSDevin Teske 	if (entry->d_name[0] == '.')
301*3fe5961aSDevin Teske 		return (0);
302*3fe5961aSDevin Teske 	len = strlen(entry->d_name);
303*3fe5961aSDevin Teske 	return (len > BSDCONF_CONF_SUFFIX_LEN &&
304*3fe5961aSDevin Teske 	    strcmp(entry->d_name + len - BSDCONF_CONF_SUFFIX_LEN,
305*3fe5961aSDevin Teske 	    BSDCONF_CONF_SUFFIX) == 0);
306*3fe5961aSDevin Teske }
307*3fe5961aSDevin Teske 
308*3fe5961aSDevin Teske /*
309*3fe5961aSDevin Teske  * Compose the full path `prefix' + `path' [+ `/' + `name' [+ `.conf']] into
310*3fe5961aSDevin Teske  * newly allocated storage. Returns NULL on allocation failure (errno set).
311*3fe5961aSDevin Teske  *
312*3fe5961aSDevin Teske  * asprintf(3) is not in POSIX.1-2008; size with snprintf(NULL, 0) then fill
313*3fe5961aSDevin Teske  * so the library still compiles under _POSIX_C_SOURCE=200809L.
314*3fe5961aSDevin Teske  */
315*3fe5961aSDevin Teske static char *
bsdconf_files_path(const char * prefix,const char * path,const char * name,const char * suffix)316*3fe5961aSDevin Teske bsdconf_files_path(const char *prefix, const char *path, const char *name,
317*3fe5961aSDevin Teske     const char *suffix)
318*3fe5961aSDevin Teske {
319*3fe5961aSDevin Teske 	char *full;
320*3fe5961aSDevin Teske 	int len;
321*3fe5961aSDevin Teske 
322*3fe5961aSDevin Teske 	len = snprintf(NULL, 0, "%s%s%s%s%s", prefix, path,
323*3fe5961aSDevin Teske 	    name != NULL ? "/" : "", name != NULL ? name : "",
324*3fe5961aSDevin Teske 	    suffix != NULL ? suffix : "");
325*3fe5961aSDevin Teske 	if (len < 0 || (full = malloc((size_t)len + 1)) == NULL)
326*3fe5961aSDevin Teske 		return (NULL);
327*3fe5961aSDevin Teske 	snprintf(full, (size_t)len + 1, "%s%s%s%s%s", prefix, path,
328*3fe5961aSDevin Teske 	    name != NULL ? "/" : "", name != NULL ? name : "",
329*3fe5961aSDevin Teske 	    suffix != NULL ? suffix : "");
330*3fe5961aSDevin Teske 	return (full);
331*3fe5961aSDevin Teske }
332*3fe5961aSDevin Teske 
333*3fe5961aSDevin Teske /*
334*3fe5961aSDevin Teske  * Append each existing `*.conf' entry of directory `path' (prefixed with
335*3fe5961aSDevin Teske  * `prefix'), sorted, to the growing `files' array. A missing or unreadable
336*3fe5961aSDevin Teske  * directory is not an error (a drop-in directory is optional by nature).
337*3fe5961aSDevin Teske  * On success, returns zero; otherwise returns -1 (errno set).
338*3fe5961aSDevin Teske  */
339*3fe5961aSDevin Teske static int
bsdconf_files_add_dir(char *** files,size_t * nfiles,size_t * size,const char * prefix,const char * path)340*3fe5961aSDevin Teske bsdconf_files_add_dir(char ***files, size_t *nfiles, size_t *size,
341*3fe5961aSDevin Teske     const char *prefix, const char *path)
342*3fe5961aSDevin Teske {
343*3fe5961aSDevin Teske 	int n, nentries;
344*3fe5961aSDevin Teske 	char *dir;
345*3fe5961aSDevin Teske 	char *full;
346*3fe5961aSDevin Teske 	struct dirent **entries;
347*3fe5961aSDevin Teske 
348*3fe5961aSDevin Teske 	if ((dir = bsdconf_files_path(prefix, path, NULL, NULL)) == NULL)
349*3fe5961aSDevin Teske 		return (-1);
350*3fe5961aSDevin Teske 	nentries = scandir(dir, &entries, bsdconf_files_filter, alphasort);
351*3fe5961aSDevin Teske 	free(dir);
352*3fe5961aSDevin Teske 	if (nentries < 0)
353*3fe5961aSDevin Teske 		return (0);
354*3fe5961aSDevin Teske 	for (n = 0; n < nentries; n++) {
355*3fe5961aSDevin Teske 		full = bsdconf_files_path(prefix, path, entries[n]->d_name,
356*3fe5961aSDevin Teske 		    NULL);
357*3fe5961aSDevin Teske 		if (bsdconf_files_add(files, nfiles, size, full) != 0) {
358*3fe5961aSDevin Teske 			while (n < nentries)
359*3fe5961aSDevin Teske 				free(entries[n++]);
360*3fe5961aSDevin Teske 			free(entries);
361*3fe5961aSDevin Teske 			return (-1);
362*3fe5961aSDevin Teske 		}
363*3fe5961aSDevin Teske 		free(entries[n]);
364*3fe5961aSDevin Teske 	}
365*3fe5961aSDevin Teske 	free(entries);
366*3fe5961aSDevin Teske 	return (0);
367*3fe5961aSDevin Teske }
368*3fe5961aSDevin Teske 
369*3fe5961aSDevin Teske /*
370*3fe5961aSDevin Teske  * Parse call-back for the list directives chased by
371*3fe5961aSDevin Teske  * bsdconf_files_discover() below. The option's `value.data' member points
372*3fe5961aSDevin Teske  * at the (char *) slot holding the current value of the directive; each
373*3fe5961aSDevin Teske  * assignment encountered replaces the slot (last assignment wins, exactly
374*3fe5961aSDevin Teske  * as the consumer of the file behaves).
375*3fe5961aSDevin Teske  */
376*3fe5961aSDevin Teske static int
bsdconf_files_list_cb(struct bsdconf_option * option,uint32_t line,char * directive,char * value)377*3fe5961aSDevin Teske bsdconf_files_list_cb(struct bsdconf_option *option, uint32_t line,
378*3fe5961aSDevin Teske     char *directive, char *value)
379*3fe5961aSDevin Teske {
380*3fe5961aSDevin Teske 	char *copy;
381*3fe5961aSDevin Teske 	char **slot = (char **)option->value.data;
382*3fe5961aSDevin Teske 
383*3fe5961aSDevin Teske 	(void)line;
384*3fe5961aSDevin Teske 	(void)directive;
385*3fe5961aSDevin Teske 
386*3fe5961aSDevin Teske 	if ((copy = strdup(bsdconf_unquote(value))) == NULL)
387*3fe5961aSDevin Teske 		return (-1);
388*3fe5961aSDevin Teske 	free(*slot);
389*3fe5961aSDevin Teske 	*slot = copy;
390*3fe5961aSDevin Teske 	return (0);
391*3fe5961aSDevin Teske }
392*3fe5961aSDevin Teske 
393*3fe5961aSDevin Teske /*
394*3fe5961aSDevin Teske  * Advance to the next whitespace-separated token of `list' at or beyond
395*3fe5961aSDevin Teske  * `cursor', storing its length through `lenp'. Returns the token or NULL
396*3fe5961aSDevin Teske  * when the list is exhausted.
397*3fe5961aSDevin Teske  */
398*3fe5961aSDevin Teske static const char *
bsdconf_files_token(const char * cursor,size_t * lenp)399*3fe5961aSDevin Teske bsdconf_files_token(const char *cursor, size_t *lenp)
400*3fe5961aSDevin Teske {
401*3fe5961aSDevin Teske 
402*3fe5961aSDevin Teske 	if (cursor == NULL)
403*3fe5961aSDevin Teske 		return (NULL);
404*3fe5961aSDevin Teske 	cursor += strspn(cursor, " \t");
405*3fe5961aSDevin Teske 	if ((*lenp = strcspn(cursor, " \t")) == 0)
406*3fe5961aSDevin Teske 		return (NULL);
407*3fe5961aSDevin Teske 	return (cursor);
408*3fe5961aSDevin Teske }
409*3fe5961aSDevin Teske 
410*3fe5961aSDevin Teske /*
411*3fe5961aSDevin Teske  * Ceiling on the number of configuration files discovery will chase,
412*3fe5961aSDevin Teske  * bounding run-away (or maliciously self-referential) file lists.
413*3fe5961aSDevin Teske  */
414*3fe5961aSDevin Teske #define BSDCONF_DISCOVER_MAX	64
415*3fe5961aSDevin Teske 
416*3fe5961aSDevin Teske /*
417*3fe5961aSDevin Teske  * Resolve the backing files of a format whose descriptor carries a
418*3fe5961aSDevin Teske  * `defaults' file by performing the same discovery its consumer does: read
419*3fe5961aSDevin Teske  * the defaults file, then chase the file-list directive (e.g.,
420*3fe5961aSDevin Teske  * loader_conf_files) -- re-reading it after every file, as the loader does
421*3fe5961aSDevin Teske  * when a file queues additional names -- and finally append the drop-in
422*3fe5961aSDevin Teske  * directory entries and local files named by the final values of the other
423*3fe5961aSDevin Teske  * two list directives (likewise applied only after the file-list walk).
424*3fe5961aSDevin Teske  * Every file named by the file-list directive is included in
425*3fe5961aSDevin Teske  * the result whether or not it exists (a listed file is a legitimate
426*3fe5961aSDevin Teske  * write target before its first directive lands); the defaults file
427*3fe5961aSDevin Teske  * itself is deliberately excluded. The write candidate stored through
428*3fe5961aSDevin Teske  * `write_idx' is the last file-list entry, the final word among the
429*3fe5961aSDevin Teske  * regular configuration files.
430*3fe5961aSDevin Teske  *
431*3fe5961aSDevin Teske  * The defaults file is `defaults' verbatim when non-NULL (a caller-level
432*3fe5961aSDevin Teske  * override; see sysconf(8)'s LOADER_DEFAULTS); otherwise the descriptor
433*3fe5961aSDevin Teske  * default prefixed with `rootdir'.
434*3fe5961aSDevin Teske  *
435*3fe5961aSDevin Teske  * On success, returns zero. Returns 1 when the defaults file is missing
436*3fe5961aSDevin Teske  * or unreadable, directing the caller to fall back to the static source
437*3fe5961aSDevin Teske  * list. Otherwise returns -1 (errno set).
438*3fe5961aSDevin Teske  */
439*3fe5961aSDevin Teske static int
bsdconf_files_discover(const struct bsdconf_format_def * def,const char * rootdir,const char * defaults,char *** files,size_t * nfiles,size_t * size,size_t * write_idx)440*3fe5961aSDevin Teske bsdconf_files_discover(const struct bsdconf_format_def *def,
441*3fe5961aSDevin Teske     const char *rootdir, const char *defaults, char ***files,
442*3fe5961aSDevin Teske     size_t *nfiles, size_t *size, size_t *write_idx)
443*3fe5961aSDevin Teske {
444*3fe5961aSDevin Teske 	int error, rv = -1;
445*3fe5961aSDevin Teske 	uint16_t processing;
446*3fe5961aSDevin Teske 	size_t len, n, nvisited = 0;
447*3fe5961aSDevin Teske 	char *dpath = NULL;
448*3fe5961aSDevin Teske 	char *full;
449*3fe5961aSDevin Teske 	char *name;
450*3fe5961aSDevin Teske 	char *conf_dirs = NULL;
451*3fe5961aSDevin Teske 	char *conf_files = NULL;
452*3fe5961aSDevin Teske 	char *local_files = NULL;
453*3fe5961aSDevin Teske 	const char *token;
454*3fe5961aSDevin Teske 	char *visited[BSDCONF_DISCOVER_MAX];
455*3fe5961aSDevin Teske 	struct bsdconf_option options[4];
456*3fe5961aSDevin Teske 
457*3fe5961aSDevin Teske 	/* Wire each list directive to the slot holding its value */
458*3fe5961aSDevin Teske 	memset(options, 0, sizeof(options));
459*3fe5961aSDevin Teske 	n = 0;
460*3fe5961aSDevin Teske 	if (def->files_directive != NULL) {
461*3fe5961aSDevin Teske 		options[n].directive = def->files_directive;
462*3fe5961aSDevin Teske 		options[n].value.data = &conf_files;
463*3fe5961aSDevin Teske 		options[n].parse = bsdconf_files_list_cb;
464*3fe5961aSDevin Teske 		n++;
465*3fe5961aSDevin Teske 	}
466*3fe5961aSDevin Teske 	if (def->dirs_directive != NULL) {
467*3fe5961aSDevin Teske 		options[n].directive = def->dirs_directive;
468*3fe5961aSDevin Teske 		options[n].value.data = &conf_dirs;
469*3fe5961aSDevin Teske 		options[n].parse = bsdconf_files_list_cb;
470*3fe5961aSDevin Teske 		n++;
471*3fe5961aSDevin Teske 	}
472*3fe5961aSDevin Teske 	if (def->local_directive != NULL) {
473*3fe5961aSDevin Teske 		options[n].directive = def->local_directive;
474*3fe5961aSDevin Teske 		options[n].value.data = &local_files;
475*3fe5961aSDevin Teske 		options[n].parse = bsdconf_files_list_cb;
476*3fe5961aSDevin Teske 		n++;
477*3fe5961aSDevin Teske 	}
478*3fe5961aSDevin Teske 
479*3fe5961aSDevin Teske 	/* Resolve and read the defaults file (missing means fall back) */
480*3fe5961aSDevin Teske 	if (defaults != NULL)
481*3fe5961aSDevin Teske 		dpath = strdup(defaults);
482*3fe5961aSDevin Teske 	else
483*3fe5961aSDevin Teske 		dpath = bsdconf_files_path(rootdir, def->defaults, NULL,
484*3fe5961aSDevin Teske 		    NULL);
485*3fe5961aSDevin Teske 	if (dpath == NULL)
486*3fe5961aSDevin Teske 		goto cleanup;
487*3fe5961aSDevin Teske 	processing = def->processing & ~BSDCONF_REQUIRE_EQUALS;
488*3fe5961aSDevin Teske 	if (bsdconf_parse(options, dpath, NULL, processing) != 0) {
489*3fe5961aSDevin Teske 		rv = 1;
490*3fe5961aSDevin Teske 		goto cleanup;
491*3fe5961aSDevin Teske 	}
492*3fe5961aSDevin Teske 
493*3fe5961aSDevin Teske 	/*
494*3fe5961aSDevin Teske 	 * Chase the file-list directive. Each pass rescans the current
495*3fe5961aSDevin Teske 	 * value of the list from the start for the first file not yet
496*3fe5961aSDevin Teske 	 * visited (any file read may have revised the list), reads it,
497*3fe5961aSDevin Teske 	 * and repeats until every listed file has been visited.
498*3fe5961aSDevin Teske 	 */
499*3fe5961aSDevin Teske 	for (;;) {
500*3fe5961aSDevin Teske 		token = conf_files;
501*3fe5961aSDevin Teske 		while ((token = bsdconf_files_token(token, &len)) != NULL) {
502*3fe5961aSDevin Teske 			for (n = 0; n < nvisited; n++)
503*3fe5961aSDevin Teske 				if (strncmp(visited[n], token, len) == 0 &&
504*3fe5961aSDevin Teske 				    visited[n][len] == '\0')
505*3fe5961aSDevin Teske 					break;
506*3fe5961aSDevin Teske 			if (n == nvisited)
507*3fe5961aSDevin Teske 				break;
508*3fe5961aSDevin Teske 			token += len;
509*3fe5961aSDevin Teske 		}
510*3fe5961aSDevin Teske 		if (token == NULL || nvisited >= BSDCONF_DISCOVER_MAX)
511*3fe5961aSDevin Teske 			break;
512*3fe5961aSDevin Teske 		if ((visited[nvisited] = strndup(token, len)) == NULL)
513*3fe5961aSDevin Teske 			goto cleanup;
514*3fe5961aSDevin Teske 		full = bsdconf_files_path(rootdir, visited[nvisited], NULL,
515*3fe5961aSDevin Teske 		    NULL);
516*3fe5961aSDevin Teske 		nvisited++;
517*3fe5961aSDevin Teske 		if (bsdconf_files_add(files, nfiles, size, full) != 0)
518*3fe5961aSDevin Teske 			goto cleanup;
519*3fe5961aSDevin Teske 		/* A listed file that does not (yet) exist is normal */
520*3fe5961aSDevin Teske 		(void)bsdconf_parse(options, full, NULL, processing);
521*3fe5961aSDevin Teske 	}
522*3fe5961aSDevin Teske 	*write_idx = (*nfiles > 0) ? *nfiles - 1 : 0;
523*3fe5961aSDevin Teske 
524*3fe5961aSDevin Teske 	/* Drop-in directories, then local files, from the final lists */
525*3fe5961aSDevin Teske 	token = conf_dirs;
526*3fe5961aSDevin Teske 	while ((token = bsdconf_files_token(token, &len)) != NULL) {
527*3fe5961aSDevin Teske 		if ((name = strndup(token, len)) == NULL)
528*3fe5961aSDevin Teske 			goto cleanup;
529*3fe5961aSDevin Teske 		token += len;
530*3fe5961aSDevin Teske 		error = bsdconf_files_add_dir(files, nfiles, size, rootdir,
531*3fe5961aSDevin Teske 		    name);
532*3fe5961aSDevin Teske 		free(name);
533*3fe5961aSDevin Teske 		if (error != 0)
534*3fe5961aSDevin Teske 			goto cleanup;
535*3fe5961aSDevin Teske 	}
536*3fe5961aSDevin Teske 	token = local_files;
537*3fe5961aSDevin Teske 	while ((token = bsdconf_files_token(token, &len)) != NULL) {
538*3fe5961aSDevin Teske 		if ((name = strndup(token, len)) == NULL)
539*3fe5961aSDevin Teske 			goto cleanup;
540*3fe5961aSDevin Teske 		token += len;
541*3fe5961aSDevin Teske 		full = bsdconf_files_path(rootdir, name, NULL, NULL);
542*3fe5961aSDevin Teske 		free(name);
543*3fe5961aSDevin Teske 		if (bsdconf_files_add(files, nfiles, size, full) != 0)
544*3fe5961aSDevin Teske 			goto cleanup;
545*3fe5961aSDevin Teske 	}
546*3fe5961aSDevin Teske 
547*3fe5961aSDevin Teske 	rv = 0;
548*3fe5961aSDevin Teske 
549*3fe5961aSDevin Teske cleanup:
550*3fe5961aSDevin Teske 	n = errno; /* preserve errno across free(3) */
551*3fe5961aSDevin Teske 	while (nvisited > 0)
552*3fe5961aSDevin Teske 		free(visited[--nvisited]);
553*3fe5961aSDevin Teske 	free(conf_files);
554*3fe5961aSDevin Teske 	free(conf_dirs);
555*3fe5961aSDevin Teske 	free(local_files);
556*3fe5961aSDevin Teske 	free(dpath);
557*3fe5961aSDevin Teske 	errno = n;
558*3fe5961aSDevin Teske 	return (rv);
559*3fe5961aSDevin Teske }
560*3fe5961aSDevin Teske 
561*3fe5961aSDevin Teske /*
562*3fe5961aSDevin Teske  * Resolve the ordered list of configuration files backing `format' into a
563*3fe5961aSDevin Teske  * newly allocated array of newly allocated paths, stored through `filesp'
564*3fe5961aSDevin Teske  * with the count stored through `nfilesp'. Files appear in the order the
565*3fe5961aSDevin Teske  * system sources them at boot; a directive in a later file overrides the
566*3fe5961aSDevin Teske  * same directive in an earlier one, making the last file listing a
567*3fe5961aSDevin Teske  * directive the authoritative source of its value. When `write_idxp' is
568*3fe5961aSDevin Teske  * non-NULL, the index of the file recommended for directives found in no
569*3fe5961aSDevin Teske  * file at all is stored through it (the format's default write path, or
570*3fe5961aSDevin Teske  * the last regular configuration file when discovery is in play).
571*3fe5961aSDevin Teske  *
572*3fe5961aSDevin Teske  * A format whose descriptor names a defaults file resolves its list by
573*3fe5961aSDevin Teske  * discovery (see bsdconf_files_discover() above); `defaults' overrides
574*3fe5961aSDevin Teske  * the descriptor's defaults file when non-NULL and is used verbatim (it
575*3fe5961aSDevin Teske  * is not prefixed with `rootdir'). For all other formats `defaults' is
576*3fe5961aSDevin Teske  * ignored and the static source list governs, as it also does when the
577*3fe5961aSDevin Teske  * defaults file is missing.
578*3fe5961aSDevin Teske  *
579*3fe5961aSDevin Teske  * Each static path is prefixed with `rootdir' unless NULL or empty.
580*3fe5961aSDevin Teske  * Sources of type BSDCONF_SOURCE_FILE are always listed, whether or not
581*3fe5961aSDevin Teske  * the file exists; sources of type BSDCONF_SOURCE_DIR contribute each
582*3fe5961aSDevin Teske  * existing `*.conf' entry (sorted); sources of type BSDCONF_SOURCE_MODDIR
583*3fe5961aSDevin Teske  * contribute `<module>.conf' only when `module' is non-NULL. A format
584*3fe5961aSDevin Teske  * with no source list resolves to its default path alone.
585*3fe5961aSDevin Teske  *
586*3fe5961aSDevin Teske  * On success, returns zero and the caller frees the result with
587*3fe5961aSDevin Teske  * bsdconf_format_files_free(). Otherwise returns -1 and errno is set
588*3fe5961aSDevin Teske  * (EINVAL for an unknown format or a format with no backing files).
589*3fe5961aSDevin Teske  */
590*3fe5961aSDevin Teske int
bsdconf_format_files(enum bsdconf_format format,const char * rootdir,const char * module,const char * defaults,char *** filesp,size_t * nfilesp,size_t * write_idxp)591*3fe5961aSDevin Teske bsdconf_format_files(enum bsdconf_format format, const char *rootdir,
592*3fe5961aSDevin Teske     const char *module, const char *defaults, char ***filesp,
593*3fe5961aSDevin Teske     size_t *nfilesp, size_t *write_idxp)
594*3fe5961aSDevin Teske {
595*3fe5961aSDevin Teske 	int error;
596*3fe5961aSDevin Teske 	size_t nfiles = 0;
597*3fe5961aSDevin Teske 	size_t size = 0;
598*3fe5961aSDevin Teske 	size_t write_idx = 0;
599*3fe5961aSDevin Teske 	char **files = NULL;
600*3fe5961aSDevin Teske 	char *path;
601*3fe5961aSDevin Teske 	const struct bsdconf_format_def *def;
602*3fe5961aSDevin Teske 	const struct bsdconf_source *source;
603*3fe5961aSDevin Teske 
604*3fe5961aSDevin Teske 	/* Check arguments */
605*3fe5961aSDevin Teske 	if (filesp == NULL || nfilesp == NULL ||
606*3fe5961aSDevin Teske 	    (def = bsdconf_format_lookup(format)) == NULL) {
607*3fe5961aSDevin Teske 		errno = EINVAL;
608*3fe5961aSDevin Teske 		return (-1);
609*3fe5961aSDevin Teske 	}
610*3fe5961aSDevin Teske 	if (rootdir == NULL)
611*3fe5961aSDevin Teske 		rootdir = "";
612*3fe5961aSDevin Teske 
613*3fe5961aSDevin Teske 	/* Discover the list when the descriptor names a defaults file */
614*3fe5961aSDevin Teske 	if (def->defaults != NULL) {
615*3fe5961aSDevin Teske 		error = bsdconf_files_discover(def, rootdir, defaults,
616*3fe5961aSDevin Teske 		    &files, &nfiles, &size, &write_idx);
617*3fe5961aSDevin Teske 		if (error < 0)
618*3fe5961aSDevin Teske 			goto cleanup;
619*3fe5961aSDevin Teske 		if (error == 0 && nfiles > 0)
620*3fe5961aSDevin Teske 			goto done;
621*3fe5961aSDevin Teske 		/* Missing defaults file; fall back to static sources */
622*3fe5961aSDevin Teske 		bsdconf_format_files_free(files, nfiles);
623*3fe5961aSDevin Teske 		files = NULL;
624*3fe5961aSDevin Teske 		nfiles = size = write_idx = 0;
625*3fe5961aSDevin Teske 	}
626*3fe5961aSDevin Teske 
627*3fe5961aSDevin Teske 	/* A format with no source list is backed by its path alone */
628*3fe5961aSDevin Teske 	if (def->sources == NULL) {
629*3fe5961aSDevin Teske 		if (def->path == NULL) {
630*3fe5961aSDevin Teske 			errno = EINVAL;
631*3fe5961aSDevin Teske 			return (-1);
632*3fe5961aSDevin Teske 		}
633*3fe5961aSDevin Teske 		path = bsdconf_files_path(rootdir, def->path, NULL, NULL);
634*3fe5961aSDevin Teske 		if (bsdconf_files_add(&files, &nfiles, &size, path) != 0)
635*3fe5961aSDevin Teske 			goto cleanup;
636*3fe5961aSDevin Teske 		goto done;
637*3fe5961aSDevin Teske 	}
638*3fe5961aSDevin Teske 
639*3fe5961aSDevin Teske 	for (source = def->sources; source->path != NULL; source++) {
640*3fe5961aSDevin Teske 		switch (source->type) {
641*3fe5961aSDevin Teske 		case BSDCONF_SOURCE_FILE:
642*3fe5961aSDevin Teske 			path = bsdconf_files_path(rootdir, source->path,
643*3fe5961aSDevin Teske 			    NULL, NULL);
644*3fe5961aSDevin Teske 			if (def->path != NULL &&
645*3fe5961aSDevin Teske 			    strcmp(source->path, def->path) == 0)
646*3fe5961aSDevin Teske 				write_idx = nfiles;
647*3fe5961aSDevin Teske 			if (bsdconf_files_add(&files, &nfiles, &size,
648*3fe5961aSDevin Teske 			    path) != 0)
649*3fe5961aSDevin Teske 				goto cleanup;
650*3fe5961aSDevin Teske 			break;
651*3fe5961aSDevin Teske 		case BSDCONF_SOURCE_DIR:
652*3fe5961aSDevin Teske 			if (bsdconf_files_add_dir(&files, &nfiles, &size,
653*3fe5961aSDevin Teske 			    rootdir, source->path) != 0)
654*3fe5961aSDevin Teske 				goto cleanup;
655*3fe5961aSDevin Teske 			break;
656*3fe5961aSDevin Teske 		case BSDCONF_SOURCE_MODDIR:
657*3fe5961aSDevin Teske 			if (module == NULL)
658*3fe5961aSDevin Teske 				break;
659*3fe5961aSDevin Teske 			path = bsdconf_files_path(rootdir, source->path,
660*3fe5961aSDevin Teske 			    module, BSDCONF_CONF_SUFFIX);
661*3fe5961aSDevin Teske 			if (bsdconf_files_add(&files, &nfiles, &size,
662*3fe5961aSDevin Teske 			    path) != 0)
663*3fe5961aSDevin Teske 				goto cleanup;
664*3fe5961aSDevin Teske 			break;
665*3fe5961aSDevin Teske 		}
666*3fe5961aSDevin Teske 	}
667*3fe5961aSDevin Teske 
668*3fe5961aSDevin Teske done:
669*3fe5961aSDevin Teske 	*filesp = files;
670*3fe5961aSDevin Teske 	*nfilesp = nfiles;
671*3fe5961aSDevin Teske 	if (write_idxp != NULL)
672*3fe5961aSDevin Teske 		*write_idxp = write_idx;
673*3fe5961aSDevin Teske 	return (0);
674*3fe5961aSDevin Teske 
675*3fe5961aSDevin Teske cleanup:
676*3fe5961aSDevin Teske 	bsdconf_format_files_free(files, nfiles);
677*3fe5961aSDevin Teske 	return (-1);
678*3fe5961aSDevin Teske }
679*3fe5961aSDevin Teske 
680*3fe5961aSDevin Teske /*
681*3fe5961aSDevin Teske  * Release an array of paths obtained from bsdconf_format_files().
682*3fe5961aSDevin Teske  */
683*3fe5961aSDevin Teske void
bsdconf_format_files_free(char ** files,size_t nfiles)684*3fe5961aSDevin Teske bsdconf_format_files_free(char **files, size_t nfiles)
685*3fe5961aSDevin Teske {
686*3fe5961aSDevin Teske 	size_t n;
687*3fe5961aSDevin Teske 
688*3fe5961aSDevin Teske 	if (files == NULL)
689*3fe5961aSDevin Teske 		return;
690*3fe5961aSDevin Teske 	for (n = 0; n < nfiles; n++)
691*3fe5961aSDevin Teske 		free(files[n]);
692*3fe5961aSDevin Teske 	free(files);
693*3fe5961aSDevin Teske }
694