xtc_cfg(3)

---

xtc_cfg(3)

typed runtime configuration registry

XTC_CFG(3) Library Functions Manual XTC_CFG(3)

xtc_cfg_register, xtc_cfg_unregister, xtc_cfg_get_bool, xtc_cfg_get_int, xtc_cfg_get_int64, xtc_cfg_get_double, xtc_cfg_get_string, xtc_cfg_get_string_copy, xtc_cfg_get_enum, xtc_cfg_set_bool, xtc_cfg_set_int, xtc_cfg_set_int64, xtc_cfg_set_double, xtc_cfg_set_string, xtc_cfg_set_enum, xtc_cfg_count, xtc_cfg_kind, xtc_cfg_load_file, xtc_cfg_reload, xtc_cfg_ref, xtc_cfg_ref_get_bool, xtc_cfg_ref_get_int, xtc_cfg_ref_get_int64, xtc_cfg_ref_get_double, xtc_cfg_ref_get_string, xtc_cfg_ref_get_enum, xtc_cfg_session_create, xtc_cfg_session_destroy, xtc_cfg_session_bind, xtc_cfg_session_current, xtc_cfg_session_push, xtc_cfg_session_commit, xtc_cfg_session_abort, xtc_cfg_session_source, xtc_cfg_session_reset, xtc_cfg_ssn_set_bool, xtc_cfg_ssn_set_int, xtc_cfg_ssn_set_int64, xtc_cfg_ssn_set_double, xtc_cfg_ssn_set_string, xtc_cfg_ssn_set_enum — typed runtime configuration registry

#include <xtc.h>
#include <xtc_cfg.h>

int
xtc_cfg_register(const xtc_cfg_spec_t *spec);

int
xtc_cfg_unregister(const char *name);

int
xtc_cfg_get_bool(const char *name, int *out);

int
xtc_cfg_get_int(const char *name, int *out);

int
xtc_cfg_get_int64(const char *name, int64_t *out);

int
xtc_cfg_get_double(const char *name, double *out);

int
xtc_cfg_get_string(const char *name, const char **out);

int
xtc_cfg_get_string_copy(const char *name, char **out);

int
xtc_cfg_get_enum(const char *name, int *out);

int
xtc_cfg_set_bool(const char *name, int v);

int
xtc_cfg_set_int(const char *name, int v);

int
xtc_cfg_set_int64(const char *name, int64_t v);

int
xtc_cfg_set_double(const char *name, double v);

int
xtc_cfg_set_string(const char *name, const char *v);

int
xtc_cfg_set_enum(const char *name, int v);

int
xtc_cfg_count(void);

int
xtc_cfg_kind(const char *name, xtc_cfg_kind_t *out);

int
xtc_cfg_load_file(const char *path);

int
xtc_cfg_reload(void);

int
xtc_cfg_ref(const char *name, xtc_cfg_ref_t *out);

int
xtc_cfg_ref_get_bool(xtc_cfg_ref_t ref, int *out);

int
xtc_cfg_ref_get_int(xtc_cfg_ref_t ref, int *out);

int
xtc_cfg_ref_get_int64(xtc_cfg_ref_t ref, int64_t *out);

int
xtc_cfg_ref_get_double(xtc_cfg_ref_t ref, double *out);

int
xtc_cfg_ref_get_string(xtc_cfg_ref_t ref, const char **out);

int
xtc_cfg_ref_get_enum(xtc_cfg_ref_t ref, int *out);

int
xtc_cfg_session_create(xtc_cfg_session_t **out);

void
xtc_cfg_session_destroy(xtc_cfg_session_t *s);

xtc_cfg_session_t *
xtc_cfg_session_bind(xtc_cfg_session_t *s);

xtc_cfg_session_t *
xtc_cfg_session_current(void);

int
xtc_cfg_session_push(xtc_cfg_session_t *s);

int
xtc_cfg_session_commit(xtc_cfg_session_t *s);

int
xtc_cfg_session_abort(xtc_cfg_session_t *s);

int
xtc_cfg_session_source(xtc_cfg_session_t *s, const char *name, xtc_cfg_source_t *out);

int
xtc_cfg_session_reset(xtc_cfg_session_t *s, const char *name);

int
xtc_cfg_ssn_set_bool(xtc_cfg_session_t *s, const char *name, int v, xtc_cfg_source_t src);

int
xtc_cfg_ssn_set_int(xtc_cfg_session_t *s, const char *name, int v, xtc_cfg_source_t src);

int
xtc_cfg_ssn_set_int64(xtc_cfg_session_t *s, const char *name, int64_t v, xtc_cfg_source_t src);

int
xtc_cfg_ssn_set_double(xtc_cfg_session_t *s, const char *name, double v, xtc_cfg_source_t src);

int
xtc_cfg_ssn_set_string(xtc_cfg_session_t *s, const char *name, const char *v, xtc_cfg_source_t src);

int
xtc_cfg_ssn_set_enum(xtc_cfg_session_t *s, const char *name, int v, xtc_cfg_source_t src);

xtc_cfg is a typed runtime configuration registry, modeled on PostgreSQL's GUC system. Modules register named knobs at startup; operators set them by name; values are validated against type and bounds.

Supported types: bool, int, int64, double, string, enum (xtc_cfg_kind_t).

There is ONE registration entry point, (), taking a xtc_cfg_spec_t:

  • name, the canonical name (dot-separated, e.g. ‘rexis.max_clients’).
  • short_desc, an ops-friendly description.
  • kind, one of the XTC_CFG_* kinds.
  • dflt, a union holding the default for that kind.
  • min_int / max_int and min_double / max_double, inclusive bounds for the numeric kinds (0 / 0 means unbounded).
  • enum_labels / n_enum_labels for XTC_CFG_ENUM: the allowed labels, the int value being the index into them.
  • validator, on_change, and cb_user, optional callbacks.

