Reference

bsdconf_put(3)

resilient configuration file writing

NAME

bsdconf_put, bsdconf_set_option – resilient configuration file writing

LIBRARY

library “libbsdconf”

SYNOPSIS

#include <bsdconf.h>

int

bsdconf_put(struct bsdconf_option options[], const char *path, uint16_t processing_options, uint16_t put_options);

int

bsdconf_set_option(struct bsdconf_option options[], const char *directive, union bsdconf_value *value);

DESCRIPTION

bsdconf_put() rewrites the configuration file at path, applying the per-directive action of each option in the array:

BSDCONF_ACTION_SET_VALUE

Set the directive to value.str, editing it in place if present or appending it to the file if absent.

BSDCONF_ACTION_CHECK

Report (via result) whether the current value differs from value.str, without modifying the file.

BSDCONF_ACTION_REMOVE

Delete the directive from the file.

Unlike bsdconf_parse(), directives are matched exactly (a pattern is not a writable target). Comments, blank lines, statement ordering, and the formatting of untouched statements are preserved. For each processed option, result is set to a bitmask of BSDCONF_DIRECTIVE_FOUND, BSDCONF_VALUE_CHANGED, BSDCONF_DIRECTIVE_ADDED, and BSDCONF_DIRECTIVE_REMOVED, and line is set to the line of the first match (if found). A non-zero match_line on input restricts the put to the statement on that physical line (zero matches any statement, as before); this allows a single ‘name+=value’ among several to be rewritten or removed without appending a new line or touching the others (make(1) list-strike edits in sysconf(8)).

The path argument to bsdconf_put() must name a regular file (only a regular file can be atomically replaced); anything else is rejected with EINVAL. When every option is BSDCONF_ACTION_CHECK, or every BSDCONF_ACTION_SET_VALUE and BSDCONF_ACTION_REMOVE would leave the file unchanged, the original is left untouched: no temporary is created, mtime is not bumped, and hard links are not severed. The original is read in full, subject to the BSDCONF_MAX_BYTES cap documented in bsdconf(3). Otherwise the file is replaced atomically: output is streamed to a temporary file created with mkstemp(3) in the target's own directory, flushed to stable storage with fsync(2), given the mode (and, if permitted, the ownership) of the original, and then moved over the original with rename(2). An unexpected system failure or power loss mid-transaction leaves the original untouched (see also SECURITY CONSIDERATIONS).

bsdconf_put() operates on exactly one file. For a format backed by more than one file (see bsdconf_format(3)), the caller decides which file to hand it, and the deterministic sourcing order makes that decision mechanical: because the last file to list a directive dictates its effective value, a directive should be rewritten in the last file of the format's ordered list that currently lists it (found by parsing the files in order with bsdconf_parse()), or appended to the format's default file when no file lists it. A removal, by contrast, must be applied to every file listing the directive, lest deleting the authoritative definition merely unmask an earlier one. This is the policy implemented by sysconf(8).

The put_options argument is a mask of the following bit fields:

BSDCONF_PUT_NO_DUPLICATES

Fail with EEXIST if a directive to be written appears more than once in the file.

BSDCONF_PUT_ALLOW_EMPTY

Permit setting an empty value.

BSDCONF_PUT_BACKUP

Save a copy of the original file with a ‘.bak’ suffix before replacing it.

BSDCONF_PUT_UNQUOTED

Emit value.str as literal file text: no quotes are added and no characters are backslash-escaped (the caller supplies the bytes that should appear on disk). Embedded whitespace is fine (the value runs to the end of the line). A value that could not round-trip under the parser (an embedded newline, unescaped comment marker, or trailing unescaped backslash) is rejected with EINVAL rather than silently corrupting the file. The reader still runs bsdconf_strunexpand() on input, so escape sequences present in the file become the logical value on read; this flag does not re-encode them on write.

BSDCONF_PUT_QUOTE_ALWAYS

Enclose every value in double-quotes, escaping embedded quotes and backslashes.

bsdconf_set_option() traverses the options-array and stages value into the option whose directive matches directive via strcmp(3).

RETURN VALUES

bsdconf_put() returns zero on success; otherwise -1 is returned and the global variable errno is set to indicate the error. bsdconf_set_option() returns 1 if a matching directive was staged, otherwise 0.

SEE ALSO

bsdconf(3), bsdconf_format(3), sysconf(8)

HISTORY

The bsdconf_put() and bsdconf_set_option() functions first appeared in FreeBSD 16.0 as part of bsdconf(3).

AUTHORS

Devin Teske ⟨[email protected]⟩ Faraz Vahedi ⟨[email protected]⟩

LIMITATIONS

bsdconf_put() matches directives by exact name and, by default, treats each name as single-valued: the first matching statement is rewritten, or a new line is appended when none matches. That suits the built-in formats when the last assignment wins.

Make(1)-style cumulative ‘+=’ chains are supported only in part. With match_line unset, a ‘+=’ BSDCONF_ACTION_SET_VALUE appends a new statement rather than collapsing earlier ones; with match_line set, one physical line can be rewritten or removed (as sysconf(8) does for make/src list strikes). Word-level ‘-=’ policy and effective-value accumulation remain the caller's responsibility; the library does not implement ‘-=’ itself.

Formats whose directives legitimately repeat without make(1) operators (for example Apache-style cumulative keywords; see bsdconf(3)) likewise parse with caller-supplied callbacks, but bsdconf_put() offers no first-class insert, remove, or reorder of occurrence N among equals—only exact-name match or match_line selection.

SECURITY CONSIDERATIONS

The write transaction is designed to be safe against both interruption and interference. The temporary file is created by mkstemp(3) (O_EXCL; never predictable, never followed) in the target's own directory, so the data never crosses a filesystem boundary and never transits a world-writable directory such as /tmp. The mode and ownership propagated onto it are taken by fstat(2) from the descriptor actually read, not by re-looking up the path, and are applied with fchmod(2) (the original mode masked to 0666, so setuid, setgid, and execute bits are not copied) and fchown(2) on the open descriptor, so a concurrently swapped file cannot influence them. A backup requested with BSDCONF_PUT_BACKUP is created mode 0600 then fchmod(2)'d to the original mode masked to 0777 (setuid and setgid bits are not restored) and refuses to follow a symbolic link planted at the ‘.bak’ name (O_NOFOLLOW).

Symbolic links in the path argument to bsdconf_put() are resolved (with realpath(3)) before work begins: writing through a symbolic link rewrites the file it points at and preserves the link itself, rather than replacing the link with a regular file. The resolution is not re-verified at open(2) time; as with any path-based interface, an actor with write access to a directory along the path can redirect it, so the containing directories must be trustworthy (as those of system configuration files are). Because replacement is by rename(2), a target with multiple hard links is severed from its other names, which afterwards continue to reference the old content; this is inherent to atomic replacement.