xref: /freebsd/lib/libbsdconf/bsdconf_put.3 (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
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