xref: /freebsd/lib/libbsdconf/bsdconf_format.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_FORMAT 3
8.Os
9.Sh NAME
10.Nm bsdconf_format_derive ,
11.Nm bsdconf_format_files ,
12.Nm bsdconf_format_files_free ,
13.Nm bsdconf_format_find ,
14.Nm bsdconf_format_guess ,
15.Nm bsdconf_format_lookup ,
16.Nm bsdconf_format_path ,
17.Nm bsdconf_format_processing ,
18.Nm bsdconf_format_put ,
19.Nm bsdconf_format_register
20.Nd configuration format descriptors and discovery
21.Sh LIBRARY
22.Lb libbsdconf
23.Sh SYNOPSIS
24.In bsdconf.h
25.Ft int
26.Fo bsdconf_format_derive
27.Fa "enum bsdconf_format base"
28.Fa "struct bsdconf_format_def *def"
29.Fc
30.Ft int
31.Fo bsdconf_format_files
32.Fa "enum bsdconf_format format"
33.Fa "const char *rootdir"
34.Fa "const char *module"
35.Fa "const char *defaults"
36.Fa "char ***filesp"
37.Fa "size_t *nfilesp"
38.Fa "size_t *write_idxp"
39.Fc
40.Ft void
41.Fo bsdconf_format_files_free
42.Fa "char **files"
43.Fa "size_t nfiles"
44.Fc
45.Ft int
46.Fo bsdconf_format_find
47.Fa "const char *keyword"
48.Fa "enum bsdconf_format *format"
49.Fc
50.Ft "enum bsdconf_format"
51.Fo bsdconf_format_guess
52.Fa "const char *path"
53.Fc
54.Ft "const struct bsdconf_format_def *"
55.Fo bsdconf_format_lookup
56.Fa "enum bsdconf_format format"
57.Fc
58.Ft "const char *"
59.Fo bsdconf_format_path
60.Fa "enum bsdconf_format format"
61.Fc
62.Ft uint16_t
63.Fo bsdconf_format_processing
64.Fa "enum bsdconf_format format"
65.Fc
66.Ft uint16_t
67.Fo bsdconf_format_put
68.Fa "enum bsdconf_format format"
69.Fc
70.Ft int
71.Fo bsdconf_format_register
72.Fa "const struct bsdconf_format_def *def"
73.Fa "enum bsdconf_format *format"
74.Fc
75.Sh DESCRIPTION
76Every supported configuration file format is described by the same small
77format descriptor
78.Pq introduced in Xr bsdconf 3 :
79.Bd -literal -offset indent
80struct bsdconf_format_def {
81    const char	*keyword;    /* target keyword (or NULL) */
82    const char	*path;       /* default write path (or NULL) */
83    const struct bsdconf_source
84		*sources;    /* ordered sources (or NULL) */
85    const char	*defaults;   /* defaults file (or NULL) */
86    const char	*defaults_env;     /* environment override */
87    const char	*files_directive;
88			     /* directive listing conf files */
89    const char	*dirs_directive;
90			     /* directive listing drop-in dirs */
91    const char	*local_directive;
92			     /* directive listing local files */
93    uint16_t	processing;  /* processing_options bitmask */
94    uint16_t	put;         /* put_options bitmask */
95};
96
97enum bsdconf_source_type {
98    BSDCONF_SOURCE_FILE = 0,  /* a single file */
99    BSDCONF_SOURCE_DIR,       /* each `*.conf' in a directory */
100    BSDCONF_SOURCE_MODDIR,    /* `<module>.conf' in a directory */
101};
102
103struct bsdconf_source {
104    enum bsdconf_source_type type;  /* how to interpret path */
105    const char	*path;              /* file or directory path */
106};
107.Ed
108.Pp
109There is exactly one parsing and writing engine;
110a format descriptor merely parameterizes it.
111The built-in formats,
112with their target keywords and default write paths,
113are:
114.Bl -column "BSDCONF_FORMAT_GENERIC" "keyword" "/boot/loader.conf"
115.It Sy format Ta Sy keyword Ta Sy "default write path"
116.It Dv BSDCONF_FORMAT_GENERIC Ta generic Ta \&-
117.It Dv BSDCONF_FORMAT_LOADER Ta loader Ta /boot/loader.conf
118.It Dv BSDCONF_FORMAT_SYSCTL Ta sysctl Ta /etc/sysctl.conf
119.It Dv BSDCONF_FORMAT_MAKE Ta make Ta /etc/make.conf
120.It Dv BSDCONF_FORMAT_SRC Ta src Ta /etc/src.conf
121.El
122.Pp
123The default write path
124.Pq the Va path member
125answers only the question of where a
126.Em new
127directive lands:
128the file to which a directive not yet present anywhere is appended.
129Which files are
130.Em consulted
131is a separate and potentially broader question,
132answered by the
133.Va sources
134member.
135When
136.Va sources
137is NULL
138.Pq as for Dv BSDCONF_FORMAT_MAKE
139the default write path is the format's one and only file and the two
140questions collapse into one.
141Otherwise
142.Po
143.Dv BSDCONF_FORMAT_LOADER ,
144.Dv BSDCONF_FORMAT_SYSCTL ,
145and
146.Dv BSDCONF_FORMAT_SRC
147.Pc
148the format is backed by several files and
149.Va sources
150lists them:
151an ordered,
152NULL-path terminated array in which each entry contributes a single file
153.Pq Dv BSDCONF_SOURCE_FILE ,
154every
155.Ql *.conf
156found in a drop-in directory
157.Pq Dv BSDCONF_SOURCE_DIR ,
158or the file
159.Ql <module>.conf
160in a directory keyed by kernel module name
161.Pq Dv BSDCONF_SOURCE_MODDIR .
162The order matches the order in which the system sources the files at boot,
163so a directive in a later file overrides the same directive in an earlier
164one and the last file listing a directive is the authoritative source of
165its value.
166The built-in source lists are:
167.Bl -tag -width BSDCONF_FORMAT_LOADER -offset indent
168.It Dv BSDCONF_FORMAT_LOADER
169discovered from
170.Pa /boot/defaults/loader.conf
171.Pq see below ;
172on a stock system
173.Pa /boot/device.hints ,
174.Pa /boot/loader.conf ,
175each
176.Pa *.conf
177in
178.Pa /boot/loader.conf.d ,
179then
180.Pa /boot/loader.conf.local
181.Pq see Xr loader.conf 5 ;
182.It Dv BSDCONF_FORMAT_SYSCTL
183.Pa /etc/sysctl.conf ,
184.Pa /etc/sysctl.conf.local ,
185then
186.Pa /etc/sysctl.kld.d/<module>.conf
187.Pq see Xr sysctl.conf 5 ;
188.It Dv BSDCONF_FORMAT_MAKE
189.Pa /etc/make.conf
190alone;
191.It Dv BSDCONF_FORMAT_SRC
192.Pa /etc/src-env.conf ,
193.Pa /etc/make.conf ,
194then
195.Pa /etc/src.conf
196.Pq the /usr/src build triad; see Xr src.conf 5 ;
197new writes prefer
198.Pa src.conf .
199.El
200.Pp
201A format whose backing files are themselves configuration data describes
202that machinery with the descriptor's
203.Va defaults
204member quintet rather than a static list alone.
205The boot loader hardcodes only
206.Pa /boot/defaults/loader.conf
207and discovers every other file from the
208.Va loader_conf_files ,
209.Va loader_conf_dirs ,
210and
211.Va local_loader_conf_files
212directives it encounters along the way
213.Pq see Xr loader.conf 5 ,
214and
215.Dv BSDCONF_FORMAT_LOADER
216names that defaults file and those three directives so that
217.Fn bsdconf_format_files
218matches that discovery:
219the defaults file is read,
220.Va loader_conf_files
221is chased
222.Po
223re-read after every file, as the loader does when a file queues
224additional names
225.Pc ,
226and the drop-in directories and local files named by the final
227.Va loader_conf_dirs
228and
229.Va local_loader_conf_files
230values are appended
231.Pq the loader likewise applies those only after the file-list walk .
232The defaults file itself is deliberately excluded from the resolved list,
233mirroring how
234.Xr sysrc 8
235excludes
236.Pa /etc/defaults/rc.conf ;
237the static
238.Va sources
239list serves as the fallback when the defaults file is missing.
240.Pp
241.Fn bsdconf_format_files
242resolves the backing files of
243.Fa format
244into a newly allocated array of paths stored through
245.Fa filesp
246.Pq with the count stored through Fa nfilesp ,
247each prefixed with
248.Fa rootdir
249unless NULL or empty.
250When
251.Fa defaults
252is non-NULL,
253it overrides the descriptor's defaults file for discovery and is used
254verbatim
255.Po
256not prefixed with
257.Fa rootdir ;
258it is ignored for formats without one
259.Pc .
260When
261.Fa write_idxp
262is non-NULL,
263the index of the file recommended for directives found in no file at all
264is stored through it:
265the format's default write path,
266or the last regular
267.Pq non-drop-in, non-local
268configuration file when discovery is in play.
269Sources of type
270.Dv BSDCONF_SOURCE_FILE
271.Pq and files named by a file-list directive
272are always listed,
273whether or not the file exists;
274directory sources contribute only the entries present on disk
275.Pq sorted ;
276module sources contribute
277.Ql <module>.conf
278only when
279.Fa module
280is non-NULL.
281The caller releases the result with
282.Fn bsdconf_format_files_free .
283.Pp
284The formats also differ in quoting on output,
285each honoring the full syntax its consumer accepts:
286.Xr loader.conf 5
287values are always written quoted
288.Pq quoted or unquoted input is accepted when parsing
289and no whitespace is permitted around the equals sign,
290as demanded by the boot loader's reader;
291.Xr sysctl.conf 5
292follows the file parser of
293.Xr sysctl 8 ,
294which trims whitespace around the equals sign and strips one pair of quotes
295around the value,
296so values are written unquoted unless quoting is required
297.Pq embedded whitespace or a comment character ;
298.Pa make.conf
299follows
300.Xr make 1
301syntax where values are never quoted
302.Pq the value runs to the end of the line
303and assignment modifiers such as
304.Ql +=
305are recognized.
306The
307.Dv BSDCONF_FORMAT_SRC
308triad
309.Po
310.Pa src-env.conf ,
311.Pa make.conf ,
312.Pa src.conf
313.Pc
314shares that
315.Pa make.conf
316syntax exactly
317.Pq all three are read by Xr make 1 when building /usr/src ,
318including the empty value,
319which for
320.Pa src.conf
321is the idiom for the value-less
322.Ql WITH_*
323and
324.Ql WITHOUT_*
325build knobs.
326.Pp
327.Fn bsdconf_format_find
328maps a target keyword
329.Pq e.g., Dq loader
330to its format.
331.Fn bsdconf_format_guess
332guesses the format of an arbitrary file from the basename of
333.Fa path ,
334matching
335.Ql <keyword>.conf
336with an optional trailing suffix
337.Pq e.g., Pa /etc/sysctl.conf.local .
338The guess is advisory and offered for consumers that opt into it
339.Pq an interactive picker suggesting a default, for example ;
340where a wrong format silently misformats a file,
341as when writing,
342the caller should require the format to be stated explicitly instead,
343as
344.Xr sysconf 8
345does with its
346.Ar target
347keyword.
348.Fn bsdconf_format_lookup
349returns the format descriptor for a format handle.
350.Fn bsdconf_format_path ,
351.Fn bsdconf_format_processing ,
352and
353.Fn bsdconf_format_put
354return the default file path and the two option bitmasks,
355respectively.
356.Sh ADDING FORMATS
357Before adding a format,
358consider whether one is needed at all:
359the parser hands each statement's raw directive and value to the caller's
360callbacks and imposes no semantics of its own,
361so a file whose directives mean something unusual
362.Po
363cumulative directives that legitimately repeat,
364for example,
365which a
366.Fn parse
367callback accumulates rather than overwrites;
368see
369.Xr bsdconf 3
370.Pc
371is read with the stock engine and bespoke callbacks;
372no format descriptor,
373flag,
374or engine change is required.
375A format descriptor parameterizes only tokenization and writing;
376reach for one when a file needs a
377.Xr sysconf 8
378target keyword,
379a source list,
380or distinct quoting on output.
381Rewriting files built from cumulative directives,
382where a change is additive rather than a replacement,
383is only partly covered by
384.Va match_line
385selection in
386.Xr bsdconf_put 3 ;
387higher-level additive policy remains the caller's concern
388.Pq see that page's Sx LIMITATIONS .
389.Pp
390Beyond callbacks,
391the format descriptor is the entire definition of a format;
392no format has private parsing or writing code.
393Support for a new configuration file format is therefore a data problem,
394approached one of two ways.
395.Pp
396An application whose format is mostly like an existing one bolts it on at
397runtime without duplicating any logic
398.Pq see Sx EXAMPLES :
399.Fn bsdconf_format_derive
400copies the format descriptor of the nearest
401.Fa base
402format into
403.Fa def ,
404the caller adjusts only the members that differ
405.Pq typically the keyword, the paths, and one or two bitmask flags ,
406and
407.Fn bsdconf_format_register
408registers the result and returns a new format handle through
409.Fa format .
410The format descriptor is copied by value but the strings and arrays it
411references
412.Po
413.Va keyword ,
414.Va path ,
415.Va sources ,
416and the
417.Va defaults
418member quintet
419.Pc
420are not;
421they must remain valid for the life of the registration.
422Registered formats participate in keyword and basename resolution exactly
423like built-ins,
424including target resolution in
425.Xr sysconf 8 Ns -style
426consumers.
427Registration is intended to occur during program initialization and is not
428thread-safe.
429.Pp
430Within the library,
431each built-in format is a self-contained translation unit
432.Pa ( bsdconf_format_<keyword>.c )
433that documents the format's syntax rules,
434cites the authority for them
435.Pq the consumer whose reader defines what is legal ,
436and defines its format descriptor,
437including its ordered source list.
438Promoting a format into the library therefore touches no existing parsing
439or writing code:
440a new file of the same shape is dropped in,
441its descriptor is declared alongside its siblings,
442one
443.Dv BSDCONF_FORMAT_*
444constant is appended to
445.Vt enum bsdconf_format ,
446one pointer is appended to the registry table,
447the file is listed in the Makefile,
448and this manual's
449.Sx DESCRIPTION
450section grows one row and one quoting note.
451The keyword becomes a
452.Xr sysconf 8
453target automatically.
454.Pp
455Many formats need no new engine capability at all.
456Formats whose statements are a space-separated directive and value with no
457equals sign
458.Pq the Apache-style directive files the ancestral parser was raised on
459parse today:
460with
461.Dv BSDCONF_BREAK_ON_EQUALS
462omitted,
463the first whitespace-delimited token is the directive and the remainder of
464the line is the raw value
465.Po which a
466.Fn parse
467callback may split further by its own rules
468.Pc ,
469and directive matching is case insensitive unless
470.Dv BSDCONF_CASE_SENSITIVE
471is set.
472Likewise
473.Dv BSDCONF_BREAK_ON_SEMICOLON
474already covers formats that terminate or chain statements with a
475semicolon.
476.Pp
477A candidate format whose syntax the existing option flags cannot express
478.Pq brace-grouped statement blocks being the canonical example
479is handled by teaching the shared engine
480.Pq the scanners in the parsing and writing cores
481one new
482.Va processing
483or
484.Va put
485flag that the new format's descriptor is the first to set.
486The capability lands once,
487composes with every existing flag,
488and becomes available to every other format
489.Pq built-in, derived, or registered
490rather than living in a private parser;
491the new format itself remains nothing more than a format descriptor in its
492own translation unit.
493.Sh RETURN VALUES
494.Fn bsdconf_format_derive
495and
496.Fn bsdconf_format_register
497return zero on success;
498otherwise -1 with
499.Va errno
500set to
501.Er EINVAL
502or
503.Er ENOSPC .
504.Fn bsdconf_format_find
505returns zero on success;
506otherwise -1.
507.Fn bsdconf_format_files
508returns zero on success;
509otherwise -1 with
510.Va errno
511set to indicate the error
512.Pq Er EINVAL for a format with no backing files .
513.Sh EXAMPLES
514Bolt on a new format at runtime by deriving from the built-in it most
515resembles,
516then set a directive in its file with full resilient write semantics:
517.Bd -literal -offset indent
518struct bsdconf_format_def def;
519enum bsdconf_format myfmt;
520struct bsdconf_option set[] = {
521	{ .type = BSDCONF_TYPE_STR, .directive = "loglevel",
522	  .value = { .str = "debug" },
523	  .action = BSDCONF_ACTION_SET_VALUE },
524	{ .directive = NULL }
525};
526
527if (bsdconf_format_derive(BSDCONF_FORMAT_SYSCTL, &def) != 0)
528	err(1, "bsdconf_format_derive");
529def.keyword = "myapp";
530def.path = "/usr/local/etc/myapp.conf";
531def.sources = NULL;	/* one file; path is the sole source */
532if (bsdconf_format_register(&def, &myfmt) != 0)
533	err(1, "bsdconf_format_register");
534
535if (bsdconf_put(set, bsdconf_format_path(myfmt),
536    bsdconf_format_processing(myfmt),
537    bsdconf_format_put(myfmt)) != 0)
538	err(1, "%s", bsdconf_format_path(myfmt));
539.Ed
540.Pp
541Promoting such a format into the library itself
542.Pq a compiled-in sibling of the built-ins
543is the same data expressed as a translation unit;
544see
545.Sx ADDING FORMATS .
546.Sh SEE ALSO
547.Xr bsdconf 3 ,
548.Xr bsdconf_put 3 ,
549.Xr loader.conf 5 ,
550.Xr src.conf 5 ,
551.Xr sysctl.conf 5 ,
552.Xr sysconf 8
553.Sh HISTORY
554The format descriptor interface first appeared in
555.Fx 16.0
556as part of
557.Xr bsdconf 3 .
558.Sh AUTHORS
559.An Devin Teske Aq Mt dteske@FreeBSD.org
560.An Faraz Vahedi Aq Mt kfv@FreeBSD.org
561