zone-update.c 23.6 KB
Newer Older
1
/*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13

    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
14
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 16
 */

17
#include "knot/common/log.h"
18
#include "knot/dnssec/zone-events.h"
Daniel Salzman's avatar
Daniel Salzman committed
19
#include "knot/updates/zone-update.h"
Libor Peltan's avatar
Libor Peltan committed
20
#include "knot/zone/adjust.h"
21
#include "knot/zone/serial.h"
22
#include "knot/zone/zone-diff.h"
23
#include "contrib/mempattern.h"
24
#include "contrib/trim.h"
25
#include "contrib/ucw/lists.h"
26
#include "contrib/ucw/mempool.h"
27

28 29
#include <urcu.h>

30
static int init_incremental(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents, bool deep_copy)
31
{
32
	if (old_contents == NULL) {
33 34
		return KNOT_EINVAL;
	}
35

36 37 38 39
	int ret = changeset_init(&update->change, zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}
40

41 42 43 44 45 46 47 48 49 50
	if (deep_copy) {
		update->new_cont_deep_copy = true;
		update->new_cont = old_contents;
	} else {
		update->new_cont_deep_copy = false;
		ret = apply_prepare_zone_copy(old_contents, &update->new_cont);
		if (ret != KNOT_EOK) {
			changeset_clear(&update->change);
			return ret;
		}
51 52
	}

53
	uint32_t apply_flags = update->flags & UPDATE_STRICT ? APPLY_STRICT : 0;
Libor Peltan's avatar
Libor Peltan committed
54 55 56 57 58
	ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
	if (ret != KNOT_EOK) {
		changeset_clear(&update->change);
		return ret;
	}
59

60
	/* Copy base SOA RR. */
61
	update->change.soa_from =
62
		node_create_rrset(old_contents->apex, KNOT_RRTYPE_SOA);
63
	if (update->change.soa_from == NULL) {
64
		zone_contents_free(update->new_cont);
65
		changeset_clear(&update->change);
66 67
		return KNOT_ENOMEM;
	}
68 69 70 71 72 73

	return KNOT_EOK;
}

static int init_full(zone_update_t *update, zone_t *zone)
{
74
	update->new_cont = zone_contents_new(zone->name, true);
75 76 77 78
	if (update->new_cont == NULL) {
		return KNOT_ENOMEM;
	}

79 80
	update->new_cont_deep_copy = true;

Libor Peltan's avatar
Libor Peltan committed
81 82 83 84 85
	int ret = apply_init_ctx(update->a_ctx, update->new_cont, 0);
	if (ret != KNOT_EOK) {
		zone_contents_free(update->new_cont);
		return ret;
	}
86

87 88 89
	return KNOT_EOK;
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103
static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
{
	/* SOA possible only within apex. */
	if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
		return KNOT_EDENIED;
	}

	knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
	zone_node_t *n = contents->apex;
	int ret = zone_contents_remove_rr(contents, &old_soa, &n);
	if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
		return ret;
	}

104 105 106 107 108 109
	ret = zone_contents_add_rr(contents, rr, &n);
	if (ret == KNOT_ETTL) {
		return KNOT_EOK;
	}

	return ret;
110 111
}

112 113
int init_base(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents,
              zone_update_flags_t flags)
114
{
115
	if (update == NULL || zone == NULL || (old_contents == NULL && (flags & UPDATE_INCREMENTAL))) {
116 117 118 119 120 121 122 123 124
		return KNOT_EINVAL;
	}

	memset(update, 0, sizeof(*update));
	update->zone = zone;

	mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
	update->flags = flags;

125 126 127 128 129
	update->a_ctx = calloc(1, sizeof(*update->a_ctx));
	if (update->a_ctx == NULL) {
		return KNOT_ENOMEM;
	}

130
	knot_sem_wait(&zone->cow_lock);
131 132
	update->a_ctx->cow_mutex = &zone->cow_lock;

133
	int ret = KNOT_EINVAL;
134
	if (flags & UPDATE_INCREMENTAL) {
135
		ret = init_incremental(update, zone, old_contents, flags & UPDATE_JOURNAL);
136
	} else if (flags & UPDATE_FULL) {
137 138 139
		ret = init_full(update, zone);
	}
	if (ret != KNOT_EOK) {
140
		knot_sem_post(&zone->cow_lock);
141
		free(update->a_ctx);
142
	}
143 144

	return ret;
145 146
}

