Support both dynamic sections and dynamic keys.
Cleanup of both internal and external APIs.
override USUAL_DIR = ..
override DEFS = -DUSUAL_TEST_CONFIG
-OBJS = test_string.o test_crypto.o test_aatree.o test_heap.o \
- test_common.o test_list.o tinytest.o test_cbtree.o \
- test_utf8.o test_strpool.o test_pgutil.o test_regex.o \
- test_cxalloc.o test_bits.o test_base.o test_netdb.o
+SRCS = test_string.c test_crypto.c test_aatree.c test_heap.c \
+ test_common.c test_list.c tinytest.c test_cbtree.c \
+ test_utf8.c test_strpool.c test_pgutil.c test_regex.c \
+ test_cxalloc.c test_bits.c test_base.c test_netdb.c \
+ test_cfparser.c
+OBJS = $(SRCS:.c=.o)
test-all: regtest.compat
config.mak: ../config.mak
cp ../config.mak .
-regtest.compat: ../usual/config.h force_compat.sed
+regtest.compat: ../usual/config.h force_compat.sed $(SRCS)
sed -f force_compat.sed $< > test_config.h
make regtest
mv regtest regtest.compat
mv libusual.a libusual_compat.a
rm test_config.h
-regtest.system: ../usual/config.h
+regtest.system: ../usual/config.h $(SRCS)
cp $< test_config.h
make regtest
mv regtest regtest.system
--- /dev/null
+#include <usual/cfparser.h>
+#include <usual/time.h>
+#include <usual/string.h>
+#include <usual/logging.h>
+
+#include "test_common.h"
+
+struct Config1 {
+ const char *str1;
+ const char *def1;
+ int int1;
+ int bool1;
+};
+
+struct Config2 {
+ const char *str2;
+ const char *def2;
+ double time_double;
+ usec_t time_usec;
+};
+
+static struct Config1 cf1;
+static struct Config2 cf2;
+
+static void cleanup(void)
+{
+ free(cf1.str1);
+ free(cf1.def1);
+ free(cf2.str2);
+ free(cf2.def2);
+ memset(&cf1, 0, sizeof(cf1));
+ memset(&cf2, 0, sizeof(cf2));
+}
+
+static const struct CfKey keys1 [] = {
+ CF_ABS("str1", CF_STR, cf1.str1, 0, NULL),
+ CF_ABS("def1", CF_STR, cf1.def1, 0, NULL),
+ CF_ABS("int", CF_INT, cf1.int1, 0, NULL),
+ CF_ABS("bool", CF_BOOL, cf1.bool1, 0, NULL),
+ { NULL },
+};
+
+static const struct CfKey keys2 [] = {
+ CF_ABS("str2", CF_STR, cf2.str2, 0, NULL),
+ CF_ABS("def2", CF_STR, cf2.def2, 0, "somedefault"),
+ CF_ABS("time1", CF_TIME_USEC, cf2.time_usec, 0, NULL),
+ CF_ABS("time2", CF_TIME_DOUBLE, cf2.time_double, 0, NULL),
+ { NULL },
+};
+
+static const struct CfSect sects [] = {
+ { "one", keys1 },
+ { "two", keys2 },
+ { NULL },
+};
+
+static struct CfContext cfdesc1 = { sects, NULL };
+
+static void test_abs(void *ptr)
+{
+ char buf[128];
+
+ int_check(1, cf_load_file(&cfdesc1, "test_cfparser.ini"));
+
+ str_check(cf1.str1, "val1");
+ tt_assert(cf1.def1 == NULL);
+ str_check(cf2.str2, "val2");
+ str_check(cf2.def2, "somedefault");
+
+ tt_assert(cf2.time_usec == (3 * USEC / 2));
+ tt_assert(cf2.time_double == 2.5);
+
+ str_check("val1", cf_get(&cfdesc1, "one", "str1", buf, sizeof(buf)));
+ int_check(1, cf_set(&cfdesc1, "one", "str1", "val2"));
+ str_check("val2", cf_get(&cfdesc1, "one", "str1", buf, sizeof(buf)));
+end:
+ cleanup();
+}
+
+/*
+ * relative addressing.
+ */
+
+#define CF_REL_BASE struct Config1
+static const struct CfKey rkeys1 [] = {
+ CF_REL("str1", CF_STR, str1, 0, NULL),
+ CF_REL("def1", CF_STR, def1, 0, NULL),
+ CF_REL("int", CF_INT, int1, 0, NULL),
+ CF_REL("bool", CF_BOOL, bool1, 0, NULL),
+ { NULL },
+};
+#undef CF_REL_BASE
+
+#define CF_REL_BASE struct Config2
+static const struct CfKey rkeys2 [] = {
+ CF_REL("str2", CF_STR, str2, 0, NULL),
+ CF_REL("def2", CF_STR, def2, 0, "somedefault"),
+ CF_REL("time1", CF_TIME_USEC, time_usec, 0, NULL),
+ CF_REL("time2", CF_TIME_DOUBLE, time_double, 0, NULL),
+ { NULL },
+};
+#undef CF_REL_BASE
+
+static void *get_two(void *top_arg, const char *sect_name)
+{
+ return &cf2;
+}
+
+static const struct CfSect rsects [] = {
+ { "one", rkeys1 },
+ { "two", rkeys2, .base_lookup = get_two, },
+ { NULL },
+};
+
+static struct CfContext cfdesc2 = { rsects, &cf1 };
+
+static void test_rel(void *ptr)
+{
+ char buf[128];
+
+ cleanup();
+
+ int_check(1, cf_load_file(&cfdesc2, "test_cfparser.ini"));
+
+ str_check(cf1.str1, "val1");
+ tt_assert(cf1.def1 == NULL);
+ str_check(cf2.str2, "val2");
+ str_check(cf2.def2, "somedefault");
+
+ tt_assert(cf2.time_usec == (3 * USEC / 2));
+ tt_assert(cf2.time_double == 2.5);
+
+ str_check("val1", cf_get(&cfdesc2, "one", "str1", buf, sizeof(buf)));
+ int_check(1, cf_set(&cfdesc2, "one", "str1", "val2"));
+ str_check("val2", cf_get(&cfdesc2, "one", "str1", buf, sizeof(buf)));
+end:
+ cleanup();
+}
+/*
+ * Describe
+ */
+
+struct testcase_t cfparser_tests[] = {
+ { "abs", test_abs },
+ { "rel", test_rel },
+ END_OF_TESTCASES
+};
+
--- /dev/null
+[one]
+
+str1 = val1
+
+int = 5
+
+bool = 1
+
+[two]
+
+str2 = val2
+
+time1 = 1.5
+time2 = 2.5
+
{ "pgutil/", pgutil_tests },
{ "regex/", regex_tests },
{ "netdb/", netdb_tests },
+ { "cfparser/", cfparser_tests },
END_OF_GROUPS
};
extern struct testcase_t bits_tests[];
extern struct testcase_t base_tests[];
extern struct testcase_t netdb_tests[];
+extern struct testcase_t cfparser_tests[];
*p = 0;
log_debug("parse_ini_file: [%s]", key);
- ok = user_handler(arg, CF_SECT, key, NULL);
+ ok = user_handler(arg, true, key, NULL);
*p++ = o1;
if (!ok)
goto failed;
log_debug("parse_ini_file: '%s' = '%s'", key, val);
- ok = user_handler(arg, CF_KEY, key, val);
+ ok = user_handler(arg, false, key, val);
+
+ log_debug("parse_ini_file: '%s' = '%s' ok:%d", key, val, ok);
/* restore data, to keep count_lines() working */
key[klen] = o1;
return false;
}
-struct LoaderCtx {
- const struct CfSect *sect_list;
- const struct CfSect *cur_sect;
- void *target;
- void *top_arg;
-};
+/*
+ * Config framework.
+ */
-static void *get_dest(struct LoaderCtx *ctx, const struct CfKey *k)
+static void *get_dest(void *base, const struct CfKey *k)
{
char *dst;
if (k->flags & CF_VAL_REL) {
- if (!ctx->target)
+ /* relative address requires base */
+ if (!base)
return NULL;
- dst = (char *)ctx->target + k->key_ofs;
+ dst = (char *)base + k->key_ofs;
} else
dst = (char *)k->key_ofs;
return dst;
}
+static const struct CfSect *find_sect(const struct CfContext *cf, const char *name)
+{
+ const struct CfSect *s;
+ for (s = cf->sect_list; s->sect_name; s++) {
+ if (strcmp(s->sect_name, name) == 0)
+ return s;
+ if (strcmp(s->sect_name, "*") == 0)
+ return s;
+ }
+ return NULL;
+}
+
+static const struct CfKey *find_key(const struct CfSect *s, const char *key)
+{
+ const struct CfKey *k;
+ for (k = s->key_list; k->key_name; k++) {
+ if (strcmp(k->key_name, key) == 0)
+ return k;
+ }
+ return k;
+}
+
+const char *cf_get(const struct CfContext *cf, const char *sect, const char *key,
+ char *buf, int buflen)
+{
+ const struct CfSect *s;
+ const struct CfKey *k;
+ void *base, *p;
+ struct CfValue cv;
+
+ /* find section */
+ s = find_sect(cf, sect);
+ if (!s)
+ return NULL;
+
+ /* find section base */
+ base = cf->base;
+ if (s->base_lookup)
+ base = s->base_lookup(base, sect);
+
+ /* handle dynamic keys */
+ if (s->set_key) {
+ if (!s->get_key)
+ return NULL;
+ return s->get_key(base, key, buf, buflen);
+ }
+
+ /* get fixed key */
+ k = find_key(s, key);
+ if (!k || !k->op.getter)
+ return NULL;
+ p = get_dest(base, k);
+ if (!p)
+ return NULL;
+ cv.key_name = k->key_name;
+ cv.extra = k->op.op_extra;
+ cv.value_p = p;
+ cv.buf = buf;
+ cv.buflen = buflen;
+ return k->op.getter(&cv);
+}
+
+bool cf_set(const struct CfContext *cf, const char *sect, const char *key, const char *val)
+{
+ const struct CfSect *s;
+ const struct CfKey *k;
+ void *base, *p;
+ struct CfValue cv;
+
+ /* find section */
+ s = find_sect(cf, sect);
+ if (!s)
+ return false;
+
+ /* find section base */
+ base = cf->base;
+ if (s->base_lookup)
+ base = s->base_lookup(base, sect);
+
+ /* handle dynamic keys */
+ if (s->set_key)
+ return s->set_key(base, key, val);
+
+ /* set fixed key */
+ k = find_key(s, key);
+ if (!k || !k->op.setter)
+ return false;
+ if (k->flags & CF_READONLY)
+ return false;
+ if ((k->flags & CF_NO_RELOAD) && cf->loaded)
+ return false;
+ p = get_dest(base, k);
+ if (!p)
+ return false;
+ cv.key_name = k->key_name;
+ cv.extra = k->op.op_extra;
+ cv.value_p = p;
+ cv.buf = NULL;
+ cv.buflen = 0;
+ return k->op.setter(&cv, val);
+}
+
+/*
+ * File loader
+ */
+
+struct LoaderCtx {
+ const struct CfContext *cf;
+ const char *cur_sect;
+ void *top_base;
+};
+
static bool fill_defaults(struct LoaderCtx *ctx)
{
const struct CfKey *k;
- void *dst;
- for (k = ctx->cur_sect->key_list; k->key_name; k++) {
- if (!k->def_value)
+ const struct CfSect *s;
+
+ s = find_sect(ctx->cf, ctx->cur_sect);
+ if (!s)
+ goto fail;
+
+ if (s->set_key)
+ return true;
+
+ for (k = s->key_list; k->key_name; k++) {
+ if (!k->def_value || k->flags & CF_READONLY)
continue;
- dst = get_dest(ctx, k);
- if (!dst)
- return false;
- if (!k->set_fn(dst, k->def_value))
- return false;
+ if (k->flags & CF_NO_RELOAD && ctx->cf->loaded)
+ continue;
+ if (!cf_set(ctx->cf, ctx->cur_sect, k->key_name, k->def_value))
+ goto fail;
}
return true;
+fail:
+ log_error("fill_defaults fail");
+ return false;
}
-static bool load_handler(void *arg, enum CfKeyType ktype, const char *key, const char *val)
+static bool load_handler(void *arg, bool is_sect, const char *key, const char *val)
{
struct LoaderCtx *ctx = arg;
- const struct CfSect *s;
- const struct CfKey *k;
- void *dst;
-
- if (ktype == CF_SECT) {
- for (s = ctx->sect_list; s->sect_name; s++) {
- if (strcmp(s->sect_name, key) != 0
- && strcmp(s->sect_name, "*") != 0)
- continue;
- ctx->cur_sect = s;
- if (s->create_target_fn)
- ctx->target = s->create_target_fn(ctx->top_arg, key);
- else
- ctx->target = ctx->top_arg;
- return fill_defaults(ctx);
- }
- log_error("load_init_file: unknown section: %s", key);
- return false;
+
+ if (is_sect) {
+ if (ctx->cur_sect)
+ free(ctx->cur_sect);
+ ctx->cur_sect = strdup(key);
+ if (!ctx->cur_sect)
+ return false;
+ return fill_defaults(ctx);
} else if (!ctx->cur_sect) {
log_error("load_init_file: value without section: %s", key);
return false;
} else {
- for (k = ctx->cur_sect->key_list; k->key_name; k++) {
- if (strcmp(k->key_name, key) != 0)
- continue;
- dst = get_dest(ctx, k);
- if (!dst)
- return false;
- return k->set_fn(dst, val);
- }
- log_error("load_init_file: unknown key: %s", key);
- return false;
+ return cf_set(ctx->cf, ctx->cur_sect, key, val);
}
-
- return true;
}
-bool load_ini_file(const char *fn, const struct CfSect *sect_list, void *top_arg)
+bool cf_load_file(const struct CfContext *cf, const char *fn)
{
struct LoaderCtx ctx = {
- .top_arg = top_arg,
- .sect_list = sect_list,
+ .cf = cf,
.cur_sect = NULL,
- .target = NULL,
};
- return parse_ini_file(fn, load_handler, &ctx);
+ bool ok = parse_ini_file(fn, load_handler, &ctx);
+ if (ctx.cur_sect)
+ free(ctx.cur_sect);
+ return ok;
}
/*
* Various value parsers.
*/
-bool cf_set_int(void *dst, const char *value)
+bool cf_set_int(struct CfValue *cv, const char *value)
{
- int *ptr = dst;
+ int *ptr = cv->value_p;
*ptr = atoi(value);
return true;
}
-bool cf_set_str(void *dst, const char *value)
+bool cf_set_str(struct CfValue *cv, const char *value)
{
- char **dst_p = dst;
+ char **dst_p = cv->value_p;
char *tmp = strdup(value);
- if (!tmp)
+ if (!tmp) {
+ log_error("cf_set_str: no mem");
return false;
+ }
if (*dst_p)
free(*dst_p);
*dst_p = tmp;
return true;
}
-bool cf_set_filename(void *dst, const char *value)
+bool cf_set_filename(struct CfValue *cv, const char *value)
{
- char **dst_p = dst;
+ char **dst_p = cv->value_p;
char *tmp, *home, *p;
int v_len, usr_len, home_len;
struct passwd *pw;
/* do we need to do tilde expansion */
if (value[0] != '~')
- return cf_set_str(dst, value);
+ return cf_set_str(cv, value);
/* find username end */
v_len = strlen(value);
return false;
}
-bool cf_set_time_usec(void *dst, const char *value)
+bool cf_set_time_usec(struct CfValue *cv, const char *value)
{
- usec_t *ptr = dst;
+ usec_t *ptr = cv->value_p;
*ptr = USEC * atof(value);
return true;
}
-bool cf_set_time_double(void *dst, const char *value)
+bool cf_set_time_double(struct CfValue *cv, const char *value)
{
- double *ptr = dst;
+ double *ptr = cv->value_p;
*ptr = atof(value);
return true;
}
+/*
+ * Various value formatters.
+ */
+
+const char *cf_get_str(struct CfValue *cv)
+{
+ char **p = cv->value_p;
+ return *p;
+}
+
+const char *cf_get_int(struct CfValue *cv)
+{
+ int *p = cv->value_p;
+ snprintf(cv->buf, cv->buflen, "%d", *p);
+ return cv->buf;
+}
+
+const char *cf_get_time_double(struct CfValue *cv)
+{
+ double *p = cv->value_p;
+ snprintf(cv->buf, cv->buflen, "%g", *p);
+ return cv->buf;
+}
+
+const char *cf_get_time_usec(struct CfValue *cv)
+{
+ struct CfValue tmp = *cv;
+ usec_t *p = cv->value_p;
+ double d = (double)(*p) / USEC;
+ tmp.value_p = &d;
+ return cf_get_time_double(&tmp);
+}
+
+/*
+ * str->int mapping
+ */
+
+const char *cf_get_lookup(struct CfValue *cv)
+{
+ int *p = cv->value_p;
+ const struct CfLookup *lk = cv->extra;
+ for (; lk->name; lk++) {
+ if (lk->value == *p)
+ return lk->name;
+ }
+ return "INVALID";
+}
+
+bool cf_set_lookup(struct CfValue *cv, const char *value)
+{
+ int *p = cv->value_p;
+ const struct CfLookup *lk = cv->extra;
+ for (; lk->name; lk++) {
+ if (strcasecmp(lk->name, value) == 0) {
+ *p = lk->value;
+ return true;
+ }
+ }
+ return false;
+}
* @{
*/
-/**
- * Parsed line type.
- */
-enum CfKeyType {
- CF_SECT, /**< Section */
- CF_KEY /**< Parameter */
-};
-
/** Callback signarure for @ref parse_ini_file() */
-typedef bool (*cf_handler_f)(void *arg, enum CfKeyType, const char *key, const char *val);
+typedef bool (*cf_handler_f)(void *arg, bool is_sect, const char *key, const char *val);
/**
* Simple parser, launches callback for each line
* @{
*/
+/** @name Per-key flags
+ * @{
+ */
+
+/** The pointer is absolute */
+#define CF_VAL_ABS 0
+
+/** The pointer is relative to base */
+#define CF_VAL_REL (1<<1)
+
+/** Value must not be changed on reload */
+#define CF_NO_RELOAD (1<<2)
+
+/** Value can only be read */
+#define CF_READONLY (1<<3)
+
+/** @} */
+
/**
- * Callback for section initialization
+ * Helper structure for passing key info to CfOps
*/
-typedef void *(*cf_create_target_f)(void *top_arg, const char *sect_name);
+struct CfValue {
+ void *value_p;
+ const void *extra;
+ const char *key_name;
+ char *buf;
+ int buflen;
+};
/**
- * Callback for setting a variable
+ * Callbacks for setting and getting a variable value.
+ *
+ * Getter requires temp buf, returns string pointer, which
+ * may or may not point to temp buf. Getter is optional.
*/
-typedef bool (*cf_setter_f)(void *dst_p, const char *value);
+struct CfOps {
+ bool (*setter)(struct CfValue *cv, const char *value);
+ const char *(*getter)(struct CfValue *cv);
+ const void *op_extra;
+};
/**
* Parameter description
struct CfKey {
/** Parameter name */
const char *key_name;
- /** Setter function, called wit absolute pointer */
- cf_setter_f set_fn;
+ /** Type-specific functions, called with absolute pointer */
+ struct CfOps op;
/** Flags: CF_VAL_ABS, CF_VAL_REL */
int flags;
/** Absolute or relative offset */
const char *def_value;
};
-/** The pointer is relative to base */
-#define CF_VAL_REL 1
-/** The pointer is absolute */
-#define CF_VAL_ABS 2
-
/**
* Section description
*/
struct CfSect {
/** Section name */
const char *sect_name;
- /** Section creation function (optional) */
- cf_create_target_f create_target_fn;
+
/** Key list */
const struct CfKey *key_list;
+
+ /** Get base pointer to dynamic sections (optional) */
+ void *(*base_lookup)(void *top_base, const char *sect_name);
+
+ /** Set dynamic keys (optional) */
+ bool (*set_key)(void *base, const char *key, const char *val);
+
+ /** Get dynamic keys (optional) */
+ const char *(*get_key)(void *base, const char *key, char *buf, int buflen);
+};
+
+/**
+ * Top-level config information
+ */
+struct CfContext {
+ /** Section list */
+ const struct CfSect *sect_list;
+ /** Top-level base pointer, needed for relative addressing */
+ void *base;
+ /** If set, then CF_NO_RELOAD keys cannot be changed anymore */
+ bool loaded;
};
+/**
+ * @name Type-specific helpers
+ * @{
+ */
+
/** Setter for string */
-bool cf_set_str(void *dst, const char *value);
+bool cf_set_str(struct CfValue *cv, const char *value);
/** Setter for filename */
-bool cf_set_filename(void *dst, const char *value);
+bool cf_set_filename(struct CfValue *cv, const char *value);
/** Setter for int */
-bool cf_set_int(void *dst, const char *value);
+bool cf_set_int(struct CfValue *cv, const char *value);
/** Setter for time-usec */
-bool cf_set_time_usec(void *dst, const char *value);
+bool cf_set_time_usec(struct CfValue *cv, const char *value);
/** Setter for time-double */
-bool cf_set_time_double(void *dst, const char *value);
-
-/* before using them do: #define CF_REL_BASE struct Foo */
-/* later: #undef CF_REL_BASE */
-
-/** Integer with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_INT(x) cf_set_int, CF_VAL_REL, offsetof(CF_REL_BASE, x)
+bool cf_set_time_double(struct CfValue *cv, const char *value);
+/** Setter for lookup */
+bool cf_set_lookup(struct CfValue *cv, const char *value);
+
+/** Getter for string */
+const char *cf_get_str(struct CfValue *cv);
+/** Getter for int */
+const char *cf_get_int(struct CfValue *cv);
+/** Getter for time-usec */
+const char *cf_get_time_usec(struct CfValue *cv);
+/** Getter for time-double */
+const char *cf_get_time_double(struct CfValue *cv);
+/** Getter for int lookup */
+const char *cf_get_lookup(struct CfValue *cv);
+
+/** @} */
-/** String with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_STR(x) cf_set_str, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Filename with offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_FILENAME(x) cf_set_filename, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_BOOL(x) cf_set_int, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_TIME_USEC(x) cf_set_time_usec, CF_VAL_REL, offsetof(CF_REL_BASE, x)
-
-/** Integer offset relative to struct referenced in CF_REL_BASE */
-#define CF_REL_TIME_DOUBLE(x) cf_set_time_double, CF_VAL_REL, offsetof(CF_REL_BASE, x)
+/**
+ * @name Shortcut CfOps for well-known types
+ * @{
+ */
-/** Integer with absolute pointer */
-#define CF_ABS_INT(x) cf_set_int, CF_VAL_ABS, (uintptr_t)&(x)
+/** Ops for string */
+#define CF_STR { cf_set_str, cf_get_str }
+/** Ops for filename */
+#define CF_FILE { cf_set_filename, cf_get_str }
+/** Ops for integer */
+#define CF_INT { cf_set_int, cf_get_int }
+/** Ops for boolean */
+#define CF_BOOL { cf_set_int, cf_get_int }
+/** Ops for time as usec */
+#define CF_TIME_USEC { cf_set_time_usec, cf_get_time_usec }
+/** Ops for time as double */
+#define CF_TIME_DOUBLE { cf_set_time_double, cf_get_time_double }
+/** Ops for lookup, takes table as argument */
+#define CF_LOOKUP(t) { cf_set_lookup, cf_get_lookup, t }
+
+/** @} */
-/** String with absolute pointer */
-#define CF_ABS_STR(x) cf_set_str, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Lookup entry for CF_LOOKUP table.
+ */
+struct CfLookup {
+ const char *name;
+ int value;
+};
-/** Filename with absolute pointer */
-#define CF_ABS_FILENAME(x) cf_set_filename, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Helper to describe CfKey with absolute addressing
+ */
+#define CF_ABS(name, ops, var, flags, def) \
+ { name, ops, flags | CF_VAL_ABS, (uintptr_t)&(var), def }
-/** Bool with absolute pointer */
-#define CF_ABS_BOOL(x) cf_set_int, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Helper to describe CfKey with relative addressing.
+ *
+ * Before using it do: #define CF_REL_BASE struct Foo
+ *
+ * The var should be field in that struct.
+ *
+ * Later: #undef CF_REL_BASE
+ */
+#define CF_REL(name, ops, var, flags, def) \
+ { name, ops, flags | CF_VAL_REL, offsetof(CF_REL_BASE, var), def }
-/** Time in seconds stored in usec_t */
-#define CF_ABS_TIME_USEC(x) cf_set_time_usec, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Load config from file.
+ */
+bool cf_load_file(const struct CfContext *cf, const char *fn) _MUSTCHECK;
-/** Time in second as double */
-#define CF_ABS_TIME_DOUBLE(x) cf_set_time_double, CF_VAL_ABS, (uintptr_t)&(x)
+/**
+ * Get single value.
+ */
+const char *cf_get(const struct CfContext *cf, const char *sect, const char *var, char *buf, int buflen);
/**
- * Main entry point
+ * Set single value.
*/
-bool load_ini_file(const char *fn, const struct CfSect *sect_list, void *top_arg) _MUSTCHECK;
+bool cf_set(const struct CfContext *cf, const char *sect, const char *var, const char *val);
/* @} */