base.c 20.9 KB
Newer Older
1
/*  Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

    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 <http://www.gnu.org/licenses/>.
*/

#include <urcu.h>

#include "knot/conf/base.h"
#include "knot/conf/confdb.h"
#include "knot/conf/tools.h"
#include "knot/common/log.h"
#include "knot/nameserver/query_module.h"
24
#include "libknot/libknot.h"
25
#include "libknot/yparser/ypformat.h"
26
#include "libknot/yparser/yptrafo.h"
27
#include "contrib/files.h"
28
#include "contrib/mempattern.h"
29
#include "contrib/sockaddr.h"
30
#include "contrib/string.h"
31
#include "contrib/ucw/mempool.h"
32

33 34 35 36 37 38 39
// The active configuration.
conf_t *s_conf;

conf_t* conf(void) {
	return s_conf;
}

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
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;
		}
	}

73 74 75 76 77 78
	if (flags & CONF_FREADONLY) {
		conf->api->txn_abort(&txn);
		return KNOT_EOK;
	} else {
		return conf->api->txn_commit(&txn);
	}
79 80
}

81
int conf_refresh_txn(
82 83 84 85 86 87 88 89 90 91 92 93
	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);
}

94
void conf_refresh_hostname(
95 96
	conf_t *conf)
{
97 98 99 100
	if (conf == NULL) {
		return;
	}

101
	free(conf->hostname);
102
	conf->hostname = sockaddr_hostname();
103 104 105 106
	if (conf->hostname == NULL) {
		// Empty hostname fallback, NULL cannot be passed to strlen!
		conf->hostname = strdup("");
	}
107
}
108

109 110 111
static void init_cache(
	conf_t *conf)
{
112 113 114 115 116 117 118 119 120 121 122 123
	conf_val_t val = conf_get(conf, C_SRV, C_MAX_IPV4_UDP_PAYLOAD);
	if (val.code != KNOT_EOK) {
		val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD);
	}
	conf->cache.srv_max_ipv4_udp_payload = conf_int(&val);

	val = conf_get(conf, C_SRV, C_MAX_IPV6_UDP_PAYLOAD);
	if (val.code != KNOT_EOK) {
		val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD);
	}
	conf->cache.srv_max_ipv6_udp_payload = conf_int(&val);

Filip Siroky's avatar
Filip Siroky committed
124 125 126 127 128 129 130 131 132 133 134 135
	val = conf_get(conf, C_SRV, C_TCP_HSHAKE_TIMEOUT);
	conf->cache.srv_tcp_hshake_timeout = 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_REPLY_TIMEOUT);
	conf->cache.srv_tcp_reply_timeout = conf_int(&val);

	val = conf_get(conf, C_SRV, C_MAX_TCP_CLIENTS);
	conf->cache.srv_max_tcp_clients = conf_int(&val);

136 137 138
	val = conf_get(conf, C_CTL, C_TIMEOUT);
	conf->cache.ctl_timeout = conf_int(&val) * 1000;

139 140 141
	conf->cache.srv_nsid = conf_get(conf, C_SRV, C_NSID);
}

142 143 144
int conf_new(
	conf_t **conf,
	const yp_item_t *scheme,
145
	const char *db_dir,
146
	conf_flag_t flags)
147 148 149 150 151 152 153 154 155 156 157
{
	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));

158 159 160
	// Initialize query modules list.
	init_list(&out->query_modules);

161 162 163
	// Initialize config scheme.
	int ret = yp_scheme_copy(&out->scheme, scheme);
	if (ret != KNOT_EOK) {
164
		goto new_error;
165 166
	}

167
	// Initialize a config mempool.
168
	out->mm = malloc(sizeof(knot_mm_t));
169
	if (out->mm == NULL) {
170 171
		ret = KNOT_ENOMEM;
		goto new_error;
172
	}
173
	mm_ctx_mempool(out->mm, MM_DEFAULT_BLKSIZE);
174 175 176

	// Set the DB api.
	out->api = knot_db_lmdb_api();
177
	struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
178
	lmdb_opts.mapsize = (size_t)CONF_MAPSIZE * 1024 * 1024;
