Files
Devin Teske 398b29f07f sysconf: add a poudriere target
poudriere.conf is handed to sysrc(8), including the jail, tree, and
set overlays poudriere(8) consults.  make, src, and src-env are
sub-targets over the matching make(1) files under poudriere.d.  -l
and -L list one path per line; -L includes overlays that do not exist
yet.  A bare -- ends the sub-target so the next word is a variable.
Bump sysconf(8) to 2.2.

Reviewed by:	fuz, kfv, bcr
Differential Revision:	https://reviews.freebsd.org/D59943
2026-10-08 09:26:43 -07:00

696 lines
20 KiB
C

/*
* Copyright (c) 2013-2026 Devin Teske <[email protected]>
* Copyright (c) 2021-2026 Faraz Vahedi <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/*
* sysconf(8): CLI entry, option parsing, and program state.
*/
#include "sysconf_priv.h"
/* Requests to process (one per name argument) */
struct request *reqs; /* set by main() */
unsigned int nreqs; /* set by main() */
/* Union of all directives, for `-a' against multi-file targets */
struct dumpent *dumps; /* set by scan_cb() */
size_t ndumps; /* set by scan_cb() */
size_t dumpsize; /* set by scan_cb() */
/* Resolved target state */
char **conf_files; /* ordered backing files */
size_t nconf_files;
size_t conf_scanidx; /* file being parsed (scan_pass) */
size_t write_idx; /* default write target index */
int defaults_idx = -1; /* defaults file index in conf_files
* (read-only, never listed); -1 if none */
enum bsdconf_format format = BSDCONF_FORMAT_GENERIC;
/* Extra display information */
const char *pgm; /* set to getprogname() by main() */
bool check = false; /* `-c' */
bool defaults_only = false; /* `-D' */
bool dump_all = false; /* `-a' */
bool existing_only = false; /* `-E' */
bool ignore_unknown = false; /* `-i' */
bool list_all = false; /* `-L' */
bool list_files = false; /* `-l' */
bool name_only = false; /* `-N' */
bool quiet = false; /* `-q' */
bool remove_mode = false; /* `-x' */
bool set_knobs = false; /* `-s' WITH_/WITHOUT_ presence */
bool show_desc = false; /* `-d' */
bool show_equals = false; /* `-e' */
bool show_file = false; /* `-F' */
bool value_only = false; /* `-n' */
bool verbose = false; /* `-v' */
bool verbose_trail = false; /* `-V' */
bool with_defaults = false; /* `-A' */
/* Option arguments */
const char *file = NULL; /* `-f file' */
bool file_stdin = false; /* `-f -' given */
const char *jailname = NULL; /* `-j jail' */
const char *module = NULL; /* `-k module' */
const char *rootdir = ""; /* `-R dir' */
/*
* Run the getopt(3) loop over `argv', setting the option globals. Called
* once for the options preceding the target keyword and once for those
* following it. Returns the number of arguments consumed (optind).
*/
static int
parse_options(int argc, char *argv[])
{
int ch;
while ((ch = getopt(argc, argv, OPTSTRING)) != -1) {
switch (ch) {
case 'A': /* dump all directives, defaults included */
with_defaults = 1;
break;
case 'a': /* dump all directives */
dump_all = 1;
break;
case 'c': /* check only; do not modify */
check = 1;
break;
case 'd': /* show directive descriptions */
show_desc = 1;
break;
case 'D': /* consult the defaults file only */
defaults_only = 1;
break;
case 'E': /* list existing files only (with -l/-L) */
existing_only = 1;
break;
case 'e': /* show name=value */
show_equals = 1;
break;
case 'F': /* show authoritative file, not value */
show_file = 1;
break;
case 'f': /* explicit file (`-' is standard input) */
if (strcmp(optarg, "-") == 0) {
file = "/dev/stdin";
file_stdin = 1;
} else {
file = optarg;
file_stdin = 0;
}
break;
case 'h': /* help/usage */
usage();
break;
case 'i': /* ignore unknown names */
ignore_unknown = 1;
break;
case 'j': /* jail name or id */
jailname = optarg;
break;
case 'k': /* kernel module drop-in */
module = optarg;
break;
case 'l': /* list backing files */
list_files = 1;
break;
case 'L': /* list all candidate files */
list_all = 1;
break;
case 'n': /* value only */
value_only = 1;
break;
case 'N': /* name only */
name_only = 1;
break;
case 'q': /* quiet */
quiet = 1;
break;
case 'R': /* root dir */
rootdir = optarg;
break;
case 's': /* set empty WITH_/WITHOUT_ knobs (make/src) */
set_knobs = 1;
break;
case 'v': /* verbose (last file + final value) */
verbose = 1;
break;
case 'V': /* verbose trail (every assignment step) */
verbose_trail = 1;
break;
case 'x': /* remove */
remove_mode = 1;
break;
case '?': /* unknown argument (based on optstring) */
default: /* unhandled argument (based on switch) */
usage();
}
}
return (optind);
}
/*
* The native configuration trinity's third member: read and modify
* loader.conf(5), sysctl.conf(5), make.conf(5), and arbitrary configuration
* files with sysctl(8) assignment syntax.
*/
int
main(int argc, char *argv[])
{
int have_reads = 0;
int have_writes = 0;
int n;
int rv = EXIT_SUCCESS;
const char *resolved = NULL;
const char *target = NULL;
pgm = getprogname();
/*
* Target selection, including the rc and poudriere pass-throughs,
* runs while argv is still pristine (see target_resolve()).
*/
resolved = target_resolve(&argc, argv);
/*
* Honor `--help' and `--version' wherever they appear (getopt(3)
* knows nothing of long options and would reject them as illegal
* short options). A bare `--' ends option processing as usual.
*/
for (n = 1; n < argc; n++) {
if (strcmp(argv[n], "--") == 0)
break;
if (strcmp(argv[n], "--help") == 0)
help();
if (strcmp(argv[n], "--version") == 0) {
printf("%s (libbsdconf %s)\n", SYSCONF_VERSION,
BSDCONF_VERSION);
exit(EXIT_SUCCESS);
}
}
/*
* Process command-line options
*/
n = parse_options(argc, argv);
argc -= n;
argv += n;
/*
* Consume the (required) target keyword and allow further options
* to follow it, mirroring the option placement freedom of
* sysctl(8) (e.g., `sysconf sysctl -L'). getopt(3) skips over its
* first argument, so hand it the target keyword's slot as the
* program name.
*
* Nested `poudriere make|src|src-env' already stripped both keywords;
* remaining argv is options plus names.
*/
if (argc > 0 && !poudriere_native()) {
target = resolved != NULL ? resolved : argv[0];
argc--;
argv++;
#ifdef __FreeBSD__
optreset = 1;
optind = 1;
#else
optind = 0; /* glibc and musl re-initialize at zero */
#endif
n = parse_options(argc + 1, argv - 1) - 1;
argc -= n;
argv += n;
}
if (jailname != NULL && *rootdir != '\0') {
warnx("-j and -R are mutually exclusive");
usage();
}
if (file != NULL && module != NULL) {
warnx("-f and -k are mutually exclusive");
usage();
}
if (existing_only && !list_files && !list_all) {
warnx("-E requires -l or -L");
usage();
}
if (set_knobs && remove_mode) {
warnx("-s and -x are mutually exclusive");
usage();
}
#ifdef __FreeBSD__
if (jailname != NULL) {
int jid;
if ((jid = jail_getid(jailname)) < 0)
errx(EXIT_FAILURE, "%s", jail_errmsg);
if (jail_attach(jid) != 0)
err(EXIT_FAILURE, "jail_attach(%d)", jid);
}
#else
if (jailname != NULL)
errx(EXIT_FAILURE, "-j is not supported on this platform");
#endif
/* Resolve the target format and its backing files */
if (poudriere_native())
poudriere_setup();
else
resolve_target(target);
/* `-k' requires a target with a module drop-in directory */
if (module != NULL) {
const struct bsdconf_format_def *def;
const struct bsdconf_source *source;
int has_moddir = 0;
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)
has_moddir = 1;
if (!has_moddir) {
warnx("target does not support -k");
usage();
}
}
/*
* `-d', `-D', and `-A' consult a body of defaults; only targets
* that have one support them (make.conf and friends have no
* defaults file, and no directive descriptions to give). The
* exception is `sysctl -d', whose descriptions come from the
* running kernel rather than any file.
*/
{
const struct bsdconf_format_def *def;
int has_defaults;
def = bsdconf_format_lookup(format);
has_defaults = def != NULL && def->defaults != NULL;
if (show_desc) {
if (format != BSDCONF_FORMAT_SYSCTL &&
!has_defaults) {
warnx("target does not support -d");
usage();
}
if (file != NULL) {
warnx("-d and -f are mutually exclusive");
usage();
}
if (list_files || list_all || remove_mode || check ||
set_knobs) {
warnx("-d cannot be combined with "
"-c/-l/-L/-s/-x");
usage();
}
#ifdef __FreeBSD__
if (format == BSDCONF_FORMAT_SYSCTL &&
*rootdir != '\0')
errx(EXIT_FAILURE, "sysctl descriptions "
"come from the running kernel; "
"-R does not apply");
#endif
}
if ((defaults_only || with_defaults) && !has_defaults) {
warnx("target does not support -%c",
defaults_only ? 'D' : 'A');
usage();
}
if ((defaults_only || with_defaults) && file != NULL) {
warnx("-%c and -f are mutually exclusive",
defaults_only ? 'D' : 'A');
usage();
}
}
/* File listings take no names and perform no other work */
if (list_files || list_all) {
if (argc != 0 || dump_all) {
warnx("-l/-L take no names");
usage();
}
exit(do_list());
}
/* `-A' with no names dumps everything, defaults included */
if (with_defaults && argc == 0)
dump_all = 1;
/* Display usage and exit if not given at least one name */
if (argc == 0 && !dump_all) {
warnx("no names provided");
usage();
}
if (argc != 0 && dump_all) {
warnx("-a does not take names");
usage();
}
/* Classify the remaining arguments */
nreqs = (unsigned int)argc;
if (nreqs > 0 &&
(reqs = calloc(nreqs, sizeof(*reqs))) == NULL)
err(EXIT_FAILURE, NULL);
if (set_knobs) {
if (format != BSDCONF_FORMAT_MAKE &&
format != BSDCONF_FORMAT_SRC) {
warnx("-s is only valid for make and src");
usage();
}
if (argc == 0) {
warnx("-s requires at least one WITH_/WITHOUT_ name");
usage();
}
}
for (n = 0; n < argc; n++) {
split_request(argv[n], &reqs[n], remove_mode);
if (set_knobs) {
if (reqs[n].newvalue != NULL) {
warnx("-s does not take a value "
"('%s')", argv[n]);
usage();
}
if (!make_knob_name_p(reqs[n].name)) {
if (strcmp(reqs[n].name,
"WITHOUT_MODULES") == 0)
warnx("WITHOUT_MODULES takes a "
"module list; use "
"WITHOUT_MODULES=...");
else
warnx("-s requires a WITH_ or "
"WITHOUT_ name ('%s')",
reqs[n].name);
usage();
}
reqs[n].newvalue = "";
}
if (reqs[n].newvalue != NULL && !reqs[n].remove)
warn_with_equals_no(reqs[n].name,
reqs[n].newvalue, NULL, 0);
if (reqs[n].remove || reqs[n].newvalue != NULL)
have_writes = 1;
else
have_reads = 1;
}
if (check && !have_writes) {
warnx("-c requires at least one name=value or -s name");
usage();
}
/* Descriptions are read-only */
if (show_desc) {
if (have_writes) {
warnx("-d does not take values");
usage();
}
#ifndef __FreeBSD__
if (format == BSDCONF_FORMAT_SYSCTL)
errx(EXIT_FAILURE,
"sysctl descriptions require FreeBSD");
#endif
/* Explicit names need no file scan at all */
if (!dump_all) {
#ifdef __FreeBSD__
if (format == BSDCONF_FORMAT_SYSCTL)
exit(describe_sysctl());
#endif
exit(describe_defaults());
}
/*
* Dumps (-ad, -Ad, -aDd) pair each scanned directive with
* its description; harvest the descriptions before the
* read sandbox slams shut.
*/
if (format != BSDCONF_FORMAT_SYSCTL)
load_descriptions();
}
/* Defaults can be read and checked against, never rewritten */
if ((defaults_only || with_defaults) && have_writes && !check)
errx(EXIT_FAILURE, "defaults are read-only");
/* Standard input can be read and checked, never rewritten */
if (file_stdin && have_writes && !check)
errx(EXIT_FAILURE, "cannot write to standard input");
#if defined(__FreeBSD__)
/*
* Refuse up front to set sysctl OIDs that can never take effect
* from sysctl.conf(5) (non-leaf, read-only, loader-only tunables)
* or whose values cannot fit the OID's CTLTYPE (so an overflowing
* assignment cannot surprise sysctl(8) at boot). Unknown OIDs are
* warned about but still written -- the module may not be loaded
* yet, matching init(8)'s handling of sysctl.conf(5). Only
* meaningful against the running system's sysctl tree, so skipped
* under -R. -i/-q quiet the unknown-OID warning; -k remains the
* module drop-in selector.
*/
if (format == BSDCONF_FORMAT_SYSCTL && have_writes && !check &&
*rootdir == '\0') {
int quiet_unknown = ignore_unknown || quiet;
unsigned int u;
for (u = 0; u < nreqs; u++) {
if (reqs[u].newvalue == NULL)
continue;
if (sysctl_writable(reqs[u].name, reqs[u].newvalue,
quiet_unknown) != 0) {
/* Convert to a no-op; count the failure */
reqs[u].newvalue = NULL;
reqs[u].remove = 0;
reqs[u].name = "";
rv = EXIT_FAILURE;
}
}
have_writes = 0;
have_reads = 0;
for (u = 0; u < nreqs; u++) {
if (reqs[u].remove || reqs[u].newvalue != NULL)
have_writes = 1;
else if (reqs[u].name[0] != '\0')
have_reads = 1;
}
if (rv != EXIT_SUCCESS && !have_writes && !have_reads)
exit(rv); /* nothing settable remains */
}
#endif
/*
* Scan the backing files to locate each request's authoritative
* definition. Pure reads and -c checks (do_checks() never calls
* put) run the scan inside a Capsicum sandbox -- except a sysctl
* description dump, whose kernel queries the sandbox would deny.
* Classify by whether we will actually write, not by whether the
* argv looked like name=value (`-c' sets have_writes for checks).
*/
scan_pass((check || !have_writes) &&
!(show_desc && format == BSDCONF_FORMAT_SYSCTL));
/* Materialize list edits now that effective values are known */
if (merge_list_requests() != EXIT_SUCCESS)
rv = EXIT_FAILURE;
if (check) {
if (do_make_strikes(1) != EXIT_SUCCESS)
rv = EXIT_FAILURE;
if (do_checks() != EXIT_SUCCESS)
rv = EXIT_FAILURE;
exit(rv);
}
/* Apply writes first so that subsequent reads see the result */
if (have_writes) {
int wrv;
if ((wrv = do_make_strikes(0)) != EXIT_SUCCESS)
rv = wrv;
if ((wrv = do_writes()) != EXIT_SUCCESS)
rv = wrv;
/* Refresh the scan for any trailing read requests */
if (have_reads || dump_all) {
for (n = 0; (unsigned int)n < nreqs; n++) {
size_t ai;
trail_free(reqs[n].trail, reqs[n].ntrail);
reqs[n].trail = NULL;
reqs[n].ntrail = 0;
for (ai = 0; ai < reqs[n].nassigns; ai++)
free(reqs[n].assigns[ai].piece);
free(reqs[n].assigns);
reqs[n].assigns = NULL;
reqs[n].nassigns = 0;
reqs[n].found = 0;
reqs[n].append_present = 0;
reqs[n].srcidx = -1;
reqs[n].line = 0;
memset(reqs[n].infile, 0, nconf_files);
}
scan_pass(0);
}
}
if (have_reads || dump_all) {
int rrv;
if ((rrv = do_reads()) != EXIT_SUCCESS)
rv = rrv;
}
exit(rv);
}
/*
* Print short usage statement to stderr and exit with error status.
*/
void
usage(void)
{
fprintf(stderr,
"usage: %s target [-AcDeFinNqsvVx] [-j jail | -R dir]"
" [-f file | -k module]\n"
" name[[+|-]=value] ...\n", pgm);
fprintf(stderr,
" %s target [-ADeFnNqvV] [-j jail | -R dir]"
" [-f file | -k module] -a\n", pgm);
fprintf(stderr,
" %s target [-AaDinNq] [-j jail | -R dir]"
" -d [name ...]\n", pgm);
fprintf(stderr,
" %s target [-E] [-j jail | -R dir] [-k module]"
" -l | -L\n", pgm);
fprintf(stderr,
" %s rc [sysrc(8) argument ...]\n", pgm);
poudriere_usage();
fprintf(stderr, "Try `%s --help' for more information.\n", pgm);
exit(EXIT_FAILURE);
}
/*
* Print long usage statement to stderr and exit with error status.
*/
void
help(void)
{
fprintf(stderr,
"usage: %s target [-AcDeFinNqsvVx] [-j jail | -R dir]"
" [-f file | -k module] name[[+|-]=value] ...\n", pgm);
fprintf(stderr, "TARGETS:\n");
#define TGTFMT "\t%-9s %s\n"
fprintf(stderr, TGTFMT, "loader",
"files named by loader_conf_files et al. in");
fprintf(stderr, TGTFMT, "",
"/boot/defaults/loader.conf (typically /boot/loader.conf,");
fprintf(stderr, TGTFMT, "",
"loader.conf.d/*.conf, loader.conf.local)");
fprintf(stderr, TGTFMT, "sysctl",
"/etc/sysctl.conf, sysctl.conf.local, sysctl.kld.d/*.conf");
fprintf(stderr, TGTFMT, "make",
"/etc/make.conf (or -f for any make-syntax file)");
fprintf(stderr, TGTFMT, "src",
"/usr/src build triad: src-env.conf, make.conf, src.conf");
fprintf(stderr, TGTFMT, "",
"(new writes prefer src.conf; env: SRC_ENV_CONF,");
fprintf(stderr, TGTFMT, "",
"__MAKE_CONF, SRCCONF)");
fprintf(stderr, TGTFMT, "rc",
"pass-through: all other arguments go to sysrc(8) verbatim");
poudriere_help();
fprintf(stderr, TGTFMT, "generic",
"no default files; requires -f file");
fprintf(stderr,
"A unique prefix of a target keyword is accepted.\n");
fprintf(stderr, "OPTIONS:\n");
#define OPTFMT "\t%-9s %s\n"
fprintf(stderr, OPTFMT, "-A",
"Include the target's defaults file in the sourcing order");
fprintf(stderr, OPTFMT, "",
"(alone, dumps everything, defaults included).");
fprintf(stderr, OPTFMT, "-a",
"Dump the union of all directives from the target's files.");
fprintf(stderr, OPTFMT, "-c",
"Check. Return success if no changes needed, else error.");
fprintf(stderr, OPTFMT, "-D",
"Consult only the target's defaults file.");
fprintf(stderr, OPTFMT, "-d",
"Show directive descriptions (from the defaults file's");
fprintf(stderr, OPTFMT, "",
"comments; for sysctl, from the running kernel). With");
fprintf(stderr, OPTFMT, "",
"-a, -A, or -D, describe every directive in scope.");
fprintf(stderr, OPTFMT, "-E",
"With -l or -L, list only files that exist on disk.");
fprintf(stderr, OPTFMT, "-e",
"Separate name and value with `=' (reads and write echoes).");
fprintf(stderr, OPTFMT, "-F",
"Show the file holding each directive's effective value.");
fprintf(stderr, OPTFMT, "-f file",
"Operate on file, in the target's format, instead of the");
fprintf(stderr, OPTFMT, "",
"target's standard files (`-' means standard input).");
fprintf(stderr, OPTFMT, "-h",
"Print a short usage statement to stderr and exit.");
fprintf(stderr, OPTFMT, "--help",
"Print this message to stderr and exit.");
fprintf(stderr, OPTFMT, "--version",
"Print the utility and library versions and exit.");
fprintf(stderr, OPTFMT, "-i",
"Ignore unknown names (and quiet unknown sysctl OID warnings).");
fprintf(stderr, OPTFMT, "-j jail",
"Operate within the jail `jail' (name or numeric id).");
fprintf(stderr, OPTFMT, "-k module",
"Include the kernel module drop-in file for `module'.");
fprintf(stderr, OPTFMT, "-l",
"List the pathnames of the target's backing files.");
fprintf(stderr, OPTFMT, "-L",
"List all candidate files, including module drop-ins.");
fprintf(stderr, OPTFMT, "-n",
"Show only directive values, not their names.");
fprintf(stderr, OPTFMT, "-N",
"Show only directive names, not their values.");
fprintf(stderr, OPTFMT, "-q",
"Quiet. Suppress unknown-directive warnings and the");
fprintf(stderr, OPTFMT, "",
"`old -> new' echo of writes.");
fprintf(stderr, OPTFMT, "-R dir",
"Operate within the root directory `dir' rather than `/'.");
fprintf(stderr, OPTFMT, "-s",
"Set empty WITH_/WITHOUT_ knobs (make and src only).");
fprintf(stderr, OPTFMT, "-v",
"Verbose. Print the pathname of the configuration file");
fprintf(stderr, OPTFMT, "",
"holding the final effective value.");
fprintf(stderr, OPTFMT, "-V",
"Trail. Reads: each assignment step (file:line, operator,");
fprintf(stderr, OPTFMT, "",
"fragment, running effective). Writes: file:line:");
fprintf(stderr, OPTFMT, "",
"name=old -> name=new (or name=value (unchanged)).");
fprintf(stderr, OPTFMT, "-x",
"Remove name(s) from the target's files.");
fprintf(stderr, "ENVIRONMENT:\n");
fprintf(stderr, OPTFMT, "BSDCONF_MAX_BYTES",
"Cap on bytes read from a configuration file (see bsdconf(3)).");
fprintf(stderr, OPTFMT, "LOADER_DEFAULTS",
"Defaults file for the loader target (in place of");
fprintf(stderr, OPTFMT, "",
"/boot/defaults/loader.conf).");
fprintf(stderr, OPTFMT, "SRC_ENV_CONF",
"src-env.conf for the src target (default /etc/src-env.conf).");
fprintf(stderr, OPTFMT, "__MAKE_CONF",
"make.conf for the src target (default /etc/make.conf).");
fprintf(stderr, OPTFMT, "SRCCONF",
"src.conf for the src target (default /etc/src.conf).");
exit(EXIT_FAILURE);
}