147 148 149 150 151 152 153
/* ------------------------------- API -------------------------------------- */

int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
{
	return init_base(update, zone, zone->contents, flags);
}

154
int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
155
				 zone_contents_t *new_cont, zone_update_flags_t flags, bool ignore_dnssec)
156 157 158 159 160 161
{
	if (update == NULL || zone == NULL || new_cont == NULL ||
	    !(flags & UPDATE_INCREMENTAL) || (flags & UPDATE_FULL)) {
		return KNOT_EINVAL;
	}

162 163 164 165 166
	changeset_t diff;
	int ret = changeset_init(&diff, zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}
167

168 169 170 171
	ret = zone_contents_diff(old_cont, new_cont, &diff, ignore_dnssec);
	if (ret != KNOT_EOK && ret != KNOT_ENODIFF && ret != KNOT_ESEMCHECK) {
		changeset_clear(&diff);
		return ret;
172 173
	}

174 175 176 177
	// True if nonempty changes were made but the serial
	// remained the same and has to be incremented.
	bool diff_semcheck = (ret == KNOT_ESEMCHECK);

178
	ret = init_base(update, zone, old_cont, flags);
179
	if (ret != KNOT_EOK) {
180
		changeset_clear(&diff);
181 182 183
		return ret;
	}

184 185 186 187
	ret = zone_update_apply_changeset(update, &diff);
	changeset_clear(&diff);
	if (ret != KNOT_EOK) {
		zone_update_clear(update);
188 189 190
		return ret;
	}

191
	if (diff_semcheck) {
192 193 194 195 196 197 198 199
		ret = zone_update_increment_soa(update, conf());
		if (ret != KNOT_EOK) {
			zone_update_clear(update);
			return ret;
		}
		log_zone_info(zone->name, "automatic SOA serial increment");
	}

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	return KNOT_EOK;
}

int zone_update_from_contents(zone_update_t *update, zone_t *zone_without_contents,
                              zone_contents_t *new_cont, zone_update_flags_t flags)
{
	if (update == NULL || zone_without_contents == NULL || new_cont == NULL) {
		return KNOT_EINVAL;
	}

	memset(update, 0, sizeof(*update));
	update->zone = zone_without_contents;

	mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
	update->flags = flags;

	update->new_cont = new_cont;
	update->new_cont_deep_copy = true;

219 220 221 222 223
	update->a_ctx = calloc(1, sizeof(*update->a_ctx));
	if (update->a_ctx == NULL) {
		return KNOT_ENOMEM;
	}

224
	knot_sem_wait(&update->zone->cow_lock);
225 226
	update->a_ctx->cow_mutex = &update->zone->cow_lock;

227 228 229
	if (flags & UPDATE_INCREMENTAL) {
		int ret = changeset_init(&update->change, zone_without_contents->name);
		if (ret != KNOT_EOK) {
230
			free(update->a_ctx);
231 232 233 234 235 236
			return ret;
		}

		update->change.soa_from = node_create_rrset(new_cont->apex, KNOT_RRTYPE_SOA);
		if (update->change.soa_from == NULL) {
			changeset_clear(&update->change);
237
			free(update->a_ctx);
238 239 240 241 242
			return KNOT_ENOMEM;
		}
	}

	uint32_t apply_flags = update->flags & UPDATE_STRICT ? APPLY_STRICT : 0;
Libor Peltan's avatar
Libor Peltan committed
243 244 245 246 247 248
	int ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
	if (ret != KNOT_EOK) {
		changeset_clear(&update->change);
		free(update->a_ctx);
		return ret;
	}
249 250 251 252

	return KNOT_EOK;
}

