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