---
name: "bsdconf_format"
section: "3"
platform: "freebsd"
package: "base"
version: "16.0-CURRENT-20261027-61c70dc16a79"
release: "FreeBSD 16.0-CURRENT (2026-10-27)"
platforms: ["freebsd"]
url: "https://new.carta.tech/bsdconf_format"
---

# bsdconf\_format(3)

configuration format descriptors and discovery

## NAME

**bsdconf\_format\_derive**, **bsdconf\_format\_files**, **bsdconf\_format\_files\_free**, **bsdconf\_format\_find**, **bsdconf\_format\_guess**, **bsdconf\_format\_lookup**, **bsdconf\_format\_path**, **bsdconf\_format\_processing**, **bsdconf\_format\_put**, **bsdconf\_format\_register** – configuration format descriptors and discovery

## LIBRARY

library “libbsdconf”

## SYNOPSIS

**#include \<bsdconf.h\>**

*int*\

**bsdconf\_format\_derive**(*enum bsdconf\_format base*, *struct bsdconf\_format\_def \*def*);

*int*\

**bsdconf\_format\_files**(*enum bsdconf\_format format*, *const char \*rootdir*, *const char \*module*, *const char \*defaults*, *char \*\*\*filesp*, *size\_t \*nfilesp*, *size\_t \*write\_idxp*);

*void*\

**bsdconf\_format\_files\_free**(*char \*\*files*, *size\_t nfiles*);

*int*\

**bsdconf\_format\_find**(*const char \*keyword*, *enum bsdconf\_format \*format*);

*enum bsdconf\_format*\

**bsdconf\_format\_guess**(*const char \*path*);

*const struct bsdconf\_format\_def \**\

**bsdconf\_format\_lookup**(*enum bsdconf\_format format*);

*const char \**\

**bsdconf\_format\_path**(*enum bsdconf\_format format*);

*uint16\_t*\

**bsdconf\_format\_processing**(*enum bsdconf\_format format*);

*uint16\_t*\

**bsdconf\_format\_put**(*enum bsdconf\_format format*);

*int*\

**bsdconf\_format\_register**(*const struct bsdconf\_format\_def \*def*, *enum bsdconf\_format \*format*);

## DESCRIPTION

Every supported configuration file format is described by the same small format descriptor (introduced in [bsdconf(3)](/bsdconf/3.md)):