179
	lmdb_opts.maxreaders = CONF_MAX_DB_READERS;
180
	lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS;
181

182
	// Open the database.
183
	if (db_dir == NULL) {
184
		// Prepare a temporary database.
185 186 187
		char tpl[] = "/tmp/knot-confdb.XXXXXX";
		lmdb_opts.path = mkdtemp(tpl);
		if (lmdb_opts.path == NULL) {
188 189
			CONF_LOG(LOG_ERR, "failed to create temporary directory (%s)",
			         knot_strerror(knot_map_errno()));
190 191 192
			ret = KNOT_ENOMEM;
			goto new_error;
		}
193 194 195 196

		ret = out->api->init(&out->db, out->mm, &lmdb_opts);

		// Remove the database to ensure it is temporary.
197 198 199
		if (!remove_path(lmdb_opts.path)) {
			CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'", lmdb_opts.path);
		}
200
	} else {
201
		// Set the specified database.
202 203
		lmdb_opts.path = db_dir;

204 205 206 207
		// Set the read-only mode.
		if (flags & CONF_FREADONLY) {
			lmdb_opts.flags.env |= KNOT_DB_LMDB_RDONLY;
		}
208

209 210
		ret = out->api->init(&out->db, out->mm, &lmdb_opts);
	}
211 212 213 214
	if (ret != KNOT_EOK) {
		goto new_error;
	}

215 216
	// Initialize and check the database.
	ret = init_and_check(out, flags);
217 218 219 220 221
	if (ret != KNOT_EOK) {
		goto new_error;
	}

	// Open common read-only transaction.
222
	ret = conf_refresh_txn(out);
223 224 225 226
	if (ret != KNOT_EOK) {
		goto new_error;
	}

227 228
	// Cache the current hostname.
	if (!(flags & CONF_FNOHOSTNAME)) {
229
		conf_refresh_hostname(out);
230 231
	}

232
	// Initialize cached values.
233
	init_cache(out);
234

235 236 237 238
	*conf = out;

	return KNOT_EOK;
new_error:
239
	conf_free(out);
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

	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 scheme.
	int ret = yp_scheme_copy(&out->scheme, s_conf->scheme);
	if (ret != KNOT_EOK) {
		free(out);
		return ret;
	}

	// Set shared items.
	out->api = s_conf->api;
	out->mm = s_conf->mm;
	out->db = s_conf->db;

	// Open common read-only transaction.
270
	ret = conf_refresh_txn(out);
271 272 273 274 275 276
	if (ret != KNOT_EOK) {
		yp_scheme_free(out->scheme);
		free(out);
		return ret;
	}

277 278 279 280 281
	// Copy the filename.
	if (s_conf->filename != NULL) {
		out->filename = strdup(s_conf->filename);
	}

282
	// Copy the hostname.
283 284 285 286
	if (s_conf->hostname != NULL) {
		out->hostname = strdup(s_conf->hostname);
	}

287 288 289
	// Initialize query modules list.
	init_list(&out->query_modules);

290
	// Initialize cached values.
291
	init_cache(out);
292

293 294
	out->is_clone = true;

295 296 297 298 299 300
	*conf = out;

	return KNOT_EOK;
}

void conf_update(
301 302
	conf_t *conf,
	conf_update_flag_t flags)
303
{
304 305 306
	// Remove the clone flag for new master configuration.
	if (conf != NULL) {
		conf->is_clone = false;
307 308 309 310 311 312 313 314 315 316

		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) {
			list_dup(&conf->query_modules, &s_conf->query_modules,
			         sizeof(struct query_module));
			conf->query_plan = s_conf->query_plan;
		}
317 318 319 320 321 322 323
	}

	conf_t **current_conf = &s_conf;
	conf_t *old_conf = rcu_xchg_pointer(current_conf, conf);

	synchronize_rcu();

324 325 326
	if (old_conf != NULL) {
		// Remove the clone flag if a single configuration.
		old_conf->is_clone = (conf != NULL) ? true : false;
327 328 329 330 331

		if (flags & CONF_UPD_FCONFIO) {
			old_conf->io.zones = NULL;
		}
		if (flags & CONF_UPD_FMODULES) {
332
			WALK_LIST_FREE(old_conf->query_modules);
333 334 335
			old_conf->query_plan = NULL;
		}

336
		conf_free(old_conf);
337 338 339 340
	}
}

