xref: /freebsd/lib/libbsdconf/bsdconf.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 3
8*3fe5961aSDevin Teske.Os
9*3fe5961aSDevin Teske.Sh NAME
10*3fe5961aSDevin Teske.Nm bsdconf ,
11*3fe5961aSDevin Teske.Nm bsdconf_parse ,
12*3fe5961aSDevin Teske.Nm bsdconf_fparse ,
13*3fe5961aSDevin Teske.Nm bsdconf_get_option ,
14*3fe5961aSDevin Teske.Nm bsdconf_spool ,
15*3fe5961aSDevin Teske.Nm bsdconf_unquote
16*3fe5961aSDevin Teske.Nd configuration file reading library
17*3fe5961aSDevin Teske.Sh LIBRARY
18*3fe5961aSDevin Teske.Lb libbsdconf
19*3fe5961aSDevin Teske.Sh SYNOPSIS
20*3fe5961aSDevin Teske.In bsdconf.h
21*3fe5961aSDevin Teske.Ft int
22*3fe5961aSDevin Teske.Fo bsdconf_parse
23*3fe5961aSDevin Teske.Fa "struct bsdconf_option options[]"
24*3fe5961aSDevin Teske.Fa "const char *path"
25*3fe5961aSDevin Teske.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct bsdconf_option *option"
26*3fe5961aSDevin Teske.Fa "uint32_t line"
27*3fe5961aSDevin Teske.Fa "char *directive"
28*3fe5961aSDevin Teske.Fa "char *value\*[rp]"
29*3fe5961aSDevin Teske.Fa "uint16_t processing_options"
30*3fe5961aSDevin Teske.Fc
31*3fe5961aSDevin Teske.Ft int
32*3fe5961aSDevin Teske.Fo bsdconf_fparse
33*3fe5961aSDevin Teske.Fa "struct bsdconf_option options[]"
34*3fe5961aSDevin Teske.Fa "int fd"
35*3fe5961aSDevin Teske.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct bsdconf_option *option"
36*3fe5961aSDevin Teske.Fa "uint32_t line"
37*3fe5961aSDevin Teske.Fa "char *directive"
38*3fe5961aSDevin Teske.Fa "char *value\*[rp]"
39*3fe5961aSDevin Teske.Fa "uint16_t processing_options"
40*3fe5961aSDevin Teske.Fc
41*3fe5961aSDevin Teske.Ft "struct bsdconf_option *"
42*3fe5961aSDevin Teske.Fo bsdconf_get_option
43*3fe5961aSDevin Teske.Fa "struct bsdconf_option options[]"
44*3fe5961aSDevin Teske.Fa "const char *directive"
45*3fe5961aSDevin Teske.Fc
46*3fe5961aSDevin Teske.Ft int
47*3fe5961aSDevin Teske.Fo bsdconf_spool
48*3fe5961aSDevin Teske.Fa "int fd"
49*3fe5961aSDevin Teske.Fc
50*3fe5961aSDevin Teske.Ft "char *"
51*3fe5961aSDevin Teske.Fo bsdconf_unquote
52*3fe5961aSDevin Teske.Fa "char *value"
53*3fe5961aSDevin Teske.Fc
54*3fe5961aSDevin Teske.Sh DESCRIPTION
55*3fe5961aSDevin TeskeThe
56*3fe5961aSDevin Teske.Nm
57*3fe5961aSDevin Teskelibrary provides a light-weight,
58*3fe5961aSDevin Teskeportable framework for reading and writing configuration
59*3fe5961aSDevin Teskefiles.
60*3fe5961aSDevin TeskeIt is the successor to the retired
61*3fe5961aSDevin Teske.Nm figpar
62*3fe5961aSDevin Teskelibrary,
63*3fe5961aSDevin Teskeextending the original read-only token parser into a unified,
64*3fe5961aSDevin Teskeresilient reader/writer engine.
65*3fe5961aSDevin Teske.Pp
66*3fe5961aSDevin TeskeDue to the fact that configuration files may have basic syntax differences,
67*3fe5961aSDevin Teskethe library does not attempt to impose any structure on the data but instead
68*3fe5961aSDevin Teskeprovides raw data to a set of callback functions.
69*3fe5961aSDevin TeskeThese callback functions can in-turn initiate abort through their return
70*3fe5961aSDevin Teskevalue,
71*3fe5961aSDevin Teskeallowing custom syntax validation during parsing.
72*3fe5961aSDevin Teske.Pp
73*3fe5961aSDevin TeskeSyntax differences between well-known file formats are described by format
74*3fe5961aSDevin Teskedescriptors:
75*3fe5961aSDevin Teskea format descriptor is a small read-only table of properties
76*3fe5961aSDevin Teske.Pq Vt struct bsdconf_format_def ; see Xr bsdconf_format 3
77*3fe5961aSDevin Teskenaming a format's target keyword,
78*3fe5961aSDevin Teskebacking files,
79*3fe5961aSDevin Teskeand tokenizing/quoting rules,
80*3fe5961aSDevin Teskewhich parameterize a single shared engine.
81*3fe5961aSDevin Teske.Pp
82*3fe5961aSDevin TeskeDespite the name, it bears no relation to a file descriptor;
83*3fe5961aSDevin Teskeit is closer in spirit to a driver's method table:
84*3fe5961aSDevin Teskestatic data describing behavior,
85*3fe5961aSDevin Teskeconsulted rather than executed.
86*3fe5961aSDevin TeskeWriting is documented in
87*3fe5961aSDevin Teske.Xr bsdconf_put 3 ;
88*3fe5961aSDevin Teskebuilt-in formats and discovery in
89*3fe5961aSDevin Teske.Xr bsdconf_format 3 .
90*3fe5961aSDevin Teske.Pp
91*3fe5961aSDevin TeskeConfiguration directives,
92*3fe5961aSDevin Tesketypes,
93*3fe5961aSDevin Teskeand callback functions are provided through data structures defined in
94*3fe5961aSDevin Teske.In bsdconf.h :
95*3fe5961aSDevin Teske.Bd -literal -offset indent
96*3fe5961aSDevin Teskestruct bsdconf_option {
97*3fe5961aSDevin Teske    enum bsdconf_type		type;	/* value type */
98*3fe5961aSDevin Teske    const char			*directive; /* keyword */
99*3fe5961aSDevin Teske    union bsdconf_value		value;	/* value */
100*3fe5961aSDevin Teske    enum bsdconf_op		op;	/* assignment operator */
101*3fe5961aSDevin Teske    uint8_t			action;	/* bsdconf_put() action */
102*3fe5961aSDevin Teske    uint16_t			result;	/* set by bsdconf_put() */
103*3fe5961aSDevin Teske    uint32_t			line;	/* set by bsdconf_put() */
104*3fe5961aSDevin Teske    uint32_t			match_line;
105*3fe5961aSDevin Teske					/* put: 0=any, else line */
106*3fe5961aSDevin Teske
107*3fe5961aSDevin Teske    /* Pointer to function used when directive is found */
108*3fe5961aSDevin Teske    int (*parse)(struct bsdconf_option *option, uint32_t line,
109*3fe5961aSDevin Teske        char *directive, char *value);
110*3fe5961aSDevin Teske};
111*3fe5961aSDevin Teske
112*3fe5961aSDevin Teskeenum bsdconf_type {
113*3fe5961aSDevin Teske    BSDCONF_TYPE_NONE      = 0x0000, /* directives with no value */
114*3fe5961aSDevin Teske    BSDCONF_TYPE_BOOL      = 0x0001, /* boolean */
115*3fe5961aSDevin Teske    BSDCONF_TYPE_INT       = 0x0002, /* signed 32-bit integer */
116*3fe5961aSDevin Teske    BSDCONF_TYPE_UINT      = 0x0004, /* unsigned 32-bit integer */
117*3fe5961aSDevin Teske    BSDCONF_TYPE_STR       = 0x0008, /* string pointer */
118*3fe5961aSDevin Teske    BSDCONF_TYPE_STRARRAY  = 0x0010, /* string array pointer */
119*3fe5961aSDevin Teske    BSDCONF_TYPE_DATA1     = 0x0020, /* void data type-1 (open) */
120*3fe5961aSDevin Teske    BSDCONF_TYPE_DATA2     = 0x0040, /* void data type-2 (open) */
121*3fe5961aSDevin Teske    BSDCONF_TYPE_DATA3     = 0x0080, /* void data type-3 (open) */
122*3fe5961aSDevin Teske    BSDCONF_TYPE_INT64     = 0x0100, /* signed 64-bit integer */
123*3fe5961aSDevin Teske    BSDCONF_TYPE_UINT64    = 0x0200, /* unsigned 64-bit integer */
124*3fe5961aSDevin Teske    BSDCONF_TYPE_RESERVED  = 0x0400, /* reserved */
125*3fe5961aSDevin Teske};
126*3fe5961aSDevin Teske
127*3fe5961aSDevin Teskeunion bsdconf_value {
128*3fe5961aSDevin Teske    void	*data;      /* Opaque pointer (DATA1..DATA3) */
129*3fe5961aSDevin Teske    char	*str;       /* Pointer to NUL-terminated string */
130*3fe5961aSDevin Teske    char	**strarray; /* Pointer to an array of strings */
131*3fe5961aSDevin Teske    int32_t	num;        /* Signed 32-bit integer value */
132*3fe5961aSDevin Teske    uint32_t	u_num;      /* Unsigned 32-bit integer value */
133*3fe5961aSDevin Teske    int64_t	num64;      /* Signed 64-bit integer value */
134*3fe5961aSDevin Teske    uint64_t	u_num64;    /* Unsigned 64-bit integer value */
135*3fe5961aSDevin Teske    bool	boolean;    /* Boolean value */
136*3fe5961aSDevin Teske};
137*3fe5961aSDevin Teske.Ed
138*3fe5961aSDevin Teske.Pp
139*3fe5961aSDevin TeskeThe
140*3fe5961aSDevin Teske.Fa processing_options
141*3fe5961aSDevin Teskeargument to
142*3fe5961aSDevin Teske.Fn bsdconf_parse ,
143*3fe5961aSDevin Teske.Fn bsdconf_fparse ,
144*3fe5961aSDevin Teskeand
145*3fe5961aSDevin Teske.Xr bsdconf_put 3
146*3fe5961aSDevin Teskeis a mask of bit fields which indicate various processing options.
147*3fe5961aSDevin TeskeThe possible flags are:
148*3fe5961aSDevin Teske.Bl -tag -width BSDCONF_BREAK_ON_SEMICOLON
149*3fe5961aSDevin Teske.It Dv BSDCONF_BREAK_ON_EQUALS
150*3fe5961aSDevin TeskeAn equals sign
151*3fe5961aSDevin Teske.Pq Ql =
152*3fe5961aSDevin Teskeis normally considered part of the directive.
153*3fe5961aSDevin TeskeThis flag enables terminating the directive at the equals sign.
154*3fe5961aSDevin TeskeAlso makes equals sign optional and transient.
155*3fe5961aSDevin Teske.It Dv BSDCONF_BREAK_ON_SEMICOLON
156*3fe5961aSDevin TeskeA semicolon
157*3fe5961aSDevin Teske.Pq Ql \&;
158*3fe5961aSDevin Teskeis normally considered part of the value.
159*3fe5961aSDevin TeskeThis flag enables terminating the value at the semicolon.
160*3fe5961aSDevin TeskeAlso allows multiple statements on a single line separated by semicolon.
161*3fe5961aSDevin Teske.It Dv BSDCONF_CASE_SENSITIVE
162*3fe5961aSDevin TeskeNormally directives are matched case insensitively using
163*3fe5961aSDevin Teske.Xr fnmatch 3 .
164*3fe5961aSDevin TeskeThis flag enables directive matching to be case sensitive.
165*3fe5961aSDevin Teske.It Dv BSDCONF_REQUIRE_EQUALS
166*3fe5961aSDevin TeskeIf a directive is not followed by an equals,
167*3fe5961aSDevin Teskeprocessing is aborted.
168*3fe5961aSDevin Teske.It Dv BSDCONF_STRICT_EQUALS
169*3fe5961aSDevin TeskeEquals must be part of the directive
170*3fe5961aSDevin Teske.Pq no whitespace before or after
171*3fe5961aSDevin Tesketo be considered a delimiter between directive and value.
172*3fe5961aSDevin TeskeRequired by file formats whose readers reject whitespace around the equals
173*3fe5961aSDevin Teskesign,
174*3fe5961aSDevin Teskesuch as the
175*3fe5961aSDevin Teske.Fx
176*3fe5961aSDevin Teskeboot loader's processing of
177*3fe5961aSDevin Teske.Xr loader.conf 5 .
178*3fe5961aSDevin Teske.It Dv BSDCONF_OPERATOR_EQUALS
179*3fe5961aSDevin TeskeRecognize
180*3fe5961aSDevin Teske.Xr make 1
181*3fe5961aSDevin Teskestyle assignment modifiers
182*3fe5961aSDevin Teske.Po
183*3fe5961aSDevin Teske.Ql += ,
184*3fe5961aSDevin Teske.Ql ?= ,
185*3fe5961aSDevin Teske.Ql := ,
186*3fe5961aSDevin Teskeand
187*3fe5961aSDevin Teske.Ql !=
188*3fe5961aSDevin Teske.Pc
189*3fe5961aSDevin Teskeand split them off the tail of the directive.
190*3fe5961aSDevin TeskeThe parsed operator is reported through the
191*3fe5961aSDevin Teske.Va op
192*3fe5961aSDevin Teskemember of the matched option
193*3fe5961aSDevin Teske.Pq one of Dv BSDCONF_OP_ASSIGN , BSDCONF_OP_APPEND , BSDCONF_OP_COND , BSDCONF_OP_EXPAND , No or Dv BSDCONF_OP_SHELL .
194*3fe5961aSDevin Teske.El
195*3fe5961aSDevin Teske.Pp
196*3fe5961aSDevin TeskeThe
197*3fe5961aSDevin Teske.Fa options
198*3fe5961aSDevin Teskestruct array pointer can be NULL and every directive will run the
199*3fe5961aSDevin Teske.Fn unknown
200*3fe5961aSDevin Teskefunction argument.
201*3fe5961aSDevin Teske.Pp
202*3fe5961aSDevin TeskeThe directive for each bsdconf_option item in the
203*3fe5961aSDevin Teske.Fn bsdconf_parse
204*3fe5961aSDevin Teskeoptions argument is matched against each parsed directive using
205*3fe5961aSDevin Teske.Xr fnmatch 3
206*3fe5961aSDevin Teskeuntil a match is found.
207*3fe5961aSDevin TeskeIf a match is found,
208*3fe5961aSDevin Teskethe
209*3fe5961aSDevin Teske.Fn parse
210*3fe5961aSDevin Teskefunction for that bsdconf_option directive is run with the line number,
211*3fe5961aSDevin Teskedirective,
212*3fe5961aSDevin Teskeand value.
213*3fe5961aSDevin TeskeOtherwise if no match,
214*3fe5961aSDevin Teskethe
215*3fe5961aSDevin Teske.Fn unknown
216*3fe5961aSDevin Teskefunction is run
217*3fe5961aSDevin Teske.Pq with the same arguments .
218*3fe5961aSDevin TeskeWhen
219*3fe5961aSDevin Teske.Dv BSDCONF_OPERATOR_EQUALS
220*3fe5961aSDevin Teskeis set,
221*3fe5961aSDevin Teske.Fn unknown
222*3fe5961aSDevin Teskereceives a non-NULL
223*3fe5961aSDevin Teske.Fa option
224*3fe5961aSDevin Teskewhose
225*3fe5961aSDevin Teske.Va op
226*3fe5961aSDevin Teskemember holds the statement's assignment operator
227*3fe5961aSDevin Teske.Pq there is no matched options-array slot to hang it on ;
228*3fe5961aSDevin Teskeotherwise
229*3fe5961aSDevin Teske.Fa option
230*3fe5961aSDevin Teskemay be
231*3fe5961aSDevin Teske.Dv NULL .
232*3fe5961aSDevin Teske.Pp
233*3fe5961aSDevin TeskeIf either
234*3fe5961aSDevin Teske.Fn parse
235*3fe5961aSDevin Teskeor
236*3fe5961aSDevin Teske.Fn unknown
237*3fe5961aSDevin Teskereturn non-zero,
238*3fe5961aSDevin Teske.Fn bsdconf_parse
239*3fe5961aSDevin Teskeaborts reading the file and returns the error value to its caller.
240*3fe5961aSDevin Teske.Pp
241*3fe5961aSDevin TeskeA value normally ends at the first unescaped newline,
242*3fe5961aSDevin Teskebut a statement may span multiple lines:
243*3fe5961aSDevin Teskea backslash immediately preceding the newline continues the value on the
244*3fe5961aSDevin Teskenext line,
245*3fe5961aSDevin Teskein the manner of
246*3fe5961aSDevin Teske.Xr make 1
247*3fe5961aSDevin Teske.Pq essential to Pa make.conf and its siblings .
248*3fe5961aSDevin TeskeThe backslash-newline pairs are removed from the value delivered to the
249*3fe5961aSDevin Teskecallbacks
250*3fe5961aSDevin Teske.Pq surrounding whitespace is preserved verbatim ,
251*3fe5961aSDevin Teskeand reported line numbers are those of each statement's first line.
252*3fe5961aSDevin Teske.Xr bsdconf_put 3
253*3fe5961aSDevin Teskerecognizes the same continuations when locating a value;
254*3fe5961aSDevin Teskerewriting a continued value replaces all of its lines with the single new
255*3fe5961aSDevin Teskevalue.
256*3fe5961aSDevin Teske.Pp
257*3fe5961aSDevin Teske.Fn bsdconf_fparse
258*3fe5961aSDevin Teskeis identical to
259*3fe5961aSDevin Teske.Fn bsdconf_parse
260*3fe5961aSDevin Teskeexcept that it operates on an already-open file descriptor
261*3fe5961aSDevin Teske.Fa fd ,
262*3fe5961aSDevin Teskewhich remains open on return
263*3fe5961aSDevin Teske.Pq the caller retains ownership .
264*3fe5961aSDevin TeskeThis allows the caller to constrain the process
265*3fe5961aSDevin Teske.Pq for example with Xr capsicum 4
266*3fe5961aSDevin Teskebefore parsing begins.
267*3fe5961aSDevin TeskeThe scanner requires a seekable descriptor;
268*3fe5961aSDevin Teskeinput that cannot seek
269*3fe5961aSDevin Teske.Pq a pipe or socket, standard input included
270*3fe5961aSDevin Teskeis detected up front and transparently spooled through
271*3fe5961aSDevin Teske.Fn bsdconf_spool ,
272*3fe5961aSDevin Teskeat the cost of one transient copy of the data.
273*3fe5961aSDevin Teske.Pp
274*3fe5961aSDevin Teske.Fn bsdconf_spool
275*3fe5961aSDevin Teskecopies the remaining contents of
276*3fe5961aSDevin Teske.Fa fd
277*3fe5961aSDevin Tesketo an unlinked temporary file
278*3fe5961aSDevin Teske.Pq created with Xr tmpfile 3
279*3fe5961aSDevin Teskeand returns a seekable descriptor referencing it,
280*3fe5961aSDevin Teskewhich the caller must
281*3fe5961aSDevin Teske.Xr close 2
282*3fe5961aSDevin Teske.Pq the backing storage is reclaimed then .
283*3fe5961aSDevin TeskeIt is exported for callers that must adapt non-seekable input themselves
284*3fe5961aSDevin Teskebefore revoking their own ability to create files,
285*3fe5961aSDevin Teskeas
286*3fe5961aSDevin Teske.Xr sysconf 8
287*3fe5961aSDevin Teskedoes before entering its
288*3fe5961aSDevin Teske.Xr capsicum 4
289*3fe5961aSDevin Teskesandbox.
290*3fe5961aSDevin Teske.Pp
291*3fe5961aSDevin Teske.Fn bsdconf_get_option
292*3fe5961aSDevin Tesketraverses the options-array and returns the option that matches via
293*3fe5961aSDevin Teske.Xr strcmp 3 ,
294*3fe5961aSDevin Teskeor
295*3fe5961aSDevin Teske.Dv NULL
296*3fe5961aSDevin Teskeif none matches.
297*3fe5961aSDevin Teske.Pp
298*3fe5961aSDevin Teske.Fn bsdconf_unquote
299*3fe5961aSDevin Teskestrips one layer of surrounding double-quotes from
300*3fe5961aSDevin Teske.Fa value
301*3fe5961aSDevin Teskein place and returns it.
302*3fe5961aSDevin TeskeParsed values retain their quotes so that data round-trips losslessly;
303*3fe5961aSDevin Teskethis helper is for display and comparison purposes.
304*3fe5961aSDevin Teske.Sh RETURN VALUES
305*3fe5961aSDevin Teske.Fn bsdconf_parse
306*3fe5961aSDevin Teskeand
307*3fe5961aSDevin Teske.Fn bsdconf_fparse
308*3fe5961aSDevin Teskereturn zero on success;
309*3fe5961aSDevin Teskeotherwise -1
310*3fe5961aSDevin Teske.Pq or the non-zero result of a callback
311*3fe5961aSDevin Teskeis returned and the global variable
312*3fe5961aSDevin Teske.Va errno
313*3fe5961aSDevin Teskeis set to indicate the error.
314*3fe5961aSDevin Teske.Fn bsdconf_spool
315*3fe5961aSDevin Teskereturns a new seekable file descriptor on success;
316*3fe5961aSDevin Teskeotherwise -1 with
317*3fe5961aSDevin Teske.Va errno
318*3fe5961aSDevin Teskeset to indicate the error.
319*3fe5961aSDevin Teske.Fn bsdconf_get_option
320*3fe5961aSDevin Teskereturns a pointer to the matching option,
321*3fe5961aSDevin Teskeor
322*3fe5961aSDevin Teske.Dv NULL
323*3fe5961aSDevin Teskewhen none matches.
324*3fe5961aSDevin Teske.Sh EXAMPLES
325*3fe5961aSDevin TeskeRead two known directives from a
326*3fe5961aSDevin Teske.Ql name=value
327*3fe5961aSDevin Teskefile,
328*3fe5961aSDevin Teskerouting every statement through a callback:
329*3fe5961aSDevin Teske.Bd -literal -offset indent
330*3fe5961aSDevin Teske#include <err.h>
331*3fe5961aSDevin Teske#include <stdio.h>
332*3fe5961aSDevin Teske#include <bsdconf.h>
333*3fe5961aSDevin Teske
334*3fe5961aSDevin Teskestatic int
335*3fe5961aSDevin Teskeshow(struct bsdconf_option *option, uint32_t line,
336*3fe5961aSDevin Teske    char *directive, char *value)
337*3fe5961aSDevin Teske{
338*3fe5961aSDevin Teske	printf("%u: %s is %s\en", line, directive,
339*3fe5961aSDevin Teske	    bsdconf_unquote(value));
340*3fe5961aSDevin Teske	return (0);
341*3fe5961aSDevin Teske}
342*3fe5961aSDevin Teske
343*3fe5961aSDevin Teskestatic struct bsdconf_option options[] = {
344*3fe5961aSDevin Teske	{ .directive = "hostname", .parse = show },
345*3fe5961aSDevin Teske	{ .directive = "timeout",  .parse = show },
346*3fe5961aSDevin Teske	{ .directive = NULL }
347*3fe5961aSDevin Teske};
348*3fe5961aSDevin Teske
349*3fe5961aSDevin Teskeint
350*3fe5961aSDevin Teskemain(void)
351*3fe5961aSDevin Teske{
352*3fe5961aSDevin Teske	if (bsdconf_parse(options, "/usr/local/etc/myapp.conf",
353*3fe5961aSDevin Teske	    NULL, BSDCONF_BREAK_ON_EQUALS) != 0)
354*3fe5961aSDevin Teske		err(1, "myapp.conf");
355*3fe5961aSDevin Teske	return (0);
356*3fe5961aSDevin Teske}
357*3fe5961aSDevin Teske.Ed
358*3fe5961aSDevin Teske.Pp
359*3fe5961aSDevin TeskeCallbacks own the semantics,
360*3fe5961aSDevin Teskeso a directive that legitimately repeats is accumulated rather than
361*3fe5961aSDevin Teskeoverwritten;
362*3fe5961aSDevin Teskeno format descriptor is involved.
363*3fe5961aSDevin TeskeThe file here is Apache-style
364*3fe5961aSDevin Teske.Pq space-separated, no equals sign ,
365*3fe5961aSDevin Teskeso
366*3fe5961aSDevin Teske.Dv BSDCONF_BREAK_ON_EQUALS
367*3fe5961aSDevin Teskeis simply omitted:
368*3fe5961aSDevin Teske.Bd -literal -offset indent
369*3fe5961aSDevin Teskestatic char *servers[16];
370*3fe5961aSDevin Teskestatic size_t nservers;
371*3fe5961aSDevin Teske
372*3fe5961aSDevin Teskestatic int
373*3fe5961aSDevin Teskeaddserver(struct bsdconf_option *option, uint32_t line,
374*3fe5961aSDevin Teske    char *directive, char *value)
375*3fe5961aSDevin Teske{
376*3fe5961aSDevin Teske	if (nservers >= 16 ||
377*3fe5961aSDevin Teske	    (servers[nservers] = strdup(value)) == NULL)
378*3fe5961aSDevin Teske		return (-1); /* abort the parse */
379*3fe5961aSDevin Teske	nservers++;
380*3fe5961aSDevin Teske	return (0);
381*3fe5961aSDevin Teske}
382*3fe5961aSDevin Teske
383*3fe5961aSDevin Teskestatic struct bsdconf_option cumulative[] = {
384*3fe5961aSDevin Teske	{ .directive = "server", .parse = addserver },
385*3fe5961aSDevin Teske	{ .directive = NULL }
386*3fe5961aSDevin Teske};
387*3fe5961aSDevin Teske
388*3fe5961aSDevin Teske	...
389*3fe5961aSDevin Teske	if (bsdconf_parse(cumulative, path, NULL, 0) != 0)
390*3fe5961aSDevin Teske		err(1, "%s", path);
391*3fe5961aSDevin Teske.Ed
392*3fe5961aSDevin Teske.Sh SEE ALSO
393*3fe5961aSDevin Teske.Xr bsdconf_format 3 ,
394*3fe5961aSDevin Teske.Xr bsdconf_put 3 ,
395*3fe5961aSDevin Teske.Xr loader.conf 5 ,
396*3fe5961aSDevin Teske.Xr sysctl.conf 5 ,
397*3fe5961aSDevin Teske.Xr sysconf 8
398*3fe5961aSDevin Teske.Sh HISTORY
399*3fe5961aSDevin TeskeThe
400*3fe5961aSDevin Teske.Nm
401*3fe5961aSDevin Teskelibrary first appeared in
402*3fe5961aSDevin Teske.Fx 16.0 .
403*3fe5961aSDevin TeskeIt supersedes the
404*3fe5961aSDevin Teske.Nm figpar
405*3fe5961aSDevin Teskelibrary which first appeared in
406*3fe5961aSDevin Teske.Fx 10.2
407*3fe5961aSDevin Teskeand was retired to the ports tree.
408*3fe5961aSDevin Teske.Sh AUTHORS
409*3fe5961aSDevin Teske.An Devin Teske Aq Mt dteske@FreeBSD.org
410*3fe5961aSDevin Teske.An Faraz Vahedi Aq Mt kfv@FreeBSD.org
411*3fe5961aSDevin Teske.Sh BUGS
412*3fe5961aSDevin TeskeThis is the first implementation of the library,
413*3fe5961aSDevin Teskeand the interface may be subject to refinement.
414*3fe5961aSDevin Teske.Pp
415*3fe5961aSDevin TeskeWrite-path limitations for cumulative directives are discussed in the
416*3fe5961aSDevin Teske.Sx LIMITATIONS
417*3fe5961aSDevin Teskesection of
418*3fe5961aSDevin Teske.Xr bsdconf_put 3 .
419*3fe5961aSDevin Teske.Sh SECURITY CONSIDERATIONS
420*3fe5961aSDevin TeskeParsing allocates buffers sized by the longest directive and value
421*3fe5961aSDevin Teskeencountered rather than by untrusted length fields,
422*3fe5961aSDevin Teskeand
423*3fe5961aSDevin Teske.Fn bsdconf_fparse
424*3fe5961aSDevin Teskeaccepts an already-open descriptor precisely so that a caller may
425*3fe5961aSDevin Teskesandbox itself
426*3fe5961aSDevin Teske.Pq for example with Xr capsicum 4
427*3fe5961aSDevin Teskebefore touching untrusted input,
428*3fe5961aSDevin Teskeas
429*3fe5961aSDevin Teske.Xr sysconf 8
430*3fe5961aSDevin Teskedoes for its read-only operations.
431*3fe5961aSDevin TeskeWrite-path hardening is documented in
432*3fe5961aSDevin Teske.Xr bsdconf_put 3 .
433