diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h index 90c94c1b73..adf83d3b88 100644 --- a/lib/isccfg/include/isccfg/cfg.h +++ b/lib/isccfg/include/isccfg/cfg.h @@ -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); /*%< diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h index b91c090130..0ea711a904 100644 --- a/lib/isccfg/include/isccfg/grammar.h +++ b/lib/isccfg/include/isccfg/grammar.h @@ -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. */ }; /*% diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c index 011d830513..e585557657 100644 --- a/lib/isccfg/parser.c +++ b/lib/isccfg/parser.c @@ -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. diff --git a/tests/isccfg/parser_test.c b/tests/isccfg/parser_test.c index a08bf2abe6..581065ac40 100644 --- a/tests/isccfg/parser_test.c +++ b/tests/isccfg/parser_test.c @@ -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