mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
Complete the native configuration trinity: sysctl(8) for live kernel state, sysrc(8) for rc.conf(5), and sysconf(8) for the remaining base configuration -- loader.conf(5), sysctl.conf(5), and the make.conf(5) family -- atop libbsdconf(3). libbsdconf resurrects figpar as a unified reader/writer. Callbacks own semantics; statements may span multiple lines via backslash continuation; non-seekable input is spooled; writes are atomic (mkstemp, fsync, rename) with mode/owner preservation. Format descriptors name each target, its files, and quoting rules without private parsers. Multi-file targets follow boot sourcing order; loader chases loader_conf_files as the boot loader does. sysconf(8) is the operator-facing tool: name / name=value on a required target, sysrc-style list edits, make append and list-strike where they belong, jail/altroot, and a capsicum sandbox for read-only use. Sysctl writes validate against the running kernel first -- unknown and read-only OIDs, CTLFLAG_TUN (pointing at the loader target), and CTLTYPE range checks -- so a typo or overflow does not land in sysctl.conf. Make and src treat WITH_/WITHOUT_ as presence knobs (as bsd.mkopt.mk / src.conf(5) do) and warn on the WITH_*=no form that does not disable the option, so a bad assignment is caught before an /usr/src build surfaces it. The rc target passes through to sysrc(8). Defaults querying (-d/-D/-A) mirrors sysrc for dumps and descriptions on targets that have a defaults file; named reads already see defaults, and -A only widens dump scope. Manuals are split pkg(8)-style (bsdconf/put/format; sysconf plus per-target pages). ATF coverage exercises the frontend. Co-authored-by: Faraz Vahedi <[email protected]> Reviewed by: fuz, kfv Differential Revision: https://reviews.freebsd.org/D58066
338 lines
9.4 KiB
C
338 lines
9.4 KiB
C
/*
|
|
* Copyright (c) 2013-2026 Devin Teske <[email protected]>
|
|
* Copyright (c) 2021-2026 Faraz Vahedi <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
/*
|
|
* Backing-file discovery and file-list reporting (-l/-L).
|
|
*/
|
|
|
|
#include "sysconf_priv.h"
|
|
|
|
static void apply_src_env_overrides(void);
|
|
|
|
/*
|
|
* Resolve the pathname of the target format's defaults file: the format's
|
|
* environment override (e.g., LOADER_DEFAULTS) verbatim when set and
|
|
* non-empty, otherwise the descriptor's defaults file prefixed with the
|
|
* `-R dir' root. Returns a newly allocated path, or NULL when the format
|
|
* has no defaults file.
|
|
*/
|
|
char *
|
|
defaults_path(void)
|
|
{
|
|
char *env;
|
|
char *path;
|
|
const struct bsdconf_format_def *def;
|
|
char buf[PATH_MAX];
|
|
|
|
if ((def = bsdconf_format_lookup(format)) == NULL ||
|
|
def->defaults == NULL)
|
|
return (NULL);
|
|
if (def->defaults_env != NULL &&
|
|
(env = getenv(def->defaults_env)) != NULL && *env != '\0') {
|
|
if ((path = strdup(env)) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
return (path);
|
|
}
|
|
if (snprintf(buf, sizeof(buf), "%s%s", rootdir, def->defaults) >=
|
|
(int)sizeof(buf))
|
|
errx(EXIT_FAILURE, "%s%s: %s", rootdir, def->defaults,
|
|
strerror(ENAMETOOLONG));
|
|
if ((path = strdup(buf)) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
return (path);
|
|
}
|
|
|
|
/*
|
|
* Resolve the target format and its ordered list of backing files from the
|
|
* command line: either the `-f file' override (a single file; the format is
|
|
* guessed from its name) or the leading `target' keyword (`loader',
|
|
* `sysctl', `make'), whose multi-file list -- optionally including the
|
|
* `-k module' drop-in -- comes from the format abstraction layer (which,
|
|
* for targets carrying a defaults file, performs the same discovery the
|
|
* boot-time consumer does; see bsdconf_format_files(3)). All paths honor
|
|
* the `-R dir' (or `-j jail') root. A target with a defaults file always
|
|
* has it prepended to the scan order (defaults first, mirroring the boot
|
|
* loader, so reads report the effective value even for directives no conf
|
|
* file mentions), but the slot is read-only bookkeeping: see defaults_idx.
|
|
* With `-D', the defaults file replaces the list outright; `-A' widens
|
|
* dump scope to include directives still at their default. Combined
|
|
* (`-AD'), the scoping of `-D' wins and `-A' retains only its
|
|
* dump-implying role, so `-ADd' describes all defaults and only defaults,
|
|
* as in sysrc(8).
|
|
*/
|
|
void
|
|
resolve_target(const char *target)
|
|
{
|
|
char *defaults;
|
|
const char *env = NULL;
|
|
const struct bsdconf_format_def *def;
|
|
|
|
if (target == NULL) {
|
|
warnx("no target provided");
|
|
usage();
|
|
}
|
|
if (bsdconf_format_find(target, &format) != 0) {
|
|
if (strcmp(target, "src-env") == 0)
|
|
warnx("unknown target `src-env'; use `src' "
|
|
"(includes src-env.conf in the build triad) "
|
|
"or `-f' for a specific file");
|
|
else
|
|
warnx("unknown target `%s'", target);
|
|
usage();
|
|
}
|
|
|
|
if (file != NULL) {
|
|
char path[PATH_MAX];
|
|
const char *prefix;
|
|
|
|
/*
|
|
* `-f file' overrides which file(s) are consulted and
|
|
* modified, but never the format; that is always stated
|
|
* explicitly by the target keyword. Standard input
|
|
* (`-f -') is the caller's own and is not subject to -R.
|
|
*/
|
|
prefix = file_stdin ? "" : rootdir;
|
|
if ((conf_files = calloc(1, sizeof(*conf_files))) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
if (snprintf(path, sizeof(path), "%s%s", prefix, file) >=
|
|
(int)sizeof(path))
|
|
errx(EXIT_FAILURE, "%s%s: %s", prefix, file,
|
|
strerror(ENAMETOOLONG));
|
|
if ((conf_files[0] = strdup(path)) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
nconf_files = 1;
|
|
write_idx = 0;
|
|
return;
|
|
}
|
|
|
|
def = bsdconf_format_lookup(format);
|
|
if (def == NULL || (def->path == NULL && def->sources == NULL))
|
|
errx(EXIT_FAILURE,
|
|
"target `%s' has no default files; specify one with -f",
|
|
target);
|
|
|
|
/* `-D' consults the defaults file alone */
|
|
if (defaults_only) {
|
|
if ((conf_files = calloc(1, sizeof(*conf_files))) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
conf_files[0] = defaults_path();
|
|
nconf_files = 1;
|
|
write_idx = 0;
|
|
return;
|
|
}
|
|
|
|
if (def->defaults_env != NULL &&
|
|
(env = getenv(def->defaults_env)) != NULL && *env == '\0')
|
|
env = NULL;
|
|
if (bsdconf_format_files(format, rootdir, module, env, &conf_files,
|
|
&nconf_files, &write_idx) != 0 || nconf_files == 0)
|
|
err(EXIT_FAILURE, "%s", target);
|
|
|
|
/*
|
|
* The src triad honors the same environment overrides make(1)
|
|
* does when building /usr/src (verbatim, not subject to -R).
|
|
*/
|
|
if (format == BSDCONF_FORMAT_SRC)
|
|
apply_src_env_overrides();
|
|
|
|
/*
|
|
* A target with a defaults file sources it first at boot, so the
|
|
* effective value of a directive may come from the defaults alone
|
|
* even when no conf file mentions it. Prepend the defaults file
|
|
* to the scan order (defaults first, so every override lands
|
|
* later) and remember its index: the defaults are read-only, so
|
|
* that slot is never chosen as a write target, never satisfies a
|
|
* removal, and is never shown by -l/-L (see defaults_idx users).
|
|
*/
|
|
if ((defaults = defaults_path()) != NULL) {
|
|
char **tmp;
|
|
size_t n;
|
|
|
|
tmp = calloc(nconf_files + 1, sizeof(*tmp));
|
|
if (tmp == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
tmp[0] = defaults;
|
|
for (n = 0; n < nconf_files; n++)
|
|
tmp[n + 1] = conf_files[n];
|
|
free(conf_files);
|
|
conf_files = tmp;
|
|
nconf_files++;
|
|
defaults_idx = 0;
|
|
write_idx++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Replace src triad paths with SRC_ENV_CONF / __MAKE_CONF / SRCCONF when
|
|
* set and non-empty (same variables share/mk consults). Values are used
|
|
* verbatim, matching LOADER_DEFAULTS.
|
|
*/
|
|
static void
|
|
apply_src_env_overrides(void)
|
|
{
|
|
static const char *const envs[] = {
|
|
"SRC_ENV_CONF",
|
|
"__MAKE_CONF",
|
|
"SRCCONF",
|
|
};
|
|
const char *env;
|
|
char *path;
|
|
size_t i;
|
|
|
|
if (nconf_files != sizeof(envs) / sizeof(envs[0]))
|
|
return;
|
|
for (i = 0; i < nconf_files; i++) {
|
|
if ((env = getenv(envs[i])) == NULL || *env == '\0')
|
|
continue;
|
|
if ((path = strdup(env)) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
free(conf_files[i]);
|
|
conf_files[i] = path;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print a candidate path unless `-E' excludes it for not existing or it
|
|
* was already printed (`seen' is a NULL-terminated list of prior paths).
|
|
*/
|
|
static void
|
|
list_candidate(const char *path, char *seen[], size_t nseen)
|
|
{
|
|
size_t n;
|
|
|
|
for (n = 0; n < nseen; n++)
|
|
if (seen[n] != NULL && strcmp(seen[n], path) == 0)
|
|
return;
|
|
if (existing_only && access(path, F_OK) != 0)
|
|
return;
|
|
puts(path);
|
|
}
|
|
|
|
/*
|
|
* Record `path' in the `seen' list (for suppressing duplicates) after
|
|
* printing it as a candidate.
|
|
*/
|
|
static void
|
|
list_candidate_seen(const char *path, char **seen, size_t *nseen,
|
|
size_t maxseen)
|
|
{
|
|
|
|
list_candidate(path, seen, *nseen);
|
|
if (*nseen < maxseen)
|
|
seen[(*nseen)++] = strdup(path);
|
|
}
|
|
|
|
/*
|
|
* Process `-l' (list the files backing the target) and `-L' (additionally
|
|
* list every drop-in candidate; for targets with a module drop-in
|
|
* directory, one candidate per loaded kernel module plus any `*.conf'
|
|
* already on disk). With `-E', only files that exist are shown. Returns
|
|
* EXIT_SUCCESS.
|
|
*/
|
|
int
|
|
do_list(void)
|
|
{
|
|
size_t n;
|
|
size_t nseen = 0;
|
|
char **seen;
|
|
const char *moddir = NULL;
|
|
const struct bsdconf_format_def *def;
|
|
const struct bsdconf_source *source;
|
|
#define LIST_MAXSEEN 512
|
|
char dpath[PATH_MAX];
|
|
char path[PATH_MAX];
|
|
|
|
if ((seen = calloc(LIST_MAXSEEN, sizeof(*seen))) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
|
|
for (n = 0; n < nconf_files; n++) {
|
|
/* The read-only defaults file is never a write candidate */
|
|
if ((int)n == defaults_idx)
|
|
continue;
|
|
list_candidate_seen(conf_files[n], seen, &nseen,
|
|
LIST_MAXSEEN);
|
|
}
|
|
|
|
if (!list_all)
|
|
goto done;
|
|
|
|
/* Locate the module drop-in directory (if the format has one) */
|
|
def = bsdconf_format_lookup(format);
|
|
if (def != NULL && def->sources != NULL)
|
|
for (source = def->sources; source->path != NULL; source++)
|
|
if (source->type == BSDCONF_SOURCE_MODDIR)
|
|
moddir = source->path;
|
|
if (moddir == NULL)
|
|
goto done;
|
|
|
|
#ifdef __FreeBSD__
|
|
/*
|
|
* One candidate per loaded kernel module, whether or not the file
|
|
* exists yet (the module being loaded is what makes the file
|
|
* eligible for sourcing by rc.subr(8)).
|
|
*/
|
|
{
|
|
char name[MAXPATHLEN];
|
|
char *dot;
|
|
int fileid;
|
|
struct kld_file_stat stat;
|
|
|
|
for (fileid = kldnext(0); fileid > 0;
|
|
fileid = kldnext(fileid)) {
|
|
stat.version = sizeof(stat);
|
|
if (kldstat(fileid, &stat) != 0)
|
|
continue;
|
|
if (strcmp(stat.name, "kernel") == 0)
|
|
continue;
|
|
if (strlcpy(name, stat.name, sizeof(name)) >=
|
|
sizeof(name))
|
|
continue;
|
|
if ((dot = strrchr(name, '.')) != NULL &&
|
|
strcmp(dot, ".ko") == 0)
|
|
*dot = '\0';
|
|
if (snprintf(path, sizeof(path), "%s%s/%s.conf",
|
|
rootdir, moddir, name) >= (int)sizeof(path))
|
|
continue;
|
|
list_candidate_seen(path, seen, &nseen,
|
|
LIST_MAXSEEN);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Plus any drop-in already on disk (module not currently loaded) */
|
|
{
|
|
int n2;
|
|
int nentries;
|
|
struct dirent **entries;
|
|
|
|
if (snprintf(dpath, sizeof(dpath), "%s%s", rootdir,
|
|
moddir) >= (int)sizeof(dpath))
|
|
goto done;
|
|
nentries = scandir(dpath, &entries, NULL, alphasort);
|
|
for (n2 = 0; n2 < nentries; n2++) {
|
|
size_t len = strlen(entries[n2]->d_name);
|
|
|
|
if (entries[n2]->d_name[0] != '.' && len > 5 &&
|
|
strcmp(entries[n2]->d_name + len - 5,
|
|
".conf") == 0 &&
|
|
snprintf(path, sizeof(path), "%s/%s", dpath,
|
|
entries[n2]->d_name) < (int)sizeof(path))
|
|
list_candidate_seen(path, seen, &nseen,
|
|
LIST_MAXSEEN);
|
|
free(entries[n2]);
|
|
}
|
|
if (nentries >= 0)
|
|
free(entries);
|
|
}
|
|
|
|
done:
|
|
for (n = 0; n < nseen; n++)
|
|
free(seen[n]);
|
|
free(seen);
|
|
return (EXIT_SUCCESS);
|
|
}
|