void conf_free(
341
	conf_t *conf)
342 343 344 345 346 347
{
	if (conf == NULL) {
		return;
	}

	yp_scheme_free(conf->scheme);
348
	free(conf->filename);
349
	free(conf->hostname);
350 351 352
	if (conf->api != NULL) {
		conf->api->txn_abort(&conf->read_txn);
	}
353

354
	if (conf->io.txn != NULL && conf->api != NULL) {
355 356
		conf->api->txn_abort(conf->io.txn_stack);
	}
357
	if (conf->io.zones != NULL) {
358
		trie_free(conf->io.zones);
359 360
		mm_free(conf->mm, conf->io.zones);
	}
361

362
	conf_deactivate_modules(&conf->query_modules, &conf->query_plan);
363

364
	if (!conf->is_clone) {
365 366 367 368 369 370 371
		if (conf->api != NULL) {
			conf->api->deinit(conf->db);
		}
		if (conf->mm != NULL) {
			mp_delete(conf->mm->ctx);
			free(conf->mm);
		}
372 373 374 375 376
	}

	free(conf);
}

377 378 379 380
#define LOG_ARGS(mod_id, msg, ...) "module '%s%s%.*s', " msg, \
	mod_id->name + 1, (mod_id->len > 0) ? "/" : "", (int)mod_id->len, \
	mod_id->data

381
void conf_activate_modules(
382
	conf_t *conf,
383
	const knot_dname_t *zone_name,
384 385 386
	list_t *query_modules,
	struct query_plan **query_plan)
{
387 388
	int ret = KNOT_EOK;

389
	if (conf == NULL || query_modules == NULL || query_plan == NULL) {
390 391
		ret = KNOT_EINVAL;
		goto activate_error;
392 393 394 395 396 397 398 399
	}

	conf_val_t val;

	// Get list of associated modules.
	if (zone_name != NULL) {
		val = conf_zone_get(conf, C_MODULE, zone_name);
	} else {
400
		val = conf_default_get(conf, C_GLOBAL_MODULE);
401 402
	}

403 404 405 406
	switch (val.code) {
	case KNOT_EOK:
		break;
	case KNOT_ENOENT: // Check if a module is configured at all.
407
	case KNOT_YP_EINVAL_ID:
408
		return;
409
	default:
410 411
		ret = val.code;
		goto activate_error;
412 413 414 415 416
	}

	// Create query plan.
	*query_plan = query_plan_create(conf->mm);
	if (*query_plan == NULL) {
417 418
		ret = KNOT_ENOMEM;
		goto activate_error;
419 420 421 422 423 424 425 426 427
	}

	// Initialize query modules list.
	init_list(query_modules);

	// Open the modules.
	while (val.code == KNOT_EOK) {
		conf_mod_id_t *mod_id = conf_mod_id(&val);
		if (mod_id == NULL) {
428 429
			ret = KNOT_ENOMEM;
			goto activate_error;
430 431 432
		}

		// Open the module.
433 434
		struct query_module *mod = query_module_open(conf, mod_id, *query_plan,
		                                             zone_name, conf->mm);
435
		if (mod == NULL) {
436 437
			ret = KNOT_ENOMEM;
			goto activate_error;
438 439
		}

440 441 442 443
		// Check the module scope.
		if ((zone_name == NULL && (mod->scope & MOD_SCOPE_GLOBAL) == 0) ||
		    (zone_name != NULL && (mod->scope & MOD_SCOPE_ZONE) == 0)) {
			if (zone_name != NULL) {
444
				log_zone_warning(zone_name, LOG_ARGS(mod_id, "out of scope"));
445
			} else {
446
				log_warning(LOG_ARGS(mod_id, "out of scope"));
447 448 449 450 451 452
			}
			query_module_close(mod);
			conf_val_next(&val);
			continue;
		}

453
		// Load the module.
454
		ret = mod->load(mod);
455
		if (ret != KNOT_EOK) {
456
			if (zone_name != NULL) {
457
				log_zone_error(zone_name, LOG_ARGS(mod_id, "failed to load"));
458
			} else {
459
				log_error(LOG_ARGS(mod_id, "failed to load"));
460
			}
461
			query_module_close(mod);
462 463
			conf_val_next(&val);
			continue;
464 465 466 467 468 469 470
		}

		add_tail(query_modules, &mod->node);

		conf_val_next(&val);
	}

471 472 473
	return;
activate_error:
	CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret));