253 254 255 256 257 258
const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
{
	if (update == NULL || dname == NULL) {
		return NULL;
	}

259
	return zone_contents_find_node(update->new_cont, dname);
260 261
}

262 263
const zone_node_t *zone_update_get_apex(zone_update_t *update)
{
264 265 266 267
	if (update == NULL) {
		return NULL;
	}

268 269 270 271 272 273
	return zone_update_get_node(update, update->zone->name);
}

uint32_t zone_update_current_serial(zone_update_t *update)
{
	const zone_node_t *apex = zone_update_get_apex(update);
274
	if (apex != NULL) {
275
		return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA)->rdata);
276 277 278 279 280
	} else {
		return 0;
	}
}

281 282 283 284 285 286 287 288
static bool zone_update_changed_nsec3param(const zone_update_t *update)
{
	if (update->zone->contents == NULL) {
		return true;
	}

	dnssec_nsec3_params_t *orig = &update->zone->contents->nsec3_params;
	dnssec_nsec3_params_t *upd = &update->new_cont->nsec3_params;
Libor Peltan's avatar
Libor Peltan committed
289
	return !dnssec_nsec3_params_match(orig, upd);
290 291
}

292 293
const knot_rdataset_t *zone_update_from(zone_update_t *update)
{
294 295 296 297
	if (update == NULL) {
		return NULL;
	}

298 299 300 301 302 303
	if (update->flags & UPDATE_INCREMENTAL) {
		const zone_node_t *apex = update->zone->contents->apex;
		return node_rdataset(apex, KNOT_RRTYPE_SOA);
	}

	return NULL;
304 305 306 307
}

const knot_rdataset_t *zone_update_to(zone_update_t *update)
{
308 309 310
	if (update == NULL) {
		return NULL;
	}
311

312 313 314 315 316 317 318 319
	if (update->flags & UPDATE_FULL) {
		const zone_node_t *apex = update->new_cont->apex;
		return node_rdataset(apex, KNOT_RRTYPE_SOA);
	} else if (update->flags & UPDATE_INCREMENTAL) {
		if (update->change.soa_to == NULL) {
			return NULL;
		}
		return &update->change.soa_to->rrs;
320 321
	}

322
	return NULL;
323 324
}

325 326
void zone_update_clear(zone_update_t *update)
{
327 328
	if (update == NULL) {
		return;
329
	}
330 331 332

	if (update->flags & UPDATE_INCREMENTAL) {
		/* Revert any changes on error, do nothing on success. */
333
		if (update->new_cont_deep_copy) {
334
			update_cleanup(update->a_ctx);
335
			zone_contents_deep_free(update->new_cont);
336
		} else {
337
			update_rollback(update->a_ctx);
338
		}
339 340
		changeset_clear(&update->change);
	} else if (update->flags & UPDATE_FULL) {
341
		assert(update->new_cont_deep_copy);
342
		update_cleanup(update->a_ctx);
343
		zone_contents_deep_free(update->new_cont);
344
	}
345
	if (update->a_ctx != NULL && update->a_ctx->cow_mutex != NULL) {
346
		knot_sem_post(update->a_ctx->cow_mutex);
347
	}
348
	free(update->a_ctx);
349 350
	mp_delete(update->mm.ctx);
	memset(update, 0, sizeof(*update));
351
}
352

353 354 355 356 357 358 359 360 361
static changeset_flag_t changeset_flags(const zone_update_t *update)
{
	if ((update->flags & UPDATE_CANCELOUT)) {
		return CHANGESET_CHECK | CHANGESET_CHECK_CANCELOUT;
	} else {
		return CHANGESET_CHECK;
	}
}

