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 * 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 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 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 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 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 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