Page MenuHomeFreeBSD

Add sysconf(8) and libbsdconf(3)
Needs ReviewPublic

Authored by dteske on Mon, Jul 6, 11:42 PM.

Details

Reviewers
kfv_kfv.io
adrian
des
bdrewery
emaste
Group Reviewers
manpages
Summary

Complete the native configuration trinity: sysctl(8) modifies kernel
state, sysrc(8) manages rc.conf(5), and sysconf(8) reads and modifies
the remaining configuration files of the base system -- loader.conf(5),
sysctl.conf(5), and the make.conf(5) family -- atop a new library,
libbsdconf(3).

The library resurrects the figpar parsing engine, re-engineered into a
unified reader/writer. The parser keeps figpar's ethos intact: raw
directive/value pairs are handed to caller callbacks that own all
semantics, so drastically different formats (space-separated
httpd-style statements, cumulative directives) are read with the stock
engine and bespoke callbacks alone. Statements may span physical
lines via backslash-newline continuation (essential to make.conf and
its siblings), and input that cannot seek -- a pipe or socket, stdin
included -- is detected up front and spooled to an unlinked temporary
through the exported bsdconf_spool(), so streams parse as readily as
files. Three bugs inherited from figpar are fixed in the port:
unquoted inline comment markers leaked into values, the terminating
semicolon was retained in the value under BREAK_ON_SEMICOLON, and
reported line numbers drifted on multi-line directives.

Writes never touch a file in place. Updates stream to a mkstemp(3)
temporary in the target directory, are flushed with fsync(2), and are
atomically rename(2)d over the target with mode and ownership
propagated, so a crash or power loss mid-write cannot truncate or
corrupt a configuration file. The transaction is hardened against
interference as well as interruption: only a regular file is accepted
as a write target (opened O_NONBLOCK so a fifo fails fast instead of
blocking), mode and ownership are taken by fstat(2) from the
descriptor actually read and applied with fchmod(2)/fchown(2), the
temporary never transits a world-writable directory, and the optional
`.bak' backup refuses to follow a planted symbolic link (O_NOFOLLOW).
Symbolic links in the target path are resolved first, so writing
through a link rewrites the file it points at and preserves the link.
The properties (and the deliberate non-goals) are documented in a
SECURITY CONSIDERATIONS section of bsdconf(3).

Above the engine sits a format descriptor layer, one format per source
file plus a central registry: a descriptor contributes a target
keyword, an ordered list of backing files, and tokenizing/quoting
bitmasks -- never private parsing code. Applications bolt on formats
at run time by deriving from the nearest built-in and registering the
result. Built-in formats honor the full syntax of their consumers:
loader.conf(5) values are always written quoted with strict equals, as
the boot loader's reader demands; sysctl.conf(5) values are quoted
only when required to round-trip; and the make(1) family (make.conf,
src.conf, src-env.conf) is written unquoted with assignment modifiers
(+=, ?=, :=, !=) and the empty value (the WITH_/WITHOUT_ knob idiom)
preserved.

Formats backed by multiple files (loader.conf + loader.conf.d/*.conf +
loader.conf.local; sysctl.conf + sysctl.conf.local +
sysctl.kld.d/<module>.conf) are consulted automatically in the order
the system sources them at boot. Reads report the authoritative
(last) value, writes land in the authoritative file so the change
survives a reboot, new directives are appended to the default file,
and removals strike every file listing the directive lest deleting the
authoritative definition merely unmask an earlier one.

The sysconf(8) frontend maps assignment syntax directly onto
sysctl(8): a name alone reads, name=value writes, and options are
accepted on either side of the required target keyword. The format is
always stated explicitly by the target and never guessed from a
filename; -f overrides which file is operated on, never how, so a
loader.conf-formatted file can be staged anywhere and any format can
be deliberately applied to any file (a generic target serves files
matching no known format). A file of `-' means standard input,
permitting reads and checks from a pipeline; fifos and /dev/stdin are
spooled before the sandbox closes, and write requests against
standard input are rejected up front. -a dumps the union across a target's
files, -l/-L list backing files and module drop-in candidates (-E
filters to those on disk), -F names the file holding each directive's
effective value, -k includes a module's sysctl.kld.d drop-in, -c
checks without modifying, -x removes, and -j/-R operate on a jail or
alternate root. Writes to the sysctl target of the running system are
validated against the kernel first: unknown OIDs, read-only OIDs, and
loader-only tunables (CTLFLAG_TUN) are reported and skipped -- the
latter with a pointer to the loader target -- while remaining valid
assignments still apply. Read-only invocations run under a
capsicum(4) sandbox.

