4 * Copyright (C) 2004, 2005, 2007-2009 Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 2000-2003 Internet Software Consortium.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
20 /* Id: diff.c,v 1.23 2009/12/01 00:47:09 each Exp */
28 #include <isc/buffer.h>
31 #include <isc/string.h>
37 #include <dns/rdataclass.h>
38 #include <dns/rdatalist.h>
39 #include <dns/rdataset.h>
40 #include <dns/rdatastruct.h>
41 #include <dns/rdatatype.h>
42 #include <dns/result.h>
46 if (result != ISC_R_SUCCESS) goto failure; \
49 #define DIFF_COMMON_LOGARGS \
50 dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF
52 static dns_rdatatype_t
53 rdata_covers(dns_rdata_t
*rdata
) {
54 return (rdata
->type
== dns_rdatatype_rrsig
?
55 dns_rdata_covers(rdata
) : 0);
59 dns_difftuple_create(isc_mem_t
*mctx
,
60 dns_diffop_t op
, dns_name_t
*name
, dns_ttl_t ttl
,
61 dns_rdata_t
*rdata
, dns_difftuple_t
**tp
)
67 REQUIRE(tp
!= NULL
&& *tp
== NULL
);
70 * Create a new tuple. The variable-size wire-format name data and
71 * rdata immediately follow the dns_difftuple_t structure
74 size
= sizeof(*t
) + name
->length
+ rdata
->length
;
75 t
= isc_mem_allocate(mctx
, size
);
77 return (ISC_R_NOMEMORY
);
81 datap
= (unsigned char *)(t
+ 1);
83 memcpy(datap
, name
->ndata
, name
->length
);
84 dns_name_init(&t
->name
, NULL
);
85 dns_name_clone(name
, &t
->name
);
86 t
->name
.ndata
= datap
;
87 datap
+= name
->length
;
91 memcpy(datap
, rdata
->data
, rdata
->length
);
92 dns_rdata_init(&t
->rdata
);
93 dns_rdata_clone(rdata
, &t
->rdata
);
94 t
->rdata
.data
= datap
;
95 datap
+= rdata
->length
;
97 ISC_LINK_INIT(&t
->rdata
, link
);
98 ISC_LINK_INIT(t
, link
);
99 t
->magic
= DNS_DIFFTUPLE_MAGIC
;
101 INSIST(datap
== (unsigned char *)t
+ size
);
104 return (ISC_R_SUCCESS
);
108 dns_difftuple_free(dns_difftuple_t
**tp
) {
109 dns_difftuple_t
*t
= *tp
;
110 REQUIRE(DNS_DIFFTUPLE_VALID(t
));
111 dns_name_invalidate(&t
->name
);
113 isc_mem_free(t
->mctx
, t
);
118 dns_difftuple_copy(dns_difftuple_t
*orig
, dns_difftuple_t
**copyp
) {
119 return (dns_difftuple_create(orig
->mctx
, orig
->op
, &orig
->name
,
120 orig
->ttl
, &orig
->rdata
, copyp
));
124 dns_diff_init(isc_mem_t
*mctx
, dns_diff_t
*diff
) {
127 ISC_LIST_INIT(diff
->tuples
);
128 diff
->magic
= DNS_DIFF_MAGIC
;
132 dns_diff_clear(dns_diff_t
*diff
) {
134 REQUIRE(DNS_DIFF_VALID(diff
));
135 while ((t
= ISC_LIST_HEAD(diff
->tuples
)) != NULL
) {
136 ISC_LIST_UNLINK(diff
->tuples
, t
, link
);
137 dns_difftuple_free(&t
);
139 ENSURE(ISC_LIST_EMPTY(diff
->tuples
));
143 dns_diff_append(dns_diff_t
*diff
, dns_difftuple_t
**tuplep
)
145 ISC_LIST_APPEND(diff
->tuples
, *tuplep
, link
);
149 /* XXX this is O(N) */
152 dns_diff_appendminimal(dns_diff_t
*diff
, dns_difftuple_t
**tuplep
)
154 dns_difftuple_t
*ot
, *next_ot
;
156 REQUIRE(DNS_DIFF_VALID(diff
));
157 REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep
));
160 * Look for an existing tuple with the same owner name,
161 * rdata, and TTL. If we are doing an addition and find a
162 * deletion or vice versa, remove both the old and the
163 * new tuple since they cancel each other out (assuming
164 * that we never delete nonexistent data or add existing
167 * If we find an old update of the same kind as
168 * the one we are doing, there must be a programming
169 * error. We report it but try to continue anyway.
171 for (ot
= ISC_LIST_HEAD(diff
->tuples
); ot
!= NULL
;
174 next_ot
= ISC_LIST_NEXT(ot
, link
);
175 if (dns_name_equal(&ot
->name
, &(*tuplep
)->name
) &&
176 dns_rdata_compare(&ot
->rdata
, &(*tuplep
)->rdata
) == 0 &&
177 ot
->ttl
== (*tuplep
)->ttl
)
179 ISC_LIST_UNLINK(diff
->tuples
, ot
, link
);
180 if ((*tuplep
)->op
== ot
->op
) {
181 UNEXPECTED_ERROR(__FILE__
, __LINE__
,
182 "unexpected non-minimal diff");
184 dns_difftuple_free(tuplep
);
186 dns_difftuple_free(&ot
);
191 if (*tuplep
!= NULL
) {
192 ISC_LIST_APPEND(diff
->tuples
, *tuplep
, link
);
196 ENSURE(*tuplep
== NULL
);
200 setresign(dns_rdataset_t
*modified
, isc_uint32_t delta
) {
201 dns_rdata_t rdata
= DNS_RDATA_INIT
;
202 dns_rdata_rrsig_t sig
;
206 result
= dns_rdataset_first(modified
);
207 INSIST(result
== ISC_R_SUCCESS
);
208 dns_rdataset_current(modified
, &rdata
);
209 (void)dns_rdata_tostruct(&rdata
, &sig
, NULL
);
210 if ((rdata
.flags
& DNS_RDATA_OFFLINE
) != 0)
213 when
= sig
.timeexpire
- delta
;
214 dns_rdata_reset(&rdata
);
216 result
= dns_rdataset_next(modified
);
217 while (result
== ISC_R_SUCCESS
) {
218 dns_rdataset_current(modified
, &rdata
);
219 (void)dns_rdata_tostruct(&rdata
, &sig
, NULL
);
220 if ((rdata
.flags
& DNS_RDATA_OFFLINE
) != 0) {
223 if (when
== 0 || sig
.timeexpire
- delta
< when
)
224 when
= sig
.timeexpire
- delta
;
226 dns_rdata_reset(&rdata
);
227 result
= dns_rdataset_next(modified
);
229 INSIST(result
== ISC_R_NOMORE
);
234 diff_apply(dns_diff_t
*diff
, dns_db_t
*db
, dns_dbversion_t
*ver
,
238 dns_dbnode_t
*node
= NULL
;
240 char namebuf
[DNS_NAME_FORMATSIZE
];
241 char typebuf
[DNS_RDATATYPE_FORMATSIZE
];
242 char classbuf
[DNS_RDATACLASS_FORMATSIZE
];
244 REQUIRE(DNS_DIFF_VALID(diff
));
245 REQUIRE(DNS_DB_VALID(db
));
247 t
= ISC_LIST_HEAD(diff
->tuples
);
251 INSIST(node
== NULL
);
255 * We create the node if it does not exist.
256 * This will cause an empty node to be created if the diff
257 * contains a deletion of an RR at a nonexistent name,
258 * but such diffs should never be created in the first
262 while (t
!= NULL
&& dns_name_equal(&t
->name
, name
)) {
263 dns_rdatatype_t type
, covers
;
267 dns_rdataset_t ardataset
;
268 dns_rdataset_t
*modified
= NULL
;
269 isc_boolean_t offline
;
272 type
= t
->rdata
.type
;
273 covers
= rdata_covers(&t
->rdata
);
276 * Collect a contiguous set of updates with
277 * the same operation (add/delete) and RR type
278 * into a single rdatalist so that the
279 * database rrset merging/subtraction code
280 * can work more efficiently than if each
281 * RR were merged into / subtracted from
282 * the database separately.
284 * This is done by linking rdata structures from the
285 * diff into "rdatalist". This uses the rdata link
286 * field, not the diff link field, so the structure
287 * of the diff itself is not affected.
292 rdl
.rdclass
= t
->rdata
.rdclass
;
294 ISC_LIST_INIT(rdl
.rdata
);
295 ISC_LINK_INIT(&rdl
, link
);
298 if (type
!= dns_rdatatype_nsec3
&&
299 covers
!= dns_rdatatype_nsec3
)
300 CHECK(dns_db_findnode(db
, name
, ISC_TRUE
,
303 CHECK(dns_db_findnsec3node(db
, name
, ISC_TRUE
,
308 dns_name_equal(&t
->name
, name
) &&
310 t
->rdata
.type
== type
&&
311 rdata_covers(&t
->rdata
) == covers
)
313 dns_name_format(name
, namebuf
, sizeof(namebuf
));
314 dns_rdatatype_format(t
->rdata
.type
, typebuf
,
316 dns_rdataclass_format(t
->rdata
.rdclass
,
319 if (t
->ttl
!= rdl
.ttl
&& warn
)
320 isc_log_write(DIFF_COMMON_LOGARGS
,
322 "'%s/%s/%s': TTL differs in "
323 "rdataset, adjusting "
325 namebuf
, typebuf
, classbuf
,
326 (unsigned long) t
->ttl
,
327 (unsigned long) rdl
.ttl
);
328 if (t
->rdata
.flags
& DNS_RDATA_OFFLINE
)
330 ISC_LIST_APPEND(rdl
.rdata
, &t
->rdata
, link
);
331 t
= ISC_LIST_NEXT(t
, link
);
335 * Convert the rdatalist into a rdataset.
337 dns_rdataset_init(&rds
);
338 CHECK(dns_rdatalist_tordataset(&rdl
, &rds
));
339 if (rds
.type
== dns_rdatatype_rrsig
)
341 case DNS_DIFFOP_ADDRESIGN
:
342 case DNS_DIFFOP_DELRESIGN
:
343 modified
= &ardataset
;
344 dns_rdataset_init(modified
);
349 rds
.trust
= dns_trust_ultimate
;
352 * Merge the rdataset into the database.
356 case DNS_DIFFOP_ADDRESIGN
:
357 result
= dns_db_addrdataset(db
, node
, ver
,
365 case DNS_DIFFOP_DELRESIGN
:
366 result
= dns_db_subtractrdataset(db
, node
, ver
,
375 if (result
== ISC_R_SUCCESS
) {
376 if (modified
!= NULL
) {
377 isc_stdtime_t resign
;
378 resign
= setresign(modified
,
380 dns_db_setsigningtime(db
, modified
,
383 } else if (result
== DNS_R_UNCHANGED
) {
385 * This will not happen when executing a
386 * dynamic update, because that code will
387 * generate strictly minimal diffs.
388 * It may happen when receiving an IXFR
389 * from a server that is not as careful.
390 * Issue a warning and continue.
393 char classbuf
[DNS_RDATATYPE_FORMATSIZE
];
394 char namebuf
[DNS_NAME_FORMATSIZE
];
396 dns_name_format(dns_db_origin(db
),
399 dns_rdataclass_format(dns_db_class(db
),
402 isc_log_write(DIFF_COMMON_LOGARGS
,
404 "%s/%s: dns_diff_apply: "
405 "update with no effect",
408 } else if (result
== DNS_R_NXRRSET
) {
413 if (modified
!= NULL
&&
414 dns_rdataset_isassociated(modified
))
415 dns_rdataset_disassociate(modified
);
418 dns_db_detachnode(db
, &node
);
419 if (modified
!= NULL
&&
420 dns_rdataset_isassociated(modified
))
421 dns_rdataset_disassociate(modified
);
424 return (ISC_R_SUCCESS
);
428 dns_db_detachnode(db
, &node
);
433 dns_diff_apply(dns_diff_t
*diff
, dns_db_t
*db
, dns_dbversion_t
*ver
) {
434 return (diff_apply(diff
, db
, ver
, ISC_TRUE
));
438 dns_diff_applysilently(dns_diff_t
*diff
, dns_db_t
*db
, dns_dbversion_t
*ver
) {
439 return (diff_apply(diff
, db
, ver
, ISC_FALSE
));
442 /* XXX this duplicates lots of code in diff_apply(). */
445 dns_diff_load(dns_diff_t
*diff
, dns_addrdatasetfunc_t addfunc
,
451 REQUIRE(DNS_DIFF_VALID(diff
));
453 t
= ISC_LIST_HEAD(diff
->tuples
);
458 while (t
!= NULL
&& dns_name_equal(&t
->name
, name
)) {
459 dns_rdatatype_t type
, covers
;
465 type
= t
->rdata
.type
;
466 covers
= rdata_covers(&t
->rdata
);
470 rdl
.rdclass
= t
->rdata
.rdclass
;
472 ISC_LIST_INIT(rdl
.rdata
);
473 ISC_LINK_INIT(&rdl
, link
);
475 while (t
!= NULL
&& dns_name_equal(&t
->name
, name
) &&
476 t
->op
== op
&& t
->rdata
.type
== type
&&
477 rdata_covers(&t
->rdata
) == covers
)
479 ISC_LIST_APPEND(rdl
.rdata
, &t
->rdata
, link
);
480 t
= ISC_LIST_NEXT(t
, link
);
484 * Convert the rdatalist into a rdataset.
486 dns_rdataset_init(&rds
);
487 CHECK(dns_rdatalist_tordataset(&rdl
, &rds
));
488 rds
.trust
= dns_trust_ultimate
;
490 INSIST(op
== DNS_DIFFOP_ADD
);
491 result
= (*addfunc
)(add_private
, name
, &rds
);
492 if (result
== DNS_R_UNCHANGED
) {
493 isc_log_write(DIFF_COMMON_LOGARGS
,
496 "update with no effect");
497 } else if (result
== ISC_R_SUCCESS
||
498 result
== DNS_R_NXRRSET
) {
507 result
= ISC_R_SUCCESS
;
513 * XXX uses qsort(); a merge sort would be more natural for lists,
514 * and perhaps safer wrt thread stack overflow.
517 dns_diff_sort(dns_diff_t
*diff
, dns_diff_compare_func
*compare
) {
518 unsigned int length
= 0;
522 REQUIRE(DNS_DIFF_VALID(diff
));
524 for (p
= ISC_LIST_HEAD(diff
->tuples
);
526 p
= ISC_LIST_NEXT(p
, link
))
529 return (ISC_R_SUCCESS
);
530 v
= isc_mem_get(diff
->mctx
, length
* sizeof(dns_difftuple_t
*));
532 return (ISC_R_NOMEMORY
);
534 for (i
= 0; i
< length
; i
++) {
535 p
= ISC_LIST_HEAD(diff
->tuples
);
537 ISC_LIST_UNLINK(diff
->tuples
, p
, link
);
539 INSIST(ISC_LIST_HEAD(diff
->tuples
) == NULL
);
540 qsort(v
, length
, sizeof(v
[0]), compare
);
541 for (i
= 0; i
< length
; i
++) {
542 ISC_LIST_APPEND(diff
->tuples
, v
[i
], link
);
544 isc_mem_put(diff
->mctx
, v
, length
* sizeof(dns_difftuple_t
*));
545 return (ISC_R_SUCCESS
);
550 * Create an rdataset containing the single RR of the given
551 * tuple. The caller must allocate the rdata, rdataset and
552 * an rdatalist structure for it to refer to.
556 diff_tuple_tordataset(dns_difftuple_t
*t
, dns_rdata_t
*rdata
,
557 dns_rdatalist_t
*rdl
, dns_rdataset_t
*rds
)
559 REQUIRE(DNS_DIFFTUPLE_VALID(t
));
560 REQUIRE(rdl
!= NULL
);
561 REQUIRE(rds
!= NULL
);
563 rdl
->type
= t
->rdata
.type
;
564 rdl
->rdclass
= t
->rdata
.rdclass
;
566 ISC_LIST_INIT(rdl
->rdata
);
567 ISC_LINK_INIT(rdl
, link
);
568 dns_rdataset_init(rds
);
569 ISC_LINK_INIT(rdata
, link
);
570 dns_rdata_clone(&t
->rdata
, rdata
);
571 ISC_LIST_APPEND(rdl
->rdata
, rdata
, link
);
572 return (dns_rdatalist_tordataset(rdl
, rds
));
576 dns_diff_print(dns_diff_t
*diff
, FILE *file
) {
580 unsigned int size
= 2048;
581 const char *op
= NULL
;
583 REQUIRE(DNS_DIFF_VALID(diff
));
585 mem
= isc_mem_get(diff
->mctx
, size
);
587 return (ISC_R_NOMEMORY
);
589 for (t
= ISC_LIST_HEAD(diff
->tuples
); t
!= NULL
;
590 t
= ISC_LIST_NEXT(t
, link
))
597 dns_rdata_t rd
= DNS_RDATA_INIT
;
599 result
= diff_tuple_tordataset(t
, &rd
, &rdl
, &rds
);
600 if (result
!= ISC_R_SUCCESS
) {
601 UNEXPECTED_ERROR(__FILE__
, __LINE__
,
602 "diff_tuple_tordataset failed: %s",
603 dns_result_totext(result
));
604 result
= ISC_R_UNEXPECTED
;
608 isc_buffer_init(&buf
, mem
, size
);
609 result
= dns_rdataset_totext(&rds
, &t
->name
,
610 ISC_FALSE
, ISC_FALSE
, &buf
);
612 if (result
== ISC_R_NOSPACE
) {
613 isc_mem_put(diff
->mctx
, mem
, size
);
615 mem
= isc_mem_get(diff
->mctx
, size
);
617 result
= ISC_R_NOMEMORY
;
623 if (result
!= ISC_R_SUCCESS
)
626 * Get rid of final newline.
628 INSIST(buf
.used
>= 1 &&
629 ((char *) buf
.base
)[buf
.used
-1] == '\n');
632 isc_buffer_usedregion(&buf
, &r
);
634 case DNS_DIFFOP_EXISTS
: op
= "exists"; break;
635 case DNS_DIFFOP_ADD
: op
= "add"; break;
636 case DNS_DIFFOP_DEL
: op
= "del"; break;
637 case DNS_DIFFOP_ADDRESIGN
: op
= "add re-sign"; break;
638 case DNS_DIFFOP_DELRESIGN
: op
= "del re-sign"; break;
641 fprintf(file
, "%s %.*s\n", op
, (int) r
.length
,
644 isc_log_write(DIFF_COMMON_LOGARGS
, ISC_LOG_DEBUG(7),
645 "%s %.*s", op
, (int) r
.length
,
648 result
= ISC_R_SUCCESS
;
651 isc_mem_put(diff
->mctx
, mem
, size
);