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

    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/>.
*/

17
#include <string.h>
18 19 20 21
#include <urcu.h>

#include "knot/conf/base.h"
#include "knot/conf/confdb.h"
22
#include "knot/conf/module.h"
23 24 25
#include "knot/conf/tools.h"
#include "knot/common/log.h"
#include "knot/nameserver/query_module.h"
26
#include "libknot/libknot.h"
27
#include "libknot/yparser/ypformat.h"
28
#include "libknot/yparser/yptrafo.h"
29
#include "contrib/files.h"
30
#include "contrib/mempattern.h"
31
#include "contrib/sockaddr.h"
32
#include "contrib/string.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);

Filip Siroky's avatar
Filip Siroky committed
125 126 127 128 129 130 131 132 133 134 135 136
	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);

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

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

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

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

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

174 175 176 177 178 179 180 181
	// Initialize query modules list.
	out->query_modules = mm_alloc(out->mm, sizeof(list_t));
	if (out->query_modules == NULL) {
		ret = KNOT_ENOMEM;
		goto new_error;
	}
	init_list(out->query_modules);

182 183
	// Set the DB api.
	out->api = knot_db_lmdb_api();
184
	struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
185
	lmdb_opts.mapsize = max_conf_size;
186
	lmdb_opts.maxreaders = CONF_MAX_DB_READERS;
187
	lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS;
188

189
	// Open the database.
190
	if (db_dir == NULL) {
191
		// Prepare a temporary database.
192 193 194
		char tpl[] = "/tmp/knot-confdb.XXXXXX";
		lmdb_opts.path = mkdtemp(tpl);
		if (lmdb_opts.path == NULL) {
195 196
			CONF_LOG(LOG_ERR, "failed to create temporary directory (%s)",
			         knot_strerror(knot_map_errno()));
197 198 199
			ret = KNOT_ENOMEM;
			goto new_error;
		}
200 201 202 203

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

		// Remove the database to ensure it is temporary.
204
		if (!remove_path(lmdb_opts.path)) {
205 206
			CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'",
			         lmdb_opts.path);
207
		}
208
	} else {
209
		// Set the specified database.
210 211
		lmdb_opts.path = db_dir;

212 213 214 215
		// Set the read-only mode.
		if (flags & CONF_FREADONLY) {
			lmdb_opts.flags.env |= KNOT_DB_LMDB_RDONLY;
		}
216

217 218
		ret = out->api->init(&out->db, out->mm, &lmdb_opts);
	}
219 220 221 222
	if (ret != KNOT_EOK) {
		goto new_error;
	}

223 224
	// Initialize and check the database.
	ret = init_and_check(out, flags);
225 226 227 228 229
	if (ret != KNOT_EOK) {
		goto new_error;
	}

	// Open common read-only transaction.
230
	ret = conf_refresh_txn(out);
231 232 233 234
	if (ret != KNOT_EOK) {
		goto new_error;
	}

235 236
	// Cache the current hostname.
	if (!(flags & CONF_FNOHOSTNAME)) {
237
		conf_refresh_hostname(out);
238 239
	}

240
	// Initialize cached values.
241
	init_cache(out);
242

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	// 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);
	}

264 265 266 267
	*conf = out;

	return KNOT_EOK;
new_error:
268
	conf_free(out);
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

	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));

286 287
	// Initialize config schema.
	int ret = yp_schema_copy(&out->schema, s_conf->schema);
288 289 290 291 292 293 294 295 296 297
	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;

298 299 300 301 302 303 304 305 306
	// Initialize query modules list.
	out->query_modules = mm_alloc(out->mm, sizeof(list_t));
	if (out->query_modules == NULL) {
		yp_schema_free(out->schema);
		free(out);
		return KNOT_ENOMEM;
	}
	init_list(out->query_modules);

307
	// Open common read-only transaction.
308
	ret = conf_refresh_txn(out);
309
	if (ret != KNOT_EOK) {
310
		mm_free(out->mm, out->query_modules);
311
		yp_schema_free(out->schema);
312 313 314 315
		free(out);
		return ret;
	}

316 317 318 319 320
	// Copy the filename.
	if (s_conf->filename != NULL) {
		out->filename = strdup(s_conf->filename);
	}

