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

    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 136 137 138
	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);

	val = conf_get(conf, C_SRV, C_RATE_LIMIT_SLIP);
	conf->cache.srv_rate_limit_slip = conf_int(&val);

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

142
	conf->cache.srv_nsid = conf_get(conf, C_SRV, C_NSID);
Filip Siroky's avatar
Filip Siroky committed
143

144
	conf->cache.srv_rate_limit_whitelist = conf_get(conf, C_SRV, C_RATE_LIMIT_WHITELIST);
145 146
}

147 148 149
int conf_new(
	conf_t **conf,
	const yp_item_t *scheme,
150
	const char *db_dir,
151
	conf_flag_t flags)
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
{
	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;
	}

170
	// Initialize a config mempool.
171
	out->mm = malloc(sizeof(knot_mm_t));
172 173 174 175 176
	if (out->mm == NULL) {
		yp_scheme_free(out->scheme);
		free(out);
		return KNOT_ENOMEM;
	}
177
	mm_ctx_mempool(out->mm, MM_DEFAULT_BLKSIZE);
178 179 180

	// Set the DB api.
	out->api = knot_db_lmdb_api();
181
	struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
182
	lmdb_opts.mapsize = (size_t)CONF_MAPSIZE * 1024 * 1024;
183
	lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS;
184

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

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

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

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

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

218 219
	// Initialize and check the database.
	ret = init_and_check(out, flags);
220 221 222 223 224 225
	if (ret != KNOT_EOK) {
		out->api->deinit(out->db);
		goto new_error;
	}

	// Open common read-only transaction.
226
	ret = conf_refresh_txn(out);
227 228 229 230 231
	if (ret != KNOT_EOK) {
		out->api->deinit(out->db);
		goto new_error;
	}

232 233 234
	// Initialize query modules list.
	init_list(&out->query_modules);

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
	*conf = out;

	return KNOT_EOK;
new_error:
247
	mp_delete(out->mm->ctx);
248
	free(out->mm);
249
	yp_scheme_free(out->scheme);
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
	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.
281
	ret = conf_refresh_txn(out);
282 283 284 285 286 287
	if (ret != KNOT_EOK) {
		yp_scheme_free(out->scheme);
		free(out);
		return ret;
	}

288 289 290 291 292
	// Copy the filename.
	if (s_conf->filename != NULL) {
		out->filename = strdup(s_conf->filename);
	}

293
	// Copy the hostname.
294 295 296 297
	if (s_conf->hostname != NULL) {
		out->hostname = strdup(s_conf->hostname);
	}

298 299 300
	// Initialize query modules list.
	init_list(&out->query_modules);

301
	// Initialize cached values.
302
	init_cache(out);
303

304 305
	out->is_clone = true;

306 307 308 309 310 311 312 313
	*conf = out;

	return KNOT_EOK;
}

void conf_update(
	conf_t *conf)
{
314 315 316
	// Remove the clone flag for new master configuration.
	if (conf != NULL) {
		conf->is_clone = false;
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
		conf_free(old_conf);
328 329 330 331
	}
}

void conf_free(
332
	conf_t *conf)
333 334 335 336 337 338 339
{
	if (conf == NULL) {
		return;
	}

	yp_scheme_free(conf->scheme);
	conf->api->txn_abort(&conf->read_txn);
340
	free(conf->filename);
341 342
	free(conf->hostname);

343 344 345 346
	if (conf->io.txn != NULL) {
		conf->api->txn_abort(conf->io.txn_stack);
	}

347
	conf_deactivate_modules(&conf->query_modules, &conf->query_plan);
348

349
	if (!conf->is_clone) {
350
		conf->api->deinit(conf->db);
Vitezslav Kriz's avatar
Vitezslav Kriz committed
351
		mp_delete(conf->mm->ctx);
352 353 354 355 356 357
		free(conf->mm);
	}

	free(conf);
}

358
void conf_activate_modules(
359
	conf_t *conf,
360
	const knot_dname_t *zone_name,
361 362 363
	list_t *query_modules,
	struct query_plan **query_plan)
{
364 365
	int ret = KNOT_EOK;

366
	if (conf == NULL || query_modules == NULL || query_plan == NULL) {
367 368
		ret = KNOT_EINVAL;
		goto activate_error;
369 370 371 372 373 374 375 376
	}

	conf_val_t val;

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

380 381 382 383
	switch (val.code) {
	case KNOT_EOK:
		break;
	case KNOT_ENOENT: // Check if a module is configured at all.
384
		return;
385
	default:
386 387
		ret = val.code;
		goto activate_error;
388 389 390 391 392
	}