362 363
int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
{
364
	if (update == NULL || rrset == NULL) {
365 366 367
		return KNOT_EINVAL;
	}

368
	if (update->flags & UPDATE_INCREMENTAL) {
369
		int ret = changeset_add_addition(&update->change, rrset, changeset_flags(update));
370 371 372 373 374 375
		if (ret != KNOT_EOK) {
			return ret;
		}

		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* replace previous SOA */
376
			ret = apply_replace_soa(update->a_ctx, &update->change);
377 378 379 380 381 382
			if (ret != KNOT_EOK) {
				changeset_remove_addition(&update->change, rrset);
			}
			return ret;
		}

383
		ret = apply_add_rr(update->a_ctx, rrset);
384 385 386 387 388 389
		if (ret != KNOT_EOK) {
			changeset_remove_addition(&update->change, rrset);
			return ret;
		}

		return KNOT_EOK;
390
	} else if (update->flags & UPDATE_FULL) {
391 392 393 394 395
		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* replace previous SOA */
			return replace_soa(update->new_cont, rrset);
		}

396
		zone_node_t *n = NULL;
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
		int ret = zone_contents_add_rr(update->new_cont, rrset, &n);
		if (ret == KNOT_ETTL) {
			char buff[KNOT_DNAME_TXT_MAXLEN + 1];
			char *owner = knot_dname_to_str(buff, rrset->owner, sizeof(buff));
			if (owner == NULL) {
				owner = "";
			}
			char type[16] = { '\0' };
			knot_rrtype_to_string(rrset->type, type, sizeof(type));
			log_zone_notice(update->new_cont->apex->owner,
			                "TTL mismatch, owner %s, type %s, "
			                "TTL set to %u", owner, type, rrset->ttl);
			return KNOT_EOK;
		}

		return ret;
413 414 415 416 417 418 419
	} else {
		return KNOT_EINVAL;
	}
}

int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
{
420
	if (update == NULL || rrset == NULL) {
421 422 423
		return KNOT_EINVAL;
	}

424
	if (update->flags & UPDATE_INCREMENTAL) {
425
		int ret = changeset_add_removal(&update->change, rrset, changeset_flags(update));
426 427 428 429 430 431 432 433 434
		if (ret != KNOT_EOK) {
			return ret;
		}

		if (rrset->type == KNOT_RRTYPE_SOA) {
			/* SOA is replaced with addition */
			return KNOT_EOK;
		}

435
		ret = apply_remove_rr(update->a_ctx, rrset);
436 437 438 439 440 441
		if (ret != KNOT_EOK) {
			changeset_remove_removal(&update->change, rrset);
			return ret;
		}

		return KNOT_EOK;
442 443
	} else if (update->flags & UPDATE_FULL) {
		zone_node_t *n = NULL;
444 445
		knot_rrset_t *rrs_copy = knot_rrset_copy(rrset, &update->mm);
		int ret = zone_contents_remove_rr(update->new_cont, rrs_copy, &n);
446
		knot_rrset_free(rrs_copy, &update->mm);
447
		return ret;
448
	} else {
449
		return KNOT_EINVAL;
450 451 452
	}
}

