changesets.c 10.4 KB
Newer Older
1
/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
Lubos Slovak's avatar
Lubos Slovak committed
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 <assert.h>
Daniel Salzman's avatar
Daniel Salzman committed
18
#include <stdlib.h>
19
#include <stdarg.h>
20

21
#include "knot/updates/changesets.h"
Daniel Salzman's avatar
Daniel Salzman committed
22
#include "libknot/libknot.h"
23
#include "contrib/macros.h"
24
#include "contrib/mempattern.h"
25

26
static int handle_soa(knot_rrset_t **soa, const knot_rrset_t *rrset)
27
{
28
29
	assert(soa);
	assert(rrset);
30

31
32
	if (*soa != NULL) {
		knot_rrset_free(soa, NULL);
33
34
	}

35
36
37
38
39
40
41
42
43
44
45
	*soa = knot_rrset_copy(rrset, NULL);
	if (*soa == NULL) {
		return KNOT_ENOMEM;
	}

	return KNOT_EOK;
}

/*! \brief Adds RRSet to given zone. */
static int add_rr_to_contents(zone_contents_t *z, const knot_rrset_t *rrset)
{
46
47
	zone_node_t *n = NULL;
	int ret = zone_contents_add_rr(z, rrset, &n);
48
	UNUSED(n);
49
	return ret;
50
51
}

52
/*! \brief Cleans up trie iterations. */
Jan Kadlec's avatar
Jan Kadlec committed
53
static void cleanup_iter_list(list_t *l)
54
{
Jan Kadlec's avatar
Jan Kadlec committed
55
56
	ptrnode_t *n, *nxt;
	WALK_LIST_DELSAFE(n, nxt, *l) {
57
58
		trie_it_t *it = (trie_it_t *)n->d;
		trie_it_free(it);
Jan Kadlec's avatar
Jan Kadlec committed
59
60
61
62
63
64
		rem_node(&n->n);
		free(n);
	}
	init_list(l);
}

65
/*! \brief Inits changeset iterator with given tries. */
66
static int changeset_iter_init(changeset_iter_t *ch_it, size_t tries, ...)
Jan Kadlec's avatar
Jan Kadlec committed
67
68
69
70
71
72
73
74
{
	memset(ch_it, 0, sizeof(*ch_it));
	init_list(&ch_it->iters);

	va_list args;
	va_start(args, tries);

	for (size_t i = 0; i < tries; ++i) {
75
		trie_t *t = va_arg(args, trie_t *);
76
77
78
79
		if (t == NULL) {
			continue;
		}

80
		trie_it_t *it = trie_it_begin(t);
81
82
83
84
85
86
87
88
89
90
		if (it == NULL) {
			cleanup_iter_list(&ch_it->iters);
			va_end(args);
			return KNOT_ENOMEM;
		}

		if (ptrlist_add(&ch_it->iters, it, NULL) == NULL) {
			cleanup_iter_list(&ch_it->iters);
			va_end(args);
			return KNOT_ENOMEM;
Jan Kadlec's avatar
Jan Kadlec committed
91
92
93
94
95
96
97
98
		}
	}

	va_end(args);

	return KNOT_EOK;
}

99
/*! \brief Gets next node from trie iterators. */
100
static void iter_next_node(changeset_iter_t *ch_it, trie_it_t *t_it)
Jan Kadlec's avatar
Jan Kadlec committed
101
{
102
	assert(!trie_it_finished(t_it));
Jan Kadlec's avatar
Jan Kadlec committed
103
104
	// Get next node, but not for the very first call.
	if (ch_it->node) {
105
		trie_it_next(t_it);
Jan Kadlec's avatar
Jan Kadlec committed
106
	}
107
	if (trie_it_finished(t_it)) {
Jan Kadlec's avatar
Jan Kadlec committed
108
109
110
111
		ch_it->node = NULL;
		return;
	}

112
	ch_it->node = (zone_node_t *)*trie_it_val(t_it);
Jan Kadlec's avatar
Jan Kadlec committed
113
114
115
	assert(ch_it->node);
	while (ch_it->node && ch_it->node->rrset_count == 0) {
		// Skip empty non-terminals.
116
117
		trie_it_next(t_it);
		if (trie_it_finished(t_it)) {
Jan Kadlec's avatar
Jan Kadlec committed
118
119
			ch_it->node = NULL;
		} else {
120
			ch_it->node = (zone_node_t *)*trie_it_val(t_it);
Jan Kadlec's avatar
Jan Kadlec committed
121
122
123
			assert(ch_it->node);
		}
	}
124

Jan Kadlec's avatar
Jan Kadlec committed
125
126
127
	ch_it->node_pos = 0;
}