321
	// Copy the hostname.
322 323 324 325
	if (s_conf->hostname != NULL) {
		out->hostname = strdup(s_conf->hostname);
	}

326
	// Initialize cached values.
327
	init_cache(out);
328

329 330
	out->is_clone = true;

331 332 333 334 335
	*conf = out;

	return KNOT_EOK;
}

336
conf_t *conf_update(
337 338
	conf_t *conf,
	conf_update_flag_t flags)
339
{
340 341 342
	// Remove the clone flag for new master configuration.
	if (conf != NULL) {
		conf->is_clone = false;
343 344 345 346 347 348

		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) {
Daniel Salzman's avatar
Daniel Salzman committed
349
			mm_free(conf->mm, conf->query_modules);
350
			conf->query_modules = s_conf->query_modules;
351 352
			conf->query_plan = s_conf->query_plan;
		}
353 354 355 356 357 358 359
	}

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

	synchronize_rcu();

360 361 362
	if (old_conf != NULL) {
		// Remove the clone flag if a single configuration.
		old_conf->is_clone = (conf != NULL) ? true : false;
363 364 365 366 367

		if (flags & CONF_UPD_FCONFIO) {
			old_conf->io.zones = NULL;
		}
		if (flags & CONF_UPD_FMODULES) {
368
			old_conf->query_modules = NULL;
369 370
			old_conf->query_plan = NULL;
		}
371 372 373 374
		if (!(flags & CONF_UPD_FNOFREE)) {
			conf_free(old_conf);
			old_conf = NULL;
		}
375
	}
376 377

	return old_conf;
378 379 380
}

void conf_free(
381
	conf_t *conf)
382 383 384 385 386
{
	if (conf == NULL) {
		return;
	}

387
	yp_schema_free(conf->schema);
388
	free(conf->filename);
389
	free(conf->hostname);
390 391 392
	if (conf->api != NULL) {
		conf->api->txn_abort(&conf->read_txn);
	}
393

394
	if (conf->io.txn != NULL && conf->api != NULL) {
395 396
		conf->api->txn_abort(conf->io.txn_stack);
	}
397
	if (conf->io.zones != NULL) {
398
		trie_free(conf->io.zones);
399
	}
400

401
	conf_mod_load_purge(conf, false);
402 403
	conf_deactivate_modules(conf->query_modules, &conf->query_plan);
	mm_free(conf->mm, conf->query_modules);
404
	conf_mod_unload_shared(conf);
405

406
	if (!conf->is_clone) {
407 408 409 410 411 412
		if (conf->api != NULL) {
			conf->api->deinit(conf->db);
		}
		if (conf->mm != NULL) {
			free(conf->mm);
		}
413 414 415 416 417
	}

	free(conf);
}

418 419 420 421 422
#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)
423

424 425 426 427 428
static void log_parser_err(
	yp_parser_t *parser,
	int ret)
{
	CONF_LOG_LINE(parser->file.name, parser->line_count,
429 430 431 432 433
	              "item '%s'%s%s%s (%s)",
	              parser->key,
	              (parser->data_len > 0) ? ", value '"  : "",
	              (parser->data_len > 0) ? parser->data : "",
	              (parser->data_len > 0) ? "'"          : "",
434
	              knot_strerror(ret));
435 436
}

437 438 439 440 441 442 443 444 445 446 447 448 449 450
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);
	}
}

451 452
static void log_call_err(
	yp_parser_t *parser,
453
	knotd_conf_check_args_t *args,
454
	int ret)
455
{
456
	CONF_LOG_LINE(args->extra->file_name, args->extra->line,
457 458 459 460 461
	              "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));
462
}
463

464
static void log_prev_err(
465
	knotd_conf_check_args_t *args,
466 467 468 469
	int ret)
{
	char buff[512] = { 0 };
	size_t len = sizeof(buff);
470

471 472 473 474 475
	// 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';
476
		}
477 478
	}

479
	CONF_LOG_LINE(args->extra->file_name, args->extra->line,
480 481
	              "%s '%s' (%s)", args->item->name + 1, buff,
	              args->err_str != NULL ? args->err_str : knot_strerror(ret));
482
}
483

