Reference

bsdconf(3)

configuration file reading library

NAME

bsdconf, bsdconf_parse, bsdconf_fparse, bsdconf_get_option, bsdconf_spool, bsdconf_unquote – configuration file reading library

LIBRARY

library “libbsdconf”

SYNOPSIS

#include <bsdconf.h>

int

bsdconf_parse(struct bsdconf_option options[], const char *path, int *unknownstruct bsdconf_option *option, uint32_t line, char *directive, char *value, uint16_t processing_options);

int

bsdconf_fparse(struct bsdconf_option options[], int fd, int *unknownstruct bsdconf_option *option, uint32_t line, char *directive, char *value, uint16_t processing_options);

struct bsdconf_option *

bsdconf_get_option(struct bsdconf_option options[], const char *directive);

int

bsdconf_spool(int fd);

char *

bsdconf_unquote(char *value);

DESCRIPTION

The bsdconf library provides a light-weight, portable framework for reading and writing configuration files. It is the successor to the retired figpar library, extending the original read-only token parser into a unified, resilient reader/writer engine.

Due to the fact that configuration files may have basic syntax differences, the library does not attempt to impose any structure on the data but instead provides raw data to a set of callback functions. These callback functions can in-turn initiate abort through their return value, allowing custom syntax validation during parsing.

Syntax differences between well-known file formats are described by format descriptors: a format descriptor is a small read-only table of properties (struct bsdconf_format_def; see bsdconf_format(3)) naming a format's target keyword, backing files, and tokenizing/quoting rules, which parameterize a single shared engine.

Despite the name, it bears no relation to a file descriptor; it is closer in spirit to a driver's method table: static data describing behavior, consulted rather than executed. Writing is documented in bsdconf_put(3); built-in formats and discovery in bsdconf_format(3).

Configuration directives, types, and callback functions are provided through data structures defined in ⟨bsdconf.h⟩:

struct bsdconf_option {
    enum bsdconf_type		type;	/* value type */
    const char			*directive; /* keyword */
    union bsdconf_value		value;	/* value */
    enum bsdconf_op		op;	/* assignment operator */
    uint8_t			action;	/* bsdconf_put() action */
    uint16_t			result;	/* set by bsdconf_put() */
    uint32_t			line;	/* set by bsdconf_put() */
    uint32_t			match_line;
					/* put: 0=any, else line */
    /* Pointer to function used when directive is found */
    int (*parse)(struct bsdconf_option *option, uint32_t line,
        char *directive, char *value);
};
enum bsdconf_type {
    BSDCONF_TYPE_NONE      = 0x0000, /* directives with no value */
    BSDCONF_TYPE_BOOL      = 0x0001, /* boolean */
    BSDCONF_TYPE_INT       = 0x0002, /* signed 32-bit integer */
    BSDCONF_TYPE_UINT      = 0x0004, /* unsigned 32-bit integer */
    BSDCONF_TYPE_STR       = 0x0008, /* string pointer */
    BSDCONF_TYPE_STRARRAY  = 0x0010, /* string array pointer */
    BSDCONF_TYPE_DATA1     = 0x0020, /* void data type-1 (open) */
    BSDCONF_TYPE_DATA2     = 0x0040, /* void data type-2 (open) */
    BSDCONF_TYPE_DATA3     = 0x0080, /* void data type-3 (open) */
    BSDCONF_TYPE_INT64     = 0x0100, /* signed 64-bit integer */
    BSDCONF_TYPE_UINT64    = 0x0200, /* unsigned 64-bit integer */
    BSDCONF_TYPE_RESERVED  = 0x0400, /* reserved */
};
union bsdconf_value {
    void	*data;      /* Opaque pointer (DATA1..DATA3) */
    char	*str;       /* Pointer to NUL-terminated string */
    char	**strarray; /* Pointer to an array of strings */
    int32_t	num;        /* Signed 32-bit integer value */
    uint32_t	u_num;      /* Unsigned 32-bit integer value */
    int64_t	num64;      /* Signed 64-bit integer value */
    uint64_t	u_num64;    /* Unsigned 64-bit integer value */
    bool	boolean;    /* Boolean value */
};

The processing_options argument to bsdconf_parse(), bsdconf_fparse(), and bsdconf_put(3) is a mask of bit fields which indicate various processing options. The possible flags are:

BSDCONF_BREAK_ON_EQUALS

An equals sign (‘=’) is normally considered part of the directive. This flag enables terminating the directive at the equals sign. Also makes equals sign optional and transient.

BSDCONF_BREAK_ON_SEMICOLON

A semicolon (‘;’) is normally considered part of the value. This flag enables terminating the value at the semicolon. Also allows multiple statements on a single line separated by semicolon.

BSDCONF_CASE_SENSITIVE

Normally directives are matched case insensitively using fnmatch(3). This flag enables directive matching to be case sensitive.

BSDCONF_REQUIRE_EQUALS

If a directive is not followed by an equals, processing is aborted.

BSDCONF_STRICT_EQUALS

Equals must be part of the directive (no whitespace before or after) to be considered a delimiter between directive and value. Required by file formats whose readers reject whitespace around the equals sign, such as the FreeBSD boot loader's processing of loader.conf(5).

BSDCONF_OPERATOR_EQUALS

Recognize make(1) style assignment modifiers( ‘+=’, ‘?=’, ‘:=’, and ‘!=’) and split them off the tail of the directive. The parsed operator is reported through the op member of the matched option (one of BSDCONF_OP_ASSIGN, BSDCONF_OP_APPEND, BSDCONF_OP_COND, BSDCONF_OP_EXPAND, or BSDCONF_OP_SHELL).

The options struct array pointer can be NULL and every directive will run the unknown() function argument.

