base.c 20.7 KB
Newer Older
1
/*  Copyright (C) 2016 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 24

    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"
#include "knot/nameserver/internet.h"
25
#include "libknot/libknot.h"
26
#include "libknot/yparser/ypformat.h"
27
#include "libknot/yparser/yptrafo.h"
28
#include "contrib/files.h"
29
#include "contrib/mempattern.h"
30
#include "contrib/sockaddr.h"
31
#include "contrib/string.h"
32
#include "contrib/ucw/mempool.h"
33

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

conf_t* conf(void) {
	return s_conf;
}

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 73
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;
		}
	}

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

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

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

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

110 111 112
static void init_cache(
	conf_t *conf)
{
113 114 115 116 117 118 119 120 121 122 123 124
	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);

125 126 127 128 129
	conf->cache.srv_nsid = conf_get(conf, C_SRV, C_NSID);
	conf->cache.srv_max_tcp_clients = conf_get(conf, C_SRV, C_MAX_TCP_CLIENTS);
	conf->cache.srv_tcp_hshake_timeout = conf_get(conf, C_SRV, C_TCP_HSHAKE_TIMEOUT);
	conf->cache.srv_tcp_idle_timeout = conf_get(conf, C_SRV, C_TCP_IDLE_TIMEOUT);
	conf->cache.srv_tcp_reply_timeout = conf_get(conf, C_SRV, C_TCP_REPLY_TIMEOUT);
130 131
	conf->cache.srv_rate_limit_slip = conf_get(conf, C_SRV, C_RATE_LIMIT_SLIP);
	conf->cache.srv_rate_limit_whitelist = conf_get(conf, C_SRV, C_RATE_LIMIT_WHITELIST);
132 133
}

134 135 136
int conf_new(
	conf_t **conf,
	const yp_item_t *scheme,
137
	const char *db_dir,
138
	conf_flag_t flags)
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
{
	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 scheme.
	int ret = yp_scheme_copy(&out->scheme, scheme);
	if (ret != KNOT_EOK) {
		free(out);
		return ret;
	}

157
	// Initialize a config mempool.
158
	out->mm = malloc(sizeof(knot_mm_t));
159 160 161 162 163
	if (out->mm == NULL) {
		yp_scheme_free(out->scheme);
		free(out);
		return KNOT_ENOMEM;
	}
164
	mm_ctx_mempool(out->mm, MM_DEFAULT_BLKSIZE);
165 166 167

	// Set the DB api.
	out->api = knot_db_lmdb_api();
168
	struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
169
	lmdb_opts.mapsize = (size_t)CONF_MAPSIZE * 1024 * 1024;
170
	lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS;
171

172
	// Open the database.
173
	if (db_dir == NULL) {
174
		// Prepare a temporary database.
175 176 177
		char tpl[] = "/tmp/knot-confdb.XXXXXX";
		lmdb_opts.path = mkdtemp(tpl);
		if (lmdb_opts.path == NULL) {
178 179
			CONF_LOG(LOG_ERR, "failed to create temporary directory (%s)",
			         knot_strerror(knot_map_errno()));
180 181 182
			ret = KNOT_ENOMEM;
			goto new_error;
		}
183 184 185 186

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

		// Remove the database to ensure it is temporary.
187 188 189
		if (!remove_path(lmdb_opts.path)) {
			CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'", lmdb_opts.path);
		}
190
	} else {
191
		// Set the specified database.
192 193
		lmdb_opts.path = db_dir;

194 195 196 197
		// Set the read-only mode.
		if (flags & CONF_FREADONLY) {
			lmdb_opts.flags.env |= KNOT_DB_LMDB_RDONLY;
		}
198

199 200
		ret = out->api->init(&out->db, out->mm, &lmdb_opts);
	}
201 202 203 204
	if (ret != KNOT_EOK) {
		goto new_error;
	}

205 206
	// Initialize and check the database.
	ret = init_and_check(out, flags);
207 208 209 210 211 212
	if (ret != KNOT_EOK) {
		out->api->deinit(out->db);
		goto new_error;
	}

	// Open common read-only transaction.
213
	ret = conf_refresh_txn(out);
214 215 216 217 218
	if (ret != KNOT_EOK) {
		out->api->deinit(out->db);
		goto new_error;
	}

219 220 221
	// Initialize query modules list.
	init_list(&out->query_modules);

222 223
	// Cache the current hostname.
	if (!(flags & CONF_FNOHOSTNAME)) {
224
		conf_refresh_hostname(out);
225 226
	}

227
	// Initialize cached values.
228
	init_cache(out);
229

230 231 232 233
	*conf = out;

	return KNOT_EOK;
new_error:
234
	mp_delete(out->mm->ctx);
235
	free(out->mm);
236
	yp_scheme_free(out->scheme);
237 238 239 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
	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 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.
268
	ret = conf_refresh_txn(out);
269 270 271 272 273 274
	if (ret != KNOT_EOK) {
		yp_scheme_free(out->scheme);
		free(out);
		return ret;
	}

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

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

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

288
	// Initialize cached values.
289
	init_cache(out);
290

291 292
	out->is_clone = true;

293 294 295 296 297 298 299 300
	*conf = out;

	return KNOT_EOK;
}

void conf_update(
	conf_t *conf)
{
301 302 303
	// Remove the clone flag for new master configuration.
	if (conf != NULL) {
		conf->is_clone = false;
304 305 306 307 308 309 310
	}

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

	synchronize_rcu();

311 312 313
	if (old_conf != NULL) {
		// Remove the clone flag if a single configuration.
		old_conf->is_clone = (conf != NULL) ? true : false;
314
		conf_free(old_conf);
315 316 317 318
	}
}

void conf_free(
319
	conf_t *conf)
320 321 322 323 324 325 326
{
	if (conf == NULL) {
		return;
	}

	yp_scheme_free(conf->scheme);
	conf->api->txn_abort(&conf->read_txn);
327
	free(conf->filename);
328 329
	free(conf->hostname);

330 331 332 333
	if (conf->io.txn != NULL) {
		conf->api->txn_abort(conf->io.txn_stack);
	}

334
	conf_deactivate_modules(&conf->query_modules, &conf->query_plan);
335

336
	if (!conf->is_clone) {
337
		conf->api->deinit(conf->db);
338
		mp_delete(conf->mm->ctx);
339 340 341 342 343 344
		free(conf->mm);
	}

	free(conf);
}

345
void conf_activate_modules(
346
	conf_t *conf,
347
	const knot_dname_t *zone_name,
348 349 350
	list_t *query_modules,
	struct query_plan **query_plan)
{
351 352
	int ret = KNOT_EOK;

353
	if (conf == NULL || query_modules == NULL || query_plan == NULL) {
354 355
		ret = KNOT_EINVAL;
		goto activate_error;
356 357 358 359 360 361 362 363
	}

	conf_val_t val;

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

367
	// Check if a module is configured at all.
368
	if (val.code == KNOT_ENOENT) {
369
		return;
370
	} else if (val.code != KNOT_EOK) {
371 372
		ret = val.code;
		goto activate_error;
373 374 375 376 377
	}

	// Create query plan.
	*query_plan = query_plan_create(conf->mm);
	if (*query_plan == NULL) {
378 379
		ret = KNOT_ENOMEM;
		goto activate_error;
380 381 382 383 384 385 386 387 388 389 390 391 392 393
	}

	if (zone_name != NULL) {
		// Only supported zone class is now IN.
		internet_query_plan(*query_plan);
	}

	// 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) {
394 395
			ret = KNOT_ENOMEM;
			goto activate_error;
396 397 398 399 400
		}

		// Open the module.
		struct query_module *mod = query_module_open(conf, mod_id, conf->mm);
		if (mod == NULL) {
401 402
			ret = KNOT_ENOMEM;
			goto activate_error;
403 404
		}

405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
		// 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) {
				log_zone_warning(zone_name,
				                 "out of scope module '%s/%.*s'",
				                 mod_id->name + 1, (int)mod_id->len,
				                 mod_id->data);
			} else {
				log_warning("out of scope module '%s/%.*s'",
				            mod_id->name + 1, (int)mod_id->len,
				            mod_id->data);
			}
			query_module_close(mod);
			conf_val_next(&val);
			continue;
		}

423
		// Load the module.
424
		ret = mod->load(*query_plan, mod, zone_name);
425
		if (ret != KNOT_EOK) {
426 427 428 429 430 431 432 433 434 435
			if (zone_name != NULL) {
				log_zone_error(zone_name,
				               "failed to load module '%s/%.*s' (%s)",
				               mod_id->name + 1, (int)mod_id->len,
				               mod_id->data, knot_strerror(ret));
			} else {
				log_error("failed to load global module '%s/%.*s' (%s)",
				          mod_id->name + 1, (int)mod_id->len,
				          mod_id->data, knot_strerror(ret));
			}
436
			query_module_close(mod);
437 438
			conf_val_next(&val);
			continue;
439 440 441 442 443 444 445
		}

		add_tail(query_modules, &mod->node);

		conf_val_next(&val);
	}

446 447 448
	return;
activate_error:
	CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret));
449 450 451 452
}

void conf_deactivate_modules(
	list_t *query_modules,
453
	struct query_plan **query_plan)
454
{
455
	if (query_modules == NULL || query_plan == NULL) {
456 457 458
		return;
	}

459 460 461 462
	// Free query plan.
	query_plan_free(*query_plan);
	*query_plan = NULL;

463 464 465 466 467 468
	// 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);
	}
469
	init_list(query_modules);
470 471
}

472 473 474 475 476
#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)
477

478 479 480 481 482 483 484 485 486
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));
487 488
}

489 490 491 492
static void log_call_err(
	yp_parser_t *parser,
	conf_check_t *args,
	int ret)
493
{
494 495 496 497 498
	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));
}
499

500 501 502 503 504 505
static void log_prev_err(
	conf_check_t *args,
	int ret)
{
	char buff[512] = { 0 };
	size_t len = sizeof(buff);
506

507 508 509 510 511
	// 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';
512
		}
513 514
	}

515 516 517
	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));
518
}
519

520
static int parser_calls(
521
	conf_t *conf,
522
	knot_db_txn_t *txn,
523
	yp_parser_t *parser,
524
	yp_check_ctx_t *ctx)
525
{
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
	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
			};

			int ret = conf_exec_callbacks(prev_item, &args);
			if (ret != KNOT_EOK) {
				log_prev_err(&args, ret);
				return ret;
			}
555

556 557
			prev_item = NULL;
		}
558

559 560
		// Stop if final processing.
		if (node == NULL) {
561 562
			return KNOT_EOK;
		}
563

564 565 566 567 568 569 570
		// 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;
			}
571

572 573 574 575 576
			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;
577

578 579 580 581
			// Defer group callbacks to the beginning of the next block.
			if ((node->item->flags & YP_FMULTI) == 0) {
				return KNOT_EOK;
			}
582

583 584
			is_id = true;
		}
585 586
	}

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
	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
	};

	int ret = conf_exec_callbacks(is_id ? node->item->var.g.id : node->item,
	                              &args);
	if (ret != KNOT_EOK) {
		log_call_err(parser, &args, ret);
	}

	return ret;
606 607 608 609
}

int conf_parse(
	conf_t *conf,
610
	knot_db_txn_t *txn,
611 612
	const char *input,
	bool is_file,
613
	void *data)
614
{
615
	if (conf == NULL || txn == NULL || input == NULL || data == NULL) {
616 617 618 619 620 621 622 623 624 625
		return KNOT_EINVAL;
	}

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

	int ret;
626 627

	// Set parser source.
628 629 630 631 632 633
	if (is_file) {
		ret = yp_set_input_file(parser, input);
	} else {
		ret = yp_set_input_string(parser, input, strlen(input));
	}
	if (ret != KNOT_EOK) {
634 635 636
		CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
		         input, knot_strerror(ret));
		goto parse_error;
637 638
	}

639
	// Initialize parser check context.
640 641 642
	yp_check_ctx_t *ctx = yp_scheme_check_init(conf->scheme);
	if (ctx == NULL) {
		ret = KNOT_ENOMEM;
643
		goto parse_error;
644 645
	}

646 647 648
	int check_ret = KNOT_EOK;

	// Parse the configuration.
649
	while ((ret = yp_parse(parser)) == KNOT_EOK) {
650 651
		check_ret = yp_scheme_check_parser(ctx, parser);
		if (check_ret != KNOT_EOK) {
652
			log_parser_err(parser, check_ret);
653 654
			break;
		}
655 656 657 658 659 660 661 662 663 664 665 666 667 668

		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);
		}
669
		if (check_ret != KNOT_EOK) {
670
			log_parser_err(parser, check_ret);
671 672
			break;
		}
673 674

		check_ret = parser_calls(conf, txn, parser, ctx);
675
		if (check_ret != KNOT_EOK) {
676 677 678 679
			break;
		}
	}

680 681
	if (ret == KNOT_EOF) {
		// Call the last block callbacks.
682
		ret = parser_calls(conf, txn, NULL, NULL);
683
	} else if (ret != KNOT_EOK) {
684
		log_parser_err(parser, ret);
685 686
	} else {
		ret = check_ret;
687 688
	}

689 690
	yp_scheme_check_deinit(ctx);
parse_error:
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
	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;
	}

706 707
	int ret;

708
	knot_db_txn_t txn;
709
	ret = conf->api->txn_begin(conf->db, &txn, 0);
710
	if (ret != KNOT_EOK) {
711
		goto import_error;
712 713
	}

714 715
	// Initialize the DB.
	ret = conf_db_init(conf, &txn, true);
716 717
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
718
		goto import_error;
719 720
	}

721
	conf_check_t args = { NULL };
722 723

	// Parse and import given file.
724
	ret = conf_parse(conf, &txn, input, is_file, &args);
725 726
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
727
		goto import_error;
728 729 730 731 732
	}

	// Commit new configuration.
	ret = conf->api->txn_commit(&txn);
	if (ret != KNOT_EOK) {
733
		goto import_error;
734 735 736
	}

	// Update read-only transaction.
737
	ret = conf_refresh_txn(conf);
738
	if (ret != KNOT_EOK) {
739
		goto import_error;
740 741
	}

742
	// Update cached values.
743
	init_cache(conf);
744

745 746 747 748 749 750 751
	// Reset the filename.
	free(conf->filename);
	conf->filename = NULL;
	if (is_file) {
		conf->filename = strdup(input);
	}

752 753 754 755
	ret = KNOT_EOK;
import_error:

	return ret;
756 757
}

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
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;
}

775 776 777
static int export_group(
	conf_t *conf,
	FILE *fp,
778 779
	const yp_item_t *group,
	const uint8_t *id,
780 781 782
	size_t id_len,
	char *out,
	size_t out_len,
783 784
	yp_style_t style,
	bool *exported)
785
{
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
	// 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;
		}

808
		conf_val_t bin;
809 810
		conf_db_get(conf, &conf->read_txn, group->name, item->name,
		            id, id_len, &bin);
811 812 813 814 815 816
		if (bin.code == KNOT_ENOENT) {
			continue;
		} else if (bin.code != KNOT_EOK) {
			return bin.code;
		}

817 818 819 820 821 822 823 824 825
		// 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;
		}

826 827 828
		// Format single/multiple-valued item.
		size_t values = conf_val_count(&bin);
		for (size_t i = 1; i <= values; i++) {
829
			conf_val(&bin);
830 831 832 833 834 835 836 837 838 839 840 841 842 843
			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);
			}
		}
	}

844 845 846
	if (*exported) {
		fprintf(fp, "\n");
	}
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869

	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);
870
		return knot_map_errno();
871 872
	}

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

875 876
	int ret;

877 878 879 880
	// Iterate over the scheme.
	for (yp_item_t *item = conf->scheme; item->name != NULL; item++) {
		bool exported = false;

881 882 883 884 885 886
		// Skip non-group items (include).
		if (item->type != YP_TGRP) {
			continue;
		}

		// Export simple group without identifiers.
887
		if ((item->flags & YP_FMULTI) == 0) {
888
			ret = export_group(conf, fp, item, NULL, 0, buff,
889
			                   buff_len, style, &exported);
890 891 892 893 894 895 896 897 898
			if (ret != KNOT_EOK) {
				goto export_error;
			}

			continue;
		}

		// Iterate over all identifiers.
		conf_iter_t iter;
899 900 901 902 903 904 905
		ret = conf_db_iter_begin(conf, &conf->read_txn, item->name, &iter);
		switch (ret) {
		case KNOT_EOK:
			break;
		case KNOT_ENOENT:
			continue;
		default:
906 907 908 909
			goto export_error;
		}

		while (ret == KNOT_EOK) {
910
			const uint8_t *id;
911 912 913 914 915 916 917
			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;
			}

918
			// Export group with identifiers.
919
			ret = export_group(conf, fp, item, id, id_len, buff,
920
			                   buff_len, style, &exported);
921 922 923 924 925 926 927
			if (ret != KNOT_EOK) {
				conf_db_iter_finish(conf, &iter);
				goto export_error;
			}

			ret = conf_db_iter_next(conf, &iter);
		}
928 929 930
		if (ret != KNOT_EOF) {
			goto export_error;
		}
931 932 933 934 935 936 937 938 939
	}

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

	return ret;
}