453 454 455 456 457 458 459 460
int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
{
	if (update == NULL || owner == NULL) {
		return KNOT_EINVAL;
	}

	if (update->flags & UPDATE_INCREMENTAL) {
		/* Remove the RRSet from the original node */
461
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
462 463
		if (node != NULL) {
			knot_rrset_t rrset = node_rrset(node, type);
464 465 466
			if (rrset.owner == NULL) {
				return KNOT_ENOENT;
			}
Daniel Salzman's avatar
Daniel Salzman committed
467
			int ret = changeset_add_removal(&update->change, &rrset,
468
			                                changeset_flags(update));
469 470 471 472
			if (ret != KNOT_EOK) {
				return ret;
			}

473 474 475 476 477
			if (type == KNOT_RRTYPE_SOA) {
				/* SOA is replaced with addition */
				return KNOT_EOK;
			}

478
			ret = apply_remove_rr(update->a_ctx, &rrset);
479 480 481
			if (ret != KNOT_EOK) {
				return ret;
			}
482
		} else {
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
			return KNOT_ENONODE;
		}
	} else if (update->flags & UPDATE_FULL) {
		/* Remove the RRSet from the non-synthesized new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
		if (node == NULL) {
			return KNOT_ENONODE;
		}

		knot_rrset_t rrset = node_rrset(node, type);
		int ret = zone_update_remove(update, &rrset);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	return KNOT_EOK;
}

int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
{
	if (update == NULL || owner == NULL) {
		return KNOT_EINVAL;
	}

	if (update->flags & UPDATE_INCREMENTAL) {
509 510
		/* Remove all RRSets from the new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
511 512 513 514
		if (node != NULL) {
			size_t rrset_count = node->rrset_count;
			for (int i = 0; i < rrset_count; ++i) {
				knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
Daniel Salzman's avatar
Daniel Salzman committed
515
				int ret = changeset_add_removal(&update->change, &rrset,
516
				                                changeset_flags(update));
517 518 519 520
				if (ret != KNOT_EOK) {
					return ret;
				}

521 522
				if (rrset.type == KNOT_RRTYPE_SOA) {
					/* SOA is replaced with addition */
523
					continue;
524 525
				}

526
				ret = apply_remove_rr(update->a_ctx, &rrset);
527 528 529 530
				if (ret != KNOT_EOK) {
					return ret;
				}
			}
531
		} else {
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
			return KNOT_ENONODE;
		}
	} else if (update->flags & UPDATE_FULL) {
		/* Remove all RRSets from the non-synthesized new node */
		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
		if (node == NULL) {
			return KNOT_ENONODE;
		}

		size_t rrset_count = node->rrset_count;
		for (int i = 0; i < rrset_count; ++i) {
			knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
			int ret = zone_update_remove(update, &rrset);
			if (ret != KNOT_EOK) {
				return ret;
			}
		}
	}

	return KNOT_EOK;
}

554 555 556 557
int zone_update_apply_changeset(zone_update_t *update, const changeset_t *changes)
{
	int ret = KNOT_EOK;
	if (update->flags & UPDATE_INCREMENTAL) {
558
		ret = changeset_merge(&update->change, changes, changeset_flags(update));
559 560
	}
	if (ret == KNOT_EOK) {
561
		ret = apply_changeset_directly(update->a_ctx, changes);
562 563 564 565 566 567 568 569 570 571
	}
	return ret;
}

int zone_update_apply_changeset_fix(zone_update_t *update, changeset_t *changes)
{
	int ret = changeset_cancelout(changes);
	if (ret == KNOT_EOK) {
		ret = changeset_preapply_fix(update->new_cont, changes);
	}
572 573
	if (ret == KNOT_EOK) {
		ret = zone_update_apply_changeset(update, changes);
574
	}
575
	return ret;
576 577
}

578 579 580 581 582 583 584 585 586 587
int zone_update_apply_changeset_reverse(zone_update_t *update, const changeset_t *changes)
{
	changeset_t reverse;
	reverse.remove = changes->add;
	reverse.add = changes->remove;
	reverse.soa_from = changes->soa_to;
	reverse.soa_to = changes->soa_from;
	return zone_update_apply_changeset(update, &reverse);
}

588
static int set_new_soa(zone_update_t *update, unsigned serial_policy)
589
{
590 591
	assert(update);

Daniel Salzman's avatar
Daniel Salzman committed
592 593
	knot_rrset_t *soa_cpy = node_create_rrset(zone_update_get_apex(update),
	                                          KNOT_RRTYPE_SOA);
594 595 596 597
	if (soa_cpy == NULL) {
		return KNOT_ENOMEM;
	}

598 599
	int ret = zone_update_remove(update, soa_cpy);
	if (ret != KNOT_EOK) {
600
		knot_rrset_free(soa_cpy, NULL);
601
		return ret;
602 603
	}

604
	uint32_t old_serial = knot_soa_serial(soa_cpy->rrs.rdata);
605
	uint32_t new_serial = serial_next(old_serial, serial_policy);
606
	if (serial_compare(old_serial, new_serial) != SERIAL_LOWER) {
607
		log_zone_warning(update->zone->name, "updated SOA serial is lower "
608
		                 "than current, serial %u -> %u",
609
		                 old_serial, new_serial);
610 611
		ret = KNOT_ESOAINVAL;
	} else {
612
		knot_soa_serial_set(soa_cpy->rrs.rdata, new_serial);
613

614 615
		ret = zone_update_add(update, soa_cpy);
	}
616
	knot_rrset_free(soa_cpy, NULL);
617 618 619 620 621 622 623 624 625 626 627 628

	return ret;
}

