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