xref: /freebsd/lib/libbsdconf/bsdconf.3 (revision c4fde87a3236b500246d4fce76dbca2a3d64f73a)
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 3
8.Os
9.Sh NAME
10.Nm bsdconf ,
11.Nm bsdconf_parse ,
12.Nm bsdconf_fparse ,
13.Nm bsdconf_get_option ,
14.Nm bsdconf_spool ,
15.Nm bsdconf_unquote
16.Nd configuration file reading library
17.Sh LIBRARY
18.Lb libbsdconf
19.Sh SYNOPSIS
20.In bsdconf.h
21.Ft int
22.Fo bsdconf_parse
23.Fa "struct bsdconf_option options[]"
24.Fa "const char *path"
25.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct bsdconf_option *option"
26.Fa "uint32_t line"
27.Fa "char *directive"
28.Fa "char *value\*[rp]"
29.Fa "uint16_t processing_options"
30.Fc
31.Ft int
32.Fo bsdconf_fparse
33.Fa "struct bsdconf_option options[]"
34.Fa "int fd"
35.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct bsdconf_option *option"
36.Fa "uint32_t line"
37.Fa "char *directive"
38.Fa "char *value\*[rp]"
39.Fa "uint16_t processing_options"
40.Fc
41.Ft "struct bsdconf_option *"
42.Fo bsdconf_get_option
43.Fa "struct bsdconf_option options[]"
44.Fa "const char *directive"
45.Fc
46.Ft int
47.Fo bsdconf_spool
48.Fa "int fd"
49.Fc
50.Ft "char *"
51.Fo bsdconf_unquote
52.Fa "char *value"
53.Fc
54.Sh DESCRIPTION
55The
56.Nm
57library provides a light-weight,
58portable framework for reading and writing configuration
59files.
60It is the successor to the retired
61.Nm figpar
62library,
63extending the original read-only token parser into a unified,
64resilient reader/writer engine.
65.Pp
66Due to the fact that configuration files may have basic syntax differences,
67the library does not attempt to impose any structure on the data but instead
68provides raw data to a set of callback functions.
69These callback functions can in-turn initiate abort through their return
70value,
71allowing custom syntax validation during parsing.
72.Pp
73Syntax differences between well-known file formats are described by format
74descriptors:
75a format descriptor is a small read-only table of properties
76.Pq Vt struct bsdconf_format_def ; see Xr bsdconf_format 3
77naming a format's target keyword,
78backing files,
79and tokenizing/quoting rules,
80which parameterize a single shared engine.
81.Pp
82Despite the name, it bears no relation to a file descriptor;
83it is closer in spirit to a driver's method table:
84static data describing behavior,
85consulted rather than executed.
86Writing is documented in
87.Xr bsdconf_put 3 ;
88built-in formats and discovery in
89.Xr bsdconf_format 3 .
90.Pp
91Configuration directives,
92types,
93and callback functions are provided through data structures defined in
94.In bsdconf.h :
95.Bd -literal -offset indent
96struct bsdconf_option {
97    enum bsdconf_type		type;	/* value type */
98    const char			*directive; /* keyword */
99    union bsdconf_value		value;	/* value */
100    enum bsdconf_op		op;	/* assignment operator */
101    uint8_t			action;	/* bsdconf_put() action */
102    uint16_t			result;	/* set by bsdconf_put() */
103    uint32_t			line;	/* set by bsdconf_put() */
104    uint32_t			match_line;
105					/* put: 0=any, else line */
106
107    /* Pointer to function used when directive is found */
108    int (*parse)(struct bsdconf_option *option, uint32_t line,
109        char *directive, char *value);
110};
111
112enum bsdconf_type {
113    BSDCONF_TYPE_NONE      = 0x0000, /* directives with no value */
114    BSDCONF_TYPE_BOOL      = 0x0001, /* boolean */
115    BSDCONF_TYPE_INT       = 0x0002, /* signed 32-bit integer */
116    BSDCONF_TYPE_UINT      = 0x0004, /* unsigned 32-bit integer */
117    BSDCONF_TYPE_STR       = 0x0008, /* string pointer */
118    BSDCONF_TYPE_STRARRAY  = 0x0010, /* string array pointer */
119    BSDCONF_TYPE_DATA1     = 0x0020, /* void data type-1 (open) */
120    BSDCONF_TYPE_DATA2     = 0x0040, /* void data type-2 (open) */
121    BSDCONF_TYPE_DATA3     = 0x0080, /* void data type-3 (open) */
122    BSDCONF_TYPE_INT64     = 0x0100, /* signed 64-bit integer */
123    BSDCONF_TYPE_UINT64    = 0x0200, /* unsigned 64-bit integer */
124    BSDCONF_TYPE_RESERVED  = 0x0400, /* reserved */
125};
126
127union bsdconf_value {
128    void	*data;      /* Opaque pointer (DATA1..DATA3) */
129    char	*str;       /* Pointer to NUL-terminated string */
130    char	**strarray; /* Pointer to an array of strings */
131    int32_t	num;        /* Signed 32-bit integer value */
132    uint32_t	u_num;      /* Unsigned 32-bit integer value */
133    int64_t	num64;      /* Signed 64-bit integer value */
134    uint64_t	u_num64;    /* Unsigned 64-bit integer value */
135    bool	boolean;    /* Boolean value */
136};
137.Ed
138.Pp
139The
140.Fa processing_options
141argument to
142.Fn bsdconf_parse ,
143.Fn bsdconf_fparse ,
144and
145.Xr bsdconf_put 3
146is a mask of bit fields which indicate various processing options.
147The possible flags are:
148.Bl -tag -width BSDCONF_BREAK_ON_SEMICOLON
149.It Dv BSDCONF_BREAK_ON_EQUALS
150An equals sign
151.Pq Ql =
152is normally considered part of the directive.
153This flag enables terminating the directive at the equals sign.
154Also makes equals sign optional and transient.
155.It Dv BSDCONF_BREAK_ON_SEMICOLON
156A semicolon
157.Pq Ql \&;
158is normally considered part of the value.
159This flag enables terminating the value at the semicolon.
160Also allows multiple statements on a single line separated by semicolon.
161.It Dv BSDCONF_CASE_SENSITIVE
162Normally directives are matched case insensitively using
163.Xr fnmatch 3 .
164This flag enables directive matching to be case sensitive.
165.It Dv BSDCONF_REQUIRE_EQUALS
166If a directive is not followed by an equals,
167processing is aborted.
168.It Dv BSDCONF_STRICT_EQUALS
169Equals must be part of the directive
170.Pq no whitespace before or after
171to be considered a delimiter between directive and value.
172Required by file formats whose readers reject whitespace around the equals
173sign,
174such as the
175.Fx
176boot loader's processing of
177.Xr loader.conf 5 .
178.It Dv BSDCONF_OPERATOR_EQUALS
179Recognize
180.Xr make 1
181style assignment modifiers
182.Po
183.Ql += ,
184.Ql ?= ,
185.Ql := ,
186and
187.Ql !=
188.Pc
189and split them off the tail of the directive.
190The parsed operator is reported through the
191.Va op
192member of the matched option
193.Pq one of Dv BSDCONF_OP_ASSIGN , BSDCONF_OP_APPEND , BSDCONF_OP_COND , BSDCONF_OP_EXPAND , No or Dv BSDCONF_OP_SHELL .
194.El
195.Pp
196The
197.Fa options
198struct array pointer can be NULL and every directive will run the
199.Fn unknown
200function argument.
201.Pp
202The directive for each bsdconf_option item in the
203.Fn bsdconf_parse
204options argument is matched against each parsed directive using
205.Xr fnmatch 3
206until a match is found.
207If a match is found,
208the
209.Fn parse
210function for that bsdconf_option directive is run with the line number,
211directive,
212and value.
213Otherwise if no match,
214the
215.Fn unknown
216function is run
217.Pq with the same arguments .
218When
219.Dv BSDCONF_OPERATOR_EQUALS
220is set,
221.Fn unknown
222receives a non-NULL
223.Fa option
224whose
225.Va op
226member holds the statement's assignment operator
227.Pq there is no matched options-array slot to hang it on ;
228otherwise
229.Fa option
230may be
231.Dv NULL .
232.Pp
233If either
234.Fn parse
235or
236.Fn unknown
237return non-zero,
238.Fn bsdconf_parse
239aborts reading the file and returns the error value to its caller.
240.Pp
241A value normally ends at the first unescaped newline,
242but a statement may span multiple lines:
243a backslash immediately preceding the newline continues the value on the
244next line,
245in the manner of
246.Xr make 1
247.Pq essential to Pa make.conf and its siblings .
248The backslash-newline pairs are removed from the value delivered to the
249callbacks
250.Pq surrounding whitespace is preserved verbatim ,
251and reported line numbers are those of each statement's first line.
252.Xr bsdconf_put 3
253recognizes the same continuations when locating a value;
254rewriting a continued value replaces all of its lines with the single new
255value.
256.Pp
257.Fn bsdconf_fparse
258is identical to
259.Fn bsdconf_parse
260except that it operates on an already-open file descriptor
261.Fa fd ,
262which remains open on return
263.Pq the caller retains ownership .
264The descriptor is read into a bounded in-memory buffer and then scanned
265as an array of characters
266.Pq the same tokenizer used by Xr bsdconf_put 3 .
267The buffer is capped at
268.Dv BSDCONF_MAX_BYTES_DEFAULT
269bytes
270.Pq 67108864; 64 MiB ;
271.Ev BSDCONF_MAX_BYTES
272overrides that ceiling
273.Pq see Sx ENVIRONMENT .
274Input larger than the cap fails with
275.Er EFBIG .
276The descriptor need not be seekable;
277a pipe or socket is read to EOF subject to the same cap.
278This allows the caller to constrain the process
279.Pq for example with Xr capsicum 4
280before parsing begins.
281.Pp
282.Fn bsdconf_spool
283copies the remaining contents of
284.Fa fd
285to an unlinked temporary file
286.Pq created with Xr tmpfile 3
287and returns a seekable descriptor referencing it,
288which the caller must
289.Xr close 2
290.Pq the backing storage is reclaimed then .
291It is exported for callers that need a seekable snapshot of a pipe or
292socket.
293.Pp
294.Fn bsdconf_get_option
295traverses the options-array and returns the option that matches via
296.Xr strcmp 3 ,
297or
298.Dv NULL
299if none matches.
300.Pp
301.Fn bsdconf_unquote
302strips one layer of surrounding double-quotes from
303.Fa value
304in place and returns it.
305Parsed values retain their quotes so that data round-trips losslessly;
306this helper is for display and comparison purposes.
307.Sh RETURN VALUES
308.Fn bsdconf_parse
309and
310.Fn bsdconf_fparse
311return zero on success;
312otherwise -1
313.Pq or the non-zero result of a callback
314is returned and the global variable
315.Va errno
316is set to indicate the error.
317Input that exceeds the
318.Ev BSDCONF_MAX_BYTES
319cap fails with
320.Er EFBIG .
321.Pp
322.Fn bsdconf_spool
323returns a new seekable file descriptor on success;
324otherwise -1 with
325.Va errno
326set to indicate the error.
327.Fn bsdconf_get_option
328returns a pointer to the matching option,
329or
330.Dv NULL
331when none matches.
332.Sh ENVIRONMENT
333.Bl -tag -width "BSDCONF_MAX_BYTES"
334.It Ev BSDCONF_MAX_BYTES
335Maximum number of bytes
336.Fn bsdconf_fparse
337and
338.Fn bsdconf_put
339will read from a configuration file or stream.
340The default is
341.Dv BSDCONF_MAX_BYTES_DEFAULT
342.Pq 67108864; 64 MiB .
343A larger value is an opt-in pain threshold for unusual files.
344Unset, empty, zero, or unparseable values restore the default.
345.El
346.Sh EXAMPLES
347Read two known directives from a
348.Ql name=value
349file,
350routing every statement through a callback:
351.Bd -literal -offset indent
352#include <err.h>
353#include <stdio.h>
354#include <bsdconf.h>
355
356static int
357show(struct bsdconf_option *option, uint32_t line,
358    char *directive, char *value)
359{
360	printf("%u: %s is %s\en", line, directive,
361	    bsdconf_unquote(value));
362	return (0);
363}
364
365static struct bsdconf_option options[] = {
366	{ .directive = "hostname", .parse = show },
367	{ .directive = "timeout",  .parse = show },
368	{ .directive = NULL }
369};
370
371int
372main(void)
373{
374	if (bsdconf_parse(options, "/usr/local/etc/myapp.conf",
375	    NULL, BSDCONF_BREAK_ON_EQUALS) != 0)
376		err(1, "myapp.conf");
377	return (0);
378}
379.Ed
380.Pp
381Callbacks own the semantics,
382so a directive that legitimately repeats is accumulated rather than
383overwritten;
384no format descriptor is involved.
385The file here is Apache-style
386.Pq space-separated, no equals sign ,
387so
388.Dv BSDCONF_BREAK_ON_EQUALS
389is simply omitted:
390.Bd -literal -offset indent
391static char *servers[16];
392static size_t nservers;
393
394static int
395addserver(struct bsdconf_option *option, uint32_t line,
396    char *directive, char *value)
397{
398	if (nservers >= 16 ||
399	    (servers[nservers] = strdup(value)) == NULL)
400		return (-1); /* abort the parse */
401	nservers++;
402	return (0);
403}
404
405static struct bsdconf_option cumulative[] = {
406	{ .directive = "server", .parse = addserver },
407	{ .directive = NULL }
408};
409
410	...
411	if (bsdconf_parse(cumulative, path, NULL, 0) != 0)
412		err(1, "%s", path);
413.Ed
414.Sh SEE ALSO
415.Xr bsdconf_format 3 ,
416.Xr bsdconf_put 3 ,
417.Xr loader.conf 5 ,
418.Xr sysctl.conf 5 ,
419.Xr sysconf 8
420.Sh HISTORY
421The
422.Nm
423library first appeared in
424.Fx 16.0 .
425It supersedes the
426.Nm figpar
427library which first appeared in
428.Fx 10.2
429and was retired to the ports tree.
430.Sh AUTHORS
431.An Devin Teske Aq Mt dteske@FreeBSD.org
432.An Faraz Vahedi Aq Mt kfv@FreeBSD.org
433.Sh BUGS
434This is the first implementation of the library,
435and the interface may be subject to refinement.
436.Pp
437Write-path limitations for cumulative directives are discussed in the
438.Sx LIMITATIONS
439section of
440.Xr bsdconf_put 3 .
441.Sh SECURITY CONSIDERATIONS
442.Fn bsdconf_fparse
443reads the descriptor into a bounded in-memory buffer rather than
444trusting untrusted length fields,
445and accepts an already-open descriptor precisely so that a caller may
446sandbox itself
447.Pq for example with Xr capsicum 4
448before touching untrusted input,
449as
450.Xr sysconf 8
451does for its read-only operations.
452The
453.Ev BSDCONF_MAX_BYTES
454cap
455.Pq see Sx ENVIRONMENT
456is the bound on that buffer.
457Write-path hardening is documented in
458.Xr bsdconf_put 3 .
459