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)):
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));
- BSDCONF_FORMAT_SYSCTL
-
/etc/sysctl.conf, /etc/sysctl.conf.local, then /etc/sysctl.kld.d/<module>.conf (see sysctl.conf(5));
- 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)); 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)), 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) 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) 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) follows the file parser of sysctl(8), 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) 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) 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) 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)) 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) 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); 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)-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) 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_put(3), loader.conf(5), src.conf(5), sysctl.conf(5), sysconf(8)
HISTORY
The format descriptor interface first appeared in FreeBSD 16.0 as part of bsdconf(3).
AUTHORS
Devin Teske ⟨[email protected]⟩ Faraz Vahedi ⟨[email protected]⟩