---
name: "bsdconf_put"
section: "3"
platform: "freebsd"
package: "base"
version: "16.0-CURRENT-20261027-61c70dc16a79"
release: "FreeBSD 16.0-CURRENT (2026-10-27)"
platforms: ["freebsd"]
url: "https://new.carta.tech/bsdconf_put"
---

# 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)](/make/1.md) list-strike edits in [sysconf(8)](/sysconf/8.md)).

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)](/bsdconf/3.md). Otherwise the file is replaced atomically: output is streamed to a temporary file created with [mkstemp(3)](/mkstemp/3.md) in the target's own directory, flushed to stable storage with [fsync(2)](/fsync/2.md), given the mode (and, if permitted, the ownership) of the original, and then moved over the original with [rename(2)](/rename/2.md). 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)](/bsdconf_format/3.md)), 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)](/sysconf/8.md).

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)](/strcmp/3.md).

## 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/3.md), [bsdconf\_format(3)](/bsdconf_format/3.md), [sysconf(8)](/sysconf/8.md)

## HISTORY

The **bsdconf\_put**() and **bsdconf\_set\_option**() functions first appeared in FreeBSD 16.0 as part of [bsdconf(3)](/bsdconf/3.md).

## AUTHORS

Devin Teske ⟨*dteske@FreeBSD.org*⟩ Faraz Vahedi ⟨*kfv@FreeBSD.org*⟩

## 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)](/sysconf/8.md) 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)](/make/1.md) operators (for example Apache-style cumulative keywords; see [bsdconf(3)](/bsdconf/3.md)) 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)](/mkstemp/3.md) (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)](/fstat/2.md) from the descriptor actually read, not by re-looking up the path, and are applied with [fchmod(2)](/fchmod/2.md) (the original mode masked to 0666, so setuid, setgid, and execute bits are not copied) and [fchown(2)](/fchown/2.md) 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)](/fchmod/2.md)'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)](/realpath/3.md)) 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)](/open/2.md) 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)](/rename/2.md), 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.