128
/*! \brief Gets next RRSet from trie iterators. */
129
static knot_rrset_t get_next_rr(changeset_iter_t *ch_it, trie_it_t *t_it)
Jan Kadlec's avatar
Jan Kadlec committed
130
131
132
133
{
	if (ch_it->node == NULL || ch_it->node_pos == ch_it->node->rrset_count) {
		iter_next_node(ch_it, t_it);
		if (ch_it->node == NULL) {
134
			assert(trie_it_finished(t_it));
Jan Kadlec's avatar
Jan Kadlec committed
135
136
137
138
139
140
141
142
143
			knot_rrset_t rr;
			knot_rrset_init_empty(&rr);
			return rr;
		}
	}

	return node_rrset_at(ch_it->node, ch_it->node_pos++);
}

144
static void check_redundancy(zone_contents_t *counterpart, const knot_rrset_t *rr)
145
146
147
{
	zone_node_t *node = zone_contents_find_node_for_rr(counterpart, rr);
	if (node == NULL) {
148
		return;
149
150
151
	}

	if (!node_rrtype_exists(node, rr->type)) {
152
		return;
153
154
155
	}

	// Subtract the data from node's RRSet.
156
157
	knot_rdataset_t *rrs = node_rdataset(node, rr->type);
	int ret = knot_rdataset_subtract(rrs, &rr->rrs, NULL);
158
	if (ret != KNOT_EOK) {
159
		return;
160
161
	}

162
	if (knot_rdataset_size(rrs) == 0) {
163
164
165
		// Remove empty type.
		node_remove_rdataset(node, rr->type);

166
167
168
169
		if (node->rrset_count == 0) {
			// Remove empty node.
			zone_tree_t *t = knot_rrset_is_nsec3rel(rr) ?
								 counterpart->nsec3_nodes : counterpart->nodes;
170
			zone_tree_delete_empty_node(t, node);
171
		}
172
173
	}

174
	return;
175
176
}

Jan Kadlec's avatar
Jan Kadlec committed
177
178
179
int changeset_init(changeset_t *ch, const knot_dname_t *apex)
{
	memset(ch, 0, sizeof(changeset_t));
180

181
182
	// Init local changes
	ch->add = zone_contents_new(apex);
Jan Kadlec's avatar
Jan Kadlec committed
183
184
185
	if (ch->add == NULL) {
		return KNOT_ENOMEM;
	}
186
	ch->remove = zone_contents_new(apex);
Jan Kadlec's avatar
Jan Kadlec committed
187
188
189
190
	if (ch->remove == NULL) {
		zone_contents_free(&ch->add);
		return KNOT_ENOMEM;
	}
Jan Kadlec's avatar
Jan Kadlec committed
191

Jan Kadlec's avatar
Jan Kadlec committed
192
	return KNOT_EOK;
193
194
}

Jan Kadlec's avatar
Jan Kadlec committed
195
changeset_t *changeset_new(const knot_dname_t *apex)
Jan Kadlec's avatar
Jan Kadlec committed
196
{
Jan Kadlec's avatar
Jan Kadlec committed
197
	changeset_t *ret = malloc(sizeof(changeset_t));
198
	if (ret == NULL) {
Jan Kadlec's avatar
Jan Kadlec committed
199
200
201
		return NULL;
	}

Jan Kadlec's avatar
Jan Kadlec committed
202
203
204
205
206
207
	if (changeset_init(ret, apex) == KNOT_EOK) {
		return ret;
	} else {
		free(ret);
		return NULL;
	}
208
209
}

210
bool changeset_empty(const changeset_t *ch)
211
{
212
	if (ch == NULL || ch->add == NULL || ch->remove == NULL) {
213
		return true;
214
215
	}

216
217
218
	if (ch->soa_to) {
		return false;
	}
219

220
	changeset_iter_t itt;
221
	changeset_iter_all(&itt, ch);
222

223
224
	knot_rrset_t rr = changeset_iter_next(&itt);
	changeset_iter_clear(&itt);
225
226

	return knot_rrset_empty(&rr);
227
228
}

Lubos Slovak's avatar
Lubos Slovak committed
229
size_t changeset_size(const changeset_t *ch)
230
{
Jan Kadlec's avatar
Jan Kadlec committed
231
	if (ch == NULL) {
232
233
		return 0;
	}
234

235
	changeset_iter_t itt;
236
	changeset_iter_all(&itt, ch);
237
238

	size_t size = 0;
239
	knot_rrset_t rr = changeset_iter_next(&itt);
240
241
	while(!knot_rrset_empty(&rr)) {
		++size;
242
		rr = changeset_iter_next(&itt);
243
	}
244
	changeset_iter_clear(&itt);
245

Jan Kadlec's avatar
Jan Kadlec committed
246
247
248
249
250
251
252
	if (!knot_rrset_empty(ch->soa_from)) {
		size += 1;
	}
	if (!knot_rrset_empty(ch->soa_to)) {
		size += 1;
	}

253
	return size;
254
255
}

