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