```
struct bsdconf_format_def {
    const char	*keyword;    /* target keyword (or NULL) */
    const char	*path;       /* default write path (or NULL) */
    const struct bsdconf_source
		*sources;    /* ordered sources (or NULL) */
    const char	*defaults;   /* defaults file (or NULL) */
    const char	*defaults_env;     /* environment override */
    const char	*files_directive;
			     /* directive listing conf files */
    const char	*dirs_directive;
			     /* directive listing drop-in dirs */
    const char	*local_directive;
			     /* directive listing local files */
    uint16_t	processing;  /* processing_options bitmask */
    uint16_t	put;         /* put_options bitmask */
};
enum bsdconf_source_type {
    BSDCONF_SOURCE_FILE = 0,  /* a single file */
    BSDCONF_SOURCE_DIR,       /* each `*.conf' in a directory */
    BSDCONF_SOURCE_MODDIR,    /* `<module>.conf' in a directory */
};
struct bsdconf_source {
    enum bsdconf_source_type type;  /* how to interpret path */
    const char	*path;              /* file or directory path */
};
```

There is exactly one parsing and writing engine; a format descriptor merely parameterizes it. The built-in formats, with their target keywords and default write paths, are:

| | | |
| --- | --- | --- |
| **format** | **keyword** | **default write path** |
| BSDCONF\_FORMAT\_GENERIC | generic | - |
| BSDCONF\_FORMAT\_LOADER | loader | /boot/loader.conf |
| BSDCONF\_FORMAT\_SYSCTL | sysctl | /etc/sysctl.conf |
| BSDCONF\_FORMAT\_MAKE | make | /etc/make.conf |
| BSDCONF\_FORMAT\_SRC | src | /etc/src.conf |

The default write path (the *path* *member*) answers only the question of where a *new* directive lands: the file to which a directive not yet present anywhere is appended. Which files are *consulted* is a separate and potentially broader question, answered by the *sources* member. When *sources* is NULL (as for BSDCONF\_FORMAT\_MAKE) the default write path is the format's one and only file and the two questions collapse into one. Otherwise( BSDCONF\_FORMAT\_LOADER, BSDCONF\_FORMAT\_SYSCTL, and BSDCONF\_FORMAT\_SRC) the format is backed by several files and *sources* lists them: an ordered, NULL-path terminated array in which each entry contributes a single file (BSDCONF\_SOURCE\_FILE), every ‘\*.conf’ found in a drop-in directory (BSDCONF\_SOURCE\_DIR), or the file ‘\<module\>.conf’ in a directory keyed by kernel module name (BSDCONF\_SOURCE\_MODDIR). The order matches the order in which the system sources the files at boot, so a directive in a later file overrides the same directive in an earlier one and the last file listing a directive is the authoritative source of its value. The built-in source lists are:

- BSDCONF\_FORMAT\_LOADER

  discovered from */boot/defaults/loader.conf* (see below); on a stock system */boot/device.hints*, */boot/loader.conf*, each *\*.conf* in */boot/loader.conf.d*, then */boot/loader.conf.local* (see [loader.conf(5)](/loader.conf/5.md));

- BSDCONF\_FORMAT\_SYSCTL

  */etc/sysctl.conf*, */etc/sysctl.conf.local*, then */etc/sysctl.kld.d/\<module\>.conf* (see [sysctl.conf(5)](/sysctl.conf/5.md));

- BSDCONF\_FORMAT\_MAKE

  */etc/make.conf* alone;

- BSDCONF\_FORMAT\_SRC

  */etc/src-env.conf*, */etc/make.conf*, then */etc/src.conf* (the /usr/src build triad; see [src.conf(5)](/src.conf/5.md)); new writes prefer *src.conf*.

A format whose backing files are themselves configuration data describes that machinery with the descriptor's *defaults* member quintet rather than a static list alone. The boot loader hardcodes only */boot/defaults/loader.conf* and discovers every other file from the *loader\_conf\_files*, *loader\_conf\_dirs*, and *local\_loader\_conf\_files* directives it encounters along the way (see [loader.conf(5)](/loader.conf/5.md)), and BSDCONF\_FORMAT\_LOADER names that defaults file and those three directives so that **bsdconf\_format\_files**() matches that discovery: the defaults file is read, *loader\_conf\_files* is chased( re-read after every file, as the loader does when a file queues additional names), and the drop-in directories and local files named by the final *loader\_conf\_dirs* and *local\_loader\_conf\_files* values are appended (the loader likewise applies those only after the file-list walk). The defaults file itself is deliberately excluded from the resolved list, mirroring how [sysrc(8)](/sysrc/8.md) excludes */etc/defaults/rc.conf*; the static *sources* list serves as the fallback when the defaults file is missing.

**bsdconf\_format\_files**() resolves the backing files of *format* into a newly allocated array of paths stored through *filesp* (with the count stored through *nfilesp*), each prefixed with *rootdir* unless NULL or empty. When *defaults* is non-NULL, it overrides the descriptor's defaults file for discovery and is used verbatim( not prefixed with *rootdir*; it is ignored for formats without one). When *write\_idxp* is non-NULL, the index of the file recommended for directives found in no file at all is stored through it: the format's default write path, or the last regular (non-drop-in, non-local) configuration file when discovery is in play. Sources of type BSDCONF\_SOURCE\_FILE (and files named by a file-list directive) are always listed, whether or not the file exists; directory sources contribute only the entries present on disk (sorted); module sources contribute ‘\<module\>.conf’ only when *module* is non-NULL. The caller releases the result with **bsdconf\_format\_files\_free**().

The formats also differ in quoting on output, each honoring the full syntax its consumer accepts: [loader.conf(5)](/loader.conf/5.md) values are always written quoted (quoted or unquoted input is accepted when parsing) and no whitespace is permitted around the equals sign, as demanded by the boot loader's reader; [sysctl.conf(5)](/sysctl.conf/5.md) follows the file parser of [sysctl(8)](/sysctl/8.md), which trims whitespace around the equals sign and strips one pair of quotes around the value, so values are written unquoted unless quoting is required (embedded whitespace or a comment character); *make.conf* follows [make(1)](/make/1.md) syntax where values are never quoted (the value runs to the end of the line) and assignment modifiers such as ‘+=’ are recognized. The BSDCONF\_FORMAT\_SRC triad( *src-env.conf*, *make.conf*, *src.conf*) shares that *make.conf* syntax exactly (all three are read by [make(1)](/make/1.md) when building /usr/src), including the empty value, which for *src.conf* is the idiom for the value-less ‘WITH\_\*’ and ‘WITHOUT\_\*’ build knobs.

**bsdconf\_format\_find**() maps a target keyword (e.g., “loader”) to its format. **bsdconf\_format\_guess**() guesses the format of an arbitrary file from the basename of *path*, matching ‘\<keyword\>.conf’ with an optional trailing suffix (e.g., */etc/sysctl.conf.local*). The guess is advisory and offered for consumers that opt into it (an interactive picker suggesting a default, for example); where a wrong format silently misformats a file, as when writing, the caller should require the format to be stated explicitly instead, as [sysconf(8)](/sysconf/8.md) does with its *target* keyword. **bsdconf\_format\_lookup**() returns the format descriptor for a format handle. **bsdconf\_format\_path**(), **bsdconf\_format\_processing**(), and **bsdconf\_format\_put**() return the default file path and the two option bitmasks, respectively.

## ADDING FORMATS

Before adding a format, consider whether one is needed at all: the parser hands each statement's raw directive and value to the caller's callbacks and imposes no semantics of its own, so a file whose directives mean something unusual( cumulative directives that legitimately repeat, for example, which a **parse**() callback accumulates rather than overwrites; see [bsdconf(3)](/bsdconf/3.md)) is read with the stock engine and bespoke callbacks; no format descriptor, flag, or engine change is required. A format descriptor parameterizes only tokenization and writing; reach for one when a file needs a [sysconf(8)](/sysconf/8.md) target keyword, a source list, or distinct quoting on output. Rewriting files built from cumulative directives, where a change is additive rather than a replacement, is only partly covered by *match\_line* selection in [bsdconf\_put(3)](/bsdconf_put/3.md); higher-level additive policy remains the caller's concern (see that page's LIMITATIONS).

Beyond callbacks, the format descriptor is the entire definition of a format; no format has private parsing or writing code. Support for a new configuration file format is therefore a data problem, approached one of two ways.

An application whose format is mostly like an existing one bolts it on at runtime without duplicating any logic (see EXAMPLES): **bsdconf\_format\_derive**() copies the format descriptor of the nearest *base* format into *def*, the caller adjusts only the members that differ (typically the keyword, the paths, and one or two bitmask flags), and **bsdconf\_format\_register**() registers the result and returns a new format handle through *format*. The format descriptor is copied by value but the strings and arrays it references( *keyword*, *path*, *sources*, and the *defaults* member quintet) are not; they must remain valid for the life of the registration. Registered formats participate in keyword and basename resolution exactly like built-ins, including target resolution in [sysconf(8)](/sysconf/8.md)-style consumers. Registration is intended to occur during program initialization and is not thread-safe.

Within the library, each built-in format is a self-contained translation unit (*bsdconf\_format\_\<keyword\>.c*) that documents the format's syntax rules, cites the authority for them (the consumer whose reader defines what is legal), and defines its format descriptor, including its ordered source list. Promoting a format into the library therefore touches no existing parsing or writing code: a new file of the same shape is dropped in, its descriptor is declared alongside its siblings, one BSDCONF\_FORMAT\_\* constant is appended to *enum* *bsdconf\_format*, one pointer is appended to the registry table, the file is listed in the Makefile, and this manual's DESCRIPTION section grows one row and one quoting note. The keyword becomes a [sysconf(8)](/sysconf/8.md) target automatically.

Many formats need no new engine capability at all. Formats whose statements are a space-separated directive and value with no equals sign (the Apache-style directive files the ancestral parser was raised on) parse today: with BSDCONF\_BREAK\_ON\_EQUALS omitted, the first whitespace-delimited token is the directive and the remainder of the line is the raw value (which a **parse**() callback may split further by its own rules), and directive matching is case insensitive unless BSDCONF\_CASE\_SENSITIVE is set. Likewise BSDCONF\_BREAK\_ON\_SEMICOLON already covers formats that terminate or chain statements with a semicolon.

A candidate format whose syntax the existing option flags cannot express (brace-grouped statement blocks being the canonical example) is handled by teaching the shared engine (the scanners in the parsing and writing cores) one new *processing* or *put* flag that the new format's descriptor is the first to set. The capability lands once, composes with every existing flag, and becomes available to every other format (built-in, derived, or registered) rather than living in a private parser; the new format itself remains nothing more than a format descriptor in its own translation unit.

## RETURN VALUES

**bsdconf\_format\_derive**() and **bsdconf\_format\_register**() return zero on success; otherwise -1 with *errno* set to EINVAL or ENOSPC. **bsdconf\_format\_find**() returns zero on success; otherwise -1. **bsdconf\_format\_files**() returns zero on success; otherwise -1 with *errno* set to indicate the error (EINVAL for a format with no backing files).

## EXAMPLES

Bolt on a new format at runtime by deriving from the built-in it most resembles, then set a directive in its file with full resilient write semantics:

```
struct bsdconf_format_def def;
enum bsdconf_format myfmt;
struct bsdconf_option set[] = {
	{ .type = BSDCONF_TYPE_STR, .directive = "loglevel",
	  .value = { .str = "debug" },
	  .action = BSDCONF_ACTION_SET_VALUE },
	{ .directive = NULL }
};
if (bsdconf_format_derive(BSDCONF_FORMAT_SYSCTL, &def) != 0)
	err(1, "bsdconf_format_derive");
