summaryrefslogtreecommitdiffstats
path: root/src/knot/conf/base.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/conf/base.c')
-rw-r--r--src/knot/conf/base.c997
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;
+}