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
265 lines
7.1 KiB
C
265 lines
7.1 KiB
C
/*
|
|
* Copyright (c) 2013-2026 Devin Teske <[email protected]>
|
|
* Copyright (c) 2021-2026 Faraz Vahedi <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
/*
|
|
* Apply or check assignments against conf files (bsdconf_put path).
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "sysconf_priv.h"
|
|
|
|
/*
|
|
* Process `-c' from the results of a completed scan_pass() without touching
|
|
* any file: a write request differs when the authoritative value does not
|
|
* match, a make(1) `+=value' request differs when no identical `+=value'
|
|
* line is already present, and a removal request differs when the
|
|
* directive is still present. Returns EXIT_SUCCESS when no changes would
|
|
* be required.
|
|
*/
|
|
int
|
|
do_checks(void)
|
|
{
|
|
int differs;
|
|
int rv = EXIT_SUCCESS;
|
|
unsigned int n;
|
|
|
|
for (n = 0; n < nreqs; n++) {
|
|
if (reqs[n].newvalue == NULL && !reqs[n].remove)
|
|
continue;
|
|
if (reqs[n].remove)
|
|
differs = reqs[n].found;
|
|
else if (reqs[n].op == BSDCONF_OP_APPEND)
|
|
differs = !reqs[n].append_present;
|
|
else
|
|
differs = !reqs[n].found ||
|
|
strcmp(reqs[n].value, reqs[n].newvalue) != 0;
|
|
if (differs) {
|
|
if (!quiet)
|
|
warnx("%s: value differs", reqs[n].name);
|
|
rv = EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Process write and remove requests using the srcidx/infile maps recorded
|
|
* by scan_pass(). A directive is rewritten in the file that authoritatively
|
|
* defines it (the last file in sourcing order that lists it) so the change
|
|
* survives a reboot; a directive defined nowhere is appended to the
|
|
* format's default file. Removals are applied to every file listing the
|
|
* directive, lest an earlier definition be unmasked. Each modified file is
|
|
* replaced in one atomic bsdconf_put() transaction. Returns EXIT_SUCCESS
|
|
* or EXIT_FAILURE.
|
|
*/
|
|
int
|
|
do_writes(void)
|
|
{
|
|
int fd;
|
|
int rv = EXIT_SUCCESS;
|
|
size_t f;
|
|
unsigned int n;
|
|
unsigned int nopts;
|
|
struct bsdconf_option *opt;
|
|
struct bsdconf_option *options;
|
|
struct request **owner;
|
|
|
|
if ((options = calloc(nreqs + 1, sizeof(*options))) == NULL ||
|
|
(owner = calloc(nreqs, sizeof(*owner))) == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
|
|
for (f = 0; f < nconf_files; f++) {
|
|
/* Collect the requests this file is responsible for */
|
|
nopts = 0;
|
|
for (n = 0; n < nreqs; n++) {
|
|
size_t target;
|
|
|
|
if (reqs[n].remove) {
|
|
if (!reqs[n].infile[f])
|
|
continue;
|
|
} else if (reqs[n].newvalue != NULL) {
|
|
/*
|
|
* A directive whose only definition is
|
|
* the read-only defaults file is written
|
|
* like one defined nowhere: appended to
|
|
* the format's default write target.
|
|
*/
|
|
if (reqs[n].srcidx < 0 ||
|
|
reqs[n].srcidx == defaults_idx)
|
|
target = write_idx;
|
|
else
|
|
target = (size_t)reqs[n].srcidx;
|
|
if (target != f)
|
|
continue;
|
|
/*
|
|
* An identical make(1) `+=value' line is
|
|
* already present: nothing to write, but
|
|
* still report the effective value unless
|
|
* -q (see report loop for the SET case).
|
|
*/
|
|
if (reqs[n].op == BSDCONF_OP_APPEND &&
|
|
reqs[n].append_present) {
|
|
if (!quiet)
|
|
print_write_echo(conf_files[f],
|
|
0, reqs[n].name,
|
|
BSDCONF_OP_APPEND,
|
|
reqs[n].value,
|
|
reqs[n].value, 0, 0, 0);
|
|
continue;
|
|
}
|
|
/*
|
|
* -s only ensures presence: if the
|
|
* WITH_/WITHOUT_ knob is already defined
|
|
* (any value), leave the file alone.
|
|
* An explicit name= (including empty)
|
|
* always writes the requested value.
|
|
*/
|
|
if (set_knobs && make_knob_p(reqs[n].name) &&
|
|
reqs[n].found) {
|
|
if (!quiet)
|
|
print_write_echo(conf_files[f],
|
|
0, reqs[n].name,
|
|
reqs[n].op,
|
|
reqs[n].value,
|
|
reqs[n].value, 0, 0,
|
|
0);
|
|
continue;
|
|
}
|
|
} else
|
|
continue;
|
|
opt = &options[nopts];
|
|
memset(opt, 0, sizeof(*opt));
|
|
opt->type = BSDCONF_TYPE_STR;
|
|
opt->directive = reqs[n].name;
|
|
opt->op = reqs[n].op;
|
|
if (reqs[n].remove) {
|
|
opt->action = BSDCONF_ACTION_REMOVE;
|
|
opt->value.str = NULL;
|
|
} else {
|
|
opt->action = BSDCONF_ACTION_SET_VALUE;
|
|
opt->value.str =
|
|
(char *)(uintptr_t)reqs[n].newvalue;
|
|
}
|
|
owner[nopts++] = &reqs[n];
|
|
}
|
|
if (nopts == 0)
|
|
continue;
|
|
memset(&options[nopts], 0, sizeof(options[nopts]));
|
|
|
|
/*
|
|
* Create the target if it does not yet exist. O_EXCL makes
|
|
* the test-and-create atomic: an existing file (or one that
|
|
* appears concurrently) is left untouched.
|
|
*/
|
|
if ((fd = open(conf_files[f], O_WRONLY | O_CREAT | O_EXCL,
|
|
0666)) >= 0)
|
|
close(fd);
|
|
else if (errno != EEXIST)
|
|
err(EXIT_FAILURE, "%s", conf_files[f]);
|
|
|
|
if (bsdconf_put(options, conf_files[f],
|
|
bsdconf_format_processing(format),
|
|
bsdconf_format_put(format)) != 0)
|
|
err(EXIT_FAILURE, "%s", conf_files[f]);
|
|
|
|
/* Report the results */
|
|
for (n = 0; n < nopts; n++) {
|
|
opt = &options[n];
|
|
if (opt->action == BSDCONF_ACTION_REMOVE) {
|
|
if ((opt->result &
|
|
BSDCONF_DIRECTIVE_REMOVED) != 0) {
|
|
owner[n]->found = 1;
|
|
/*
|
|
* Removals are quiet unless -v/-V
|
|
* (or -q which is already silent).
|
|
*/
|
|
if (!quiet &&
|
|
(verbose || verbose_trail))
|
|
print_write_echo(
|
|
conf_files[f],
|
|
opt->line,
|
|
opt->directive,
|
|
opt->op, "", "", 0, 0,
|
|
1);
|
|
}
|
|
continue;
|
|
}
|
|
if (!quiet) {
|
|
const char *old = owner[n]->value;
|
|
char *neweff = NULL;
|
|
const char *shown;
|
|
|
|
/*
|
|
* Gate the echo on whether bsdconf_put()
|
|
* actually edited the file
|
|
* (BSDCONF_VALUE_CHANGED). With -V, shape the
|
|
* echo like a read trail
|
|
* (`file:line: name=old -> name=new'). For
|
|
* make(1) `+=', show the effective value
|
|
* before and after the append rather than
|
|
* the fragment alone.
|
|
*/
|
|
if (opt->op == BSDCONF_OP_APPEND) {
|
|
neweff = make_apply(BSDCONF_OP_APPEND,
|
|
old, opt->value.str);
|
|
if (neweff == NULL)
|
|
err(EXIT_FAILURE, NULL);
|
|
shown = neweff;
|
|
} else
|
|
shown = opt->value.str;
|
|
/*
|
|
* -V shows statement shape at file:line; a
|
|
* newly added statement is `(added)' rather
|
|
* than `op -> op+value'. -v keeps effective
|
|
* old -> new for `+='.
|
|
*/
|
|
if (verbose_trail &&
|
|
(opt->result &
|
|
BSDCONF_DIRECTIVE_ADDED) != 0)
|
|
print_write_echo(conf_files[f],
|
|
opt->line, opt->directive, opt->op,
|
|
"", opt->value.str,
|
|
(opt->result &
|
|
BSDCONF_VALUE_CHANGED) != 0, 1, 0);
|
|
else if (verbose_trail)
|
|
print_write_echo(conf_files[f],
|
|
opt->line, opt->directive, opt->op,
|
|
old == NULL ? "" : old, shown,
|
|
(opt->result &
|
|
BSDCONF_VALUE_CHANGED) != 0, 0, 0);
|
|
else
|
|
print_write_echo(conf_files[f],
|
|
opt->line, opt->directive, opt->op,
|
|
old, shown,
|
|
(opt->result &
|
|
BSDCONF_VALUE_CHANGED) != 0, 0, 0);
|
|
if (neweff != NULL) {
|
|
free(owner[n]->value);
|
|
owner[n]->value = neweff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* A removal that matched no file at all is an unknown directive */
|
|
for (n = 0; n < nreqs; n++) {
|
|
if (!reqs[n].remove || reqs[n].found)
|
|
continue;
|
|
if (ignore_unknown)
|
|
continue;
|
|
if (!quiet)
|
|
warnx("unknown directive '%s'", reqs[n].name);
|
|
rv = EXIT_FAILURE;
|
|
}
|
|
|
|
free(options);
|
|
free(owner);
|
|
return (rv);
|
|
}
|