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