256
int changeset_add_addition(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
Jan Kadlec's avatar
Jan Kadlec committed
257
{
258
259
260
261
262
263
264
265
266
	if (!ch || !rrset) {
		return KNOT_EINVAL;
	}

	if (rrset->type == KNOT_RRTYPE_SOA) {
		/* Do not add SOAs into actual contents. */
		return handle_soa(&ch->soa_to, rrset);
	}

267
268
	/* Check if there's any removal and remove that, then add this
	 * addition anyway. Required to change TTLs. */
269
	if (flags & CHANGESET_CHECK) {
270
271
272
273
274
275
		/* If we delete the rrset, we need to hold a copy to add it later */
		rrset = knot_rrset_copy(rrset, NULL);
		if (rrset == NULL) {
			return KNOT_ENOMEM;
		}

276
		check_redundancy(ch->remove, rrset);
277
	}
278

279
	int ret = add_rr_to_contents(ch->add, rrset);
280

281
	if (flags & CHANGESET_CHECK) {
282
283
284
285
		knot_rrset_free((knot_rrset_t **)&rrset, NULL);
	}

	return ret;
Jan Kadlec's avatar
Jan Kadlec committed
286
287
}

288
int changeset_add_removal(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
Jan Kadlec's avatar
Jan Kadlec committed
289
{
290
291
292
293
294
295
296
297
298
	if (!ch || !rrset) {
		return KNOT_EINVAL;
	}

	if (rrset->type == KNOT_RRTYPE_SOA) {
		/* Do not add SOAs into actual contents. */
		return handle_soa(&ch->soa_from, rrset);
	}

299
300
	/* Check if there's any addition and remove that, then add this
	 * removal anyway. */
301
	if (flags & CHANGESET_CHECK) {
302
303
304
305
306
307
		/* If we delete the rrset, we need to hold a copy to add it later */
		rrset = knot_rrset_copy(rrset, NULL);
		if (rrset == NULL) {
			return KNOT_ENOMEM;
		}

308
		check_redundancy(ch->add, rrset);
309
	}
310

311
	int ret = add_rr_to_contents(ch->remove, rrset);
312

313
	if (flags & CHANGESET_CHECK) {
314
315
316
317
		knot_rrset_free((knot_rrset_t **)&rrset, NULL);
	}

	return ret;
Jan Kadlec's avatar
Jan Kadlec committed
318
319
}

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
int changeset_remove_addition(changeset_t *ch, const knot_rrset_t *rrset)
{
	if (rrset->type == KNOT_RRTYPE_SOA) {
		/* Do not add SOAs into actual contents. */
		if (ch->soa_to != NULL) {
			knot_rrset_free(&ch->soa_to, NULL);
			ch->soa_to = NULL;
		}
		return KNOT_EOK;
	}

	zone_node_t *n = NULL;
	return zone_contents_remove_rr(ch->add, rrset, &n);
}

int changeset_remove_removal(changeset_t *ch, const knot_rrset_t *rrset)
{
	if (rrset->type == KNOT_RRTYPE_SOA) {
		/* Do not add SOAs into actual contents. */
		if (ch->soa_from != NULL) {
			knot_rrset_free(&ch->soa_from, NULL);
			ch->soa_from = NULL;
		}
		return KNOT_EOK;
	}

	zone_node_t *n = NULL;
	return zone_contents_remove_rr(ch->remove, rrset, &n);
}

Jan Kadlec's avatar
Jan Kadlec committed
350
int changeset_merge(changeset_t *ch1, const changeset_t *ch2)
351
{
352
	changeset_iter_t itt;
353
	changeset_iter_add(&itt, ch2);
354

355
	knot_rrset_t rrset = changeset_iter_next(&itt);
356
	while (!knot_rrset_empty(&rrset)) {
357
		int ret = changeset_add_addition(ch1, &rrset, CHANGESET_CHECK);
358
		if (ret != KNOT_EOK) {
359
360
			changeset_iter_clear(&itt);
			return ret;
361
		}
362
		rrset = changeset_iter_next(&itt);
363
	}
364
	changeset_iter_clear(&itt);
365

366
	changeset_iter_rem(&itt, ch2);
367

368
	rrset = changeset_iter_next(&itt);
369
	while (!knot_rrset_empty(&rrset)) {
370
		int ret = changeset_add_removal(ch1, &rrset, CHANGESET_CHECK);
371
		if (ret != KNOT_EOK) {
372
373
			changeset_iter_clear(&itt);
			return ret;
374
		}
375
		rrset = changeset_iter_next(&itt);
376
	}
377
	changeset_iter_clear(&itt);
378

379
380
	// Use soa_to and serial from the second changeset
	// soa_to from the first changeset is redundant, delete it
Jan Kadlec's avatar
Jan Kadlec committed
381
	knot_rrset_t *soa_copy = knot_rrset_copy(ch2->soa_to, NULL);
Jan Kadlec's avatar
Jan Kadlec committed
382
	if (soa_copy == NULL && ch2->soa_to) {
Jan Kadlec's avatar
Jan Kadlec committed
383
384
		return KNOT_ENOMEM;
	}
385
	knot_rrset_free(&ch1->soa_to, NULL);
Jan Kadlec's avatar
Jan Kadlec committed
386
	ch1->soa_to = soa_copy;
387
388

	return KNOT_EOK;
389
390
}

391
392
393
394
395
396
397
398
399
400
401
402
403
void changesets_clear(list_t *chgs)
{
	if (chgs) {
		changeset_t *chg, *nxt;
		WALK_LIST_DELSAFE(chg, nxt, *chgs) {
			changeset_clear(chg);
			rem_node(&chg->n);
		}
		init_list(chgs);
	}
}

void changesets_free(list_t *chgs)
404
{
Jan Kadlec's avatar
Jan Kadlec committed
405
	if (chgs) {
406
407
		changeset_t *chg, *nxt;
		WALK_LIST_DELSAFE(chg, nxt, *chgs) {
408
			rem_node(&chg->n);
409
			changeset_free(chg);
410
		}
411
		init_list(chgs);
412
	}
413
414
}

Jan Kadlec's avatar
Jan Kadlec committed
415
void changeset_clear(changeset_t *ch)
416
{
Jan Kadlec's avatar
Jan Kadlec committed
417
418
	if (ch == NULL) {
		return;
419
	}
420

Jan Kadlec's avatar
Jan Kadlec committed
421
422
423
	// Delete RRSets in lists, in case there are any left
	zone_contents_deep_free(&ch->add);
	zone_contents_deep_free(&ch->remove);
424

Jan Kadlec's avatar
Jan Kadlec committed
425
426
	knot_rrset_free(&ch->soa_from, NULL);
	knot_rrset_free(&ch->soa_to, NULL);
427

Jan Kadlec's avatar
Jan Kadlec committed
428
429
430
	// Delete binary data
	free(ch->data);
}
431

Jan Kadlec's avatar
Jan Kadlec committed
432
433
434
435
void changeset_free(changeset_t *ch)
{
	changeset_clear(ch);
	free(ch);
436
437
}

438
int changeset_iter_add(changeset_iter_t *itt, const changeset_t *ch)
439
{
440
	return changeset_iter_init(itt, 2, ch->add->nodes, ch->add->nsec3_nodes);
441
442
}

443
int changeset_iter_rem(changeset_iter_t *itt, const changeset_t *ch)
444
{
445
	return changeset_iter_init(itt, 2, ch->remove->nodes, ch->remove->nsec3_nodes);
446
447
}

448
int changeset_iter_all(changeset_iter_t *itt, const changeset_t *ch)
449
{
450
	return changeset_iter_init(itt, 4, ch->add->nodes, ch->add->nsec3_nodes,
451
	                           ch->remove->nodes, ch->remove->nsec3_nodes);
452
453
}

454
455
knot_rrset_t changeset_iter_next(changeset_iter_t *it)
{
456
	assert(it);
457
458
459
460
	ptrnode_t *n = NULL;
	knot_rrset_t rr;
	knot_rrset_init_empty(&rr);
	WALK_LIST(n, it->iters) {
461
462
		trie_it_t *t_it = (trie_it_t *)n->d;
		if (trie_it_finished(t_it)) {
463
464
465
466
467
468
469
470
			continue;
		}

		rr = get_next_rr(it, t_it);
		if (!knot_rrset_empty(&rr)) {
			// Got valid RRSet.
			return rr;
		}
471
472
	}

473
	return rr;
474
475
}

476
void changeset_iter_clear(changeset_iter_t *it)
477
{
Jan Kadlec's avatar
Jan Kadlec committed
478
	if (it) {
479
		cleanup_iter_list(&it->iters);
480
481
		it->node = NULL;
		it->node_pos = 0;
Jan Kadlec's avatar
Jan Kadlec committed
482
	}
483
}