474 475 476 477
}

void conf_deactivate_modules(
	list_t *query_modules,
478
	struct query_plan **query_plan)
479
{
480
	if (query_modules == NULL || query_plan == NULL) {
481 482 483
		return;
	}

484 485 486 487
	// Free query plan.
	query_plan_free(*query_plan);
	*query_plan = NULL;

488 489 490 491 492 493
	// Free query modules list.
	struct query_module *mod = NULL, *next = NULL;
	WALK_LIST_DELSAFE(mod, next, *query_modules) {
		mod->unload(mod);
		query_module_close(mod);
	}
494
	init_list(query_modules);
495 496
}

497 498 499 500 501
#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)
502

503 504 505 506 507 508 509 510 511
static void log_parser_err(
	yp_parser_t *parser,
	int ret)
{
	CONF_LOG_LINE(parser->file.name, parser->line_count,
	              "item '%.*s', value '%.*s' (%s)",
	              (int)parser->key_len, parser->key,
	              (int)parser->data_len, parser->data,
	              knot_strerror(ret));
512 513
}

514 515 516 517
static void log_call_err(
	yp_parser_t *parser,
	conf_check_t *args,
	int ret)
518
{
519 520 521 522 523
	CONF_LOG_LINE(args->file_name, args->line,
	              "item '%s', value '%.*s' (%s)", args->item->name + 1,
	              (int)parser->data_len, parser->data,
	              args->err_str != NULL ? args->err_str : knot_strerror(ret));
}
524

525 526 527 528 529 530
static void log_prev_err(
	conf_check_t *args,
	int ret)
{
	char buff[512] = { 0 };
	size_t len = sizeof(buff);
531

532 533 534 535 536
	// 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';
537
		}
538 539
	}

540 541 542
	CONF_LOG_LINE(args->file_name, args->line,
	              "%s '%s' (%s)", args->item->name + 1, buff,
	              args->err_str != NULL ? args->err_str : knot_strerror(ret));
543
}
544

545
static int parser_calls(
546
	conf_t *conf,
547
	knot_db_txn_t *txn,
548
	yp_parser_t *parser,
549
	yp_check_ctx_t *ctx)
550
{
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
	static const yp_item_t *prev_item = NULL;
	static size_t prev_id_len = 0;
	static uint8_t prev_id[YP_MAX_ID_LEN] = { 0 };
	static const char *prev_file_name = NULL;
	static size_t prev_line = 0;

	// Zero ctx means the final previous processing.
	yp_node_t *node = (ctx != NULL) ? &ctx->nodes[ctx->current] : NULL;
	bool is_id = false;

	// Preprocess key0 item.
	if (node == NULL || node->parent == NULL) {
		// Execute previous block callbacks.
		if (prev_item != NULL) {
			conf_check_t args = {
				.conf = conf,
				.txn = txn,
				.item = prev_item,
				.id = prev_id,
				.id_len = prev_id_len,
				.file_name = prev_file_name,
				.line = prev_line
			};

575
			int ret = conf_exec_callbacks(&args);
576 577 578 579
			if (ret != KNOT_EOK) {
				log_prev_err(&args, ret);
				return ret;
			}
580

581 582
			prev_item = NULL;
		}
583

584 585
		// Stop if final processing.
		if (node == NULL) {
586 587
			return KNOT_EOK;
		}
588

589 590 591 592 593 594 595
		// Store block context.
		if (node->item->type == YP_TGRP) {
			// Ignore alone group without identifier.
			if ((node->item->flags & YP_FMULTI) != 0 &&
			    node->id_len == 0) {
				return KNOT_EOK;
			}
596

597 598 599 600 601
			prev_item = node->item;
			memcpy(prev_id, node->id, node->id_len);
			prev_id_len = node->id_len;
			prev_file_name = parser->file.name;
			prev_line = parser->line_count;
602

603 604 605 606
			// Defer group callbacks to the beginning of the next block.
			if ((node->item->flags & YP_FMULTI) == 0) {
				return KNOT_EOK;
			}
607

608 609
			is_id = true;
		}
610 611
	}

612 613 614 615 616 617 618 619 620 621 622 623
	conf_check_t args = {
		.conf = conf,
		.txn = txn,
		.item = is_id ? node->item->var.g.id : node->item,
		.id = node->id,
		.id_len = node->id_len,
		.data = node->data,
		.data_len = node->data_len,
		.file_name = parser->file.name,
		.line = parser->line_count
	};

624
	int ret = conf_exec_callbacks(&args);
625 626 627 628 629
	if (ret != KNOT_EOK) {
		log_call_err(parser, &args, ret);
	}

	return ret;
630 631 632 633
}