The library is strict POSIX C and builds with -std=c99
-D_POSIX_C_SOURCE=200809L with no FreeBSD headers; capsicum, jail,
kld, and sysctl validation layers in the frontend are conditional, and
bsdconf.h supplies a __BEGIN_DECLS fallback for libcs lacking
<sys/cdefs.h>. The bsdconf(3) manual defines the format descriptor at
first use, walks through adding formats (callbacks first, descriptors
second), and closes with a compiled-and-run EXAMPLES section covering
callback parsing, cumulative-directive accumulation, and run-time
format registration. The parser, writer, every format, and every
frontend flag were exercised in a fake-root harness under
AddressSanitizer/UndefinedBehaviorSanitizer, including quoting and
line-continuation round-trips against the real consumers' rules and a
symlink-attack test of the backup path; both manual pages pass
mandoc -Tlint. Not yet exercised: a full buildworld against the new
Makefiles.

libfigpar remains in the tree untouched; its removal is left to a
future commit once downstream consumers are migrated.

Co-authored-by: Faraz Vahedi <kfv@kfv.io>

Test Plan

Standalone builds of the full library + sysconf(8) with
cc -Wall -Wextra -Wshadow -Werror, plus AddressSanitizer/
UndefinedBehaviorSanitizer builds; all functional tests below were run
under the sanitized binary (leak-free).

Every library translation unit also compiles under strict POSIX
(-std=c99 -D_POSIX_C_SOURCE=200809L, FreeBSD undefined) to keep the
Linux portability promise honest.

