diff options
Diffstat (limited to 'src/knot/conf/base.c')
-rw-r--r-- | src/knot/conf/base.c | 997 |
1 files changed, 997 insertions, 0 deletions
diff --git a/src/knot/conf/base.c b/src/knot/conf/base.c new file mode 100644 index 0000000..279e0df --- /dev/null +++ b/src/knot/conf/base.c @@ -0,0 +1,997 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <urcu.h> + +#include "knot/conf/base.h" +#include "knot/conf/confdb.h" +#include "knot/conf/module.h" +#include "knot/conf/tools.h" +#include "knot/common/log.h" +#include "knot/nameserver/query_module.h" +#include "libknot/libknot.h" +#include "libknot/yparser/ypformat.h" +#include "libknot/yparser/yptrafo.h" +#include "contrib/files.h" +#include "contrib/sockaddr.h" +#include "contrib/string.h" + +// The active configuration. +conf_t *s_conf; + +conf_t* conf(void) { + return s_conf; +} + +static int init_and_check( + conf_t *conf, + conf_flag_t flags) +{ + if (conf == NULL) { + return KNOT_EINVAL; + } + + knot_db_txn_t txn; + unsigned txn_flags = (flags & CONF_FREADONLY) ? KNOT_DB_RDONLY : 0; + int ret = conf->api->txn_begin(conf->db, &txn, txn_flags); + if (ret != KNOT_EOK) { + return ret; + } + + // Initialize the database. + if (!(flags & CONF_FREADONLY)) { + ret = conf_db_init(conf, &txn, false); + if (ret != KNOT_EOK) { + conf->api->txn_abort(&txn); + return ret; + } + } + + // Check the database. + if (!(flags & CONF_FNOCHECK)) { + ret = conf_db_check(conf, &txn); + if (ret < KNOT_EOK) { + conf->api->txn_abort(&txn); + return ret; + } + } + + if (flags & CONF_FREADONLY) { + conf->api->txn_abort(&txn); + return KNOT_EOK; + } else { + return conf->api->txn_commit(&txn); + } +} + +int conf_refresh_txn( + conf_t *conf) +{ + if (conf == NULL) { + return KNOT_EINVAL; + } + + // Close previously opened transaction. + conf->api->txn_abort(&conf->read_txn); + + return conf->api->txn_begin(conf->db, &conf->read_txn, KNOT_DB_RDONLY); +} + +void conf_refresh_hostname( + conf_t *conf) +{ + if (conf == NULL) { + return; + } + + free(conf->hostname); + conf->hostname = sockaddr_hostname(); + if (conf->hostname == NULL) { + // Empty hostname fallback, NULL cannot be passed to strlen! + conf->hostname = strdup(""); + } +} + +static int infinite_adjust( + int timeout) +{ + return (timeout > 0) ? timeout : -1; +} + +static void init_cache( + conf_t *conf, + bool reinit_cache) +{ + /* + * For UDP, TCP, XDP, and background workers, cache the number of running + * workers. Cache the setting of TCP reuseport too. These values + * can't change in runtime, while config data can. + */ + + static bool first_init = true; + static bool running_tcp_reuseport; + static bool running_socket_affinity; + static size_t running_udp_threads; + static size_t running_tcp_threads; + static size_t running_xdp_threads; + static size_t running_bg_threads; + + if (first_init || reinit_cache) { + running_tcp_reuseport = conf_tcp_reuseport(conf); + running_socket_affinity = conf_socket_affinity(conf); + running_udp_threads = conf_udp_threads(conf); + running_tcp_threads = conf_tcp_threads(conf); + running_xdp_threads = conf_xdp_threads(conf); + running_bg_threads = conf_bg_threads(conf); + + first_init = false; + } + + conf_val_t val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD_IPV4); + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_IPV4_UDP_PAYLOAD); + } + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD); + } + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD); + } + conf->cache.srv_udp_max_payload_ipv4 = conf_int(&val); + + val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD_IPV6); + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_IPV6_UDP_PAYLOAD); + } + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD); + } + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD); + } + conf->cache.srv_udp_max_payload_ipv6 = conf_int(&val); + + val = conf_get(conf, C_SRV, C_TCP_IDLE_TIMEOUT); + conf->cache.srv_tcp_idle_timeout = conf_int(&val); + + val = conf_get(conf, C_SRV, C_TCP_IO_TIMEOUT); + conf->cache.srv_tcp_io_timeout = infinite_adjust(conf_int(&val)); + + val = conf_get(conf, C_SRV, C_TCP_RMT_IO_TIMEOUT); + if (val.code == KNOT_EOK) { + conf->cache.srv_tcp_remote_io_timeout = infinite_adjust(conf_int(&val)); + } else { + int timeout = conf_int(&val); // New default value. + conf_val_t legacy = conf_get(conf, C_SRV, C_TCP_REPLY_TIMEOUT); + if (legacy.code == KNOT_EOK) { + timeout = 1000 * conf_int(&legacy); // Explicit legacy value. + } + conf->cache.srv_tcp_remote_io_timeout = infinite_adjust(timeout); + } + + conf->cache.srv_tcp_reuseport = running_tcp_reuseport; + + conf->cache.srv_socket_affinity = running_socket_affinity; + + conf->cache.srv_udp_threads = running_udp_threads; + + conf->cache.srv_tcp_threads = running_tcp_threads; + + conf->cache.srv_xdp_threads = running_xdp_threads; + + conf->cache.srv_bg_threads = running_bg_threads; + + conf->cache.srv_tcp_max_clients = conf_tcp_max_clients(conf); + + val = conf_get(conf, C_CTL, C_TIMEOUT); + conf->cache.ctl_timeout = conf_int(&val) * 1000; + /* infinite_adjust() call isn't needed, 0 is adjusted later anyway. */ + + conf->cache.srv_nsid = conf_get(conf, C_SRV, C_NSID); + + val = conf_get(conf, C_SRV, C_ECS); + conf->cache.srv_ecs = conf_bool(&val); + + val = conf_get(conf, C_SRV, C_ANS_ROTATION); + conf->cache.srv_ans_rotate = conf_bool(&val); +} + +int conf_new( + conf_t **conf, + const yp_item_t *schema, + const char *db_dir, + size_t max_conf_size, + conf_flag_t flags) +{ + if (conf == NULL) { + return KNOT_EINVAL; + } + + conf_t *out = malloc(sizeof(conf_t)); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, sizeof(conf_t)); + + // Initialize config schema. + int ret = yp_schema_copy(&out->schema, schema); + if (ret != KNOT_EOK) { + goto new_error; + } + + // Initialize query modules list. + out->query_modules = malloc(sizeof(list_t)); + if (out->query_modules == NULL) { + ret = KNOT_ENOMEM; + goto new_error; + } + init_list(out->query_modules); + + // Set the DB api. + out->api = knot_db_lmdb_api(); + struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER; + lmdb_opts.mapsize = max_conf_size; + lmdb_opts.maxreaders = CONF_MAX_DB_READERS; + lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS; + + // Open the database. + if (db_dir == NULL) { + // Prepare a temporary database. + char tpl[] = "/tmp/knot-confdb.XXXXXX"; + lmdb_opts.path = mkdtemp(tpl); + if (lmdb_opts.path == NULL) { + CONF_LOG(LOG_ERR, "failed to create temporary directory (%s)", + knot_strerror(knot_map_errno())); + ret = KNOT_ENOMEM; + goto new_error; + } + + ret = out->api->init(&out->db, NULL, &lmdb_opts); + + // Remove the database to ensure it is temporary. + if (!remove_path(lmdb_opts.path)) { + CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'", + lmdb_opts.path); + } + } else { + // Set the specified database. + lmdb_opts.path = db_dir; + + // Set the read-only mode. + if (flags & CONF_FREADONLY) { + lmdb_opts.flags.env |= KNOT_DB_LMDB_RDONLY; + } + + ret = out->api->init(&out->db, NULL, &lmdb_opts); + } + if (ret != KNOT_EOK) { + goto new_error; + } + + // Initialize and check the database. + ret = init_and_check(out, flags); + if (ret != KNOT_EOK) { + goto new_error; + } + + // Open common read-only transaction. + ret = conf_refresh_txn(out); + if (ret != KNOT_EOK) { + goto new_error; + } + + // Cache the current hostname. + if (!(flags & CONF_FNOHOSTNAME)) { + conf_refresh_hostname(out); + } + + // Initialize cached values. + init_cache(out, false); + + // Load module schemas. + if (flags & (CONF_FREQMODULES | CONF_FOPTMODULES)) { + ret = conf_mod_load_common(out); + if (ret != KNOT_EOK && (flags & CONF_FREQMODULES)) { + goto new_error; + } + + for (conf_iter_t iter = conf_iter(out, C_MODULE); + iter.code == KNOT_EOK; conf_iter_next(out, &iter)) { + conf_val_t id = conf_iter_id(out, &iter); + conf_val_t file = conf_id_get(out, C_MODULE, C_FILE, &id); + ret = conf_mod_load_extra(out, conf_str(&id), conf_str(&file), false); + if (ret != KNOT_EOK && (flags & CONF_FREQMODULES)) { + conf_iter_finish(out, &iter); + goto new_error; + } + } + + conf_mod_load_purge(out, false); + } + + *conf = out; + + return KNOT_EOK; +new_error: + conf_free(out); + + return ret; +} + +int conf_clone( + conf_t **conf) +{ + if (conf == NULL || s_conf == NULL) { + return KNOT_EINVAL; + } + + conf_t *out = malloc(sizeof(conf_t)); + if (out == NULL) { + return KNOT_ENOMEM; + } + memset(out, 0, sizeof(conf_t)); + + // Initialize config schema. + int ret = yp_schema_copy(&out->schema, s_conf->schema); + if (ret != KNOT_EOK) { + free(out); + return ret; + } + + // Set shared items. + out->api = s_conf->api; + out->db = s_conf->db; + + // Initialize query modules list. + out->query_modules = malloc(sizeof(list_t)); + if (out->query_modules == NULL) { + yp_schema_free(out->schema); + free(out); + return KNOT_ENOMEM; + } + init_list(out->query_modules); + + // Open common read-only transaction. + ret = conf_refresh_txn(out); + if (ret != KNOT_EOK) { + free(out->query_modules); + yp_schema_free(out->schema); + free(out); + return ret; + } + + // Copy the filename. + if (s_conf->filename != NULL) { + out->filename = strdup(s_conf->filename); + } + + // Copy the hostname. + if (s_conf->hostname != NULL) { + out->hostname = strdup(s_conf->hostname); + } + + out->catalog = s_conf->catalog; + + // Initialize cached values. + init_cache(out, false); + + out->is_clone = true; + + *conf = out; + + return KNOT_EOK; +} + +conf_t *conf_update( + conf_t *conf, + conf_update_flag_t flags) +{ + // Remove the clone flag for new master configuration. + if (conf != NULL) { + conf->is_clone = false; + + if ((flags & CONF_UPD_FCONFIO) && s_conf != NULL) { + conf->io.flags = s_conf->io.flags; + conf->io.zones = s_conf->io.zones; + } + if ((flags & CONF_UPD_FMODULES) && s_conf != NULL) { + free(conf->query_modules); + conf->query_modules = s_conf->query_modules; + conf->query_plan = s_conf->query_plan; + } + } + + conf_t **current_conf = &s_conf; + conf_t *old_conf = rcu_xchg_pointer(current_conf, conf); + + synchronize_rcu(); + + if (old_conf != NULL) { + // Remove the clone flag if a single configuration. + old_conf->is_clone = (conf != NULL) ? true : false; + + if (flags & CONF_UPD_FCONFIO) { + old_conf->io.zones = NULL; + } + if (flags & CONF_UPD_FMODULES) { + old_conf->query_modules = NULL; + old_conf->query_plan = NULL; + } + if (!(flags & CONF_UPD_FNOFREE)) { + conf_free(old_conf); + old_conf = NULL; + } + } + + return old_conf; +} + +void conf_free( + conf_t *conf) +{ + if (conf == NULL) { + return; + } + + yp_schema_free(conf->schema); + free(conf->filename); + free(conf->hostname); + if (conf->api != NULL) { + conf->api->txn_abort(&conf->read_txn); + } + + if (conf->io.txn != NULL && conf->api != NULL) { + conf->api->txn_abort(conf->io.txn_stack); + } + if (conf->io.zones != NULL) { + trie_free(conf->io.zones); + } + + conf_mod_load_purge(conf, false); + conf_deactivate_modules(conf->query_modules, &conf->query_plan); + free(conf->query_modules); + conf_mod_unload_shared(conf); + + if (!conf->is_clone) { + if (conf->api != NULL) { + conf->api->deinit(conf->db); + } + } + + free(conf); +} + +#define CONF_LOG_LINE(file, line, msg, ...) do { \ + CONF_LOG(LOG_ERR, "%s%s%sline %zu" msg, \ + (file != NULL ? "file '" : ""), (file != NULL ? file : ""), \ + (file != NULL ? "', " : ""), line, ##__VA_ARGS__); \ + } while (0) + +static void log_parser_err( + yp_parser_t *parser, + int ret) +{ + if (parser->event == YP_ENULL) { + CONF_LOG_LINE(parser->file.name, parser->line_count, + " (%s)", knot_strerror(ret)); + } else { + CONF_LOG_LINE(parser->file.name, parser->line_count, + ", item '%s'%s%.*s%s (%s)", parser->key, + (parser->data_len > 0) ? ", value '" : "", + (int)parser->data_len, + (parser->data_len > 0) ? parser->data : "", + (parser->data_len > 0) ? "'" : "", + knot_strerror(ret)); + } +} + +static void log_parser_schema_err( + yp_parser_t *parser, + int ret) +{ + // Emit better message for 'unknown module' error. + if (ret == KNOT_YP_EINVAL_ITEM && parser->event == YP_EKEY0 && + strncmp(parser->key, KNOTD_MOD_NAME_PREFIX, strlen(KNOTD_MOD_NAME_PREFIX)) == 0) { + CONF_LOG_LINE(parser->file.name, parser->line_count, + ", unknown module '%s'", parser->key); + } else { + log_parser_err(parser, ret); + } +} + +static void log_call_err( + yp_parser_t *parser, + knotd_conf_check_args_t *args, + int ret) +{ + CONF_LOG_LINE(args->extra->file_name, args->extra->line, + ", item '%s'%s%s%s (%s)", args->item->name + 1, + (parser->data_len > 0) ? ", value '" : "", + (parser->data_len > 0) ? parser->data : "", + (parser->data_len > 0) ? "'" : "", + (args->err_str != NULL) ? args->err_str : knot_strerror(ret)); +} + +static void log_prev_err( + knotd_conf_check_args_t *args, + int ret) +{ + char buff[512] = { 0 }; + size_t len = sizeof(buff); + + // Get the previous textual identifier. + if ((args->item->flags & YP_FMULTI) != 0) { + if (yp_item_to_txt(args->item->var.g.id, args->id, args->id_len, + buff, &len, YP_SNOQUOTE) != KNOT_EOK) { + buff[0] = '\0'; + } + } + + CONF_LOG_LINE(args->extra->file_name, args->extra->line, + ", %s '%s' (%s)", args->item->name + 1, buff, + args->err_str != NULL ? args->err_str : knot_strerror(ret)); +} + +static int finalize_previous_section( + conf_t *conf, + knot_db_txn_t *txn, + yp_parser_t *parser, + yp_check_ctx_t *ctx) +{ + yp_node_t *node = &ctx->nodes[0]; + + // Return if no previous section or include or empty multi-section. + if (node->item == NULL || node->item->type != YP_TGRP || + (node->id_len == 0 && (node->item->flags & YP_FMULTI) != 0)) { + return KNOT_EOK; + } + + knotd_conf_check_extra_t extra = { + .conf = conf, + .txn = txn, + .file_name = parser->file.name, + .line = parser->line_count + }; + knotd_conf_check_args_t args = { + .item = node->item, + .id = node->id, + .id_len = node->id_len, + .data = node->data, + .data_len = node->data_len, + .extra = &extra + }; + + int ret = conf_exec_callbacks(&args); + if (ret != KNOT_EOK) { + log_prev_err(&args, ret); + } + + return ret; +} + +static int finalize_item( + conf_t *conf, + knot_db_txn_t *txn, + yp_parser_t *parser, + yp_check_ctx_t *ctx) +{ + yp_node_t *node = &ctx->nodes[ctx->current]; + + // Section callbacks are executed before another section. + if (node->item->type == YP_TGRP && node->id_len == 0) { + return KNOT_EOK; + } + + knotd_conf_check_extra_t extra = { + .conf = conf, + .txn = txn, + .file_name = parser->file.name, + .line = parser->line_count + }; + knotd_conf_check_args_t args = { + .item = (parser->event == YP_EID) ? node->item->var.g.id : node->item, + .id = node->id, + .id_len = node->id_len, + .data = node->data, + .data_len = node->data_len, + .extra = &extra + }; + + int ret = conf_exec_callbacks(&args); + if (ret != KNOT_EOK) { + log_call_err(parser, &args, ret); + } + + return ret; +} + +int conf_parse( + conf_t *conf, + knot_db_txn_t *txn, + const char *input, + bool is_file) +{ + if (conf == NULL || txn == NULL || input == NULL) { + return KNOT_EINVAL; + } + + yp_parser_t *parser = malloc(sizeof(yp_parser_t)); + if (parser == NULL) { + return KNOT_ENOMEM; + } + yp_init(parser); + + int ret; + + // Set parser source. + if (is_file) { + ret = yp_set_input_file(parser, input); + } else { + ret = yp_set_input_string(parser, input, strlen(input)); + } + if (ret != KNOT_EOK) { + CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)", + input, knot_strerror(ret)); + goto parse_error; + } + + // Initialize parser check context. + yp_check_ctx_t *ctx = yp_schema_check_init(&conf->schema); + if (ctx == NULL) { + ret = KNOT_ENOMEM; + goto parse_error; + } + + int check_ret = KNOT_EOK; + + // Parse the configuration. + while ((ret = yp_parse(parser)) == KNOT_EOK) { + if (parser->event == YP_EKEY0 || parser->event == YP_EID) { + check_ret = finalize_previous_section(conf, txn, parser, ctx); + if (check_ret != KNOT_EOK) { + break; + } + } + + check_ret = yp_schema_check_parser(ctx, parser); + if (check_ret != KNOT_EOK) { + log_parser_schema_err(parser, check_ret); + break; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + yp_node_t *parent = node->parent; + + if (parent == NULL) { + check_ret = conf_db_set(conf, txn, node->item->name, + NULL, node->id, node->id_len, + node->data, node->data_len); + } else { + check_ret = conf_db_set(conf, txn, parent->item->name, + node->item->name, parent->id, + parent->id_len, node->data, + node->data_len); + } + if (check_ret != KNOT_EOK) { + log_parser_err(parser, check_ret); + break; + } + + check_ret = finalize_item(conf, txn, parser, ctx); + if (check_ret != KNOT_EOK) { + break; + } + } + + if (ret == KNOT_EOF) { + ret = finalize_previous_section(conf, txn, parser, ctx); + } else if (ret != KNOT_EOK) { + log_parser_err(parser, ret); + } else { + ret = check_ret; + } + + yp_schema_check_deinit(ctx); +parse_error: + yp_deinit(parser); + free(parser); + + return ret; +} + +int conf_import( + conf_t *conf, + const char *input, + bool is_file, + bool reinit_cache) +{ + if (conf == NULL || input == NULL) { + return KNOT_EINVAL; + } + + int ret; + + knot_db_txn_t txn; + ret = conf->api->txn_begin(conf->db, &txn, 0); + if (ret != KNOT_EOK) { + goto import_error; + } + + // Initialize the DB. + ret = conf_db_init(conf, &txn, true); + if (ret != KNOT_EOK) { + conf->api->txn_abort(&txn); + goto import_error; + } + + // Parse and import given file. + ret = conf_parse(conf, &txn, input, is_file); + if (ret != KNOT_EOK) { + conf->api->txn_abort(&txn); + goto import_error; + } + // Load purge must be here as conf_parse may be called recursively! + conf_mod_load_purge(conf, false); + + // Commit new configuration. + ret = conf->api->txn_commit(&txn); + if (ret != KNOT_EOK) { + goto import_error; + } + + // Update read-only transaction. + ret = conf_refresh_txn(conf); + if (ret != KNOT_EOK) { + goto import_error; + } + + // Update cached values. + init_cache(conf, reinit_cache); + + // Reset the filename. + free(conf->filename); + conf->filename = NULL; + if (is_file) { + conf->filename = strdup(input); + } + + ret = KNOT_EOK; +import_error: + + return ret; +} + +static int export_group_name( + FILE *fp, + const yp_item_t *group, + char *out, + size_t out_len, + yp_style_t style) +{ + int ret = yp_format_key0(group, NULL, 0, out, out_len, style, true, true); + if (ret != KNOT_EOK) { + return ret; + } + + fprintf(fp, "%s", out); + + return KNOT_EOK; +} + +static int export_group( + conf_t *conf, + FILE *fp, + const yp_item_t *group, + const uint8_t *id, + size_t id_len, + char *out, + size_t out_len, + yp_style_t style, + bool *exported) +{ + // Export the multi-group name. + if ((group->flags & YP_FMULTI) != 0 && !(*exported)) { + int ret = export_group_name(fp, group, out, out_len, style); + if (ret != KNOT_EOK) { + return ret; + } + *exported = true; + } + + // Iterate through all possible group items. + for (yp_item_t *item = group->sub_items; item->name != NULL; item++) { + // Export the identifier. + if (group->var.g.id == item && (group->flags & YP_FMULTI) != 0) { + int ret = yp_format_id(group->var.g.id, id, id_len, out, + out_len, style); + if (ret != KNOT_EOK) { + return ret; + } + fprintf(fp, "%s", out); + continue; + } + + conf_val_t bin; + conf_db_get(conf, &conf->read_txn, group->name, item->name, + id, id_len, &bin); + if (bin.code == KNOT_ENOENT) { + continue; + } else if (bin.code != KNOT_EOK) { + return bin.code; + } + + // Export the single-group name if an item is set. + if ((group->flags & YP_FMULTI) == 0 && !(*exported)) { + int ret = export_group_name(fp, group, out, out_len, style); + if (ret != KNOT_EOK) { + return ret; + } + *exported = true; + } + + // Format single/multiple-valued item. + size_t values = conf_val_count(&bin); + for (size_t i = 1; i <= values; i++) { + conf_val(&bin); + int ret = yp_format_key1(item, bin.data, bin.len, out, + out_len, style, i == 1, + i == values); + if (ret != KNOT_EOK) { + return ret; + } + fprintf(fp, "%s", out); + + if (values > 1) { + conf_val_next(&bin); + } + } + } + + if (*exported) { + fprintf(fp, "\n"); + } + + return KNOT_EOK; +} + +static int export_item( + conf_t *conf, + FILE *fp, + const yp_item_t *item, + char *buff, + size_t buff_len, + yp_style_t style) +{ + bool exported = false; + + // Skip non-group items (include). + if (item->type != YP_TGRP) { + return KNOT_EOK; + } + + // Export simple group without identifiers. + if (!(item->flags & YP_FMULTI)) { + return export_group(conf, fp, item, NULL, 0, buff, buff_len, + style, &exported); + } + + // Iterate over all identifiers. + conf_iter_t iter; + int ret = conf_db_iter_begin(conf, &conf->read_txn, item->name, &iter); + switch (ret) { + case KNOT_EOK: + break; + case KNOT_ENOENT: + return KNOT_EOK; + default: + return ret; + } + + while (ret == KNOT_EOK) { + const uint8_t *id; + size_t id_len; + ret = conf_db_iter_id(conf, &iter, &id, &id_len); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf, &iter); + return ret; + } + + // Export group with identifiers. + ret = export_group(conf, fp, item, id, id_len, buff, buff_len, + style, &exported); + if (ret != KNOT_EOK) { + conf_db_iter_finish(conf, &iter); + return ret; + } + + ret = conf_db_iter_next(conf, &iter); + } + if (ret != KNOT_EOF) { + return ret; + } + + return KNOT_EOK; +} + +int conf_export( + conf_t *conf, + const char *file_name, + yp_style_t style) +{ + if (conf == NULL) { + return KNOT_EINVAL; + } + + // Prepare common buffer; + const size_t buff_len = 2 * CONF_MAX_DATA_LEN; // Rough limit. + char *buff = malloc(buff_len); + if (buff == NULL) { + return KNOT_ENOMEM; + } + + FILE *fp = (file_name != NULL) ? fopen(file_name, "w") : stdout; + if (fp == NULL) { + free(buff); + return knot_map_errno(); + } + + fprintf(fp, "# Configuration export (Knot DNS %s)\n\n", PACKAGE_VERSION); + + const char *mod_prefix = KNOTD_MOD_NAME_PREFIX; + const size_t mod_prefix_len = strlen(mod_prefix); + + int ret; + + // Iterate over the schema. + for (yp_item_t *item = conf->schema; item->name != NULL; item++) { + // Don't export module sections again. + if (strncmp(item->name + 1, mod_prefix, mod_prefix_len) == 0) { + break; + } + + // Export module sections before the template section. + if (strcmp(&item->name[1], &C_TPL[1]) == 0) { + for (yp_item_t *mod = item + 1; mod->name != NULL; mod++) { + // Skip non-module sections. + if (strncmp(mod->name + 1, mod_prefix, mod_prefix_len) != 0) { + continue; + } + + // Export module section. + ret = export_item(conf, fp, mod, buff, buff_len, style); + if (ret != KNOT_EOK) { + goto export_error; + } + } + } + + // Export non-module section. + ret = export_item(conf, fp, item, buff, buff_len, style); + if (ret != KNOT_EOK) { + goto export_error; + } + } + + ret = KNOT_EOK; +export_error: + if (file_name != NULL) { + fclose(fp); + } + free(buff); + + return ret; +} |