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