484
static int finalize_previous_section(
485
	conf_t *conf,
486
	knot_db_txn_t *txn,
487
	yp_parser_t *parser,
488
	yp_check_ctx_t *ctx)
489
{
490
	yp_node_t *node = &ctx->nodes[0];
491

492 493
	// Return if no previous section or include or empty multi-section.
	if (node->item == NULL || node->item->type != YP_TGRP ||
494 495 496
	    (node->id_len == 0 && (node->item->flags & YP_FMULTI) != 0)) {
		return KNOT_EOK;
	}
497

498
	knotd_conf_check_extra_t extra = {
499 500
		.conf = conf,
		.txn = txn,
501 502 503 504
		.file_name = parser->file.name,
		.line = parser->line_count
	};
	knotd_conf_check_args_t args = {
505 506 507
		.item = node->item,
		.id = node->id,
		.id_len = node->id_len,
508 509
		.data = node->data,
		.data_len = node->data_len,
510
		.extra = &extra
511
	};
512

513 514 515 516
	int ret = conf_exec_callbacks(&args);
	if (ret != KNOT_EOK) {
		log_prev_err(&args, ret);
	}
517

518 519
	return ret;
}
520

521 522 523 524 525 526 527
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];
528

529 530 531
	// Section callbacks are executed before another section.
	if (node->item->type == YP_TGRP && node->id_len == 0) {
		return KNOT_EOK;
532 533
	}

534
	knotd_conf_check_extra_t extra = {
535 536
		.conf = conf,
		.txn = txn,
537 538 539 540
		.file_name = parser->file.name,
		.line = parser->line_count
	};
	knotd_conf_check_args_t args = {
541
		.item = (parser->event == YP_EID) ? node->item->var.g.id : node->item,
542 543 544 545
		.id = node->id,
		.id_len = node->id_len,
		.data = node->data,
		.data_len = node->data_len,
546
		.extra = &extra
547 548
	};

549
	int ret = conf_exec_callbacks(&args);
550 551 552 553 554
	if (ret != KNOT_EOK) {
		log_call_err(parser, &args, ret);
	}

	return ret;
555 556 557 558
}

int conf_parse(
	conf_t *conf,
559
	knot_db_txn_t *txn,
560
	const char *input,
561
	bool is_file)
562
{
563
	if (conf == NULL || txn == NULL || input == NULL) {
564 565 566 567 568 569 570 571 572 573
		return KNOT_EINVAL;
	}

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

	int ret;
574 575

	// Set parser source.
576 577 578 579 580 581
	if (is_file) {
		ret = yp_set_input_file(parser, input);
	} else {
		ret = yp_set_input_string(parser, input, strlen(input));
	}
	if (ret != KNOT_EOK) {
582 583 584
		CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
		         input, knot_strerror(ret));
		goto parse_error;
585 586
	}

587
	// Initialize parser check context.
588
	yp_check_ctx_t *ctx = yp_schema_check_init(&conf->schema);
589 590
	if (ctx == NULL) {
		ret = KNOT_ENOMEM;
591
		goto parse_error;
592 593
	}

594 595 596
	int check_ret = KNOT_EOK;

	// Parse the configuration.
597
	while ((ret = yp_parse(parser)) == KNOT_EOK) {
598 599 600 601 602 603 604
		if (parser->event == YP_EKEY0 || parser->event == YP_EID) {
			check_ret = finalize_previous_section(conf, txn, parser, ctx);
			if (check_ret != KNOT_EOK) {
				break;
			}
		}

605
		check_ret = yp_schema_check_parser(ctx, parser);
606
		if (check_ret != KNOT_EOK) {
607
			log_parser_schema_err(parser, check_ret);
608 609
			break;
		}
610 611 612 613 614 615 616 617 618 619 620 621 622 623

		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);
		}
624
		if (check_ret != KNOT_EOK) {
625
			log_parser_err(parser, check_ret);
626 627
			break;
		}
628

629
		check_ret = finalize_item(conf, txn, parser, ctx);
630
		if (check_ret != KNOT_EOK) {
631 632 633 634
			break;
		}
	}

