1.\" Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org> 2.\" Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org> 3.\" 4.\" SPDX-License-Identifier: BSD-2-Clause 5.\" 6.Dd August 2, 2026 7.Dt BSDCONF_PUT 3 8.Os 9.Sh NAME 10.Nm bsdconf_put , 11.Nm bsdconf_set_option 12.Nd resilient configuration file writing 13.Sh LIBRARY 14.Lb libbsdconf 15.Sh SYNOPSIS 16.In bsdconf.h 17.Ft int 18.Fo bsdconf_put 19.Fa "struct bsdconf_option options[]" 20.Fa "const char *path" 21.Fa "uint16_t processing_options" 22.Fa "uint16_t put_options" 23.Fc 24.Ft int 25.Fo bsdconf_set_option 26.Fa "struct bsdconf_option options[]" 27.Fa "const char *directive" 28.Fa "union bsdconf_value *value" 29.Fc 30.Sh DESCRIPTION 31.Fn bsdconf_put 32rewrites the configuration file at 33.Fa path , 34applying the per-directive action of each option in the array: 35.Bl -tag -width BSDCONF_ACTION_SET_VALUE 36.It Dv BSDCONF_ACTION_SET_VALUE 37Set the directive to 38.Va value.str , 39editing it in place if present or appending it to the file if absent. 40.It Dv BSDCONF_ACTION_CHECK 41Report 42.Pq via Va result 43whether the current value differs from 44.Va value.str , 45without modifying the file. 46.It Dv BSDCONF_ACTION_REMOVE 47Delete the directive from the file. 48.El 49.Pp 50Unlike 51.Fn bsdconf_parse , 52directives are matched exactly 53.Pq a pattern is not a writable target . 54Comments, 55blank lines, 56statement ordering, 57and the formatting of untouched statements are preserved. 58For each processed option, 59.Va result 60is set to a bitmask of 61.Dv BSDCONF_DIRECTIVE_FOUND , 62.Dv BSDCONF_VALUE_CHANGED , 63.Dv BSDCONF_DIRECTIVE_ADDED , 64and 65.Dv BSDCONF_DIRECTIVE_REMOVED , 66and 67.Va line 68is set to the line of the first match 69.Pq if found . 70A non-zero 71.Va match_line 72on input restricts the put to the statement on that physical line 73.Pq zero matches any statement, as before ; 74this allows a single 75.Ql name+=value 76among several to be rewritten or removed without appending a new line 77or touching the others 78.Pq make(1) list-strike edits in Xr sysconf 8 . 79.Pp 80The 81.Fa path 82argument to 83.Fn bsdconf_put 84must name a regular file 85.Pq only a regular file can be atomically replaced ; 86anything else is rejected with 87.Er EINVAL . 88When every option is 89.Dv BSDCONF_ACTION_CHECK , 90or every 91.Dv BSDCONF_ACTION_SET_VALUE 92and 93.Dv BSDCONF_ACTION_REMOVE 94would leave the file unchanged, 95the original is left untouched: 96no temporary is created, 97.Va mtime 98is not bumped, 99and hard links are not severed. 100Otherwise the file is replaced atomically: 101output is streamed to a temporary file created with 102.Xr mkstemp 3 103in the target's own directory, 104flushed to stable storage with 105.Xr fsync 2 , 106given the mode 107.Pq and, if permitted, the ownership 108of the original, 109and then moved over the original with 110.Xr rename 2 . 111An unexpected system failure or power loss mid-transaction leaves the 112original untouched 113.Pq see also Sx SECURITY CONSIDERATIONS . 114.Pp 115.Fn bsdconf_put 116operates on exactly one file. 117For a format backed by more than one file 118.Pq see Xr bsdconf_format 3 , 119the caller decides which file to hand it, 120and the deterministic sourcing order makes that decision mechanical: 121because the last file to list a directive dictates its effective value, 122a directive should be rewritten in the last file of the format's ordered 123list that currently lists it 124.Pq found by parsing the files in order with Fn bsdconf_parse , 125or appended to the format's default file when no file lists it. 126A removal, 127by contrast, 128must be applied to every file listing the directive, 129lest deleting the authoritative definition merely unmask an earlier one. 130This is the policy implemented by 131.Xr sysconf 8 . 132.Pp 133The 134.Fa put_options 135argument is a mask of the following bit fields: 136.Bl -tag -width BSDCONF_PUT_NO_DUPLICATES 137.It Dv BSDCONF_PUT_NO_DUPLICATES 138Fail with 139.Er EEXIST 140if a directive to be written appears more than once in the file. 141.It Dv BSDCONF_PUT_ALLOW_EMPTY 142Permit setting an empty value. 143.It Dv BSDCONF_PUT_BACKUP 144Save a copy of the original file with a 145.Ql .bak 146suffix before replacing it. 147.It Dv BSDCONF_PUT_UNQUOTED 148Emit 149.Va value.str 150as literal file text: 151no quotes are added and no characters are backslash-escaped 152.Pq the caller supplies the bytes that should appear on disk . 153Embedded whitespace is fine 154.Pq the value runs to the end of the line . 155A value that could not round-trip under the parser 156.Pq an embedded newline, unescaped comment marker, or trailing unescaped backslash 157is rejected with 158.Er EINVAL 159rather than silently corrupting the file. 160The reader still runs 161.Fn bsdconf_strunexpand 162on input, so escape sequences present in the file become the logical value 163on read; this flag does not re-encode them on write. 164.It Dv BSDCONF_PUT_QUOTE_ALWAYS 165Enclose every value in double-quotes, 166escaping embedded quotes and backslashes. 167.El 168.Pp 169.Fn bsdconf_set_option 170traverses the options-array and stages 171.Fa value 172into the option whose directive matches 173.Fa directive 174via 175.Xr strcmp 3 . 176.Sh RETURN VALUES 177.Fn bsdconf_put 178returns zero on success; 179otherwise -1 is returned and the global variable 180.Va errno 181is set to indicate the error. 182.Fn bsdconf_set_option 183returns 1 if a matching directive was staged, 184otherwise 0. 185.Sh SEE ALSO 186.Xr bsdconf 3 , 187.Xr bsdconf_format 3 , 188.Xr sysconf 8 189.Sh HISTORY 190The 191.Fn bsdconf_put 192and 193.Fn bsdconf_set_option 194functions first appeared in 195.Fx 16.0 196as part of 197.Xr bsdconf 3 . 198.Sh AUTHORS 199.An Devin Teske Aq Mt dteske@FreeBSD.org 200.An Faraz Vahedi Aq Mt kfv@FreeBSD.org 201.Sh LIMITATIONS 202.Fn bsdconf_put 203matches directives by exact name and, by default, treats each name as 204single-valued: the first matching statement is rewritten, or a new line 205is appended when none matches. 206That suits the built-in formats when the last assignment wins. 207.Pp 208Make(1)-style cumulative 209.Ql += 210chains are supported only in part. 211With 212.Va match_line 213unset, a 214.Ql += 215.Dv BSDCONF_ACTION_SET_VALUE 216appends a new statement rather than collapsing earlier ones; with 217.Va match_line 218set, one physical line can be rewritten or removed 219.Pq as Xr sysconf 8 does for make/src list strikes . 220Word-level 221.Ql -= 222policy and effective-value accumulation remain the caller's 223responsibility; 224the library does not implement 225.Ql -= 226itself. 227.Pp 228Formats whose directives legitimately repeat without make(1) operators 229.Pq for example Apache-style cumulative keywords; see Xr bsdconf 3 230likewise parse with caller-supplied callbacks, but 231.Fn bsdconf_put 232offers no first-class insert, remove, or reorder of occurrence 233.Em N 234among equals—only exact-name match or 235.Va match_line 236selection. 237.Sh SECURITY CONSIDERATIONS 238The write transaction is designed to be safe against both interruption and 239interference. 240The temporary file is created by 241.Xr mkstemp 3 242.Pq Dv O_EXCL ; never predictable, never followed 243in the target's own directory, 244so the data never crosses a filesystem boundary and never transits a 245world-writable directory such as 246.Pa /tmp . 247The mode and ownership propagated onto it are taken by 248.Xr fstat 2 249from the descriptor actually read, 250not by re-looking up the path, 251and are applied with 252.Xr fchmod 2 253.Pq the original mode masked to 0666, so setuid, setgid, and execute bits are not copied 254and 255.Xr fchown 2 256on the open descriptor, 257so a concurrently swapped file cannot influence them. 258A backup requested with 259.Dv BSDCONF_PUT_BACKUP 260is created mode 0600 then 261.Xr fchmod 2 Ns 'd 262to the original mode masked to 0777 263.Pq setuid and setgid bits are not restored 264and refuses to follow a symbolic link planted at the 265.Ql .bak 266name 267.Pq Dv O_NOFOLLOW . 268.Pp 269Symbolic links in the 270.Fa path 271argument to 272.Fn bsdconf_put 273are resolved 274.Pq with Xr realpath 3 275before work begins: 276writing through a symbolic link rewrites the file it points at and 277preserves the link itself, 278rather than replacing the link with a regular file. 279The resolution is not re-verified at 280.Xr open 2 281time; 282as with any path-based interface, 283an actor with write access to a directory along the path can redirect it, 284so the containing directories must be trustworthy 285.Pq as those of system configuration files are . 286Because replacement is by 287.Xr rename 2 , 288a target with multiple hard links is severed from its other names, 289which afterwards continue to reference the old content; 290this is inherent to atomic replacement. 291