The directive for each bsdconf_option item in the bsdconf_parse() options argument is matched against each parsed directive using fnmatch(3) until a match is found. If a match is found, the parse() function for that bsdconf_option directive is run with the line number, directive, and value. Otherwise if no match, the unknown() function is run (with the same arguments). When BSDCONF_OPERATOR_EQUALS is set, unknown() receives a non-NULL option whose op member holds the statement's assignment operator (there is no matched options-array slot to hang it on); otherwise option may be NULL.

If either parse() or unknown() return non-zero, bsdconf_parse() aborts reading the file and returns the error value to its caller.

A value normally ends at the first unescaped newline, but a statement may span multiple lines: a backslash immediately preceding the newline continues the value on the next line, in the manner of make(1) (essential to make.conf and its siblings). The backslash-newline pairs are removed from the value delivered to the callbacks (surrounding whitespace is preserved verbatim), and reported line numbers are those of each statement's first line. bsdconf_put(3) recognizes the same continuations when locating a value; rewriting a continued value replaces all of its lines with the single new value.

bsdconf_fparse() is identical to bsdconf_parse() except that it operates on an already-open file descriptor fd, which remains open on return (the caller retains ownership). The descriptor is read into a bounded in-memory buffer and then scanned as an array of characters (the same tokenizer used by bsdconf_put(3)). The buffer is capped at BSDCONF_MAX_BYTES_DEFAULT bytes (67108864; 64 MiB); BSDCONF_MAX_BYTES overrides that ceiling (see ENVIRONMENT). Input larger than the cap fails with EFBIG. The descriptor need not be seekable; a pipe or socket is read to EOF subject to the same cap. This allows the caller to constrain the process (for example with capsicum(4)) before parsing begins.

bsdconf_spool() copies the remaining contents of fd to an unlinked temporary file (created with tmpfile(3)) and returns a seekable descriptor referencing it, which the caller must close(2) (the backing storage is reclaimed then). It is exported for callers that need a seekable snapshot of a pipe or socket.

bsdconf_get_option() traverses the options-array and returns the option that matches via strcmp(3), or NULL if none matches.

bsdconf_unquote() strips one layer of surrounding double-quotes from value in place and returns it. Parsed values retain their quotes so that data round-trips losslessly; this helper is for display and comparison purposes.

RETURN VALUES

bsdconf_parse() and bsdconf_fparse() return zero on success; otherwise -1 (or the non-zero result of a callback) is returned and the global variable errno is set to indicate the error. Input that exceeds the BSDCONF_MAX_BYTES cap fails with EFBIG.

bsdconf_spool() returns a new seekable file descriptor on success; otherwise -1 with errno set to indicate the error. bsdconf_get_option() returns a pointer to the matching option, or NULL when none matches.

ENVIRONMENT

BSDCONF_MAX_BYTES

Maximum number of bytes bsdconf_fparse() and bsdconf_put() will read from a configuration file or stream. The default is BSDCONF_MAX_BYTES_DEFAULT (67108864; 64 MiB). A larger value is an opt-in pain threshold for unusual files. Unset, empty, zero, or unparseable values restore the default.

EXAMPLES

Read two known directives from a ‘name=value’ file, routing every statement through a callback:

#include <err.h>
#include <stdio.h>
#include <bsdconf.h>
static int
show(struct bsdconf_option *option, uint32_t line,
    char *directive, char *value)
{
	printf("%u: %s is %s\n", line, directive,
	    bsdconf_unquote(value));
	return (0);
}
static struct bsdconf_option options[] = {
	{ .directive = "hostname", .parse = show },
	{ .directive = "timeout",  .parse = show },
	{ .directive = NULL }
};
int
main(void)
{
	if (bsdconf_parse(options, "/usr/local/etc/myapp.conf",
	    NULL, BSDCONF_BREAK_ON_EQUALS) != 0)
		err(1, "myapp.conf");
	return (0);
}

Callbacks own the semantics, so a directive that legitimately repeats is accumulated rather than overwritten; no format descriptor is involved. The file here is Apache-style (space-separated, no equals sign), so BSDCONF_BREAK_ON_EQUALS is simply omitted:

static char *servers[16];
static size_t nservers;
static int
addserver(struct bsdconf_option *option, uint32_t line,
    char *directive, char *value)
{
	if (nservers >= 16 ||
	    (servers[nservers] = strdup(value)) == NULL)
		return (-1); /* abort the parse */
	nservers++;
	return (0);
}
static struct bsdconf_option cumulative[] = {
	{ .directive = "server", .parse = addserver },
	{ .directive = NULL }
};
	...
	if (bsdconf_parse(cumulative, path, NULL, 0) != 0)
		err(1, "%s", path);

SEE ALSO

bsdconf_format(3), bsdconf_put(3), loader.conf(5), sysctl.conf(5), sysconf(8)

HISTORY

The bsdconf library first appeared in FreeBSD 16.0. It supersedes the figpar library which first appeared in FreeBSD 10.2 and was retired to the ports tree.

AUTHORS

Devin Teske ⟨[email protected]⟩ Faraz Vahedi ⟨[email protected]⟩

BUGS

This is the first implementation of the library, and the interface may be subject to refinement.

Write-path limitations for cumulative directives are discussed in the LIMITATIONS section of bsdconf_put(3).

SECURITY CONSIDERATIONS

bsdconf_fparse() reads the descriptor into a bounded in-memory buffer rather than trusting untrusted length fields, and accepts an already-open descriptor precisely so that a caller may sandbox itself (for example with capsicum(4)) before touching untrusted input, as sysconf(8) does for its read-only operations. The BSDCONF_MAX_BYTES cap (see ENVIRONMENT) is the bound on that buffer. Write-path hardening is documented in bsdconf_put(3).