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 /*
9*3fe5961aSDevin Teske * Backing-file discovery and file-list reporting (-l/-L).
10*3fe5961aSDevin Teske */
11*3fe5961aSDevin Teske
12*3fe5961aSDevin Teske #include "sysconf_priv.h"
13*3fe5961aSDevin Teske
14*3fe5961aSDevin Teske static void apply_src_env_overrides(void);
15*3fe5961aSDevin Teske
16*3fe5961aSDevin Teske /*
17*3fe5961aSDevin Teske * Resolve the pathname of the target format's defaults file: the format's
18*3fe5961aSDevin Teske * environment override (e.g., LOADER_DEFAULTS) verbatim when set and
19*3fe5961aSDevin Teske * non-empty, otherwise the descriptor's defaults file prefixed with the
20*3fe5961aSDevin Teske * `-R dir' root. Returns a newly allocated path, or NULL when the format
21*3fe5961aSDevin Teske * has no defaults file.
22*3fe5961aSDevin Teske */
23*3fe5961aSDevin Teske char *
defaults_path(void)24*3fe5961aSDevin Teske defaults_path(void)
25*3fe5961aSDevin Teske {
26*3fe5961aSDevin Teske char *env;
27*3fe5961aSDevin Teske char *path;
28*3fe5961aSDevin Teske const struct bsdconf_format_def *def;
29*3fe5961aSDevin Teske char buf[PATH_MAX];
30*3fe5961aSDevin Teske
31*3fe5961aSDevin Teske if ((def = bsdconf_format_lookup(format)) == NULL ||
32*3fe5961aSDevin Teske def->defaults == NULL)
33*3fe5961aSDevin Teske return (NULL);
34*3fe5961aSDevin Teske if (def->defaults_env != NULL &&
35*3fe5961aSDevin Teske (env = getenv(def->defaults_env)) != NULL && *env != '\0') {
36*3fe5961aSDevin Teske if ((path = strdup(env)) == NULL)
37*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
38*3fe5961aSDevin Teske return (path);
39*3fe5961aSDevin Teske }
40*3fe5961aSDevin Teske if (snprintf(buf, sizeof(buf), "%s%s", rootdir, def->defaults) >=
41*3fe5961aSDevin Teske (int)sizeof(buf))
42*3fe5961aSDevin Teske errx(EXIT_FAILURE, "%s%s: %s", rootdir, def->defaults,
43*3fe5961aSDevin Teske strerror(ENAMETOOLONG));
44*3fe5961aSDevin Teske if ((path = strdup(buf)) == NULL)
45*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
46*3fe5961aSDevin Teske return (path);
47*3fe5961aSDevin Teske }
48*3fe5961aSDevin Teske
49*3fe5961aSDevin Teske /*
50*3fe5961aSDevin Teske * Resolve the target format and its ordered list of backing files from the
51*3fe5961aSDevin Teske * command line: either the `-f file' override (a single file; the format is
52*3fe5961aSDevin Teske * guessed from its name) or the leading `target' keyword (`loader',
53*3fe5961aSDevin Teske * `sysctl', `make'), whose multi-file list -- optionally including the
54*3fe5961aSDevin Teske * `-k module' drop-in -- comes from the format abstraction layer (which,
55*3fe5961aSDevin Teske * for targets carrying a defaults file, performs the same discovery the
56*3fe5961aSDevin Teske * boot-time consumer does; see bsdconf_format_files(3)). All paths honor
57*3fe5961aSDevin Teske * the `-R dir' (or `-j jail') root. A target with a defaults file always
58*3fe5961aSDevin Teske * has it prepended to the scan order (defaults first, mirroring the boot
59*3fe5961aSDevin Teske * loader, so reads report the effective value even for directives no conf
60*3fe5961aSDevin Teske * file mentions), but the slot is read-only bookkeeping: see defaults_idx.
61*3fe5961aSDevin Teske * With `-D', the defaults file replaces the list outright; `-A' widens
62*3fe5961aSDevin Teske * dump scope to include directives still at their default. Combined
63*3fe5961aSDevin Teske * (`-AD'), the scoping of `-D' wins and `-A' retains only its
64*3fe5961aSDevin Teske * dump-implying role, so `-ADd' describes all defaults and only defaults,
65*3fe5961aSDevin Teske * as in sysrc(8).
66*3fe5961aSDevin Teske */
67*3fe5961aSDevin Teske void
resolve_target(const char * target)68*3fe5961aSDevin Teske resolve_target(const char *target)
69*3fe5961aSDevin Teske {
70*3fe5961aSDevin Teske char *defaults;
71*3fe5961aSDevin Teske const char *env = NULL;
72*3fe5961aSDevin Teske const struct bsdconf_format_def *def;
73*3fe5961aSDevin Teske
74*3fe5961aSDevin Teske if (target == NULL) {
75*3fe5961aSDevin Teske warnx("no target provided");
76*3fe5961aSDevin Teske usage();
77*3fe5961aSDevin Teske }
78*3fe5961aSDevin Teske if (bsdconf_format_find(target, &format) != 0) {
79*3fe5961aSDevin Teske if (strcmp(target, "src-env") == 0)
80*3fe5961aSDevin Teske warnx("unknown target `src-env'; use `src' "
81*3fe5961aSDevin Teske "(includes src-env.conf in the build triad) "
82*3fe5961aSDevin Teske "or `-f' for a specific file");
83*3fe5961aSDevin Teske else
84*3fe5961aSDevin Teske warnx("unknown target `%s'", target);
85*3fe5961aSDevin Teske usage();
86*3fe5961aSDevin Teske }
87*3fe5961aSDevin Teske
88*3fe5961aSDevin Teske if (file != NULL) {
89*3fe5961aSDevin Teske char path[PATH_MAX];
90*3fe5961aSDevin Teske const char *prefix;
91*3fe5961aSDevin Teske
92*3fe5961aSDevin Teske /*
93*3fe5961aSDevin Teske * `-f file' overrides which file(s) are consulted and
94*3fe5961aSDevin Teske * modified, but never the format; that is always stated
95*3fe5961aSDevin Teske * explicitly by the target keyword. Standard input
96*3fe5961aSDevin Teske * (`-f -') is the caller's own and is not subject to -R.
97*3fe5961aSDevin Teske */
98*3fe5961aSDevin Teske prefix = file_stdin ? "" : rootdir;
99*3fe5961aSDevin Teske if ((conf_files = calloc(1, sizeof(*conf_files))) == NULL)
100*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
101*3fe5961aSDevin Teske if (snprintf(path, sizeof(path), "%s%s", prefix, file) >=
102*3fe5961aSDevin Teske (int)sizeof(path))
103*3fe5961aSDevin Teske errx(EXIT_FAILURE, "%s%s: %s", prefix, file,
104*3fe5961aSDevin Teske strerror(ENAMETOOLONG));
105*3fe5961aSDevin Teske if ((conf_files[0] = strdup(path)) == NULL)
106*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
107*3fe5961aSDevin Teske nconf_files = 1;
108*3fe5961aSDevin Teske write_idx = 0;
109*3fe5961aSDevin Teske return;
110*3fe5961aSDevin Teske }
111*3fe5961aSDevin Teske
112*3fe5961aSDevin Teske def = bsdconf_format_lookup(format);
113*3fe5961aSDevin Teske if (def == NULL || (def->path == NULL && def->sources == NULL))
114*3fe5961aSDevin Teske errx(EXIT_FAILURE,
115*3fe5961aSDevin Teske "target `%s' has no default files; specify one with -f",
116*3fe5961aSDevin Teske target);
117*3fe5961aSDevin Teske
118*3fe5961aSDevin Teske /* `-D' consults the defaults file alone */
119*3fe5961aSDevin Teske if (defaults_only) {
120*3fe5961aSDevin Teske if ((conf_files = calloc(1, sizeof(*conf_files))) == NULL)
121*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
122*3fe5961aSDevin Teske conf_files[0] = defaults_path();
123*3fe5961aSDevin Teske nconf_files = 1;
124*3fe5961aSDevin Teske write_idx = 0;
125*3fe5961aSDevin Teske return;
126*3fe5961aSDevin Teske }
127*3fe5961aSDevin Teske
128*3fe5961aSDevin Teske if (def->defaults_env != NULL &&
129*3fe5961aSDevin Teske (env = getenv(def->defaults_env)) != NULL && *env == '\0')
130*3fe5961aSDevin Teske env = NULL;
131*3fe5961aSDevin Teske if (bsdconf_format_files(format, rootdir, module, env, &conf_files,
132*3fe5961aSDevin Teske &nconf_files, &write_idx) != 0 || nconf_files == 0)
133*3fe5961aSDevin Teske err(EXIT_FAILURE, "%s", target);
134*3fe5961aSDevin Teske
135*3fe5961aSDevin Teske /*
136*3fe5961aSDevin Teske * The src triad honors the same environment overrides make(1)
137*3fe5961aSDevin Teske * does when building /usr/src (verbatim, not subject to -R).
138*3fe5961aSDevin Teske */
139*3fe5961aSDevin Teske if (format == BSDCONF_FORMAT_SRC)
140*3fe5961aSDevin Teske apply_src_env_overrides();
141*3fe5961aSDevin Teske
142*3fe5961aSDevin Teske /*
143*3fe5961aSDevin Teske * A target with a defaults file sources it first at boot, so the
144*3fe5961aSDevin Teske * effective value of a directive may come from the defaults alone
145*3fe5961aSDevin Teske * even when no conf file mentions it. Prepend the defaults file
146*3fe5961aSDevin Teske * to the scan order (defaults first, so every override lands
147*3fe5961aSDevin Teske * later) and remember its index: the defaults are read-only, so
148*3fe5961aSDevin Teske * that slot is never chosen as a write target, never satisfies a
149*3fe5961aSDevin Teske * removal, and is never shown by -l/-L (see defaults_idx users).
150*3fe5961aSDevin Teske */
151*3fe5961aSDevin Teske if ((defaults = defaults_path()) != NULL) {
152*3fe5961aSDevin Teske char **tmp;
153*3fe5961aSDevin Teske size_t n;
154*3fe5961aSDevin Teske
155*3fe5961aSDevin Teske tmp = calloc(nconf_files + 1, sizeof(*tmp));
156*3fe5961aSDevin Teske if (tmp == NULL)
157*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
158*3fe5961aSDevin Teske tmp[0] = defaults;
159*3fe5961aSDevin Teske for (n = 0; n < nconf_files; n++)
160*3fe5961aSDevin Teske tmp[n + 1] = conf_files[n];
161*3fe5961aSDevin Teske free(conf_files);
162*3fe5961aSDevin Teske conf_files = tmp;
163*3fe5961aSDevin Teske nconf_files++;
164*3fe5961aSDevin Teske defaults_idx = 0;
165*3fe5961aSDevin Teske write_idx++;
166*3fe5961aSDevin Teske }
167*3fe5961aSDevin Teske }
168*3fe5961aSDevin Teske
169*3fe5961aSDevin Teske /*
170*3fe5961aSDevin Teske * Replace src triad paths with SRC_ENV_CONF / __MAKE_CONF / SRCCONF when
171*3fe5961aSDevin Teske * set and non-empty (same variables share/mk consults). Values are used
172*3fe5961aSDevin Teske * verbatim, matching LOADER_DEFAULTS.
173*3fe5961aSDevin Teske */
174*3fe5961aSDevin Teske static void
apply_src_env_overrides(void)175*3fe5961aSDevin Teske apply_src_env_overrides(void)
176*3fe5961aSDevin Teske {
177*3fe5961aSDevin Teske static const char *const envs[] = {
178*3fe5961aSDevin Teske "SRC_ENV_CONF",
179*3fe5961aSDevin Teske "__MAKE_CONF",
180*3fe5961aSDevin Teske "SRCCONF",
181*3fe5961aSDevin Teske };
182*3fe5961aSDevin Teske const char *env;
183*3fe5961aSDevin Teske char *path;
184*3fe5961aSDevin Teske size_t i;
185*3fe5961aSDevin Teske
186*3fe5961aSDevin Teske if (nconf_files != sizeof(envs) / sizeof(envs[0]))
187*3fe5961aSDevin Teske return;
188*3fe5961aSDevin Teske for (i = 0; i < nconf_files; i++) {
189*3fe5961aSDevin Teske if ((env = getenv(envs[i])) == NULL || *env == '\0')
190*3fe5961aSDevin Teske continue;
191*3fe5961aSDevin Teske if ((path = strdup(env)) == NULL)
192*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
193*3fe5961aSDevin Teske free(conf_files[i]);
194*3fe5961aSDevin Teske conf_files[i] = path;
195*3fe5961aSDevin Teske }
196*3fe5961aSDevin Teske }
197*3fe5961aSDevin Teske
198*3fe5961aSDevin Teske /*
199*3fe5961aSDevin Teske * Print a candidate path unless `-E' excludes it for not existing or it
200*3fe5961aSDevin Teske * was already printed (`seen' is a NULL-terminated list of prior paths).
201*3fe5961aSDevin Teske */
202*3fe5961aSDevin Teske static void
list_candidate(const char * path,char * seen[],size_t nseen)203*3fe5961aSDevin Teske list_candidate(const char *path, char *seen[], size_t nseen)
204*3fe5961aSDevin Teske {
205*3fe5961aSDevin Teske size_t n;
206*3fe5961aSDevin Teske
207*3fe5961aSDevin Teske for (n = 0; n < nseen; n++)
208*3fe5961aSDevin Teske if (seen[n] != NULL && strcmp(seen[n], path) == 0)
209*3fe5961aSDevin Teske return;
210*3fe5961aSDevin Teske if (existing_only && access(path, F_OK) != 0)
211*3fe5961aSDevin Teske return;
212*3fe5961aSDevin Teske puts(path);
213*3fe5961aSDevin Teske }
214*3fe5961aSDevin Teske
215*3fe5961aSDevin Teske /*
216*3fe5961aSDevin Teske * Record `path' in the `seen' list (for suppressing duplicates) after
217*3fe5961aSDevin Teske * printing it as a candidate.
218*3fe5961aSDevin Teske */
219*3fe5961aSDevin Teske static void
list_candidate_seen(const char * path,char ** seen,size_t * nseen,size_t maxseen)220*3fe5961aSDevin Teske list_candidate_seen(const char *path, char **seen, size_t *nseen,
221*3fe5961aSDevin Teske size_t maxseen)
222*3fe5961aSDevin Teske {
223*3fe5961aSDevin Teske
224*3fe5961aSDevin Teske list_candidate(path, seen, *nseen);
225*3fe5961aSDevin Teske if (*nseen < maxseen)
226*3fe5961aSDevin Teske seen[(*nseen)++] = strdup(path);
227*3fe5961aSDevin Teske }
228*3fe5961aSDevin Teske
229*3fe5961aSDevin Teske /*
230*3fe5961aSDevin Teske * Process `-l' (list the files backing the target) and `-L' (additionally
231*3fe5961aSDevin Teske * list every drop-in candidate; for targets with a module drop-in
232*3fe5961aSDevin Teske * directory, one candidate per loaded kernel module plus any `*.conf'
233*3fe5961aSDevin Teske * already on disk). With `-E', only files that exist are shown. Returns
234*3fe5961aSDevin Teske * EXIT_SUCCESS.
235*3fe5961aSDevin Teske */
236*3fe5961aSDevin Teske int
do_list(void)237*3fe5961aSDevin Teske do_list(void)
238*3fe5961aSDevin Teske {
239*3fe5961aSDevin Teske size_t n;
240*3fe5961aSDevin Teske size_t nseen = 0;
241*3fe5961aSDevin Teske char **seen;
242*3fe5961aSDevin Teske const char *moddir = NULL;
243*3fe5961aSDevin Teske const struct bsdconf_format_def *def;
244*3fe5961aSDevin Teske const struct bsdconf_source *source;
245*3fe5961aSDevin Teske #define LIST_MAXSEEN 512
246*3fe5961aSDevin Teske char dpath[PATH_MAX];
247*3fe5961aSDevin Teske char path[PATH_MAX];
248*3fe5961aSDevin Teske
249*3fe5961aSDevin Teske if ((seen = calloc(LIST_MAXSEEN, sizeof(*seen))) == NULL)
250*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
251*3fe5961aSDevin Teske
252*3fe5961aSDevin Teske for (n = 0; n < nconf_files; n++) {
253*3fe5961aSDevin Teske /* The read-only defaults file is never a write candidate */
254*3fe5961aSDevin Teske if ((int)n == defaults_idx)
255*3fe5961aSDevin Teske continue;
256*3fe5961aSDevin Teske list_candidate_seen(conf_files[n], seen, &nseen,
257*3fe5961aSDevin Teske LIST_MAXSEEN);
258*3fe5961aSDevin Teske }
259*3fe5961aSDevin Teske
260*3fe5961aSDevin Teske if (!list_all)
261*3fe5961aSDevin Teske goto done;
262*3fe5961aSDevin Teske
263*3fe5961aSDevin Teske /* Locate the module drop-in directory (if the format has one) */
264*3fe5961aSDevin Teske def = bsdconf_format_lookup(format);
265*3fe5961aSDevin Teske if (def != NULL && def->sources != NULL)
266*3fe5961aSDevin Teske for (source = def->sources; source->path != NULL; source++)
267*3fe5961aSDevin Teske if (source->type == BSDCONF_SOURCE_MODDIR)
268*3fe5961aSDevin Teske moddir = source->path;
269*3fe5961aSDevin Teske if (moddir == NULL)
270*3fe5961aSDevin Teske goto done;
271*3fe5961aSDevin Teske
272*3fe5961aSDevin Teske #ifdef __FreeBSD__
273*3fe5961aSDevin Teske /*
274*3fe5961aSDevin Teske * One candidate per loaded kernel module, whether or not the file
275*3fe5961aSDevin Teske * exists yet (the module being loaded is what makes the file
276*3fe5961aSDevin Teske * eligible for sourcing by rc.subr(8)).
277*3fe5961aSDevin Teske */
278*3fe5961aSDevin Teske {
279*3fe5961aSDevin Teske char name[MAXPATHLEN];
280*3fe5961aSDevin Teske char *dot;
281*3fe5961aSDevin Teske int fileid;
282*3fe5961aSDevin Teske struct kld_file_stat stat;
283*3fe5961aSDevin Teske
284*3fe5961aSDevin Teske for (fileid = kldnext(0); fileid > 0;
285*3fe5961aSDevin Teske fileid = kldnext(fileid)) {
286*3fe5961aSDevin Teske stat.version = sizeof(stat);
287*3fe5961aSDevin Teske if (kldstat(fileid, &stat) != 0)
288*3fe5961aSDevin Teske continue;
289*3fe5961aSDevin Teske if (strcmp(stat.name, "kernel") == 0)
290*3fe5961aSDevin Teske continue;
291*3fe5961aSDevin Teske if (strlcpy(name, stat.name, sizeof(name)) >=
292*3fe5961aSDevin Teske sizeof(name))
293*3fe5961aSDevin Teske continue;
294*3fe5961aSDevin Teske if ((dot = strrchr(name, '.')) != NULL &&
295*3fe5961aSDevin Teske strcmp(dot, ".ko") == 0)
296*3fe5961aSDevin Teske *dot = '\0';
297*3fe5961aSDevin Teske if (snprintf(path, sizeof(path), "%s%s/%s.conf",
298*3fe5961aSDevin Teske rootdir, moddir, name) >= (int)sizeof(path))
299*3fe5961aSDevin Teske continue;
300*3fe5961aSDevin Teske list_candidate_seen(path, seen, &nseen,
301*3fe5961aSDevin Teske LIST_MAXSEEN);
302*3fe5961aSDevin Teske }
303*3fe5961aSDevin Teske }
304*3fe5961aSDevin Teske #endif
305*3fe5961aSDevin Teske
306*3fe5961aSDevin Teske /* Plus any drop-in already on disk (module not currently loaded) */
307*3fe5961aSDevin Teske {
308*3fe5961aSDevin Teske int n2;
309*3fe5961aSDevin Teske int nentries;
310*3fe5961aSDevin Teske struct dirent **entries;
311*3fe5961aSDevin Teske
312*3fe5961aSDevin Teske if (snprintf(dpath, sizeof(dpath), "%s%s", rootdir,
313*3fe5961aSDevin Teske moddir) >= (int)sizeof(dpath))
314*3fe5961aSDevin Teske goto done;
315*3fe5961aSDevin Teske nentries = scandir(dpath, &entries, NULL, alphasort);
316*3fe5961aSDevin Teske for (n2 = 0; n2 < nentries; n2++) {
317*3fe5961aSDevin Teske size_t len = strlen(entries[n2]->d_name);
318*3fe5961aSDevin Teske
319*3fe5961aSDevin Teske if (entries[n2]->d_name[0] != '.' && len > 5 &&
320*3fe5961aSDevin Teske strcmp(entries[n2]->d_name + len - 5,
321*3fe5961aSDevin Teske ".conf") == 0 &&
322*3fe5961aSDevin Teske snprintf(path, sizeof(path), "%s/%s", dpath,
323*3fe5961aSDevin Teske entries[n2]->d_name) < (int)sizeof(path))
324*3fe5961aSDevin Teske list_candidate_seen(path, seen, &nseen,
325*3fe5961aSDevin Teske LIST_MAXSEEN);
326*3fe5961aSDevin Teske free(entries[n2]);
327*3fe5961aSDevin Teske }
328*3fe5961aSDevin Teske if (nentries >= 0)
329*3fe5961aSDevin Teske free(entries);
330*3fe5961aSDevin Teske }
331*3fe5961aSDevin Teske
332*3fe5961aSDevin Teske done:
333*3fe5961aSDevin Teske for (n = 0; n < nseen; n++)
334*3fe5961aSDevin Teske free(seen[n]);
335*3fe5961aSDevin Teske free(seen);
336*3fe5961aSDevin Teske return (EXIT_SUCCESS);
337*3fe5961aSDevin Teske }
338