int zone_update_increment_soa(zone_update_t *update, conf_t *conf)
{
	if (update == NULL || conf == NULL) {
		return KNOT_EINVAL;
	}

	conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
	return set_new_soa(update, conf_opt(&val));
629 630
}

Libor Peltan's avatar
Libor Peltan committed
631
static int commit_incremental(conf_t *conf, zone_update_t *update)
632
{
633
	assert(update);
634

635
	int ret = KNOT_EOK;
Libor Peltan's avatar
Libor Peltan committed
636
	if (zone_update_to(update) == NULL && !changeset_empty(&update->change)) {
637
		/* No SOA in the update, create one according to the current policy */
638
		ret = zone_update_increment_soa(update, conf);
639
		if (ret != KNOT_EOK) {
640
			zone_update_clear(update);
641 642
			return ret;
		}
643
	}
644

645 646 647 648 649 650
	ret = zone_contents_load_nsec3param(update->new_cont);
	if (ret != KNOT_EOK) {
		zone_update_clear(update);
		return ret;
	}

651
	if (update->new_cont_deep_copy || zone_update_changed_nsec3param(update)) {
652 653 654 655
		ret = zone_adjust_full(update->new_cont);
	} else {
		ret = zone_adjust_incremental_update(update);
	}
656 657 658 659
	if (ret != KNOT_EOK) {
		zone_update_clear(update);
		return ret;
	}
660

661
	/* Write changes to journal if all went well. */
662
	conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
Libor Peltan's avatar
Libor Peltan committed
663
	if (conf_opt(&val) != JOURNAL_CONTENT_NONE && !changeset_empty(&update->change)) {
664
		ret = zone_change_store(conf, update->zone, &update->change);
665
		if (ret != KNOT_EOK) {
666 667
			return ret;
		}
668
	}
669 670 671 672

	return KNOT_EOK;
}

Libor Peltan's avatar
Libor Peltan committed
673
static int commit_full(conf_t *conf, zone_update_t *update)
674
{
675 676 677 678 679 680 681 682
	assert(update);

	/* Check if we have SOA. We might consider adding full semantic check here.
	 * But if we wanted full sem-check I'd consider being it controlled by a flag
	 * - to enable/disable it on demand. */
	if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
		return KNOT_ESEMCHECK;
	}
683

Libor Peltan's avatar
Libor Peltan committed
684
	int ret = zone_adjust_full(update->new_cont);
685 686 687 688
	if (ret != KNOT_EOK) {
		zone_update_clear(update);
		return ret;
	}
689

690 691
	/* Store new zone contents in journal. */
	conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
692 693
	unsigned content = conf_opt(&val);
	if (content == JOURNAL_CONTENT_ALL) {
694
		ret = zone_in_journal_store(conf, update->zone, update->new_cont);
695
	} else if (content != JOURNAL_CONTENT_NONE) { // zone_in_journal_store does this automatically
696
		ret = zone_changes_clear(conf, update->zone);
697 698
	}

699
	return ret;
700 701
}

702 703 704 705 706 707 708 709 710 711 712 713
/*! \brief Routine for calling call_rcu() easier way.
 *
 * Consider moving elsewhere, as it has no direct relation to zone-update.
 */
typedef struct {
	struct rcu_head rcuhead;
	void (*callback)(void *);
	void *ctx;
	bool free_ctx;
} callrcu_wrapper_t;