int conf_parse(
	conf_t *conf,
634
	knot_db_txn_t *txn,
635
	const char *input,
636
	bool is_file)
637
{
638
	if (conf == NULL || txn == NULL || input == NULL) {
639 640 641 642 643 644 645 646 647 648
		return KNOT_EINVAL;
	}

	yp_parser_t *parser = malloc(sizeof(yp_parser_t));
	if (parser == NULL) {
		return KNOT_ENOMEM;
	}
	yp_init(parser);

	int ret;
649 650

	// Set parser source.
651 652 653 654 655 656
	if (is_file) {
		ret = yp_set_input_file(parser, input);
	} else {
		ret = yp_set_input_string(parser, input, strlen(input));
	}
	if (ret != KNOT_EOK) {
657 658 659
		CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
		         input, knot_strerror(ret));
		goto parse_error;
660 661
	}

662
	// Initialize parser check context.
663
	yp_check_ctx_t *ctx = yp_scheme_check_init(&conf->scheme);
664 665
	if (ctx == NULL) {
		ret = KNOT_ENOMEM;
666
		goto parse_error;
667 668
	}

669 670 671
	int check_ret = KNOT_EOK;

	// Parse the configuration.
672
	while ((ret = yp_parse(parser)) == KNOT_EOK) {
673 674
		check_ret = yp_scheme_check_parser(ctx, parser);
		if (check_ret != KNOT_EOK) {
675
			log_parser_err(parser, check_ret);
676 677
			break;
		}
678 679 680 681 682 683 684 685 686 687 688 689 690 691

		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);
		}
692
		if (check_ret != KNOT_EOK) {
693
			log_parser_err(parser, check_ret);
694 695
			break;
		}
696 697

		check_ret = parser_calls(conf, txn, parser, ctx);
698
		if (check_ret != KNOT_EOK) {
699 700 701 702
			break;
		}
	}

703 704
	if (ret == KNOT_EOF) {
		// Call the last block callbacks.
705
		ret = parser_calls(conf, txn, NULL, NULL);
706
	} else if (ret != KNOT_EOK) {
707
		log_parser_err(parser, ret);
708 709
	} else {
		ret = check_ret;
710 711
	}

712 713
	yp_scheme_check_deinit(ctx);
parse_error:
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
	yp_deinit(parser);
	free(parser);

	return ret;
}

int conf_import(
	conf_t *conf,
	const char *input,
	bool is_file)
{
	if (conf == NULL || input == NULL) {
		return KNOT_EINVAL;
	}

729 730
	int ret;

731
	knot_db_txn_t txn;
732
	ret = conf->api->txn_begin(conf->db, &txn, 0);
733
	if (ret != KNOT_EOK) {
734
		goto import_error;
735 736
	}

737 738
	// Initialize the DB.
	ret = conf_db_init(conf, &txn, true);
739 740
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
741
		goto import_error;
742 743 744
	}

	// Parse and import given file.
745
	ret = conf_parse(conf, &txn, input, is_file);
746 747
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
748
		goto import_error;