. The spec carries NO backing-storage pointer: a registered variable lives inside the registry and is read back through the accessors below, not through a caller-supplied variable that the registry writes. A value set with () is observed with () or, on a hot path, through an xtc_cfg_ref_t. () removes a knob by name.

There is no string-parsing single setter: the typed setters (), xtc_cfg_set_int(), (), (), xtc_cfg_set_string(), and () assign a value of the matching C type, validating it against the registered bounds and validator and firing the on-change callback. String parsing happens only in xtc_cfg_load_file(), which reads ‘name = value’ lines; xtc_cfg_reload() re-reads the last loaded file.

Read a value with (), (), (), (), xtc_cfg_get_string(), or () for the corresponding registered kind.

. () and () lend the registry's (or the bound session's) own buffer, not a copy. It is valid only until the next set of that key: (), a session set, commit, reset, abort or destroy for a session value, () or (), and xtc_cfg_unregister() all free it, and another thread may do so at any moment. Read the borrowed pointer only where no concurrent set is possible. Otherwise use (), which duplicates the value under the registry lock and stores a private copy in *out that the caller releases with (); a registered string whose value is NULL copies as NULL. It returns the same codes as xtc_cfg_get_string(), plus XTC_E_NOMEM if the copy cannot be allocated.

() returns the number of registered knobs; () reports the xtc_cfg_kind_t of a named knob.

For a knob read on a hot path, the name-keyed getters are too slow: each does a registry lookup. () resolves a name to an opaque, pointer-stable xtc_cfg_ref_t handle once; thereafter (), (), (), (), xtc_cfg_ref_get_string(), and () read the current value with no name lookup, still observing live () updates. The handle stays valid until the variable is unregistered, so it can be cached for the process lifetime like a compiled-in pointer.

A single global value per name is what a database server cannot use for most settings. A session layers per-variable OVERRIDES over the global registry: a read resolves the session override first, then the global value, and a session set never touches the global value. () and () manage a session; () makes one the current session for the calling fiber (returning the previous binding) so the unscoped getters resolve through it, and () reports the bound session. The binding is per-FIBER, not per-thread: it is keyed on the calling fiber's identity, so two fibers on one loop can each bind their own session, and a work-stolen fiber that resumes on a different thread keeps its binding. Off a fiber (a plain OS thread) it is per-thread. Within a session the overrides form a transactional stack of levels: () opens a level, () folds it into its parent (the values survive), and () discards it -- backing SET LOCAL, SET inside a rolled-back transaction, function-local SET, and subtransactions. (), (), (), (), (), and () set a per-session override at the current level, each carrying a source rank (XTC_CFG_SRC_DEFAULT through XTC_CFG_SRC_OVERRIDE); a lower-ranked source cannot replace a higher one, and () reports the winner. () drops the current level's override for a name (RESET), so the parent level or the global value shows through again.

Unregistering a variable that a live session still overrides is safe in EITHER order: the entry's storage is refcounted and outlives the unregister until the last override naming it is torn down, while being immediately invisible to every lookup.

Every function returning int returns XTC_OK on success and a negative XTC_E_* code on failure: XTC_E_INVAL for a NULL argument, a kind mismatch, a validator rejection, a duplicate registration, or an unregistered name passed to a name-keyed getter, setter, xtc_cfg_kind() or xtc_cfg_unregister(); XTC_E_RANGE for a value outside the registered bounds (or outside 0..1 for a bool, or not a label index for an enum), from the global and the per-session setters alike; XTC_E_NOTFOUND for an unregistered name passed to xtc_cfg_ref(), xtc_cfg_session_source() or xtc_cfg_session_reset(); XTC_E_NOMEM on allocation failure. There is no distinct code for a duplicate registration; an earlier version of this page named one that does not exist in <xtc.h>. xtc_cfg_session_source() reports XTC_CFG_SRC_DEFAULT for a name the session does not override. xtc_cfg_count() returns the count. xtc_cfg_session_bind() and xtc_cfg_session_current() return a session pointer or NULL. xtc_cfg_session_destroy() returns nothing and is NULL-safe.

Releases before 1.50 returned XTC_E_INVAL from the per-session setters (xtc_cfg_ssn_set_*()) for an out-of-bounds value, while the global setters already returned XTC_E_RANGE; both now return XTC_E_RANGE.

A value from a configuration file is parsed WIDE and then range-checked against the target C type, so ‘4294967297’ for an XTC_CFG_INT knob is rejected as invalid rather than silently truncated to 1.

static const xtc_cfg_spec_t spec = {
	.name       = "rexis.max_clients",
	.short_desc = "Maximum concurrent client connections",
	.kind       = XTC_CFG_INT,
	.dflt       = { .d_int = 1000 },
	.min_int    = 1,
	.max_int    = 100000,
};
int v;

if (xtc_cfg_register(&spec) != XTC_OK)
	return -1;

/* Operator runtime change, then read it back. */
(void)xtc_cfg_set_int("rexis.max_clients", 5000);
(void)xtc_cfg_get_int("rexis.max_clients", &v);     /* v == 5000 */

/* Hot path: resolve once, read with no name lookup. */
xtc_cfg_ref_t ref;
if (xtc_cfg_ref("rexis.max_clients", &ref) == XTC_OK)
	(void)xtc_cfg_ref_get_int(ref, &v);

xtc_log(3), xtc_stats(3), xtc(7)

Appeared in xtc 0.1.

September 21, 2026 Debian

View the mdoc source