static void callrcu_wrapper_cb(struct rcu_head *param)
714
{
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
	callrcu_wrapper_t *wrap = (callrcu_wrapper_t *)param;
	wrap->callback(wrap->ctx);
	if (wrap->free_ctx) {
		free(wrap->ctx);
	}
	free(wrap);

	// Trim extra heap.
	mem_trim();
}

/* NOTE: Does nothing if not enough memory. */
static void callrcu_wrapper(void *ctx, void *callback, bool free_ctx)
{
	callrcu_wrapper_t *wrap = calloc(1, sizeof(callrcu_wrapper_t));
	if (wrap != NULL) {
		wrap->callback = callback;
		wrap->ctx = ctx;
		wrap->free_ctx = free_ctx;
		call_rcu((struct rcu_head *)wrap, callrcu_wrapper_cb);
	}
736 737
}

738
int zone_update_commit(conf_t *conf, zone_update_t *update)
739
{
740
	if (conf == NULL || update == NULL) {
741 742 743 744
		return KNOT_EINVAL;
	}

	int ret = KNOT_EOK;
745
	if (update->flags & UPDATE_INCREMENTAL) {
Libor Peltan's avatar
Libor Peltan committed
746 747 748 749 750 751
		if (changeset_empty(&update->change) &&
		    update->zone->contents != NULL && !update->new_cont_deep_copy) {
			changeset_clear(&update->change);
			return KNOT_EOK;
		}
		ret = commit_incremental(conf, update);
752
	} else {
Libor Peltan's avatar
Libor Peltan committed
753
		ret = commit_full(conf, update);
754 755 756
	}
	if (ret != KNOT_EOK) {
		return ret;
757 758
	}

759
	/* Check the zone size. */
760
	conf_val_t val = conf_zone_get(conf, C_MAX_ZONE_SIZE, update->zone->name);
761
	size_t size_limit = conf_int(&val);
762

Libor Peltan's avatar
Libor Peltan committed
763
	if (update->new_cont->size > size_limit) {
764
		/* Recoverable error. */
765 766 767
		return KNOT_EZONESIZE;
	}

768 769 770 771 772 773 774 775
	/* Check if the zone was re-signed upon zone load to ensure proper flush
	 * even if the SOA serial wasn't incremented by re-signing. */
	val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
	bool dnssec = conf_bool(&val);
	if (!changeset_empty(&update->change) && dnssec) {
		update->zone->zonefile.resigned = true;
	}

776
	/* Abort control transaction if any. */
777 778
	if (update->zone->control_update != NULL &&
	    update->zone->control_update != update) {
779 780 781 782
		log_zone_warning(update->zone->name, "control transaction aborted");
		zone_control_clear(update->zone);
	}

783
	/* Switch zone contents. */
784
	zone_contents_t *old_contents;
Libor Peltan's avatar
Libor Peltan committed
785
	old_contents = zone_switch_contents(update->zone, update->new_cont);
786 787 788

	/* Sync RCU. */
	if (update->flags & UPDATE_FULL) {
789
		assert(update->new_cont_deep_copy);
790
		callrcu_wrapper(old_contents, zone_contents_deep_free, false);
791
	} else if (update->flags & UPDATE_INCREMENTAL) {
792
		if (update->new_cont_deep_copy) {
793
			callrcu_wrapper(old_contents, zone_contents_deep_free, false);
794
		} else {
795
			callrcu_wrapper(old_contents, update_free_zone, false);
796
		}
797
		changeset_clear(&update->change);
798
	}
799
	callrcu_wrapper(update->a_ctx, update_cleanup, true);
800
	update->a_ctx = NULL;
801
	update->new_cont = NULL;
802

803 804 805 806 807 808
	/* Sync zonefile immediately if configured. */
	val = conf_zone_get(conf, C_ZONEFILE_SYNC, update->zone->name);
	if (conf_int(&val) == 0) {
		zone_events_schedule_now(update->zone, ZONE_EVENT_FLUSH);
	}

809
	return KNOT_EOK;
810 811
}