749 750 751 752 753
	}

	// Commit new configuration.
	ret = conf->api->txn_commit(&txn);
	if (ret != KNOT_EOK) {
754
		goto import_error;
755 756 757
	}

	// Update read-only transaction.
758
	ret = conf_refresh_txn(conf);
759
	if (ret != KNOT_EOK) {
760
		goto import_error;
761 762
	}

763
	// Update cached values.
764
	init_cache(conf);
765

766 767 768 769 770 771 772
	// Reset the filename.
	free(conf->filename);
	conf->filename = NULL;
	if (is_file) {
		conf->filename = strdup(input);
	}

773 774 775 776
	ret = KNOT_EOK;
import_error:

	return ret;
777 778
}

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
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;
}

796 797 798
static int export_group(
	conf_t *conf,
	FILE *fp,
799 800
	const yp_item_t *group,
	const uint8_t *id,
801 802 803
	size_t id_len,
	char *out,
	size_t out_len,
804 805
	yp_style_t style,
	bool *exported)
806
{
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
	// 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;
		}

829
		conf_val_t bin;
830 831
		conf_db_get(conf, &conf->read_txn, group->name, item->name,
		            id, id_len, &bin);
832 833 834 835 836 837
		if (bin.code == KNOT_ENOENT) {
			continue;
		} else if (bin.code != KNOT_EOK) {
			return bin.code;
		}

838 839 840 841 842 843 844 845 846
		// 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;
		}

847 848 849
		// Format single/multiple-valued item.
		size_t values = conf_val_count(&bin);
		for (size_t i = 1; i <= values; i++) {
850
			conf_val(&bin);
851 852 853 854 855 856 857 858 859 860 861 862 863 864
			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);
			}
		}
	}

865 866 867
	if (*exported) {
		fprintf(fp, "\n");
	}
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890

	return KNOT_EOK;
}

int conf_export(
	conf_t *conf,
	const char *file_name,
	yp_style_t style)
{
	if (conf == NULL || file_name == 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 = fopen(file_name, "w");
	if (fp == NULL) {
		free(buff);
891
		return knot_map_errno();
892 893
	}

894 895
	fprintf(fp, "# Configuration export (Knot DNS %s)\n\n", PACKAGE_VERSION);

896 897
	int ret;

898 899 900 901
	// Iterate over the scheme.
	for (yp_item_t *item = conf->scheme; item->name != NULL; item++) {
		bool exported = false;

902 903 904 905 906 907
		// Skip non-group items (include).
		if (item->type != YP_TGRP) {
			continue;
		}

		// Export simple group without identifiers.
908
		if ((item->flags & YP_FMULTI) == 0) {
909
			ret = export_group(conf, fp, item, NULL, 0, buff,
910
			                   buff_len, style, &exported);
911 912 913 914 915 916 917 918 919
			if (ret != KNOT_EOK) {
				goto export_error;
			}

			continue;
		}

		// Iterate over all identifiers.
		conf_iter_t iter;
920 921 922 923 924 925 926
		ret = conf_db_iter_begin(conf, &conf->read_txn, item->name, &iter);
		switch (ret) {
		case KNOT_EOK:
			break;
		case KNOT_ENOENT:
			continue;
		default:
927 928 929 930
			goto export_error;
		}

		while (ret == KNOT_EOK) {
931
			const uint8_t *id;
932 933 934 935 936 937 938
			size_t id_len;
			ret = conf_db_iter_id(conf, &iter, &id, &id_len);
			if (ret != KNOT_EOK) {
				conf_db_iter_finish(conf, &iter);
				goto export_error;
			}

939
			// Export group with identifiers.
940
			ret = export_group(conf, fp, item, id, id_len, buff,
941
			                   buff_len, style, &exported);
942 943 944 945 946 947 948
			if (ret != KNOT_EOK) {
				conf_db_iter_finish(conf, &iter);
				goto export_error;
			}

			ret = conf_db_iter_next(conf, &iter);
		}
949 950 951
		if (ret != KNOT_EOF) {
			goto export_error;
		}
952 953 954 955 956 957 958 959 960
	}

	ret = KNOT_EOK;
export_error:
	fclose(fp);
	free(buff);

	return ret;
}