def.keyword = "myapp";
def.path = "/usr/local/etc/myapp.conf";
def.sources = NULL;	/* one file; path is the sole source */
if (bsdconf_format_register(&def, &myfmt) != 0)
	err(1, "bsdconf_format_register");
if (bsdconf_put(set, bsdconf_format_path(myfmt),
    bsdconf_format_processing(myfmt),
    bsdconf_format_put(myfmt)) != 0)
	err(1, "%s", bsdconf_format_path(myfmt));
```

Promoting such a format into the library itself (a compiled-in sibling of the built-ins) is the same data expressed as a translation unit; see ADDING FORMATS.

## SEE ALSO

[bsdconf(3)](/bsdconf/3.md), [bsdconf\_put(3)](/bsdconf_put/3.md), [loader.conf(5)](/loader.conf/5.md), [src.conf(5)](/src.conf/5.md), [sysctl.conf(5)](/sysctl.conf/5.md), [sysconf(8)](/sysconf/8.md)

## HISTORY

The format descriptor interface first appeared in FreeBSD 16.0 as part of [bsdconf(3)](/bsdconf/3.md).

## AUTHORS

Devin Teske ⟨*dteske@FreeBSD.org*⟩ Faraz Vahedi ⟨*kfv@FreeBSD.org*⟩