812 813 814 815
static int iter_init_tree_iters(zone_update_iter_t *it, zone_update_t *update,
                                bool nsec3)
{
	/* Set zone iterator. */
816
	zone_contents_t *_contents = update->new_cont;
817 818

	/* Begin iteration. We can safely assume _contents is a valid pointer. */
819
	zone_tree_t *tree = nsec3 ? _contents->nsec3_nodes : _contents->nodes;
820
	if (zone_tree_it_begin(tree, &it->tree_it) != KNOT_EOK) {
821 822 823
		return KNOT_ENOMEM;
	}

824
	it->cur_node = zone_tree_it_val(&it->tree_it);
825

826 827 828
	return KNOT_EOK;
}

829
static int iter_get_next_node(zone_update_iter_t *it)
830
{
831 832 833
	zone_tree_it_next(&it->tree_it);
	if (zone_tree_it_finished(&it->tree_it)) {
		zone_tree_it_free(&it->tree_it);
834
		it->cur_node = NULL;
835 836 837
		return KNOT_ENOENT;
	}

838
	it->cur_node = zone_tree_it_val(&it->tree_it);
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853

	return KNOT_EOK;
}

static int iter_init(zone_update_iter_t *it, zone_update_t *update, const bool nsec3)
{
	memset(it, 0, sizeof(*it));

	it->update = update;
	it->nsec3 = nsec3;
	int ret = iter_init_tree_iters(it, update, nsec3);
	if (ret != KNOT_EOK) {
		return ret;
	}

854
	it->cur_node = zone_tree_it_val(&it->tree_it);
855 856 857 858 859 860

	return KNOT_EOK;
}

int zone_update_iter(zone_update_iter_t *it, zone_update_t *update)
{
861 862 863 864
	if (it == NULL || update == NULL) {
		return KNOT_EINVAL;
	}

865 866 867 868 869
	return iter_init(it, update, false);
}

int zone_update_iter_nsec3(zone_update_iter_t *it, zone_update_t *update)
{
870 871 872 873
	if (it == NULL || update == NULL) {
		return KNOT_EINVAL;
	}

874 875
	if (update->flags & UPDATE_FULL) {
		if (update->new_cont->nsec3_nodes == NULL) {
876
			/* No NSEC3 tree. */
877 878 879 880 881
			return KNOT_ENOENT;
		}
	} else {
		if (update->change.add->nsec3_nodes == NULL &&
		    update->change.remove->nsec3_nodes == NULL) {
882
			/* No NSEC3 changes. */
883 884 885 886 887 888 889 890 891 892 893 894 895
			return KNOT_ENOENT;
		}
	}

	return iter_init(it, update, true);
}

int zone_update_iter_next(zone_update_iter_t *it)
{
	if (it == NULL) {
		return KNOT_EINVAL;
	}

896
	if (it->tree_it.it != NULL) {
897
		int ret = iter_get_next_node(it);
898 899 900 901 902 903 904 905 906 907
		if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
			return ret;
		}
	}

	return KNOT_EOK;
}

const zone_node_t *zone_update_iter_val(zone_update_iter_t *it)
{
908
	if (it != NULL) {
909
		return it->cur_node;
910 911 912 913 914
	} else {
		return NULL;
	}
}

915
void zone_update_iter_finish(zone_update_iter_t *it)
916
{
917
	if (it == NULL) {
918
		return;
919 920
	}

921
	zone_tree_it_free(&it->tree_it);
922 923
}

924
bool zone_update_no_change(zone_update_t *update)
925
{
926 927 928 929
	if (update == NULL) {
		return true;
	}

930 931 932
	if (update->flags & UPDATE_INCREMENTAL) {
		return changeset_empty(&update->change);
	} else {
933 934 935
		/* This branch does not make much sense and FULL update will most likely
		 * be a change every time anyway, just return false. */
		return false;
936
	}
937
}