Functional testing in a fake root (-R; no root privilege required)
with fixtures for the loader, sysctl, make, src, and src-env targets:

  • Every flag exercised: -a -c -e -E -F -f -i -j/-R exclusivity, -k, -l, -L, -n, -N, -q, -v, -x, --help, and `-f -' (stdin).
  • Option placement on both sides of the target keyword.
  • Quoting round-trips against each consumer's actual rules: loader.conf always-quoted with strict equals; sysctl.conf quoted only when required; make.conf family verbatim with += ?= := != operators and empty values (WITHOUT_* idiom).
  • Multi-file semantics: authoritative (last-file) reads, writes to the authoritative file, appends to the default file, removals from every file listing the directive (no unmasking).
  • Backslash-newline continuation: read, and collapse-on-rewrite, without disturbing neighboring statements.
  • Streams: /dev/stdin and fifo reads (spooled, including inside the capsicum sandbox); writes to a fifo or stdin cleanly rejected.
  • Security: .bak symlink-attack refused (O_NOFOLLOW), create-if-missing is O_EXCL-atomic, temp file mode/ownership propagation verified.
  • Live-system sysctl validation: read-only OIDs and loader-only tunables (CTLFLAG_TUN) rejected with a pointer to the loader target while remaining valid assignments still apply.

Both manual pages pass mandoc -Tlint; the three EXAMPLES in bsdconf.3
were extracted, compiled with -Werror under the sanitizers, and run.

Not yet done: a full make buildworld against the new Makefiles
(pending; the library and utility build standalone with the same
sources and flags).

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 74592
Build 71475: arc lint + arc unit

Event Timeline

dteske added a reviewer: adrian.
dteske added reviewers: des, manpages.
dteske added a reviewer: bdrewery.
dteske edited the test plan for this revision. (Show Details)

Playing around (and yes, my hostname is literally "BSDCan2026" because this is running on my Framework Laptop 12 that I purchased just-prior-to and took to BSDCan 2026 conference):

Test loader -ae as a litmus (can it show all the configuration options on my system?):

dteske@BSDCan2026 ~ $ sysconf loader -ae
kernel="kernel_latest"
kernels-"kernel_latest kernel_test kernel"
aesni_load="YES"
geom_eli_load="YES"
kern.geom.label.disk_ident.enable="0"
kern.geom. label.gptid.enable="0"
zfs_load-"YES"
compat.linuxkpi.80211.hw_crypto="1"
compat.linuxkpi.iwlwifi_11n_disable="0"
compat.linuxkpi.iwlwifi_disable_11n="0"
compat.linuxkpi.iwlwifi_disable_11ac="0"

Test loader -av; adding -v to print where directives are defined:

dteske@BSDCan2026 ~ $ sysconf loader -av
/boot/loader.conf: kernel: kernel_latest
/boot/loader.conf: kernels: kernel_latest kernel_test kernel
/boot/loader.conf: aesni_load: YES
/boot/loader.conf: geom_eli_load: YES
/boot/Loader.conf: kern.geom.label.disk_ident. enable: 0
/boot/loader.conf: kern.geom.label.gptid.enable: 0
/boot/loader.conf: zfs_load: YES
/boot/loader.conf.d/iwlwifi-ax210.conf: compat.linuxkpi.80211.hw_crypto: 1
/boot/loader.conf.d/iwlwifi-ax210.conf: compat.linuxkpi.iwlwifi_11n_disable: 0
/boot/loader.conf.d/iwlwifi-ax210.conf: compat.linuxkpi.iwlwifi_disable_11n: 0
/boot/loader.conf.d/iwlwifi-ax210.conf: compat.linuxkpi.iwlwifi_disable_11ac: 0

Test loader <key> to fetch the value of <key>:

dteske@BSDCan2026 ~ $ sysconf loader compat.linuxkpi.80211.hw_crypto
compat.linuxkpi.80211.hw_crypto: 1

Test sysctl -a to show all the sysctl.conf(5) settings on my laptop:

dteske@BSDCan2026 ~ $ sysconf sysctl -a
vfs.zfs.vdev.min_auto_ashift: 12
hw.acpi .power_button_state: fw_suspend
hw.acpi.lid_switch_state: suspend_to_idle

Test sysctl -av; adding -v to see where directives are configured:

deske@BSDCan2026 ~ $ sysconf sysctl -av
/etc/sysctl.conf: vfs.zfs.vdev.min_auto_ashift: 12
/etc/sysctl.conf: hw.acpi.power_button_state: fw_suspend
/etc/sysctl.conf: hw.acpi.lid_switch_state: suspend_to_idle

Test sysctl -ave; just because; adding -e to get it in <key>=<value> format:

dteske@BSDCan2026 ~ $ sysconf sysctl -ave
/etc/sysctl.conf: vfs.zfs.vdev.min_auto_ashift=12
/etc/sysctl.conf: hw.acpi.power_button_state=fw_suspend
/etc/sysctl.conf: hw.acpi.lid_switch_state-suspend_to_idle

Test trying to get a directive that we know is unconfigured/unknown (should produce an error):

dteske@BSDCan2026 ~ $ sysconf sysctl foobar
sysconf: unknown directive 'foobar"

Test first getting a value, then setting it, then setting it back (while also testing without root privileges first to make sure we get permission denied):

dteske@BSDCan2026 ~ $ sysconf loader geom_eli_load=NO
sysconf: /boot/Loader.conf: Permission denied
deske@BSDCan2026 ~ $ sudo sysconf loader geom_eli_load=NO
[sudo] Password:
dteske@BSDCan2026 ~ S sysconf loader geom_eli_load
geom_eli_load: NO
dteske@BSDCan2026 ~ $ sudo sysconf loader geom_eli_load-YES
dteske@BSDCan2026 ~ $ sysconf loader geom_eli_load
geom_eli_load: YES

I think I might update the code to print the before/after value. I think it should, when setting a value, like sysrc(8), show (for example) <old> -> <new>

Echo 'name: old -> new' on writes after the fashion of sysctl(8)/sysrc(8), silenced by -q

Honor loader_conf_files discovery from /boot/defaults/loader.conf (kevans); add sysrc-style -d/-D/-A defaults querying, LOADER_DEFAULTS, and a FILES section

Let -d compose with the dump flags as in sysrc(8): -ad/-Ad/-aDd dump descriptions of every directive in scope; sysctl -ad pairs configured directives with kernel OID descriptions

Allow -A with -D as in sysrc(8): -D's scoping wins, -A keeps its dump-implying role, so -ADd describes all defaults and only defaults

Add rc pass-through to sysrc(8) (all arguments verbatim, unvalidated), sysrc-style name+=word/name-=word list editing for non-make targets, and --version

Defaults-aware reads: the defaults file is always scanned first (like the boot loader), so 'sysconf loader acpi_video_load' reports the effective boot value; the defaults slot is read-only and never appears in -l/-L, write targeting, or removals

Touch-ups: --version prints '1.0 Jul-6,2026' (sysrc format), -h/--help advertise name[[+|-]=value], SYNOPSIS renders name[[+|-]=value] via sysrc.8's mdoc idiom

@kfv_kfv.io are you okay with using the abridged license form shown in style.9 and the freebsd license guide?

Adopt the abridged license form per style(9) and the FreeBSD license guide across all new files (requested by ziaee): copyright lines first, then SPDX-License-Identifier: BSD-2-Clause, dropping the full 2-clause text

Adversarial parser fuzzing (ASan/UBSan) found and fixed two figpar-inherited memory-safety bugs: NULL directive buffer on an empty-name line, and a bsdconf_strexpand() heap over-read/over-write when a value shrinks as backslash escapes collapse (plus a one-byte over-read on a trailing backslash)