	// Create query plan.
	*query_plan = query_plan_create(conf->mm);
	if (*query_plan == NULL) {
393 394
		ret = KNOT_ENOMEM;
		goto activate_error;
395 396 397 398 399 400 401 402 403
	}

	// 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) {
404 405
			ret = KNOT_ENOMEM;
			goto activate_error;
406 407 408 409 410
		}

		// Open the module.
		struct query_module *mod = query_module_open(conf, mod_id, conf->mm);
		if (mod == NULL) {
411 412
			ret = KNOT_ENOMEM;
			goto activate_error;
413 414
		}

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
		// 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;
		}

433
		// Load the module.
434
		ret = mod->load(*query_plan, mod, zone_name);
435
		if (ret != KNOT_EOK) {
436 437 438 439 440 441 442 443 444 445
			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));
			}
446
			query_module_close(mod);
447 448
			conf_val_next(&val);
			continue;
449 450 451 452 453 454 455
		}

		add_tail(query_modules, &mod->node);

		conf_val_next(&val);
	}

456 457 458
	return;
activate_error:
	CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret));
459 460 461 462
}

void conf_deactivate_modules(
	list_t *query_modules,
463
	struct query_plan **query_plan)
464
{
465
	if (query_modules == NULL || query_plan == NULL) {
466 467 468
		return;
	}

469 470 471 472
	// Free query plan.
	query_plan_free(*query_plan);
	*query_plan = NULL;

473 474 475 476 477 478
	// 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);
	}
479
	init_list(query_modules);
480 481
}

482 483 484 485 486
#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)
487

488 489 490 491 492 493 494 495 496
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));
497 498
}

499 500 501 502
static void log_call_err(
	yp_parser_t *parser,
	conf_check_t *args,
	int ret)
503
{
504 505 506 507 508
	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));
}
509

510 511 512 513 514 515
static void log_prev_err(
	conf_check_t *args,
	int ret)
{
	char buff[512] = { 0 };
	size_t len = sizeof(buff);
516

517 518 519 520 521
	// 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';
522
		}
523 524
	}

525 526 527
	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));
528
}
529

530
static int parser_calls(
531
	conf_t *conf,
532
	knot_db_txn_t *txn,
533
	yp_parser_t *parser,
534
	yp_check_ctx_t *ctx)
535
{
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
	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;
			}
565

566 567
			prev_item = NULL;
		}
568

569 570
		// Stop if final processing.
		if (node == NULL) {
571 572
			return KNOT_EOK;
		}
573

574 575 576 577 578 579 580
		// 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;
			}
581

582 583 584 585 586
			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;
587

588 589 590 591
			// Defer group callbacks to the beginning of the next block.
			if ((node->item->flags & YP_FMULTI) == 0) {
				return KNOT_EOK;
			}
592

593 594
			is_id = true;
		}
595 596
	}

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
	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;
616 617 618 619
}

int conf_parse(
	conf_t *conf,
620
	knot_db_txn_t *txn,
621
	const char *input,
622
	bool is_file)
623
{
624
	if (conf == NULL || txn == NULL || input == NULL) {
625 626 627 628 629 630 631 632 633 634
		return KNOT_EINVAL;
	}

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

	int ret;
635 636

	// Set parser source.
637 638 639 640 641 642
	if (is_file) {
		ret = yp_set_input_file(parser, input);
	} else {
		ret = yp_set_input_string(parser, input, strlen(input));
	}
	if (ret != KNOT_EOK) {
643 644 645
		CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
		         input, knot_strerror(ret));
		goto parse_error;
646 647
	}

648
	// Initialize parser check context.
649 650 651
	yp_check_ctx_t *ctx = yp_scheme_check_init(conf->scheme);
	if (ctx == NULL) {
		ret = KNOT_ENOMEM;
652
		goto parse_error;
653 654
	}

655 656 657
	int check_ret = KNOT_EOK;

	// Parse the configuration.
658
	while ((ret = yp_parse(parser)) == KNOT_EOK) {
659 660
		check_ret = yp_scheme_check_parser(ctx, parser);
		if (check_ret != KNOT_EOK) {
661
			log_parser_err(parser, check_ret);
662 663
			break;
		}
664 665 666 667 668 669 670 671 672 673 674 675 676 677

		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);
		}
678
		if (check_ret != KNOT_EOK) {
679
			log_parser_err(parser, check_ret);
680 681
			break;
		}
682 683

		check_ret = parser_calls(conf, txn, parser, ctx);
684
		if (check_ret != KNOT_EOK) {
685 686 687 688
			break;
		}
	}

