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