635
	if (ret == KNOT_EOF) {
636
		ret = finalize_previous_section(conf, txn, parser, ctx);
637
	} else if (ret != KNOT_EOK) {
638
		log_parser_err(parser, ret);
639 640
	} else {
		ret = check_ret;
641 642
	}

643
	yp_schema_check_deinit(ctx);
644
parse_error:
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
	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;
	}

660 661
	int ret;

662
	knot_db_txn_t txn;
663
	ret = conf->api->txn_begin(conf->db, &txn, 0);
664
	if (ret != KNOT_EOK) {
665
		goto import_error;
666 667
	}

668 669
	// Initialize the DB.
	ret = conf_db_init(conf, &txn, true);
670 671
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
672
		goto import_error;
673 674 675
	}

	// Parse and import given file.
676
	ret = conf_parse(conf, &txn, input, is_file);
677 678
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
679
		goto import_error;
680
	}
681 682
	// Load purge must be here as conf_parse may be called recursively!
	conf_mod_load_purge(conf, false);
683 684 685 686

	// Commit new configuration.
	ret = conf->api->txn_commit(&txn);
	if (ret != KNOT_EOK) {
687
		goto import_error;
688 689 690
	}

	// Update read-only transaction.
691
	ret = conf_refresh_txn(conf);
692
	if (ret != KNOT_EOK) {
693
		goto import_error;
694 695
	}

696
	// Update cached values.
697
	init_cache(conf);
698

699 700 701 702 703 704 705
	// Reset the filename.
	free(conf->filename);
	conf->filename = NULL;
	if (is_file) {
		conf->filename = strdup(input);
	}

706 707 708 709
	ret = KNOT_EOK;
import_error:

	return ret;
710 711
}

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
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;
}

729 730 731
static int export_group(
	conf_t *conf,
	FILE *fp,
732 733
	const yp_item_t *group,
	const uint8_t *id,
734 735 736
	size_t id_len,
	char *out,
	size_t out_len,
737 738
	yp_style_t style,
	bool *exported)
739
{
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
	// 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;
		}

762
		conf_val_t bin;
763 764
		conf_db_get(conf, &conf->read_txn, group->name, item->name,
		            id, id_len, &bin);
765 766 767 768 769 770
		if (bin.code == KNOT_ENOENT) {
			continue;
		} else if (bin.code != KNOT_EOK) {
			return bin.code;
		}

771 772 773 774 775 776 777 778 779
		// 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;
		}

780 781 782
		// Format single/multiple-valued item.
		size_t values = conf_val_count(&bin);
		for (size_t i = 1; i <= values; i++) {
783
			conf_val(&bin);
784 785 786 787 788 789 790 791 792 793 794 795 796 797
			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);
			}
		}
	}

798 799 800
	if (*exported) {
		fprintf(fp, "\n");
	}
801 802 803 804

	return KNOT_EOK;
}

805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
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;
}

864 865 866 867 868
int conf_export(
	conf_t *conf,
	const char *file_name,
	yp_style_t style)
{
869
	if (conf == NULL) {
870 871 872 873 874 875 876 877 878 879
		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;
	}

880
	FILE *fp = (file_name != NULL) ? fopen(file_name, "w") : stdout;
881 882
	if (fp == NULL) {
		free(buff);
883
		return knot_map_errno();
884 885
	}

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

888 889 890
	const char *mod_prefix = KNOTD_MOD_NAME_PREFIX;
	const size_t mod_prefix_len = strlen(mod_prefix);

891 892
	int ret;

893 894
	// Iterate over the schema.
	for (yp_item_t *item = conf->schema; item->name != NULL; item++) {
895 896
		// Don't export module sections again.
		if (strncmp(item->name + 1, mod_prefix, mod_prefix_len) == 0) {
897
			break;
898 899
		}

900 901 902 903 904 905 906 907 908 909 910 911 912
		// 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;
				}
913 914
			}
		}
915 916 917 918

		// Export non-module section.
		ret = export_item(conf, fp, item, buff, buff_len, style);
		if (ret != KNOT_EOK) {
919 920
			goto export_error;
		}
921 922 923 924
	}

	ret = KNOT_EOK;
export_error:
925 926 927
	if (file_name != NULL) {
		fclose(fp);
	}
928 929 930 931
	free(buff);

	return ret;
}