Introduction of cfgmgr

Few points to note (and possibly discuss):

- cfgmgr is build on top of LMDB, as it brings transaction (and so
  thread safe) support out of the box

- LMDB keys are build in a way that we can support repeatable (see
  https://pad.isc.org/p/cfgmgr-proposal-v2 even if most of it is
  outdated, the section 2.0.2 about the way it's build is still
  relevant, otherwise I hope the code changes here are clear enough)

- Each thread own its own cfgmgr context (which basically means LMDB
  transaction, and where the API points to inside the configuration)

- In order to avoid allocations everytime we get/set a value (as well
  as few other operations) a single buffer is pre-allocated per-thread
  and per-transaction. Now, few internal helper functions directly use
  it (instead or, let's say, work on a parameter) and this might be
  confusing and error prone, I'm happy to change that if it is a
  worry.

- The data type which can be read/wrote from cfgmgr is not exhaustive,
  more data type will be added (i.e. duration type, uint64 if needed,
  etc.)

- This current implementation does not support inheritance (i.e. a
  non-specified view option won't use the option one). It's something
  we need to discuss, I see some options that could be put on top of
  that's here.

- The "default" values, however, should be fine out-of-the-box: I
  think the default configuration in bin/named/config.c can be
  parse/"added" in cfgmgr first then user one on top of that (because
  writting an existing value override it). Obviously there would be
  no-way to "go back" to the default config only, but I don't see such
  use case in existing code. Even if we'd need such thing, that would
  be quite invasive and a full config reload would be advisable. (so
  we could add an API to entirely drop the cfgmgr data and start from
  scratch. But I don't see such use case right now anyway, so it's not
  there)

- I hope the things a user of cfgmgr must know should be clearly
   explained in the cfgmgr.h file (if it's not the case, then I need
   to fix it -- That said I'll likely re-work the doc anyway).

- I initially implemented a mechanism which would dynamically re-size
  key buffers in case keys are very long, but I remove this as LMDB
  doesn't supports key more than 511 bytes anyway. Instead I made
  assertions every time we build a key to make sure we don't exceed
  this value.
This commit is contained in:
Colin Vidal
2024-12-10 14:42:48 +01:00
parent e2c1941efd
commit 03d2b4a670
5 changed files with 1601 additions and 3 deletions

View File

@@ -10,7 +10,8 @@ libisccfg_la_HEADERS = \
include/isccfg/duration.h \
include/isccfg/grammar.h \
include/isccfg/kaspconf.h \
include/isccfg/namedconf.h
include/isccfg/namedconf.h \
include/isccfg/cfgmgr.h
libisccfg_la_SOURCES = \
$(libisccfg_la_HEADERS) \
@@ -19,7 +20,8 @@ libisccfg_la_SOURCES = \
duration.c \
kaspconf.c \
namedconf.c \
parser.c
parser.c \
cfgmgr.c
libisccfg_la_CPPFLAGS = \
$(AM_CPPFLAGS) \

717
lib/isccfg/cfgmgr.c Normal file
View File

@@ -0,0 +1,717 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <lmdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <isc/list.h>
#include <isc/mem.h>
#include <isc/thread.h>
#include <isc/util.h>
#include <isccfg/cfgmgr.h>
#define DBPATH "/tmp/lmdb-exp"
/*
* See MDB_MAXKEYSIZE documentation, but not accessible as defined in
* internal implementation. Having key with longer size won't work
* with LMDB. The value is 511 by default.
*/
#define BUFLEN 511
typedef struct openedclause openedclause_t;
struct openedclause {
char *name;
unsigned long id;
ISC_LINK(openedclause_t) link;
};
typedef ISC_LIST(openedclause_t) openedclauses_t;
typedef struct {
openedclauses_t openedclauses;
char *prefix;
char *buffer;
MDB_cursor *cursor;
MDB_txn *txn;
bool readonly;
} context_t;
static isc_mem_t *mctx = NULL;
static MDB_env *env = NULL;
static thread_local context_t ctx =
(context_t){ .openedclauses = ISC_LIST_INITIALIZER,
.prefix = NULL,
.buffer = NULL,
.cursor = NULL,
.txn = NULL,
.readonly = false };
static unsigned long
parseid(const char *dbkey) {
unsigned long id = 0;
size_t idstarts;
size_t idends;
size_t keylen;
REQUIRE(ctx.buffer != NULL);
REQUIRE(dbkey != NULL);
/*
* starts checking after the prefix dot delimiter, i.e. if
* prefix is "foo" then the key will be "foo.1235..." so the
* start of the id (character 1) is at character index 4
*/
idstarts = strlen(ctx.buffer);
idends = idstarts;
INSIST(idstarts > 0 && idends == idstarts);
INSIST(ctx.buffer[idstarts - 1] == '.');
keylen = strlen(dbkey);
/*
* Cutting the key form the dot after the identifier
*/
REQUIRE(keylen > idends);
while (dbkey[idends] != '.') {
idends++;
INSIST(keylen > idends);
}
/*
* strtoul will stops as soon as it doesn't encounder a
* non-digit number, so no need to get an extra buffer, copy
* the dbkey and add a null byte after the last digit.
*/
id = strtoul(dbkey + idstarts, NULL, 10);
ENSURE(id > 0);
return id;
}
isc_result_t
cfgmgr_init(void) {
int result = ISC_R_SUCCESS;
char dbname[BUFLEN];
char dblockname[BUFLEN];
uint32_t random;
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses));
REQUIRE(ctx.prefix == NULL);
REQUIRE(ctx.buffer == NULL);
REQUIRE(ctx.cursor == NULL);
REQUIRE(ctx.txn == NULL);
REQUIRE(mctx == NULL);
REQUIRE(env == NULL);
isc_mem_create(&mctx);
INSIST(mctx != NULL);
result = mdb_env_create(&env);
if (result != 0) {
result = ISC_R_FAILURE;
goto cleanup;
}
/*
* Using MDB_NOSYNC as it avoid force disk flush after a
* transaction. It's quicker and in our case we don't need it
* as we delete the only link to the inode right away (so disk
* corruption doesn't matter: as soon as the process is dead,
* the disk data is dead as well)
*/
random = arc4random();
REQUIRE(snprintf(dbname, BUFLEN, "%s-%u", DBPATH, random) < BUFLEN);
REQUIRE(snprintf(dblockname, BUFLEN, "%s-%u-lock", DBPATH, random) <
BUFLEN);
result = mdb_env_open(env, dbname, MDB_NOSYNC | MDB_NOSUBDIR, 0600);
if (result != 0) {
result = ISC_R_FAILURE;
goto cleanup;
}
remove(dbname);
remove(dblockname);
ENSURE(env != NULL);
goto out;
cleanup:
if (env != NULL) {
mdb_env_close(env);
env = NULL;
}
out:
return result;
}
void
cfgmgr_deinit(void) {
/*
* Well, I'm on the fence about those context checks... It's
* good to have, but because they thread specific, it doesn't
* means there isn't a thread somewhere which haven't released
* its opened clauses and not the one calling cfgmgr_deinit,
* then we'll leak those - even if unlikely as this function
* should be call late in shutdown flow. (That said it's just
* an extra clue, because destroying the context will assert
* anyway, as some memory would not be released yet).
*/
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses));
REQUIRE(ctx.prefix == NULL);
REQUIRE(ctx.buffer == NULL);
REQUIRE(ctx.cursor == NULL);
REQUIRE(ctx.txn == NULL);
REQUIRE(env != NULL);
REQUIRE(mctx != NULL);
mdb_env_close(env);
env = NULL;
isc_mem_destroy(&mctx);
ENSURE(mctx == NULL);
}
static void
buildkey(const char *name, bool trailingdot) {
size_t written;
const char *prefix = ISC_LIST_EMPTY(ctx.openedclauses) ? ""
: ctx.prefix;
const char *dot = trailingdot ? "." : "";
REQUIRE(ctx.buffer != NULL);
written = snprintf(ctx.buffer, BUFLEN, "%s%s%s", prefix, name, dot);
INSIST(written <= BUFLEN);
}
static isc_result_t
open_findclause(const char *name, unsigned long *id) {
isc_result_t result = ISC_R_SUCCESS;
MDB_val dbkey;
size_t dotpos = 0;
REQUIRE(name != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
buildkey(name, true);
dotpos = strlen(ctx.buffer) - 1;
dbkey = (MDB_val){ .mv_size = strlen(ctx.buffer) + 1,
.mv_data = (char *)ctx.buffer };
/*
* Let's use LMDB prefix search because the first clause
* key/val won't just have the "name.id" prefix, but also the
* id and the first property name (so "name.id.prop").
*/
if (mdb_cursor_get(ctx.cursor, &dbkey, NULL, MDB_SET_RANGE) != 0) {
result = ISC_R_NOTFOUND;
goto out;
}
/*
* LMDB found a key which starts by "prefix", so let's make
* sure it's actually the same prefix by checking the found
* key has an immediate leading dot
*/
if (dbkey.mv_size <= dotpos || ((char *)dbkey.mv_data)[dotpos] != '.') {
result = ISC_R_NOTFOUND;
goto out;
}
/*
* We found the clause. Let's extract its ID
*/
*id = parseid(dbkey.mv_data);
out:
return result;
}
static void
updateprefix(void) {
size_t written = 0;
REQUIRE(ctx.prefix != NULL);
if (ISC_LIST_EMPTY(ctx.openedclauses)) {
return;
}
for (openedclause_t *clause = ISC_LIST_TAIL(ctx.openedclauses);
clause != NULL; clause = ISC_LIST_PREV(clause, link))
{
written += snprintf(ctx.prefix + written, BUFLEN - written,
"%s.%zu.", clause->name, clause->id);
INSIST(written <= BUFLEN);
}
}
static void
pushclause(const char *name, unsigned long id) {
openedclause_t *clause = isc_mem_get(mctx, sizeof(*clause));
*clause = (openedclause_t){
.name = isc_mem_allocate(mctx, strlen(name) + 1), .id = id
};
strcpy(clause->name, name);
ENSURE(clause->id > 0);
ISC_LIST_PREPEND(ctx.openedclauses, clause, link);
updateprefix();
}
static void
freectx(void) {
REQUIRE(ctx.buffer != NULL && ctx.prefix != NULL);
isc_mem_free(mctx, ctx.buffer);
ctx.buffer = NULL;
isc_mem_free(mctx, ctx.prefix);
ctx.prefix = NULL;
ctx.txn = NULL;
ctx.cursor = NULL;
}
static isc_result_t
starttransaction(bool readonly) {
isc_result_t result = ISC_R_SUCCESS;
MDB_dbi dbi;
REQUIRE(env != NULL);
REQUIRE(ctx.prefix == NULL);
REQUIRE(ctx.buffer == NULL);
REQUIRE(ctx.txn == NULL);
REQUIRE(ctx.cursor == NULL);
if (mdb_txn_begin(env, NULL, readonly ? MDB_RDONLY : 0, &ctx.txn) != 0)
{
result = ISC_R_FAILURE;
goto cleanup;
}
INSIST(ctx.txn != NULL);
if (mdb_dbi_open(ctx.txn, NULL, MDB_CREATE | MDB_DUPSORT, &dbi) != 0) {
result = ISC_R_FAILURE;
goto cleanup;
}
if (mdb_cursor_open(ctx.txn, dbi, &ctx.cursor) != 0) {
result = ISC_R_FAILURE;
goto cleanup;
}
INSIST(ctx.cursor != NULL);
ctx.readonly = readonly;
ctx.buffer = isc_mem_allocate(mctx, BUFLEN);
ctx.prefix = isc_mem_allocate(mctx, BUFLEN);
goto out;
cleanup:
if (ctx.txn) {
mdb_txn_abort(ctx.txn);
freectx();
}
ENSURE(ctx.buffer == NULL && ctx.prefix == NULL);
out:
return result;
}
static isc_result_t
open_toplevel(const char *name, bool readonly) {
isc_result_t result = ISC_R_SUCCESS;
unsigned long id = 0;
REQUIRE(env != NULL);
REQUIRE(name != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses));
REQUIRE(ctx.prefix == NULL);
REQUIRE(ctx.buffer == NULL);
REQUIRE(ctx.txn == NULL);
REQUIRE(ctx.cursor == NULL);
/*
* We're opening a clause at top-level, so let's start a
* transaction
*/
result = starttransaction(readonly);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Now let's try to find the clause...
*/
result = open_findclause(name, &id);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* The clause is found, let's enqueue the clause in
* context. the clause is now opened
*/
pushclause(name, id);
goto out;
cleanup:
if (ctx.txn) {
mdb_txn_abort(ctx.txn);
freectx();
}
out:
return result;
}
static isc_result_t
open_nested(const char *name) {
isc_result_t result = ISC_R_SUCCESS;
unsigned long id = 0;
REQUIRE(env != NULL);
REQUIRE(name != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
REQUIRE(ctx.prefix != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
result = open_findclause(name, &id);
if (result != ISC_R_SUCCESS) {
goto out;
}
pushclause(name, id);
out:
return result;
}
isc_result_t
cfgmgr_openrw(const char *name) {
return open_toplevel(name, false);
}
isc_result_t
cfgmgr_open(const char *name) {
return ISC_LIST_EMPTY(ctx.openedclauses) ? open_toplevel(name, true)
: open_nested(name);
}
static void
popclause(void) {
REQUIRE(env != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
openedclause_t *clause = ISC_LIST_HEAD(ctx.openedclauses);
ISC_LIST_UNLINK(ctx.openedclauses, clause, link);
isc_mem_free(mctx, clause->name);
isc_mem_put(mctx, clause, sizeof(*clause));
updateprefix();
}
isc_result_t
cfgmgr_close(void) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(env != NULL);
if (ISC_LIST_EMPTY(ctx.openedclauses)) {
REQUIRE(ctx.prefix == NULL);
REQUIRE(ctx.buffer == NULL);
REQUIRE(ctx.txn == NULL);
REQUIRE(ctx.cursor == NULL);
result = ISC_R_NOTBOUND;
goto out;
}
popclause();
if (ISC_LIST_EMPTY(ctx.openedclauses)) {
mdb_cursor_close(ctx.cursor);
if (mdb_txn_commit(ctx.txn) != 0) {
result = ISC_R_FAILURE;
}
freectx();
}
out:
return result;
}
isc_result_t
cfgmgr_delclause(void) {
MDB_val dbkey;
REQUIRE(env != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
REQUIRE(ctx.prefix != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
REQUIRE(ctx.readonly == false);
dbkey = (MDB_val){ .mv_size = strlen(ctx.prefix) + 1,
.mv_data = ctx.prefix };
do {
/*
* even though the key is modified by mdb_cursor_get
* on each run (and is the exact current key) we're
* good: MDB_SET_RANGE of the current key will point
* to the next one with the same prefix as soon it
* gets deleted
*/
int mdbres = mdb_cursor_get(ctx.cursor, &dbkey, NULL,
MDB_SET_RANGE);
if (mdbres == MDB_NOTFOUND) {
break;
}
if (strncmp(ctx.prefix, dbkey.mv_data, strlen(ctx.prefix)) != 0)
{
break;
}
/*
* NDB_NODUPDATA not strictly needed here, but we
* avoid extra iterations if there are lists in the
* clause
*/
REQUIRE(mdb_cursor_del(ctx.cursor, MDB_NODUPDATA) == 0);
} while (1);
return cfgmgr_close();
}
isc_result_t
cfgmgr_newclause(const char *name) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(name != NULL);
REQUIRE(env != NULL);
if (ctx.txn == NULL || ctx.cursor == NULL) {
result = starttransaction(false);
}
if (result == ISC_R_SUCCESS) {
INSIST(ctx.txn != NULL);
INSIST(ctx.buffer != NULL);
INSIST(ctx.cursor != NULL);
INSIST(ctx.readonly == false);
pushclause(name, arc4random());
INSIST(ctx.prefix != NULL);
}
return result;
}
isc_result_t
cfgmgr_nextclause(void) {
isc_result_t result = ISC_R_SUCCESS;
MDB_val dbkey;
unsigned long id;
size_t idstarts = 0;
size_t written = 0;
REQUIRE(env != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
REQUIRE(ctx.prefix != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
/*
* Let's pick the very next id (even if doesn't exists) of the
* current clause
*/
id = ISC_LIST_HEAD(ctx.openedclauses)->id + 1;
/*
* Variant of updateprefix, but this time we put a incremented
* key for the currently opened clause. We also keep track of
* when the current clause id starts.
*/
for (openedclause_t *clause = ISC_LIST_TAIL(ctx.openedclauses);
clause != NULL; clause = ISC_LIST_PREV(clause, link))
{
bool last = ISC_LIST_PREV(clause, link) == NULL;
if (last) {
idstarts = written + strlen(clause->name) + 1;
}
written += snprintf(ctx.buffer + written, BUFLEN - written,
"%s.%zu.", clause->name,
last ? id : clause->id);
INSIST(written <= BUFLEN);
}
INSIST(idstarts > 0);
dbkey = (MDB_val){ .mv_size = strlen(ctx.buffer) + 1,
.mv_data = ctx.buffer };
/*
* Looking for similar prefix name but with a bigger
* id. Thanks for LMDB sort and MDB_SET_RANGE, we'll bump to
* the first key/val of the next clause of the same type
*/
if (mdb_cursor_get(ctx.cursor, &dbkey, NULL, MDB_SET_RANGE) != 0) {
result = ISC_R_NOMORE;
goto out;
}
/*
* Let's check if next found clause is same name
*/
REQUIRE(idstarts < BUFLEN);
if (strncmp(ctx.buffer, dbkey.mv_data, idstarts) != 0) {
result = ISC_R_NOMORE;
goto out;
}
/*
* Gets the actual id of the next clause (so let's get rid of
* the fake id part from the prefix). Instead of pop/push a
* new clause, let's simply replace the id and update the
* prefix.
*/
ctx.buffer[idstarts] = 0;
ISC_LIST_HEAD(ctx.openedclauses)->id = parseid(dbkey.mv_data);
updateprefix();
out:
return result;
}
static isc_result_t
getval(const char *name, cfgmgr_val_t *value) {
isc_result_t result = ISC_R_SUCCESS;
MDB_val dbkey;
MDB_val dbval;
const int opt = name == NULL ? MDB_NEXT_DUP : MDB_SET;
REQUIRE(env != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
REQUIRE(ctx.prefix != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
REQUIRE(value != NULL);
if (name != NULL) {
buildkey(name, false);
}
dbkey = (MDB_val){ .mv_size = name == NULL ? 0 : strlen(ctx.buffer) + 1,
.mv_data = name == NULL ? NULL : ctx.buffer };
if (mdb_cursor_get(ctx.cursor, &dbkey, &dbval, opt) != 0) {
result = opt == MDB_NEXT_DUP ? ISC_R_NOMORE : ISC_R_NOTFOUND;
goto out;
}
memcpy(value, dbval.mv_data, sizeof(*value));
if (value->type == STRING) {
value->data.string = ((char *)dbval.mv_data) +
sizeof(value->type);
}
out:
return result;
}
isc_result_t
cfgmgr_getval(const char *name, cfgmgr_val_t *value) {
return getval(name, value);
}
isc_result_t
cfgmgr_getnextlistval(cfgmgr_val_t *value) {
return getval(NULL, value);
}
static isc_result_t
setval(const char *name, const cfgmgr_val_t *value, bool list) {
isc_result_t result = ISC_R_SUCCESS;
MDB_val dbkey;
MDB_val dbval;
REQUIRE(env != NULL);
REQUIRE(ISC_LIST_EMPTY(ctx.openedclauses) == false);
REQUIRE(ctx.prefix != NULL);
REQUIRE(ctx.buffer != NULL);
REQUIRE(ctx.txn != NULL);
REQUIRE(ctx.cursor != NULL);
REQUIRE(ctx.readonly == false);
REQUIRE(name != NULL);
REQUIRE(value != NULL || (value == NULL && list == false));
buildkey(name, false);
dbkey = (MDB_val){ .mv_size = strlen(ctx.buffer) + 1,
.mv_data = ctx.buffer };
if (value == NULL) {
if (mdb_cursor_get(ctx.cursor, &dbkey, NULL, MDB_SET) ==
MDB_NOTFOUND)
{
result = ISC_R_NOTFOUND;
goto out;
}
REQUIRE(mdb_cursor_del(ctx.cursor, MDB_NODUPDATA) == 0);
goto out;
}
if (value->type == STRING) {
dbval.mv_size = sizeof(*value) + strlen(value->data.string) + 1;
dbval.mv_data = isc_mem_allocate(mctx, dbval.mv_size);
memcpy(dbval.mv_data, value, sizeof(value->type));
strcpy(((char *)dbval.mv_data) + sizeof(value->type),
value->data.string);
} else {
dbval = (MDB_val){ .mv_size = sizeof(*value),
/*
* LMDB won't modify the mv_data buffer but
* its API is designed w/o the const buffer.
*/
.mv_data = (void *)value };
}
if (list == false) {
/*
* Can't use MDB_NOOVERWRITE as it would override the
* data if the key/value already exists. Making a
* value copy ahead just in case is likely more
* expensive than an extra lookup
*/
if (mdb_cursor_get(ctx.cursor, &dbkey, NULL, MDB_SET) !=
MDB_NOTFOUND)
{
REQUIRE(mdb_cursor_del(ctx.cursor, 0) == 0);
}
}
REQUIRE(mdb_cursor_put(ctx.cursor, &dbkey, &dbval, 0) == 0);
if (value->type == STRING) {
isc_mem_free(mctx, dbval.mv_data);
}
out:
return result;
}
isc_result_t
cfgmgr_setval(const char *name, const cfgmgr_val_t *value) {
return setval(name, value, false);
}
isc_result_t
cfgmgr_setnextlistval(const char *name, const cfgmgr_val_t *value) {
return setval(name, value, true);
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#pragma once
#include <isc/sockaddr.h>
#include <isc/types.h>
/*
* Supported data types for read/write operations from/to cfgmgr.
*/
typedef enum { STRING = 0, BOOL, NONE, SOCKADDR, UINT32 } cfgmgr_type_t;
/*
* Generic value holding the actual value and type value for
* read/write from/to cfgmgr.
*
* cfgmgr_type_t::NONE doesn't have associated value,
*/
typedef struct {
cfgmgr_type_t type;
union {
const char *string;
bool boolean;
isc_sockaddr_t sockaddr;
uint32_t uint32;
} data;
} cfgmgr_val_t;
/*
* Get the property "name" in the opened clause into the caller
* allocated "value" and returns ISC_R_SUCCESS. Returns ISC_R_NOTFOUND
* and "*value" is not mutated if "name" is not found. Changes being
* made by other threads aren't visible until the clause (and its
* parent, if nested) is closed. If "name" is a list property, get its
* head.
*/
isc_result_t
cfgmgr_getval(const char *name, cfgmgr_val_t *value);
/*
* Write "value" into the property "name" in the opened clause and
* returns ISC_R_SUCCESS. If the property already exists, it is
* overridden and even if the type is different. If "value" is NULL
* and the property exists, it will be deleted (applies for list
* properties as well). Changes being made can be visible only by the
* current thread until the clause (and its parent, if nested) is
* closed.
*/
isc_result_t
cfgmgr_setval(const char *name, const cfgmgr_val_t *value);
/*
* Same as cfgmgr_getval but applies for elements after the head of a
* list property. The head is read using cfgmgr_getval as any other
* value, then subsequents calls to cfgmgr_getnextlistval will get the
* next elements in the list. When the end of the list is reached,
* ISC_R_NOMORE is returned. Calls to cfgmgr_getnextlistval name has
* to be made in immediate sequence (without intermediate
* cfgmgr_{set,get}val calls) to retreive each list element.
*/
isc_result_t
cfgmgr_getnextlistval(cfgmgr_val_t *value);
/*
* Same as cfgmgr_setval but applies for a list property. Writes by
* appending "*value" at the end of the list property "name" in the
* opened clause and returns ISC_R_SUCCESS. If "name" property wasn't
* existing before (or wasn't a list) it's overriden. It is not
* possible to delete individual list element, only the whole list can
* be removed using cfgmgr_setval.
*/
isc_result_t
cfgmgr_setnextlistval(const char *name, const cfgmgr_val_t *value);
/*
* If the opened clause is a repeatable clause (i.e. view, acl, etc.),
* internally closes the opened clause and open the next clause of the
* same type and returns ISC_R_SUCCESS. When there is no next clause
* of the same type, ISC_R_NOMORE is returned.
*/
isc_result_t
cfgmgr_nextclause(void);
/*
* If used at top-level, create and open as read-write a new clause
* "name". If used inside an opened parent clause, then the parent (or
* parent or the parent, recursively) clause must have been opened
* read-write (so using cfgmgr_openrw or cfgmgr_newclause).
*
* Returns ISC_R_SUCCESS. Note that in order to have the new clause
* actually written in cfgmgr, at least one property needs to be set
* to that clause.
*/
isc_result_t
cfgmgr_newclause(const char *name);
/*
* Delete and close the opened clause. (And thus all its properties,
* including nested clauses). If the clause was nested, the currently
* opened clause is now the parent clause. Otherwise, no clause is
* opened. Returns ISC_R_SUCCESS.
*/
isc_result_t
cfgmgr_delclause(void);
/*
* Close the currently opened clause and returns ISC_R_SUCCESS. If the
* closed clause was nested, the currently opened clause is now the
* parent clause. Otherwise, no close is opened. If no clause was
* opened when the function was called, ISC_R_NOTBOUND is
* returned. Closing the top-level clause will applies all
* modifications done inside the clause. If something is going wrong,
* ISC_R_FAILURE is returned, and all modification made are discarded.
*/
isc_result_t
cfgmgr_close(void);
/*
* Open the top-level clause "name" for reading and writting and
* returns ISC_R_SUCCESS. If the clause "name" is not found, returns
* ISC_R_NOTFOUND.
*
* This call will block if another thread has already a clause opened
* for reading and writting. Use cfgmgr_openro for reading only.
*/
isc_result_t
cfgmgr_openrw(const char *name);
/*
* Open the clause "name" and returns ISC_R_SUCCES or ISC_R_NOTFOUND
* is the clause is not found. Two possible cases:
*
* - if called at top-level, it open the top-level clause as read
* only.
*
* - if called form within an opened clause, it open it with the same
* access than the already opened clause.
*/
isc_result_t
cfgmgr_open(const char *name);
isc_result_t
cfgmgr_init(void);
void
cfgmgr_deinit(void);

View File

@@ -16,6 +16,7 @@ LDADD += \
check_PROGRAMS = \
duration_test \
parser_test \
grammar_test
grammar_test \
cfgmgr_test
include $(top_srcdir)/Makefile.tests

721
tests/isccfg/cfgmgr_test.c Normal file
View File

@@ -0,0 +1,721 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <pthread.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isccfg/cfgmgr.h>
#include <tests/isc.h>
#define SUCCESS(result) assert_int_equal(result, ISC_R_SUCCESS)
#define NOTFOUND(result) assert_int_equal(result, ISC_R_NOTFOUND)
#define NOTBOUND(result) assert_int_equal(result, ISC_R_NOTBOUND)
ISC_RUN_TEST_IMPL(cfgmgr_rw) {
cfgmgr_val_t val1;
cfgmgr_val_t val2;
SUCCESS(cfgmgr_init());
NOTFOUND(cfgmgr_open("foo"));
SUCCESS(cfgmgr_newclause("foo"));
val1 = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 4058304 };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 4058304);
val1 = (cfgmgr_val_t){ .type = NONE };
SUCCESS(cfgmgr_setval("prop3", &val1));
SUCCESS(cfgmgr_getval("prop3", &val2));
assert_int_equal(val2.type, NONE);
val1 = (cfgmgr_val_t){ .type = BOOL, .data.boolean = true };
SUCCESS(cfgmgr_setval("prop2", &val1));
SUCCESS(cfgmgr_getval("prop2", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
val1 = (cfgmgr_val_t){ .type = BOOL, .data.boolean = false };
SUCCESS(cfgmgr_setval("anotherprop", &val1));
SUCCESS(cfgmgr_getval("anotherprop", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, false);
/*
* Let's check and adding other properties didn't affect the
* ones added previously
*/
SUCCESS(cfgmgr_getval("prop3", &val2));
assert_int_equal(val2.type, NONE);
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 4058304);
SUCCESS(cfgmgr_getval("prop2", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
/*
* Non existent property - it doesn't not mutate val.
*/
NOTFOUND(cfgmgr_getval("prop4", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
NOTFOUND(cfgmgr_getval("p", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
/*
* Everything still there when closing and re-opening
* (read-only) the clause
*/
SUCCESS(cfgmgr_getval("prop3", &val2));
assert_int_equal(val2.type, NONE);
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 4058304);
SUCCESS(cfgmgr_getval("prop2", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
SUCCESS(cfgmgr_close());
NOTBOUND(cfgmgr_close());
/*
* Adding other clause (intentionally with a different name,
* but a common prefix in the name - those are still different
* clauses
*/
SUCCESS(cfgmgr_newclause("foo1"));
val1 = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 1234 };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 1234);
val1 = (cfgmgr_val_t){ .type = NONE };
SUCCESS(cfgmgr_setval("somestuff", &val1));
SUCCESS(cfgmgr_getval("somestuff", &val2));
assert_int_equal(val2.type, NONE);
/*
* Make sure we don't mixes clause properties
*/
NOTFOUND(cfgmgr_getval("prop2", &val2));
NOTFOUND(cfgmgr_getval("prop3", &val2));
SUCCESS(cfgmgr_close());
/*
* let's reopen rw this time
*/
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 4058304);
SUCCESS(cfgmgr_getval("prop2", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_openrw("foo1"));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 1234);
SUCCESS(cfgmgr_getval("somestuff", &val2));
assert_int_equal(val2.type, NONE);
/*
* because we used openrw, we can do that
*/
val1.type = UINT32;
val1.data.uint32 = 999;
SUCCESS(cfgmgr_setval("somestuff2", &val1));
SUCCESS(cfgmgr_getval("somestuff2", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 999);
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_parseid) {
cfgmgr_val_t val;
/*
* Excercise the fact that even if properties/clause names are
* number, this doesn't confuse the id parser (in particular,
* validates the usage of strtoul is correct). This also
* exercise the nested clause and repeatable clauses with such
* odd names
*/
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("123"));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 666666 };
SUCCESS(cfgmgr_setval("123123", &val));
SUCCESS(cfgmgr_newclause("456"));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 777777 };
SUCCESS(cfgmgr_setval("456456", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_newclause("456"));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 888888 };
SUCCESS(cfgmgr_setval("456456", &val));
SUCCESS(cfgmgr_close());
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 9999 };
SUCCESS(cfgmgr_setval("456456", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("123"));
SUCCESS(cfgmgr_getval("123123", &val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 666666);
SUCCESS(cfgmgr_getval("456456", &val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 9999);
bool found_777777 = false;
bool found_888888 = false;
SUCCESS(cfgmgr_open("456"));
SUCCESS(cfgmgr_getval("456456", &val));
assert_int_equal(val.type, UINT32);
if (val.data.uint32 == 777777) {
found_777777 = true;
} else if (val.data.uint32 == 888888) {
found_888888 = true;
} else {
REQUIRE(false);
}
SUCCESS(cfgmgr_nextclause());
SUCCESS(cfgmgr_getval("456456", &val));
assert_int_equal(val.type, UINT32);
if (val.data.uint32 == 777777) {
found_777777 = true;
} else if (val.data.uint32 == 888888) {
found_888888 = true;
} else {
REQUIRE(false);
}
assert_true(found_777777);
assert_true(found_888888);
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_override) {
cfgmgr_val_t val1;
cfgmgr_val_t val2;
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("foo"));
val1 = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 4058304 };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 4058304);
val1 = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 666 };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, UINT32);
assert_int_equal(val2.data.uint32, 666);
val1 = (cfgmgr_val_t){ .type = BOOL, .data.boolean = false };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, false);
val1 = (cfgmgr_val_t){ .type = BOOL, .data.boolean = true };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, BOOL);
assert_int_equal(val2.data.boolean, true);
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_rw_string) {
cfgmgr_val_t val1;
cfgmgr_val_t val2;
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("foo"));
val1 = (cfgmgr_val_t){ .type = STRING, .data.string = "hey there!" };
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, STRING);
assert_string_equal(val2.data.string, "hey there!");
val1 = (cfgmgr_val_t){
.type = STRING,
.data.string = "hey there! hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!"
};
SUCCESS(cfgmgr_setval("prop1", &val1));
SUCCESS(cfgmgr_getval("prop1", &val2));
assert_int_equal(val2.type, STRING);
assert_string_equal(
val2.data.string,
"hey there! hey there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey there!hey there!hey "
"there!hey there!hey there!hey there!hey there!hey there!hey "
"there!");
val1 = (cfgmgr_val_t){ .type = STRING,
.data.string = "foobarbaz stuff" };
SUCCESS(cfgmgr_setval("shorterstring", &val1));
SUCCESS(cfgmgr_getval("shorterstring", &val2));
assert_int_equal(val2.type, STRING);
assert_string_equal(val2.data.string, "foobarbaz stuff");
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_list) {
cfgmgr_val_t val;
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("foo"));
val = (cfgmgr_val_t){ .type = STRING, .data.string = "lst1" };
SUCCESS(cfgmgr_setnextlistval("proplist", &val));
val.data.string = "lst2";
SUCCESS(cfgmgr_setnextlistval("proplist", &val));
val.data.string = "lst3";
SUCCESS(cfgmgr_setnextlistval("proplist", &val));
val.data.string = "lst4";
SUCCESS(cfgmgr_setnextlistval("proplist", &val));
val.data.string = "otherpropval";
SUCCESS(cfgmgr_setval("otherprop", &val));
val.data.string = "zzzval";
SUCCESS(cfgmgr_setval("zzz", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
SUCCESS(cfgmgr_getval("proplist", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst1");
/*
* calling it again, we stick to the head
*/
SUCCESS(cfgmgr_getval("proplist", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst1");
/*
* now we're moving on...
*/
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst2");
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst3");
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst4");
assert_int_equal(cfgmgr_getnextlistval(&val), ISC_R_NOMORE);
assert_int_equal(cfgmgr_getnextlistval(&val), ISC_R_NOMORE);
/*
* and start from the begining again
*/
SUCCESS(cfgmgr_getval("proplist", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst1");
/*
* move on in the list but re-start again
*/
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst2");
SUCCESS(cfgmgr_getval("proplist", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "lst1");
/*
* calling after reading a non-list property
*/
SUCCESS(cfgmgr_getval("zzz", &val));
assert_int_equal(cfgmgr_getnextlistval(&val), ISC_R_NOMORE);
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_repeatable_clauses) {
cfgmgr_val_t val1;
cfgmgr_val_t val2;
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("view"));
SUCCESS(cfgmgr_setval("p1", &(cfgmgr_val_t){ .type = STRING,
.data.string = "view1 p1 "
"val" }));
SUCCESS(cfgmgr_setval(
"p2", &(cfgmgr_val_t){ .type = BOOL, .data.boolean = false }));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_newclause("view"));
SUCCESS(cfgmgr_setval("p1", &(cfgmgr_val_t){ .type = STRING,
.data.string = "view2 p2 "
"val" }));
SUCCESS(cfgmgr_setval(
"p2", &(cfgmgr_val_t){ .type = BOOL, .data.boolean = true }));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("view"));
SUCCESS(cfgmgr_getval("p2", &val2));
assert_int_equal(val2.type, BOOL);
if (val2.data.boolean) {
SUCCESS(cfgmgr_getval("p1", &val1));
assert_int_equal(val1.type, STRING);
assert_string_equal(val1.data.string, "view2 p2 val");
} else {
SUCCESS(cfgmgr_getval("p1", &val1));
assert_int_equal(val1.type, STRING);
assert_string_equal(val1.data.string, "view1 p1 val");
}
SUCCESS(cfgmgr_nextclause());
SUCCESS(cfgmgr_getval("p2", &val2));
assert_int_equal(val2.type, BOOL);
if (val2.data.boolean) {
SUCCESS(cfgmgr_getval("p1", &val1));
assert_int_equal(val1.type, STRING);
assert_string_equal(val1.data.string, "view2 p2 val");
} else {
SUCCESS(cfgmgr_getval("p1", &val1));
assert_int_equal(val1.type, STRING);
assert_string_equal(val1.data.string, "view1 p1 val");
}
assert_int_equal(cfgmgr_nextclause(), ISC_R_NOMORE);
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_nested_clauses) {
cfgmgr_val_t val;
SUCCESS(cfgmgr_init());
/*
* Let's start by writting then reading
* foo { bar { baz { gee: none; }; }; };
*/
SUCCESS(cfgmgr_newclause("foo"));
SUCCESS(cfgmgr_newclause("bar"));
SUCCESS(cfgmgr_newclause("baz"));
val.type = NONE;
SUCCESS(cfgmgr_setval("gee", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
NOTBOUND(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
SUCCESS(cfgmgr_open("bar"));
SUCCESS(cfgmgr_open("baz"));
val.type = UINT32;
SUCCESS(cfgmgr_getval("gee", &val));
assert_int_equal(val.type, NONE);
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
/*
* then let's delete bar and add some properties in foo and
* another nested clause
*/
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_open("bar"));
SUCCESS(cfgmgr_delclause());
SUCCESS(cfgmgr_newclause("foonewsubclause"));
val = (cfgmgr_val_t){ .type = STRING, .data.string = "abc" };
SUCCESS(cfgmgr_setval("propsubclause", &val));
SUCCESS(cfgmgr_close());
val = (cfgmgr_val_t){ .type = STRING, .data.string = "propfooval" };
SUCCESS(cfgmgr_setval("propfoo", &val));
SUCCESS(cfgmgr_close());
NOTBOUND(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
SUCCESS(cfgmgr_getval("propfoo", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "propfooval");
NOTFOUND(cfgmgr_open("bar"));
SUCCESS(cfgmgr_open("foonewsubclause"));
SUCCESS(cfgmgr_getval("propsubclause", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "abc");
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
/*
* Let's mix nested and repeatable clauses
*/
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_newclause("foonewsubclause"));
bool abc_found = false;
bool def_found = false;
val = (cfgmgr_val_t){ .type = STRING, .data.string = "def" };
SUCCESS(cfgmgr_setval("propsubclause", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
NOTBOUND(cfgmgr_close());
val.data.string = NULL;
SUCCESS(cfgmgr_open("foo"));
SUCCESS(cfgmgr_open("foonewsubclause"));
SUCCESS(cfgmgr_getval("propsubclause", &val));
assert_int_equal(val.type, STRING);
if (strncmp(val.data.string, "abc", 3) == 0) {
abc_found = true;
} else if (strncmp(val.data.string, "def", 3) == 0) {
def_found = true;
} else {
REQUIRE(false);
}
SUCCESS(cfgmgr_nextclause());
SUCCESS(cfgmgr_getval("propsubclause", &val));
assert_int_equal(val.type, STRING);
if (strncmp(val.data.string, "abc", 3) == 0) {
abc_found = true;
} else if (strncmp(val.data.string, "def", 3) == 0) {
def_found = true;
} else {
REQUIRE(false);
}
assert_true(abc_found);
assert_true(def_found);
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
ISC_RUN_TEST_IMPL(cfgmgr_delete) {
cfgmgr_val_t val;
SUCCESS(cfgmgr_init());
/*
* foo is not found because nothing has been written in there
*/
SUCCESS(cfgmgr_newclause("foo"));
SUCCESS(cfgmgr_close());
NOTFOUND(cfgmgr_open("foo"));
SUCCESS(cfgmgr_newclause("foo"));
val = (cfgmgr_val_t){ .type = NONE };
SUCCESS(cfgmgr_setval("prop1", &val));
val = (cfgmgr_val_t){ .type = STRING, .data.string = "prop2val" };
SUCCESS(cfgmgr_setval("prop2", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
SUCCESS(cfgmgr_getval("prop1", &val));
SUCCESS(cfgmgr_getval("prop2", &val));
SUCCESS(cfgmgr_close());
/*
* let's delete prop1 and add a list as prop3
*/
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_setval("prop1", NULL));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 123 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 456 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_open("foo"));
NOTFOUND(cfgmgr_getval("prop1", &val));
SUCCESS(cfgmgr_getval("prop2", &val));
assert_int_equal(val.type, STRING);
assert_string_equal(val.data.string, "prop2val");
SUCCESS(cfgmgr_getval("prop3", &val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 123);
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 456);
SUCCESS(cfgmgr_close());
/*
* let's delete prop2 and prop3, the whole close disappears
*/
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_setval("prop2", NULL));
SUCCESS(cfgmgr_setval("prop3", NULL));
SUCCESS(cfgmgr_close());
NOTFOUND(cfgmgr_open("foo"));
/*
* let's now delete a clause in one go (w/o explicitely
* deleting its properties. Another clause exists as well, it
* is not deleted.
*/
SUCCESS(cfgmgr_newclause("foo"));
val = (cfgmgr_val_t){ .type = NONE };
SUCCESS(cfgmgr_setval("prop1", &val));
val = (cfgmgr_val_t){ .type = STRING, .data.string = "prop2val" };
SUCCESS(cfgmgr_setval("prop2", &val));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 123 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 456 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_newclause("fooo"));
val = (cfgmgr_val_t){ .type = NONE };
SUCCESS(cfgmgr_setval("prop1", &val));
val = (cfgmgr_val_t){ .type = STRING, .data.string = "prop2val" };
SUCCESS(cfgmgr_setval("prop2", &val));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 123 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
val = (cfgmgr_val_t){ .type = UINT32, .data.uint32 = 456 };
SUCCESS(cfgmgr_setnextlistval("prop3", &val));
SUCCESS(cfgmgr_close());
SUCCESS(cfgmgr_openrw("foo"));
SUCCESS(cfgmgr_getval("prop1", &val));
SUCCESS(cfgmgr_getval("prop2", &val));
SUCCESS(cfgmgr_getval("prop3", &val));
SUCCESS(cfgmgr_delclause());
NOTFOUND(cfgmgr_open("foo"));
SUCCESS(cfgmgr_open("fooo"));
SUCCESS(cfgmgr_getval("prop1", &val));
SUCCESS(cfgmgr_getval("prop2", &val));
SUCCESS(cfgmgr_getval("prop3", &val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 123);
SUCCESS(cfgmgr_getnextlistval(&val));
assert_int_equal(val.type, UINT32);
assert_int_equal(val.data.uint32, 456);
assert_int_equal(cfgmgr_getnextlistval(&val), ISC_R_NOMORE);
SUCCESS(cfgmgr_close());
cfgmgr_deinit();
}
static void *
cfgmgr_threads_worker(void *arg) {
sem_t *sems = arg;
/*
* This one open ro, so won't block
*/
cfgmgr_open("foo");
sem_wait(&sems[0]);
sem_post(&sems[1]);
cfgmgr_close();
return NULL;
}
ISC_RUN_TEST_IMPL(cfgmgr_threads) {
pthread_t thread;
sem_t sems[2];
REQUIRE(sem_init(&sems[0], 0, 0) == 0);
REQUIRE(sem_init(&sems[1], 0, 0) == 0);
SUCCESS(cfgmgr_init());
SUCCESS(cfgmgr_newclause("foo"));
SUCCESS(cfgmgr_setval("p", &(cfgmgr_val_t){ .type = NONE }));
cfgmgr_close();
REQUIRE(pthread_create(&thread, 0, cfgmgr_threads_worker, &sems) == 0);
SUCCESS(cfgmgr_openrw("foo"));
REQUIRE(sem_post(&sems[0]) == 0);
REQUIRE(sem_wait(&sems[1]) == 0);
cfgmgr_close();
REQUIRE(pthread_join(thread, NULL) == 0);
cfgmgr_deinit();
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(cfgmgr_rw)
ISC_TEST_ENTRY(cfgmgr_override)
ISC_TEST_ENTRY(cfgmgr_rw_string)
ISC_TEST_ENTRY(cfgmgr_list)
ISC_TEST_ENTRY(cfgmgr_delete)
ISC_TEST_ENTRY(cfgmgr_repeatable_clauses)
ISC_TEST_ENTRY(cfgmgr_nested_clauses)
ISC_TEST_ENTRY(cfgmgr_threads)
ISC_TEST_ENTRY(cfgmgr_parseid)
ISC_TEST_LIST_END
ISC_TEST_MAIN