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