new: dev: introduce cfg_obj_clone to clone a config tree

Introduce `cfg_obj_clone` which takes a `cfg_obj_t` node and clones it.
it allocates a new node, copies its scalar values and recursively
allocates child nodes, copying their scalar values as well and so on.
    
Internally, a new method `cfg_copyfunc_t` copy is added in `cfg_rep_t`,
which enables implementing a copy function specific for each
representation type a node can hold.

This is pre-require work for MR !11121 !11122 !11123

Merge branch 'colin/effective-config-clone' into 'main'

See merge request isc-projects/bind9!11124
This commit is contained in:
Colin Vidal 2025-10-27 22:15:39 +01:00
commit d951cedd02
4 changed files with 274 additions and 14 deletions

View file

@ -131,6 +131,23 @@ cfg_parser_currentfile(cfg_parser_t *pctx);
* existent.
*/
void
cfg_obj_clone(const cfg_obj_t *source, cfg_obj_t **target);
/*%<
* Allocate a new configuration object and copy the value from the `source`
* object into the newly allocated object. The copy is a "deep" copy, i.e. if
* `source` is a list, map, tuple, etc, it recursively clones the children
* and copies their values as well. The cloned node is attached to the
* memory context of the source node.
*
* Require:
* \li 'source' is a valid cfg_obj_t with copy function set.
* \li 'target' is non-NULL and '*target' is NULL.
*
* Ensures:
* \li 'target' contains the cloned object.
*/
bool
cfg_obj_isvoid(const cfg_obj_t *obj);
/*%<

View file

@ -100,6 +100,7 @@ typedef isc_result_t (*cfg_parsefunc_t)(cfg_parser_t *, const cfg_type_t *type,
typedef void (*cfg_printfunc_t)(cfg_printer_t *, const cfg_obj_t *);
typedef void (*cfg_docfunc_t)(cfg_printer_t *, const cfg_type_t *);
typedef void (*cfg_freefunc_t)(cfg_obj_t *);
typedef void (*cfg_copyfunc_t)(cfg_obj_t *to, const cfg_obj_t *from);
/*
* Structure definitions
@ -169,6 +170,7 @@ struct cfg_netprefix {
struct cfg_rep {
const char *name; /*%< For debugging only */
cfg_freefunc_t free; /*%< How to free this kind of data. */
cfg_copyfunc_t copy; /*%< Deep copy of the node. */
};
/*%

View file

@ -160,26 +160,181 @@ static void
doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type);
#endif /* HAVE_GEOIP2 */
void
cfg_obj_clone(const cfg_obj_t *source, cfg_obj_t **target) {
REQUIRE(source != NULL);
REQUIRE(source->type != NULL);
REQUIRE(source->type->rep != NULL);
REQUIRE(source->type->rep->copy != NULL);
REQUIRE(target != NULL && *target == NULL);
cfg_obj_create(source->mctx, source->file, source->line, source->type,
target);
source->type->rep->copy(*target, source);
}
static void
copy_uint32(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.uint32 = from->value.uint32;
}
static void
copy_uint64(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.uint64 = from->value.uint64;
}
static void
copy_boolean(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.boolean = from->value.boolean;
}
static void
copy_sockaddr(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.sockaddr = from->value.sockaddr;
}
static void
copy_sockaddrtls(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.sockaddrtls.sockaddr = from->value.sockaddrtls.sockaddr;
if (from->value.sockaddrtls.tls.base != NULL) {
size_t len = from->value.sockaddrtls.tls.length;
to->value.sockaddrtls.tls.base = isc_mem_get(to->mctx, len + 1);
to->value.sockaddrtls.tls.length = len;
memmove(to->value.sockaddrtls.tls.base,
from->value.sockaddrtls.tls.base, len + 1);
}
}
static void
copy_netprefix(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.netprefix = from->value.netprefix;
}
static void
copy_duration(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.duration = from->value.duration;
}
static void
copy_string(cfg_obj_t *to, const cfg_obj_t *from) {
to->value.string.length = from->value.string.length;
to->value.string.base = isc_mem_get(to->mctx,
to->value.string.length + 1);
memmove(to->value.string.base, from->value.string.base,
to->value.string.length + 1);
}
static void
copy_map_destroy(char *key, unsigned int type, isc_symvalue_t symval,
void *arg) {
cfg_obj_t *obj = symval.as_pointer;
UNUSED(key);
UNUSED(type);
UNUSED(arg);
cfg_obj_detach(&obj);
}
static bool
copy_map_add(char *key, unsigned int type, isc_symvalue_t value, void *arg) {
cfg_obj_t *to = arg;
cfg_obj_t *toelt = NULL;
/*
* Only `as_pointer` is used to store the cfg_obj_t object (see
* cfg_map_parsebody)
*/
cfg_obj_clone(value.as_pointer, &toelt);
value.as_pointer = toelt;
INSIST(isc_symtab_define(to->value.map.symtab, key, type, value,
isc_symexists_reject) == ISC_R_SUCCESS);
/*
* Do not delete the existing element from `from` table.
*/
return false;
}
static void
copy_map(cfg_obj_t *to, const cfg_obj_t *from) {
if (from->value.map.id != NULL) {
cfg_obj_clone(from->value.map.id, &to->value.map.id);
}
isc_symtab_create(to->mctx, copy_map_destroy, NULL, false,
&to->value.map.symtab);
isc_symtab_foreach(from->value.map.symtab, copy_map_add, to);
/*
* clausesets are statically defined
*/
to->value.map.clausesets = from->value.map.clausesets;
}
static void
copy_list(cfg_obj_t *to, const cfg_obj_t *from) {
const cfg_listelt_t *fromelt = cfg_list_first(from);
ISC_LIST_INIT(to->value.list);
while (fromelt != NULL) {
cfg_listelt_t *toelt = isc_mem_get(to->mctx, sizeof(*toelt));
*toelt = (cfg_listelt_t){ .link = ISC_LINK_INITIALIZER };
cfg_obj_clone(fromelt->obj, &toelt->obj);
ISC_LIST_APPEND(to->value.list, toelt, link);
fromelt = cfg_list_next(fromelt);
}
}
static void
copy_tuple(cfg_obj_t *to, const cfg_obj_t *from) {
const cfg_tuplefielddef_t *fields = from->type->of;
const cfg_tuplefielddef_t *field;
size_t size = 0;
for (field = fields; field->name != NULL; field++) {
size++;
}
to->value.tuple = isc_mem_cget(to->mctx, size, sizeof(cfg_obj_t *));
for (size_t j = 0; j < size; j++) {
cfg_obj_clone(from->value.tuple[j], &to->value.tuple[j]);
}
}
static void
copy_noop(cfg_obj_t *to, const cfg_obj_t *from) {
UNUSED(to);
UNUSED(from);
}
/*
* Data representations. These correspond to members of the
* "value" union in struct cfg_obj (except "void", which does
* not need a union member).
*/
cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop };
cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
cfg_rep_t cfg_rep_string = { "string", free_string };
cfg_rep_t cfg_rep_boolean = { "boolean", free_noop };
cfg_rep_t cfg_rep_map = { "map", free_map };
cfg_rep_t cfg_rep_list = { "list", free_list };
cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple };
cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop };
cfg_rep_t cfg_rep_sockaddrtls = { "sockaddrtls", free_sockaddrtls };
cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop };
cfg_rep_t cfg_rep_void = { "void", free_noop };
cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop };
cfg_rep_t cfg_rep_percentage = { "percentage", free_noop };
cfg_rep_t cfg_rep_duration = { "duration", free_noop };
cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop, copy_uint32 };
cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop, copy_uint64 };
cfg_rep_t cfg_rep_string = { "string", free_string, copy_string };
cfg_rep_t cfg_rep_boolean = { "boolean", free_noop, copy_boolean };
cfg_rep_t cfg_rep_map = { "map", free_map, copy_map };
cfg_rep_t cfg_rep_list = { "list", free_list, copy_list };
cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple, copy_tuple };
cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop, copy_sockaddr };
cfg_rep_t cfg_rep_sockaddrtls = { "sockaddrtls", free_sockaddrtls,
copy_sockaddrtls };
cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop, copy_netprefix };
cfg_rep_t cfg_rep_void = { "void", free_noop, copy_noop };
cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop, copy_uint32 };
cfg_rep_t cfg_rep_percentage = { "percentage", free_noop, copy_uint32 };
cfg_rep_t cfg_rep_duration = { "duration", free_noop, copy_duration };
/*
* Configuration type definitions.

View file

@ -206,12 +206,98 @@ ISC_RUN_TEST_IMPL(cfg_map_nextclause) {
} while (name != NULL);
}
static void
cfg_clone_copy_dumpconf(void *closure, const char *text, int textlen) {
isc_buffer_putmem((isc_buffer_t *)closure, (const unsigned char *)text,
textlen);
}
ISC_RUN_TEST_IMPL(cfg_clone_copy) {
cfg_obj_t *orig = NULL;
cfg_obj_t *clone = NULL;
isc_result_t result;
isc_buffer_t buf;
isc_buffer_t dumpb1;
char dumpbdata1[10024];
isc_buffer_t dumpb2;
char dumpbdata2[10024];
/*
* This is a modified subset of the default conf which contains
* all the possible types cloned and copied.
*/
static char conf[] = "\
options {\n\
answer-cookie yes;\n\
cookie-algorithm siphash24;\n\
dump-file \"named_dump.db\";\n\
notify-rate 20;\n\
allow-recursion {\n\
\"localhost\";\n\
\"localnets\";\n\
};\n\
prefetch 2 9;\n\
check-dup-records warn;\n\
max-ixfr-ratio 100%;\n\
};\n\
remote-servers \"foo\" {\n\
2801:1b8:10::b;\n\
192.0.32.132;\n\
};\n\
view \"_bind\" chaos {\n\
zone \"version.bind\" chaos {\n\
type primary;\n\
database \"_builtin version\";\n\
};\n\
max-cache-size 2097152;\n\
rate-limit {\n\
min-table-size 10;\n\
slip 0;\n\
};\n\
};\n";
isc_buffer_init(&buf, conf, sizeof(conf));
isc_buffer_add(&buf, sizeof(conf) - 1);
result = cfg_parse_buffer(isc_g_mctx, &buf, "", 0, &cfg_type_namedconf,
0, &orig);
assert_int_equal(result, ISC_R_SUCCESS);
isc_buffer_init(&dumpb1, dumpbdata1, sizeof(dumpbdata1));
cfg_printx(orig, 0, cfg_clone_copy_dumpconf, &dumpb1);
isc_buffer_putuint8(&dumpb1, 0);
/*
* The point of the test is not really to test the stringify code of the
* cfg_obj_t tree, but let's do it as a sanity check first.
*/
assert_int_equal(strcmp(conf, dumpbdata1), 0);
/*
* The original tree can be freed anytime, it is not connected in any
* way to the clone.
*/
cfg_obj_clone(orig, &clone);
cfg_obj_detach(&orig);
/*
* Dumping the clone and comparing its output to the original
* dump of the orinal config verify-ish the two assumptions above.
*/
isc_buffer_init(&dumpb2, dumpbdata2, sizeof(dumpbdata2));
cfg_printx(clone, 0, cfg_clone_copy_dumpconf, &dumpb2);
assert_int_equal(strcmp(dumpbdata1, dumpbdata2), 0);
cfg_obj_detach(&clone);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(addzoneconf)
ISC_TEST_ENTRY(parse_buffer)
ISC_TEST_ENTRY(cfg_map_firstclause)
ISC_TEST_ENTRY(cfg_map_nextclause)
ISC_TEST_ENTRY(cfg_clone_copy)
ISC_TEST_LIST_END