689 690
	if (ret == KNOT_EOF) {
		// Call the last block callbacks.
691
		ret = parser_calls(conf, txn, NULL, NULL);
692
	} else if (ret != KNOT_EOK) {
693
		log_parser_err(parser, ret);
694 695
	} else {
		ret = check_ret;
696 697
	}

698 699
	yp_scheme_check_deinit(ctx);
parse_error:
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
	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;
	}

Daniel Salzman's avatar
Daniel Salzman committed
715 716
	int ret;

717
	knot_db_txn_t txn;
Daniel Salzman's avatar
Daniel Salzman committed
718
	ret = conf->api->txn_begin(conf->db, &txn, 0);
719
	if (ret != KNOT_EOK) {
Daniel Salzman's avatar
Daniel Salzman committed
720
		goto import_error;
721 722
	}

723 724
	// Initialize the DB.
	ret = conf_db_init(conf, &txn, true);
725 726
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
Daniel Salzman's avatar
Daniel Salzman committed
727
		goto import_error;
728 729 730
	}

	// Parse and import given file.
731
	ret = conf_parse(conf, &txn, input, is_file);
732 733
	if (ret != KNOT_EOK) {
		conf->api->txn_abort(&txn);
Daniel Salzman's avatar
Daniel Salzman committed
734
		goto import_error;
735 736 737 738 739
	}

	// Commit new configuration.
	ret = conf->api->txn_commit(&txn);
	if (ret != KNOT_EOK) {
Daniel Salzman's avatar
Daniel Salzman committed
740
		goto import_error;
741 742 743
	}

	// Update read-only transaction.
744
	ret = conf_refresh_txn(conf);
745
	if (ret != KNOT_EOK) {
Daniel Salzman's avatar
Daniel Salzman committed
746
		goto import_error;
747 748
	}

749
	// Update cached values.
750
	init_cache(conf);
751

752 753 754 755 756 757 758
	// Reset the filename.
	free(conf->filename);
	conf->filename = NULL;
	if (is_file) {
		conf->filename = strdup(input);
	}

Daniel Salzman's avatar
Daniel Salzman committed
759 760 761 762
	ret = KNOT_EOK;
import_error:

	return ret;
763 764
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
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;
}

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

815
		conf_val_t bin;
816 817
		conf_db_get(conf, &conf->read_txn, group->name, item->name,
		            id, id_len, &bin);
818 819 820 821 822 823
		if (bin.code == KNOT_ENOENT) {
			continue;
		} else if (bin.code != KNOT_EOK) {
			return bin.code;
		}

824 825 826 827 828 829 830 831 832
		// 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;
		}

833 834 835
		// Format single/multiple-valued item.
		size_t values = conf_val_count(&bin);
		for (size_t i = 1; i <= values; i++) {
836
			conf_val(&bin);
837 838 839 840 841 842 843 844 845 846 847 848 849 850
			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);
			}
		}
	}

851 852 853
	if (*exported) {
		fprintf(fp, "\n");
	}
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876

	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);
877
		return knot_map_errno();
878 879
	}

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

882 883
	int ret;

884 885 886 887
	// Iterate over the scheme.
	for (yp_item_t *item = conf->scheme; item->name != NULL; item++) {
		bool exported = false;

888 889 890 891 892 893
		// Skip non-group items (include).
		if (item->type != YP_TGRP) {
			continue;
		}

		// Export simple group without identifiers.
894
		if ((item->flags & YP_FMULTI) == 0) {
895
			ret = export_group(conf, fp, item, NULL, 0, buff,
896
			                   buff_len, style, &exported);
897 898 899 900 901 902 903 904 905
			if (ret != KNOT_EOK) {
				goto export_error;
			}

			continue;
		}

		// Iterate over all identifiers.
		conf_iter_t iter;
906 907 908 909 910 911 912
		ret = conf_db_iter_begin(conf, &conf->read_txn, item->name, &iter);
		switch (ret) {
		case KNOT_EOK:
			break;
		case KNOT_ENOENT:
			continue;
		default:
913 914 915 916
			goto export_error;
		}

		while (ret == KNOT_EOK) {
917
			const uint8_t *id;
918 919 920 921 922 923 924
			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;
			}

925
			// Export group with identifiers.
926
			ret = export_group(conf, fp, item, id, id_len, buff,
927
			                   buff_len, style, &exported);
928 929 930 931 932 933 934
			if (ret != KNOT_EOK) {
				conf_db_iter_finish(conf, &iter);
				goto export_error;
			}

			ret = conf_db_iter_next(conf, &iter);
		}
935 936 937
		if (ret != KNOT_EOF) {
			goto export_error;
		}
938 939 940 941 942 943 944 945 946
	}

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

	return ret;
}