Files
Devin Teske 74cab6e237 libbsdconf: parse from a bounded in-memory buffer
Copy the descriptor into a buffer of at most 64 MiB (raise it with
BSDCONF_MAX_BYTES) and tokenize with bsdconf_scan(), the walker
bsdconf_put() already uses.  Input above the cap fails with EFBIG.
Bump libbsdconf to 1.2.0 and sysconf(8) to 2.0.

Suggested by:	fuz
Reviewed by:	fuz
Differential Revision:	https://reviews.freebsd.org/D59751
2026-09-22 15:31:20 -07:00

296 lines
11 KiB
C

/*
* Copyright (c) 2002-2026 Devin Teske <[email protected]>
* Copyright (c) 2021-2026 Faraz Vahedi <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef _BSDCONF_H_
#define _BSDCONF_H_
#ifdef __FreeBSD__
#include <sys/cdefs.h>
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
* Supplant functionality missing on non-FreeBSD systems (musl libc, for
* example, provides no <sys/cdefs.h>). On glibc, <stdint.h> pulls in the
* definitions via <features.h>.
*/
#ifndef __BEGIN_DECLS
#ifdef __cplusplus
#define __BEGIN_DECLS extern "C" {
#define __END_DECLS }
#else
#define __BEGIN_DECLS
#define __END_DECLS
#endif
#endif
/*
* Library version info
*/
#define BSDCONF_VERSION "1.2.0 2026-09-21"
#define BSDCONF_VERSION_MAJOR 1
#define BSDCONF_VERSION_MINOR 2
#define BSDCONF_VERSION_PATCH 0
/*
* Ceiling on bytes read from a configuration file or stream. Override with
* the BSDCONF_MAX_BYTES environment variable (an unsigned decimal count).
*/
#define BSDCONF_MAX_BYTES_DEFAULT (64U * 1024U * 1024U)
/*
* Union for storing various types of data in a single common container.
*
* NB: When writing a value with bsdconf_put(), the caller supplies the value
* as a NUL-terminated string via the `str' member regardless of `type'; the
* type only governs how the value is rendered (see bsdconf_put() below).
* Signed and unsigned 32- and 64-bit members match sysctl(3) CTLTYPE widths.
* Until CTLTYPE grows a floating-point kind, the union needs no float member;
* a value that happens to contain a decimal point is still read and written
* as a string.
*/
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 */
};
/*
* Option types (based on above value union)
*/
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 */
};
/*
* Assignment operators. The default for a given file format is
* BSDCONF_OP_ASSIGN; the remainder require BSDCONF_OPERATOR_EQUALS (make(1)
* style configuration files such as make.conf(5)).
*/
enum bsdconf_op {
BSDCONF_OP_DEFAULT = 0, /* format's natural operator */
BSDCONF_OP_ASSIGN, /* `=' assign */
BSDCONF_OP_APPEND, /* `+=' append (make) */
BSDCONF_OP_COND, /* `?=' assign if undefined (make) */
BSDCONF_OP_EXPAND, /* `:=' assign with expansion (make) */
BSDCONF_OP_SHELL, /* `!=' assign shell output (make) */
};
/*
* Known configuration file formats (see bsdconf_format(3)). Formats numbered
* BSDCONF_FORMAT_USER and above are assigned by bsdconf_format_register().
*/
enum bsdconf_format {
BSDCONF_FORMAT_GENERIC = 0, /* quote values only when required */
BSDCONF_FORMAT_LOADER, /* loader.conf(5); always quoted */
BSDCONF_FORMAT_SYSCTL, /* sysctl.conf(5); quote when needed */
BSDCONF_FORMAT_MAKE, /* make.conf(5); `+=' et al. */
BSDCONF_FORMAT_SRC, /* src build triad; make(1) syntax */
BSDCONF_FORMAT_USER = 32, /* first registered format */
};
/*
* A single entry in a format's ordered list of configuration sources. Most
* formats are backed by more than one file (read in a deterministic order
* with directives in later files overriding earlier ones) and some pull
* additional files from drop-in directories.
*/
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 */
};
/*
* The format descriptor: a read-only table of properties characterizing a
* configuration file format (no relation to file descriptors). The built-in
* formats (above) are described by format descriptors of this same shape; a
* new format is "bolted on" by registering a format descriptor of its own --
* typically derived from the built-in it most resembles (see
* bsdconf_format_derive() below) with only the differing members adjusted.
*
* A format whose backing files are themselves configuration data -- the
* boot loader reads 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 finds along the way -- describes
* that machinery with the `defaults' member quintet below, and
* bsdconf_format_files() performs the same discovery the consumer does.
* The static `sources' list remains as the fallback for systems whose
* defaults file is missing.
*/
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; NULL-path
terminated (or NULL if `path'
is the only source) */
const char *defaults; /* defaults file (or NULL) */
const char *defaults_env; /* environment variable overriding
the defaults file (or NULL) */
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 */
};
/*
* Options to bsdconf_parse() and bsdconf_put() for processing_options bitmask
*/
enum bsdconf_processing {
BSDCONF_BREAK_ON_EQUALS = 0x0001, /* stop at `=' */
BSDCONF_BREAK_ON_SEMICOLON = 0x0002, /* `;' starts a new line */
BSDCONF_CASE_SENSITIVE = 0x0004, /* applies to directives */
BSDCONF_REQUIRE_EQUALS = 0x0008, /* assignment directives */
BSDCONF_STRICT_EQUALS = 0x0010, /* `=' part of directive */
BSDCONF_OPERATOR_EQUALS = 0x0020, /* `+=' `?=' `:=' `!=' */
};
/*
* Options to bsdconf_put() for put_options bitmask
*/
enum bsdconf_put_flags {
BSDCONF_PUT_NO_DUPLICATES = 0x0001, /* error if found twice */
BSDCONF_PUT_ALLOW_EMPTY = 0x0002, /* allow empty SET_VALUE */
BSDCONF_PUT_BACKUP = 0x0004, /* back up file (`.bak') */
BSDCONF_PUT_UNQUOTED = 0x0008, /* value.str as file text */
BSDCONF_PUT_QUOTE_ALWAYS = 0x0010, /* always quote on output */
};
/*
* Per-directive actions for bsdconf_put()
*/
enum bsdconf_action {
BSDCONF_ACTION_SET_VALUE = 0x0000, /* set/replace (default) */
BSDCONF_ACTION_CHECK = 0x0001, /* compare against current */
BSDCONF_ACTION_REMOVE = 0x0002, /* remove from config */
};
/*
* Per-directive result codes set by bsdconf_put()
*/
enum bsdconf_result {
BSDCONF_DIRECTIVE_FOUND = 0x0001, /* vs not found (see added) */
BSDCONF_VALUE_CHANGED = 0x0002, /* vs no change required */
BSDCONF_DIRECTIVE_ADDED = 0x0004, /* vs already existed */
BSDCONF_DIRECTIVE_REMOVED = 0x0008, /* vs not found */
};
/*
* Anatomy of a config file option; used for both reading and writing.
*
* When parsing with bsdconf_parse(), `directive' is an fnmatch(3) pattern and
* `parse' is invoked for each matching statement (with `op' set to the
* statement's assignment operator beforehand).
*
* When writing with bsdconf_put(), `directive' is an exact token (a pattern
* is not a writable target), `action' selects the operation, and `result'
* and `line' report what was done. A non-zero `match_line' restricts the
* put to the statement on that physical line (0 matches any, as before);
* make(1) `+=' SET_VALUE still appends a new line when `match_line' is 0,
* but rewrites the matched statement when `match_line' selects it.
*/
struct bsdconf_option {
enum bsdconf_type type; /* Option value type */
const char *directive; /* config file keyword */
union bsdconf_value value; /* NB: set by parse action;
* value to write for put */
enum bsdconf_op op; /* assignment operator */
uint8_t action; /* bsdconf_put() action */
uint16_t result; /* NB: set by bsdconf_put() */
uint32_t line; /* NB: set by bsdconf_put() */
uint32_t match_line; /* put: 0=any, else only line */
/*
* Function pointer; action to be taken when the directive is found
* by bsdconf_parse(). Non-zero return aborts the parse (and is
* propagated to the bsdconf_parse() caller).
*/
int (*parse)(struct bsdconf_option *option, uint32_t line,
char *directive, char *value);
};
/*
* Function prototypes
*
* All functions returning int return zero on success (except
* bsdconf_spool(), which returns a new file descriptor); otherwise -1 (or
* the non-zero result of a parse call-back) and errno should be consulted.
*/
__BEGIN_DECLS
int bsdconf_parse(struct bsdconf_option _options[],
const char *_path,
int (*_unknown)(struct bsdconf_option *_option,
uint32_t _line, char *_directive, char *_value),
uint16_t _processing_options);
int bsdconf_fparse(struct bsdconf_option _options[],
int _fd,
int (*_unknown)(struct bsdconf_option *_option,
uint32_t _line, char *_directive, char *_value),
uint16_t _processing_options);
int bsdconf_spool(int _fd);
struct bsdconf_option *bsdconf_get_option(struct bsdconf_option _options[],
const char *_directive);
char *bsdconf_unquote(char *_value);
int bsdconf_set_option(struct bsdconf_option _options[],
const char *_directive,
union bsdconf_value *_value);
int bsdconf_put(struct bsdconf_option _options[],
const char *_path, uint16_t _processing_options,
uint16_t _put_options);
/*
* Format abstraction layer (see bsdconf_format(3))
*/
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);
__END_DECLS
#endif /* !_BSDCONF_H_ */