diff --git a/lib/isc/cfgmgr.c b/lib/isc/cfgmgr.c index 51e79f4945..610549eb1e 100644 --- a/lib/isc/cfgmgr.c +++ b/lib/isc/cfgmgr.c @@ -51,6 +51,9 @@ typedef struct { MDB_cursor *builtincursor; MDB_cursor *usercursor; MDB_cursor *runningcursor; + MDB_dbi builtindbi; + MDB_dbi userdbi; + MDB_dbi runningdbi; MDB_txn *txn; bool readonly; isc_cfgmgr_mode_t mode; @@ -75,17 +78,16 @@ static thread_local char isc__cfgmgr_lasterror[LASTERRORLEN]; static bool isc__cfgmgr_openlmdbcursor(const char *dbname, MDB_cursor **cursor, - bool readonly) { - MDB_dbi dbi; + MDB_dbi *dbi, bool readonly) { int result; result = mdb_dbi_open(isc__cfgmgr_ctx.txn, dbname, - readonly ? 0 : MDB_CREATE, &dbi); + readonly ? 0 : MDB_CREATE, dbi); if (result != 0) { return false; } - result = mdb_cursor_open(isc__cfgmgr_ctx.txn, dbi, cursor); + result = mdb_cursor_open(isc__cfgmgr_ctx.txn, *dbi, cursor); if (result != 0) { return false; } @@ -117,11 +119,13 @@ isc__cfgmgr_startlmdbtransaction(bool readonly) { INSIST(isc__cfgmgr_ctx.txn != NULL); if (!isc__cfgmgr_openlmdbcursor( - "builtin", &isc__cfgmgr_ctx.builtincursor, readonly) || + "builtin", &isc__cfgmgr_ctx.builtincursor, + &isc__cfgmgr_ctx.builtindbi, readonly) || !isc__cfgmgr_openlmdbcursor("user", &isc__cfgmgr_ctx.usercursor, - readonly) || - !isc__cfgmgr_openlmdbcursor( - "running", &isc__cfgmgr_ctx.runningcursor, readonly)) + &isc__cfgmgr_ctx.userdbi, readonly) || + !isc__cfgmgr_openlmdbcursor("running", + &isc__cfgmgr_ctx.runningcursor, + &isc__cfgmgr_ctx.runningdbi, readonly)) { isc__cfgmgr_closelmdbcursors(); mdb_txn_abort(isc__cfgmgr_ctx.txn); @@ -268,6 +272,22 @@ isc__cfgmgr_lmdbcursor(void) { UNREACHABLE(); } +static MDB_dbi +isc__cfgmgr_lmdbdbi(void) { + switch (isc__cfgmgr_ctx.mode) { + case ISC_CFGMGR_MODEUNKNOWN: + UNREACHABLE(); + case ISC_CFGMGR_MODEBUILTIN: + return isc__cfgmgr_ctx.builtindbi; + case ISC_CFGMGR_MODEUSER: + return isc__cfgmgr_ctx.userdbi; + case ISC_CFGMGR_MODERUNNING: + return isc__cfgmgr_ctx.runningdbi; + } + + UNREACHABLE(); +} + static void isc__cfgmgr_setlasterror(const char *fmt, ...) { va_list ap; @@ -391,18 +411,34 @@ isc_cfgmgr_rollback(void) { isc__cfgmgr_checkofftransaction(); } -static void -isc__cfgmgr_buildkey(const char *name, bool addleading, char *b, size_t len) { - const char *prefix = "/"; - const char *leading = addleading ? "/" : ""; - - REQUIRE(name != NULL); - - if (ISC_LIST_EMPTY(isc__cfgmgr_ctx.openednodes) == false) { - prefix = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes)->prefix; +static isc__cfgmgr_node_t * +isc__cfgmgr_curnode(void) { + if (ISC_LIST_EMPTY(isc__cfgmgr_ctx.openednodes)) { + return NULL; } - REQUIRE(snprintf(b, len, "%s%s%s", prefix, name, leading) <= (int)len); + return ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes); +} + +static const char * +isc__cfgmgr_curprefix(void) { + const isc__cfgmgr_node_t *node = isc__cfgmgr_curnode(); + + if (node) { + return node->prefix; + } + + return "/"; +} + +static void +isc__cfgmgr_buildkey(const char *name, const char *term, char *b, size_t len) { + REQUIRE(name != NULL); + REQUIRE(term != NULL); + REQUIRE(b != NULL); + + REQUIRE(snprintf(b, len, "%s%s%s", isc__cfgmgr_curprefix(), name, + term) <= (int)len); } void @@ -421,7 +457,7 @@ isc_cfgmgr_open(const char *name) { }; strcpy(node->name, name); - isc__cfgmgr_buildkey(name, true, node->prefix, BUFLEN); + isc__cfgmgr_buildkey(name, "/", node->prefix, BUFLEN); ISC_LIST_APPEND(isc__cfgmgr_ctx.openednodes, node, link); } @@ -431,7 +467,7 @@ isc_cfgmgr_close(void) { isc__cfgmgr_checkopenednodes(); - node = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes); + node = isc__cfgmgr_curnode(); ISC_LIST_UNLINK(isc__cfgmgr_ctx.openednodes, node, link); isc_mem_free(isc__cfgmgr_mctx, node->name); isc_mem_put(isc__cfgmgr_mctx, node->prefix, BUFLEN); @@ -440,19 +476,19 @@ isc_cfgmgr_close(void) { void isc_cfgmgr_delnode(void) { - MDB_val dbkey; - char *prefix; + const char *prefix; isc__cfgmgr_checkopenednodes(); REQUIRE(isc__cfgmgr_ctx.readonly == false); - prefix = ISC_LIST_TAIL(isc__cfgmgr_ctx.openednodes)->prefix; - dbkey = (MDB_val){ .mv_size = strlen(prefix) + 1, .mv_data = prefix }; - + prefix = isc__cfgmgr_curprefix(); do { /* * Recursively delete nested nodes and properties. */ + MDB_val dbkey = { .mv_size = strlen(prefix) + 1, + .mv_data = (void *)prefix }; + int result = mdb_cursor_get(isc__cfgmgr_lmdbcursor(), &dbkey, NULL, MDB_SET_RANGE); @@ -481,7 +517,7 @@ isc_cfgmgr_getval(const char *name, isc_cfgmgr_val_t *value) { REQUIRE(name != NULL); REQUIRE(value != NULL); - isc__cfgmgr_buildkey(name, false, key, sizeof(key)); + isc__cfgmgr_buildkey(name, "", key, sizeof(key)); dbkey = (MDB_val){ .mv_size = strlen(key) + 1, .mv_data = key }; if (mdb_cursor_get(isc__cfgmgr_lmdbcursor(), &dbkey, &dbval, MDB_SET) != @@ -519,7 +555,7 @@ isc_cfgmgr_setval(const char *name, const isc_cfgmgr_val_t *value) { REQUIRE((value != NULL && value->type != ISC_CFGMGR_UNDEFINED) || value == NULL); - isc__cfgmgr_buildkey(name, false, key, sizeof(key)); + isc__cfgmgr_buildkey(name, "", key, sizeof(key)); dbkey = (MDB_val){ .mv_size = strlen(key) + 1, .mv_data = key }; if (value == NULL) { @@ -568,61 +604,115 @@ out: return result; } -void -isc_cfgmgr_foreachnodes(void (*action)(void *state, const char *name), - void *state) { - UNUSED(state); - UNUSED(action); +static void +isc__cfgmgr_foreach(void (*actionnode)(void *state, const char *name), + void (*actionproperty)(void *state, const char *name, + const isc_cfgmgr_val_t *val), + void *state) { + MDB_cursor *cursor; + MDB_val dbkey; + const char *prefix = isc__cfgmgr_curprefix(); + size_t prefixlen = strlen(prefix); + size_t i; + char name[BUFLEN]; + int result; + bool foundnode; + + isc__cfgmgr_checkontransaction(); + REQUIRE(mdb_cursor_open(isc__cfgmgr_ctx.txn, isc__cfgmgr_lmdbdbi(), + &cursor) == 0); + + name[0] = 0; + dbkey.mv_data = (void *)prefix; + dbkey.mv_size = prefixlen; + result = mdb_cursor_get(cursor, &dbkey, NULL, MDB_SET_RANGE); /* - * Not implemented yet, but here's the plan. Let's consider the - * following keys: + * The current implementation is slow: first, it looked up the first key + * which has the same prefix than the current one. Then, the code find + * the first "/" after the prefix to find the next sub-node name. If + * there none, the current entry is a property and it retry on the next + * one. * - * /foo/bar/gee/f - * /foo/bar/gff/g - * /foo/bar/gdd - * /foo/prop - * /foo/zz/a - * /foo/zz/c + * When a sub-node name is found, it checks if it matches the previous + * found name, if it does, it moves on to the next entry (so this is + * typically _lot_ of LMDB lookup to go to the next sub-node, + * especially with zone with lot of properties). * - * Then action will be called once, with "bar" as name, then another - * time with "zz" as name. To be fast, the implementation consist to - * have a internal sub-node in "foo" (or any node with sub-nodes) which - * contains its direct sub-nodes. So here, we would actually also have - * the following key,values: + * When a sub-node name is found and does not match the previous one, + * the action function is called, passing the sub-node name as "name". * - * __internal_idx__/foo/bar/,bar - * __internal_idx__/foo/zz/,zz + * This also enable to iterate over direct properties of the currently + * opened node. This seems particularly useful as base-API to build + * custom configuration printer. (Other approach I attempted was to + * provide a "configuration format" that cfgmgr was reading to validate + * or call user-provided function to build configuration blocks (when a + * node is found) or properties. I might come back to this, but in + * meantime this seems handy to write tests/validate approaches). * - * What _foreachnodes is doing is: - * - * - create a new (local) cursor - * - make a search for "__internal_idx__/foo/" (so it takes the - * current prefix and prefix "__internal_idx__" on it) - * - then, for each entry until we reach the end of entries starting - * by "__internal_idex__/foo/", we call _foreachnodes, passing the - * value as "name". - * - * The caller is free to open the sub-node, or do whathever needed, i.e. - * also call _foreachnodes on its own sub-nodes, because _foreachnodes - * call has its own cursor, it won't mess it up. - * - * In order to maintains this internal __internal__idx__ node, every - * time a property is added to a node "bar", its prefix is taken, then - * "__internal_idx__" is appened to it, making a key. This key is added - * with the value "bar" (current node name). Then this key,val is added - * to current LMDB DB. - * - * The similar logic is applied if the node is removed (taking the node - * prefix, prefixing it with "__internal_idx__" and delete all entries - * starting with this key. However, it is a little bit more subtle: it - * also needs to verify, anytime a property is removed, if there are - * remaining properties for the current node. If not, then it also need - * to remove the "__internal_idx__/nodeprefix" as well. - * - * Because the API enforces the fact that a regular key is always - * starting with "/", the "__internal_idx__" key,values are indeed - * internal and can't never be accessed/overridden by API misuse, nor - * clash with view/zones/any user provided names. + * I tried various alternatives and tricks (i.e. adding "indexes" to + * have a list of sub-nodes), but this is tricky and very time consuming + * to get right, so I leave this problem on the side for now, at least + * the API work and make it possible to move on/experiment with it. */ +again: + if (result != MDB_SUCCESS || + strncmp(prefix, dbkey.mv_data, prefixlen) != 0) + { + goto out; + } + + i = prefixlen; + foundnode = false; + while (i < dbkey.mv_size) { + if (((char *)dbkey.mv_data)[i] == '/') { + foundnode = true; + break; + } + i++; + } + + if (foundnode && actionnode) { + char newname[BUFLEN]; + size_t namelen = i - prefixlen; + + INSIST(namelen < sizeof(newname)); + strncpy(newname, (char *)dbkey.mv_data + prefixlen, namelen); + newname[namelen] = 0; + + if (strcmp(name, newname) != 0) { + actionnode(state, newname); + strcpy(name, newname); + } + } else if (!foundnode && actionproperty) { + char propname[BUFLEN]; + size_t namelen = i - prefixlen; + isc_cfgmgr_val_t val; + + INSIST(namelen < sizeof(propname)); + strncpy(propname, (char *)dbkey.mv_data + prefixlen, namelen); + propname[namelen] = 0; + + REQUIRE(isc_cfgmgr_getval(propname, &val) == ISC_R_SUCCESS); + actionproperty(state, propname, &val); + } + + result = mdb_cursor_get(cursor, &dbkey, NULL, MDB_NEXT); + goto again; + +out: + mdb_cursor_close(cursor); +} + +void +isc_cfgmgr_foreachnodes(void(*action)(void *state, const char *name), + void *state) { + isc__cfgmgr_foreach(action, NULL, state); +} + +void +isc_cfgmgr_foreachproperties(void (*action)(void *state, const char *name, + const isc_cfgmgr_val_t *val), + void *state) { + isc__cfgmgr_foreach(NULL, action, state); } diff --git a/lib/isc/include/isc/cfgmgr.h b/lib/isc/include/isc/cfgmgr.h index a850bed4d6..3b8f7a3f3d 100644 --- a/lib/isc/include/isc/cfgmgr.h +++ b/lib/isc/include/isc/cfgmgr.h @@ -153,6 +153,15 @@ void isc_cfgmgr_foreachnodes(void(*action)(void *state, const char *name), void *state); +/* + * Calls function "action" for each direct property of the currently opened + * node. Must be called under a transaction. + */ +void +isc_cfgmgr_foreachproperties(void (*action)(void *state, const char *name, + const isc_cfgmgr_val_t *val), + void *state); + /* * Starts a read-only transaction and returns ISC_R_SUCCESS. If there is an * issue creating a transaction, ISC_R_FAILURE is returned. Prior calling this diff --git a/tests/isc/cfgmgr_test.c b/tests/isc/cfgmgr_test.c index b27e9eb6dc..55143c074f 100644 --- a/tests/isc/cfgmgr_test.c +++ b/tests/isc/cfgmgr_test.c @@ -772,7 +772,7 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_delete) { isc_cfgmgr_open("foo"); assert_int_equal(result, ISC_R_SUCCESS); - result = isc_cfgmgr_getval("prop1", &val); +result = isc_cfgmgr_getval("prop1", &val); assert_int_equal(result, ISC_R_SUCCESS); result = isc_cfgmgr_getval("prop2", &val); @@ -1319,6 +1319,288 @@ ISC_RUN_TEST_IMPL(isc_cfgmgr_opentests) { isc_cfgmgr_deinit(); } +static void +isc__cfgmgr_foreachnodesaction(void *state, const char *name) { + isc_buffer_t *b = state; + + isc_buffer_printf(b, "%s ", name); +} + +static void +isc__cfgmgr_foreachpropertiesaction(void *state, const char *name, + const isc_cfgmgr_val_t *val) { + isc_buffer_t *b = state; + + isc_buffer_printf(b, "%s=", name); + switch (val->type) { + case ISC_CFGMGR_UINT32: + isc_buffer_printf(b, "%u ", val->uint32); + break; + case ISC_CFGMGR_STRING: + isc_buffer_printf(b, "%s ", val->string); + break; + default: + UNREACHABLE(); + } +} + +static void +isc__cfgmgr_foreachnodesactionreentrant(void *state, const char *name) { + isc_buffer_t *b = state; + + isc_buffer_printf(b, "%s[", name); + isc_cfgmgr_open(name); + isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesactionreentrant, state); + isc_cfgmgr_close(); + isc_buffer_printf(b, "] "); +} + +ISC_RUN_TEST_IMPL(isc_cfgmgr_foreachnodes) { + isc_result_t result; + isc_cfgmgr_val_t val; + + result = isc_cfgmgr_init(mctx, TEST_DBPATH); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_cfgmgr_rwtransaction(); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_cfgmgr_open("bar"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 42 }; + result = isc_cfgmgr_setval("propbar1", &val); + assert_int_equal(result, ISC_R_SUCCESS); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "barstr" }; + result = isc_cfgmgr_setval("another-stuff", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo"); + + isc_cfgmgr_open("foo2"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "foo2str" }; + result = isc_cfgmgr_setval("another-foo-stuff", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo1"); + + isc_cfgmgr_open("foo11"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo12"); + result = isc_cfgmgr_setval("prop", &val); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 456 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo13"); + result = isc_cfgmgr_setval("prop", &val); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 789 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_close(); /* foo1 */ + isc_cfgmgr_close(); /* foo */ + + isc_buffer_t b; + char data[512]; + data[0] = 0; + + /* + * bar doesn't have direct sub-nodes + */ + isc_cfgmgr_open("bar"); + isc_buffer_init(&b, data, sizeof(data)); + isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesaction, &b); + assert_string_equal(b.base, ""); + isc_cfgmgr_close(); + + /* + * foo1 has 3 direct sub-nodes + */ + isc_cfgmgr_open("foo"); + isc_cfgmgr_open("foo1"); + isc_buffer_init(&b, data, sizeof(data)); + isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesaction, &b); + assert_string_equal(b.base, "foo11 foo12 foo13 "); + isc_cfgmgr_close(); + isc_cfgmgr_close(); + + /* + * isc_cfgmgr_foreachnodes is re-entrant + */ + isc_cfgmgr_open("foo"); + isc_buffer_init(&b, data, sizeof(data)); + isc_cfgmgr_foreachnodes(isc__cfgmgr_foreachnodesactionreentrant, &b); + assert_string_equal(b.base, "foo1[foo11[] foo12[] foo13[] ] foo2[] "); + isc_cfgmgr_close(); + + /* + * foreachproperties + */ + isc_cfgmgr_open("bar"); + isc_buffer_init(&b, data, sizeof(data)); + isc_cfgmgr_foreachproperties(isc__cfgmgr_foreachpropertiesaction, &b); + assert_string_equal(b.base, "another-stuff=barstr propbar1=42 "); + isc_cfgmgr_close(); + + isc_cfgmgr_rollback(); + isc_cfgmgr_deinit(); +} + +typedef struct isc__cfgmgr_dump isc__cfgmgr_dump_t; +struct isc__cfgmgr_dump { + size_t indent; + size_t lvl; + isc_buffer_t buffer; + char data[512]; +}; + +static void +isc__cfgmgr_dumptestsprop(void *state, const char *name, + const isc_cfgmgr_val_t *val) { + isc__cfgmgr_dump_t *dump = state; + + for (size_t i = 0; i < dump->lvl * dump->indent; i++) { + isc_buffer_putstr(&dump->buffer, " "); + } + + isc_buffer_printf(&dump->buffer, "%s ", name); + switch (val->type) { + case ISC_CFGMGR_UINT32: + isc_buffer_printf(&dump->buffer, "%u", val->uint32); + break; + case ISC_CFGMGR_STRING: + isc_buffer_printf(&dump->buffer, "%s", val->string); + break; + default: + UNREACHABLE(); + } + isc_buffer_putstr(&dump->buffer, ";\n"); +} + +static void +isc__cfgmgr_dumptestnode(void *state, const char *name) { + isc__cfgmgr_dump_t *dump = state; + + for (size_t i = 0; i < dump->lvl * dump->indent; i++) { + isc_buffer_putstr(&dump->buffer, " "); + } + + isc_buffer_printf(&dump->buffer, "%s {\n", name); + + dump->lvl++; + isc_cfgmgr_open(name); + isc_cfgmgr_foreachproperties(isc__cfgmgr_dumptestsprop, state); + isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, state); + isc_cfgmgr_close(); + dump->lvl--; + + for (size_t i = 0; i < dump->lvl * dump->indent; i++) { + isc_buffer_putstr(&dump->buffer, " "); + } + isc_buffer_printf(&dump->buffer, "};\n"); +} + +ISC_RUN_TEST_IMPL(isc_cfgmgr_dumptests) { + isc_result_t result; + isc_cfgmgr_val_t val; + + result = isc_cfgmgr_init(mctx, TEST_DBPATH); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_cfgmgr_rwtransaction(); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_cfgmgr_open("bar"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 42 }; + result = isc_cfgmgr_setval("propbar1", &val); + assert_int_equal(result, ISC_R_SUCCESS); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "barstr" }; + result = isc_cfgmgr_setval("another-stuff", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo"); + + isc_cfgmgr_open("foo2"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_STRING, .string = "foo2str" }; + result = isc_cfgmgr_setval("another-foo-stuff", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo1"); + + isc_cfgmgr_open("foo11"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo12"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 }; + result = isc_cfgmgr_setval("prop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 456 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_open("foo13"); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 123 }; + result = isc_cfgmgr_setval("prop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + val = (isc_cfgmgr_val_t){ .type = ISC_CFGMGR_UINT32, .uint32 = 789 }; + result = isc_cfgmgr_setval("fooprop", &val); + assert_int_equal(result, ISC_R_SUCCESS); + isc_cfgmgr_close(); + + isc_cfgmgr_close(); /* foo1 */ + isc_cfgmgr_close(); /* foo */ + + isc__cfgmgr_dump_t dump = { + .lvl = 0, + .indent = 8, + }; + isc_buffer_init(&dump.buffer, dump.data, sizeof(dump.data)); + dump.data[0] = 0; + + /* TO BE FIXED... */ + isc_cfgmgr_foreachnodes(isc__cfgmgr_dumptestnode, &dump); + + const char expected[] = "bar {\n" + " another-stuff barstr;\n" + " propbar1 42;\n" + "};\n" + "foo {\n" + " foo1 {\n" + " foo11 {\n" + " fooprop 123;\n" + " };\n" + " foo12 {\n" + " fooprop 456;\n" + " prop 123;\n" + " };\n" + " foo13 {\n" + " fooprop 789;\n" + " prop 123;\n" + " };\n" + " };\n" + " foo2 {\n" + " another-foo-stuff foo2str;\n" + " };\n" + "};\n"; + assert_string_equal(dump.buffer.base, expected); + + isc_cfgmgr_rollback(); + isc_cfgmgr_deinit(); +} + // ISC_RUN_TEST_IMPL(isc_cfgmgr_builtintests) { // isc_result_t result; // isc_cfgmgr_val_t val; @@ -1786,6 +2068,8 @@ ISC_TEST_ENTRY(isc_cfgmgr_delete) ISC_TEST_ENTRY(isc_cfgmgr_nested_nodes) ISC_TEST_ENTRY(isc_cfgmgr_threads) ISC_TEST_ENTRY(isc_cfgmgr_opentests) +ISC_TEST_ENTRY(isc_cfgmgr_foreachnodes) +ISC_TEST_ENTRY(isc_cfgmgr_dumptests) // ISC_TEST_ENTRY(isc_cfgmgr_validationtest) // ISC_TEST_ENTRY(isc_cfgmgr_getparent)