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 September 16, 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. 100The original is read in full, subject to the 101.Ev BSDCONF_MAX_BYTES 102cap documented in 103.Xr bsdconf 3 . 104Otherwise the file is replaced atomically: 105output is streamed to a temporary file created with 106.Xr mkstemp 3 107in the target's own directory, 108flushed to stable storage with 109.Xr fsync 2 , 110given the mode 111.Pq and, if permitted, the ownership 112of the original, 113and then moved over the original with 114.Xr rename 2 . 115An unexpected system failure or power loss mid-transaction leaves the 116original untouched 117.Pq see also Sx SECURITY CONSIDERATIONS . 118.Pp 119.Fn bsdconf_put 120operates on exactly one file. 121For a format backed by more than one file 122.Pq see Xr bsdconf_format 3 , 123the caller decides which file to hand it, 124and the deterministic sourcing order makes that decision mechanical: 125because the last file to list a directive dictates its effective value, 126a directive should be rewritten in the last file of the format's ordered 127list that currently lists it 128.Pq found by parsing the files in order with Fn bsdconf_parse , 129or appended to the format's default file when no file lists it. 130A removal, 131by contrast, 132must be applied to every file listing the directive, 133lest deleting the authoritative definition merely unmask an earlier one. 134This is the policy implemented by 135.Xr sysconf 8 . 136.Pp 137The 138.Fa put_options 139argument is a mask of the following bit fields: 140.Bl -tag -width BSDCONF_PUT_NO_DUPLICATES 141.It Dv BSDCONF_PUT_NO_DUPLICATES 142Fail with 143.Er EEXIST 144if a directive to be written appears more than once in the file. 145.It Dv BSDCONF_PUT_ALLOW_EMPTY 146Permit setting an empty value. 147.It Dv BSDCONF_PUT_BACKUP 148Save a copy of the original file with a 149.Ql .bak 150suffix before replacing it. 151.It Dv BSDCONF_PUT_UNQUOTED 152Emit 153.Va value.str 154as literal file text: 155no quotes are added and no characters are backslash-escaped 156.Pq the caller supplies the bytes that should appear on disk . 157Embedded whitespace is fine 158.Pq the value runs to the end of the line . 159A value that could not round-trip under the parser 160.Pq an embedded newline, unescaped comment marker, or trailing unescaped backslash 161is rejected with 162.Er EINVAL 163rather than silently corrupting the file. 164The reader still runs 165.Fn bsdconf_strunexpand 166on input, so escape sequences present in the file become the logical value 167on read; this flag does not re-encode them on write. 168.It Dv BSDCONF_PUT_QUOTE_ALWAYS 169Enclose every value in double-quotes, 170escaping embedded quotes and backslashes. 171.El 172.Pp 173.Fn bsdconf_set_option 174traverses the options-array and stages 175.Fa value 176into the option whose directive matches 177.Fa directive 178via 179.Xr strcmp 3 . 180.Sh RETURN VALUES 181.Fn bsdconf_put 182returns zero on success; 183otherwise -1 is returned and the global variable 184.Va errno 185is set to indicate the error. 186.Fn bsdconf_set_option 187returns 1 if a matching directive was staged, 188otherwise 0. 189.Sh SEE ALSO 190.Xr bsdconf 3 , 191.Xr bsdconf_format 3 , 192.Xr sysconf 8 193.Sh HISTORY 194The 195.Fn bsdconf_put 196and 197.Fn bsdconf_set_option 198functions first appeared in 199.Fx 16.0 200as part of 201.Xr bsdconf 3 . 202.Sh AUTHORS 203.An Devin Teske Aq Mt dteske@FreeBSD.org 204.An Faraz Vahedi Aq Mt kfv@FreeBSD.org 205.Sh LIMITATIONS 206.Fn bsdconf_put 207matches directives by exact name and, by default, treats each name as 208single-valued: the first matching statement is rewritten, or a new line 209is appended when none matches. 210That suits the built-in formats when the last assignment wins. 211.Pp 212Make(1)-style cumulative 213.Ql += 214chains are supported only in part. 215With 216.Va match_line 217unset, a 218.Ql += 219.Dv BSDCONF_ACTION_SET_VALUE 220appends a new statement rather than collapsing earlier ones; with 221.Va match_line 222set, one physical line can be rewritten or removed 223.Pq as Xr sysconf 8 does for make/src list strikes . 224Word-level 225.Ql -= 226policy and effective-value accumulation remain the caller's 227responsibility; 228the library does not implement 229.Ql -= 230itself. 231.Pp 232Formats whose directives legitimately repeat without make(1) operators 233.Pq for example Apache-style cumulative keywords; see Xr bsdconf 3 234likewise parse with caller-supplied callbacks, but 235.Fn bsdconf_put 236offers no first-class insert, remove, or reorder of occurrence 237.Em N 238among equals—only exact-name match or 239.Va match_line 240selection. 241.Sh SECURITY CONSIDERATIONS 242The write transaction is designed to be safe against both interruption and 243interference. 244The temporary file is created by 245.Xr mkstemp 3 246.Pq Dv O_EXCL ; never predictable, never followed 247in the target's own directory, 248so the data never crosses a filesystem boundary and never transits a 249world-writable directory such as 250.Pa /tmp . 251The mode and ownership propagated onto it are taken by 252.Xr fstat 2 253from the descriptor actually read, 254not by re-looking up the path, 255and are applied with 256.Xr fchmod 2 257.Pq the original mode masked to 0666, so setuid, setgid, and execute bits are not copied 258and 259.Xr fchown 2 260on the open descriptor, 261so a concurrently swapped file cannot influence them. 262A backup requested with 263.Dv BSDCONF_PUT_BACKUP 264is created mode 0600 then 265.Xr fchmod 2 Ns 'd 266to the original mode masked to 0777 267.Pq setuid and setgid bits are not restored 268and refuses to follow a symbolic link planted at the 269.Ql .bak 270name 271.Pq Dv O_NOFOLLOW . 272.Pp 273Symbolic links in the 274.Fa path 275argument to 276.Fn bsdconf_put 277are resolved 278.Pq with Xr realpath 3 279before work begins: 280writing through a symbolic link rewrites the file it points at and 281preserves the link itself, 282rather than replacing the link with a regular file. 283The resolution is not re-verified at 284.Xr open 2 285time; 286as with any path-based interface, 287an actor with write access to a directory along the path can redirect it, 288so the containing directories must be trustworthy 289.Pq as those of system configuration files are . 290Because replacement is by 291.Xr rename 2 , 292a target with multiple hard links is severed from its other names, 293which afterwards continue to reference the old content; 294this is inherent to atomic replacement. 295