Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: diff.c,v 1.11 2026/01/29 18:37:48 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*! \file */
     17 
     18 #include <inttypes.h>
     19 #include <stdbool.h>
     20 #include <stddef.h>
     21 #include <stdlib.h>
     22 
     23 #include <isc/buffer.h>
     24 #include <isc/file.h>
     25 #include <isc/mem.h>
     26 #include <isc/result.h>
     27 #include <isc/string.h>
     28 #include <isc/util.h>
     29 
     30 #include <dns/callbacks.h>
     31 #include <dns/db.h>
     32 #include <dns/diff.h>
     33 #include <dns/log.h>
     34 #include <dns/rdataclass.h>
     35 #include <dns/rdatalist.h>
     36 #include <dns/rdataset.h>
     37 #include <dns/rdatastruct.h>
     38 #include <dns/rdatatype.h>
     39 #include <dns/time.h>
     40 
     41 #define DIFF_COMMON_LOGARGS \
     42 	dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF
     43 
     44 static dns_rdatatype_t
     45 rdata_covers(dns_rdata_t *rdata) {
     46 	return rdata->type == dns_rdatatype_rrsig ? dns_rdata_covers(rdata) : 0;
     47 }
     48 
     49 isc_result_t
     50 dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name,
     51 		     dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp) {
     52 	dns_difftuple_t *t;
     53 	unsigned int size;
     54 	unsigned char *datap;
     55 
     56 	REQUIRE(tp != NULL && *tp == NULL);
     57 
     58 	/*
     59 	 * Create a new tuple.  The variable-size wire-format name data and
     60 	 * rdata immediately follow the dns_difftuple_t structure
     61 	 * in memory.
     62 	 */
     63 	size = sizeof(*t) + name->length + rdata->length;
     64 	t = isc_mem_allocate(mctx, size);
     65 	t->mctx = NULL;
     66 	isc_mem_attach(mctx, &t->mctx);
     67 	t->op = op;
     68 
     69 	datap = (unsigned char *)(t + 1);
     70 
     71 	memmove(datap, name->ndata, name->length);
     72 	dns_name_init(&t->name, NULL);
     73 	dns_name_clone(name, &t->name);
     74 	t->name.ndata = datap;
     75 	datap += name->length;
     76 
     77 	t->ttl = ttl;
     78 
     79 	dns_rdata_init(&t->rdata);
     80 	dns_rdata_clone(rdata, &t->rdata);
     81 	if (rdata->data != NULL) {
     82 		memmove(datap, rdata->data, rdata->length);
     83 		t->rdata.data = datap;
     84 		datap += rdata->length;
     85 	} else {
     86 		t->rdata.data = NULL;
     87 		INSIST(rdata->length == 0);
     88 	}
     89 
     90 	ISC_LINK_INIT(&t->rdata, link);
     91 	ISC_LINK_INIT(t, link);
     92 	t->magic = DNS_DIFFTUPLE_MAGIC;
     93 
     94 	INSIST(datap == (unsigned char *)t + size);
     95 
     96 	*tp = t;
     97 	return ISC_R_SUCCESS;
     98 }
     99 
    100 void
    101 dns_difftuple_free(dns_difftuple_t **tp) {
    102 	dns_difftuple_t *t = *tp;
    103 	*tp = NULL;
    104 	isc_mem_t *mctx;
    105 
    106 	REQUIRE(DNS_DIFFTUPLE_VALID(t));
    107 
    108 	dns_name_invalidate(&t->name);
    109 	t->magic = 0;
    110 	mctx = t->mctx;
    111 	isc_mem_free(mctx, t);
    112 	isc_mem_detach(&mctx);
    113 }
    114 
    115 isc_result_t
    116 dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp) {
    117 	return dns_difftuple_create(orig->mctx, orig->op, &orig->name,
    118 				    orig->ttl, &orig->rdata, copyp);
    119 }
    120 
    121 void
    122 dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff) {
    123 	diff->mctx = mctx;
    124 	ISC_LIST_INIT(diff->tuples);
    125 	diff->magic = DNS_DIFF_MAGIC;
    126 	diff->size = 0;
    127 }
    128 
    129 void
    130 dns_diff_clear(dns_diff_t *diff) {
    131 	dns_difftuple_t *t;
    132 	REQUIRE(DNS_DIFF_VALID(diff));
    133 	while ((t = ISC_LIST_HEAD(diff->tuples)) != NULL) {
    134 		ISC_LIST_UNLINK(diff->tuples, t, link);
    135 		dns_difftuple_free(&t);
    136 	}
    137 	diff->size = 0;
    138 	ENSURE(ISC_LIST_EMPTY(diff->tuples));
    139 }
    140 
    141 void
    142 dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuplep) {
    143 	REQUIRE(DNS_DIFF_VALID(diff));
    144 	ISC_LIST_APPEND(diff->tuples, *tuplep, link);
    145 	diff->size += 1;
    146 	*tuplep = NULL;
    147 }
    148 
    149 bool
    150 dns_diff_is_boundary(const dns_diff_t *diff, dns_name_t *new_name) {
    151 	REQUIRE(DNS_DIFF_VALID(diff));
    152 	REQUIRE(DNS_NAME_VALID(new_name));
    153 
    154 	if (ISC_LIST_EMPTY(diff->tuples)) {
    155 		return false;
    156 	}
    157 
    158 	dns_difftuple_t *tail = ISC_LIST_TAIL(diff->tuples);
    159 	return !dns_name_caseequal(&tail->name, new_name);
    160 }
    161 
    162 size_t
    163 dns_diff_size(const dns_diff_t *diff) {
    164 	REQUIRE(DNS_DIFF_VALID(diff));
    165 	return diff->size;
    166 }
    167 
    168 /* XXX this is O(N) */
    169 
    170 void
    171 dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep) {
    172 	dns_difftuple_t *ot, *next_ot;
    173 
    174 	REQUIRE(DNS_DIFF_VALID(diff));
    175 	REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep));
    176 
    177 	/*
    178 	 * Look for an existing tuple with the same owner name,
    179 	 * rdata, and TTL.   If we are doing an addition and find a
    180 	 * deletion or vice versa, remove both the old and the
    181 	 * new tuple since they cancel each other out (assuming
    182 	 * that we never delete nonexistent data or add existing
    183 	 * data).
    184 	 *
    185 	 * If we find an old update of the same kind as
    186 	 * the one we are doing, there must be a programming
    187 	 * error.  We report it but try to continue anyway.
    188 	 */
    189 	for (ot = ISC_LIST_HEAD(diff->tuples); ot != NULL; ot = next_ot) {
    190 		next_ot = ISC_LIST_NEXT(ot, link);
    191 		if (dns_name_caseequal(&ot->name, &(*tuplep)->name) &&
    192 		    dns_rdata_compare(&ot->rdata, &(*tuplep)->rdata) == 0 &&
    193 		    ot->ttl == (*tuplep)->ttl)
    194 		{
    195 			ISC_LIST_UNLINK(diff->tuples, ot, link);
    196 			INSIST(diff->size > 0);
    197 			diff->size -= 1;
    198 
    199 			if ((*tuplep)->op == ot->op) {
    200 				UNEXPECTED_ERROR("unexpected non-minimal diff");
    201 			} else {
    202 				dns_difftuple_free(tuplep);
    203 			}
    204 			dns_difftuple_free(&ot);
    205 			break;
    206 		}
    207 	}
    208 
    209 	if (*tuplep != NULL) {
    210 		ISC_LIST_APPEND(diff->tuples, *tuplep, link);
    211 		diff->size += 1;
    212 		*tuplep = NULL;
    213 	}
    214 }
    215 
    216 static isc_stdtime_t
    217 setresign(dns_rdataset_t *modified) {
    218 	dns_rdata_t rdata = DNS_RDATA_INIT;
    219 	dns_rdata_rrsig_t sig;
    220 	int64_t when;
    221 	isc_result_t result;
    222 
    223 	result = dns_rdataset_first(modified);
    224 	INSIST(result == ISC_R_SUCCESS);
    225 	dns_rdataset_current(modified, &rdata);
    226 	(void)dns_rdata_tostruct(&rdata, &sig, NULL);
    227 	if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
    228 		when = 0;
    229 	} else {
    230 		when = dns_time64_from32(sig.timeexpire);
    231 	}
    232 	dns_rdata_reset(&rdata);
    233 
    234 	result = dns_rdataset_next(modified);
    235 	while (result == ISC_R_SUCCESS) {
    236 		dns_rdataset_current(modified, &rdata);
    237 		(void)dns_rdata_tostruct(&rdata, &sig, NULL);
    238 		if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
    239 			goto next_rr;
    240 		}
    241 		if (when == 0 || dns_time64_from32(sig.timeexpire) < when) {
    242 			when = dns_time64_from32(sig.timeexpire);
    243 		}
    244 	next_rr:
    245 		dns_rdata_reset(&rdata);
    246 		result = dns_rdataset_next(modified);
    247 	}
    248 	INSIST(result == ISC_R_NOMORE);
    249 	return (isc_stdtime_t)when;
    250 }
    251 
    252 static void
    253 getownercase(dns_rdataset_t *rdataset, dns_name_t *name) {
    254 	if (dns_rdataset_isassociated(rdataset)) {
    255 		dns_rdataset_getownercase(rdataset, name);
    256 	}
    257 }
    258 
    259 static void
    260 setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
    261 	if (dns_rdataset_isassociated(rdataset)) {
    262 		dns_rdataset_setownercase(rdataset, name);
    263 	}
    264 }
    265 
    266 static const char *
    267 optotext(dns_diffop_t op) {
    268 	switch (op) {
    269 	case DNS_DIFFOP_ADD:
    270 		return "add";
    271 	case DNS_DIFFOP_ADDRESIGN:
    272 		return "add-resign";
    273 	case DNS_DIFFOP_DEL:
    274 		return "del";
    275 	case DNS_DIFFOP_DELRESIGN:
    276 		return "del-resign";
    277 	default:
    278 		return "unknown";
    279 	}
    280 }
    281 
    282 static isc_result_t
    283 diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver,
    284 	   bool warn) {
    285 	dns_difftuple_t *t;
    286 	dns_dbnode_t *node = NULL;
    287 	isc_result_t result;
    288 	char namebuf[DNS_NAME_FORMATSIZE];
    289 	char typebuf[DNS_RDATATYPE_FORMATSIZE];
    290 	char classbuf[DNS_RDATACLASS_FORMATSIZE];
    291 
    292 	REQUIRE(DNS_DIFF_VALID(diff));
    293 	REQUIRE(DNS_DB_VALID(db));
    294 
    295 	t = ISC_LIST_HEAD(diff->tuples);
    296 	while (t != NULL) {
    297 		dns_name_t *name;
    298 
    299 		INSIST(node == NULL);
    300 		name = &t->name;
    301 		/*
    302 		 * Find the node.
    303 		 * We create the node if it does not exist.
    304 		 * This will cause an empty node to be created if the diff
    305 		 * contains a deletion of an RR at a nonexistent name,
    306 		 * but such diffs should never be created in the first
    307 		 * place.
    308 		 */
    309 
    310 		while (t != NULL && dns_name_equal(&t->name, name)) {
    311 			dns_rdatatype_t type, covers;
    312 			dns_rdataclass_t rdclass;
    313 			dns_diffop_t op;
    314 			dns_rdatalist_t rdl;
    315 			dns_rdataset_t rds;
    316 			dns_rdataset_t ardataset;
    317 			unsigned int options;
    318 
    319 			op = t->op;
    320 			type = t->rdata.type;
    321 			rdclass = t->rdata.rdclass;
    322 			covers = rdata_covers(&t->rdata);
    323 
    324 			/*
    325 			 * Collect a contiguous set of updates with
    326 			 * the same operation (add/delete) and RR type
    327 			 * into a single rdatalist so that the
    328 			 * database rrset merging/subtraction code
    329 			 * can work more efficiently than if each
    330 			 * RR were merged into / subtracted from
    331 			 * the database separately.
    332 			 *
    333 			 * This is done by linking rdata structures from the
    334 			 * diff into "rdatalist".  This uses the rdata link
    335 			 * field, not the diff link field, so the structure
    336 			 * of the diff itself is not affected.
    337 			 */
    338 
    339 			dns_rdatalist_init(&rdl);
    340 			rdl.type = type;
    341 			rdl.covers = covers;
    342 			rdl.rdclass = t->rdata.rdclass;
    343 			rdl.ttl = t->ttl;
    344 
    345 			node = NULL;
    346 			if (type != dns_rdatatype_nsec3 &&
    347 			    covers != dns_rdatatype_nsec3)
    348 			{
    349 				CHECK(dns_db_findnode(db, name, true, &node));
    350 			} else {
    351 				CHECK(dns_db_findnsec3node(db, name, true,
    352 							   &node));
    353 			}
    354 
    355 			while (t != NULL && dns_name_equal(&t->name, name) &&
    356 			       t->op == op && t->rdata.type == type &&
    357 			       rdata_covers(&t->rdata) == covers)
    358 			{
    359 				/*
    360 				 * Remember the add name for
    361 				 * dns_rdataset_setownercase.
    362 				 */
    363 				name = &t->name;
    364 				if (t->ttl != rdl.ttl && warn) {
    365 					dns_name_format(name, namebuf,
    366 							sizeof(namebuf));
    367 					dns_rdatatype_format(t->rdata.type,
    368 							     typebuf,
    369 							     sizeof(typebuf));
    370 					dns_rdataclass_format(t->rdata.rdclass,
    371 							      classbuf,
    372 							      sizeof(classbuf));
    373 					isc_log_write(DIFF_COMMON_LOGARGS,
    374 						      ISC_LOG_WARNING,
    375 						      "'%s/%s/%s': TTL differs "
    376 						      "in "
    377 						      "rdataset, adjusting "
    378 						      "%lu -> %lu",
    379 						      namebuf, typebuf,
    380 						      classbuf,
    381 						      (unsigned long)t->ttl,
    382 						      (unsigned long)rdl.ttl);
    383 				}
    384 				ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
    385 				t = ISC_LIST_NEXT(t, link);
    386 			}
    387 
    388 			/*
    389 			 * Convert the rdatalist into a rdataset.
    390 			 */
    391 			dns_rdataset_init(&rds);
    392 			dns_rdataset_init(&ardataset);
    393 			dns_rdatalist_tordataset(&rdl, &rds);
    394 			rds.trust = dns_trust_ultimate;
    395 
    396 			/*
    397 			 * Merge the rdataset into the database.
    398 			 */
    399 			switch (op) {
    400 			case DNS_DIFFOP_ADD:
    401 			case DNS_DIFFOP_ADDRESIGN:
    402 				options = DNS_DBADD_MERGE | DNS_DBADD_EXACT |
    403 					  DNS_DBADD_EXACTTTL;
    404 				result = dns_db_addrdataset(db, node, ver, 0,
    405 							    &rds, options,
    406 							    &ardataset);
    407 				break;
    408 			case DNS_DIFFOP_DEL:
    409 			case DNS_DIFFOP_DELRESIGN:
    410 				options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD;
    411 				result = dns_db_subtractrdataset(db, node, ver,
    412 								 &rds, options,
    413 								 &ardataset);
    414 				break;
    415 			default:
    416 				UNREACHABLE();
    417 			}
    418 
    419 			if (result == ISC_R_SUCCESS) {
    420 				if (rds.type == dns_rdatatype_rrsig &&
    421 				    (op == DNS_DIFFOP_DELRESIGN ||
    422 				     op == DNS_DIFFOP_ADDRESIGN))
    423 				{
    424 					isc_stdtime_t resign;
    425 					resign = setresign(&ardataset);
    426 					dns_db_setsigningtime(db, &ardataset,
    427 							      resign);
    428 				}
    429 				if (op == DNS_DIFFOP_ADD ||
    430 				    op == DNS_DIFFOP_ADDRESIGN)
    431 				{
    432 					setownercase(&ardataset, name);
    433 				}
    434 				if (op == DNS_DIFFOP_DEL ||
    435 				    op == DNS_DIFFOP_DELRESIGN)
    436 				{
    437 					getownercase(&ardataset, name);
    438 				}
    439 			} else if (result == DNS_R_UNCHANGED) {
    440 				/*
    441 				 * This will not happen when executing a
    442 				 * dynamic update, because that code will
    443 				 * generate strictly minimal diffs.
    444 				 * It may happen when receiving an IXFR
    445 				 * from a server that is not as careful.
    446 				 * Issue a warning and continue.
    447 				 */
    448 				if (warn) {
    449 					dns_name_format(dns_db_origin(db),
    450 							namebuf,
    451 							sizeof(namebuf));
    452 					dns_rdataclass_format(dns_db_class(db),
    453 							      classbuf,
    454 							      sizeof(classbuf));
    455 					isc_log_write(DIFF_COMMON_LOGARGS,
    456 						      ISC_LOG_WARNING,
    457 						      "%s/%s: dns_diff_apply: "
    458 						      "update with no effect",
    459 						      namebuf, classbuf);
    460 				}
    461 				if (op == DNS_DIFFOP_ADD ||
    462 				    op == DNS_DIFFOP_ADDRESIGN)
    463 				{
    464 					setownercase(&ardataset, name);
    465 				}
    466 				if (op == DNS_DIFFOP_DEL ||
    467 				    op == DNS_DIFFOP_DELRESIGN)
    468 				{
    469 					getownercase(&ardataset, name);
    470 				}
    471 			} else if (result == DNS_R_NXRRSET) {
    472 				/*
    473 				 * OK.
    474 				 */
    475 				if (op == DNS_DIFFOP_DEL ||
    476 				    op == DNS_DIFFOP_DELRESIGN)
    477 				{
    478 					getownercase(&ardataset, name);
    479 				}
    480 				if (dns_rdataset_isassociated(&ardataset)) {
    481 					dns_rdataset_disassociate(&ardataset);
    482 				}
    483 			} else {
    484 				if (result == DNS_R_NOTEXACT) {
    485 					dns_name_format(name, namebuf,
    486 							sizeof(namebuf));
    487 					dns_rdatatype_format(type, typebuf,
    488 							     sizeof(typebuf));
    489 					dns_rdataclass_format(rdclass, classbuf,
    490 							      sizeof(classbuf));
    491 					isc_log_write(
    492 						DIFF_COMMON_LOGARGS,
    493 						ISC_LOG_ERROR,
    494 						"dns_diff_apply: %s/%s/%s: %s "
    495 						"%s",
    496 						namebuf, typebuf, classbuf,
    497 						optotext(op),
    498 						isc_result_totext(result));
    499 				}
    500 				if (dns_rdataset_isassociated(&ardataset)) {
    501 					dns_rdataset_disassociate(&ardataset);
    502 				}
    503 				CHECK(result);
    504 			}
    505 			dns_db_detachnode(db, &node);
    506 			if (dns_rdataset_isassociated(&ardataset)) {
    507 				dns_rdataset_disassociate(&ardataset);
    508 			}
    509 		}
    510 	}
    511 	return ISC_R_SUCCESS;
    512 
    513 cleanup:
    514 	if (node != NULL) {
    515 		dns_db_detachnode(db, &node);
    516 	}
    517 	return result;
    518 }
    519 
    520 isc_result_t
    521 dns_diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
    522 	return diff_apply(diff, db, ver, true);
    523 }
    524 
    525 isc_result_t
    526 dns_diff_applysilently(const dns_diff_t *diff, dns_db_t *db,
    527 		       dns_dbversion_t *ver) {
    528 	return diff_apply(diff, db, ver, false);
    529 }
    530 
    531 /* XXX this duplicates lots of code in diff_apply(). */
    532 
    533 isc_result_t
    534 dns_diff_load(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks) {
    535 	dns_difftuple_t *t;
    536 	isc_result_t result;
    537 
    538 	REQUIRE(DNS_DIFF_VALID(diff));
    539 
    540 	if (callbacks->setup != NULL) {
    541 		callbacks->setup(callbacks->add_private);
    542 	}
    543 
    544 	t = ISC_LIST_HEAD(diff->tuples);
    545 	while (t != NULL) {
    546 		dns_name_t *name;
    547 
    548 		name = &t->name;
    549 		while (t != NULL && dns_name_caseequal(&t->name, name)) {
    550 			dns_rdatatype_t type, covers;
    551 			dns_diffop_t op;
    552 			dns_rdatalist_t rdl;
    553 			dns_rdataset_t rds;
    554 
    555 			op = t->op;
    556 			type = t->rdata.type;
    557 			covers = rdata_covers(&t->rdata);
    558 
    559 			dns_rdatalist_init(&rdl);
    560 			rdl.type = type;
    561 			rdl.covers = covers;
    562 			rdl.rdclass = t->rdata.rdclass;
    563 			rdl.ttl = t->ttl;
    564 
    565 			while (t != NULL &&
    566 			       dns_name_caseequal(&t->name, name) &&
    567 			       t->op == op && t->rdata.type == type &&
    568 			       rdata_covers(&t->rdata) == covers)
    569 			{
    570 				ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
    571 				t = ISC_LIST_NEXT(t, link);
    572 			}
    573 
    574 			/*
    575 			 * Convert the rdatalist into a rdataset.
    576 			 */
    577 			dns_rdataset_init(&rds);
    578 			dns_rdatalist_tordataset(&rdl, &rds);
    579 			rds.trust = dns_trust_ultimate;
    580 
    581 			INSIST(op == DNS_DIFFOP_ADD);
    582 			result = callbacks->add(callbacks->add_private, name,
    583 						&rds DNS__DB_FILELINE);
    584 			if (result == DNS_R_UNCHANGED) {
    585 				isc_log_write(DIFF_COMMON_LOGARGS,
    586 					      ISC_LOG_WARNING,
    587 					      "dns_diff_load: "
    588 					      "update with no effect");
    589 			} else if (result == ISC_R_SUCCESS ||
    590 				   result == DNS_R_NXRRSET)
    591 			{
    592 				/*
    593 				 * OK.
    594 				 */
    595 			} else {
    596 				CHECK(result);
    597 			}
    598 		}
    599 	}
    600 	result = ISC_R_SUCCESS;
    601 
    602 cleanup:
    603 	if (callbacks->commit != NULL) {
    604 		callbacks->commit(callbacks->add_private);
    605 	}
    606 	return result;
    607 }
    608 
    609 /*
    610  * XXX uses qsort(); a merge sort would be more natural for lists,
    611  * and perhaps safer wrt thread stack overflow.
    612  */
    613 isc_result_t
    614 dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) {
    615 	unsigned int length = 0;
    616 	unsigned int i;
    617 	dns_difftuple_t **v;
    618 	dns_difftuple_t *p;
    619 	REQUIRE(DNS_DIFF_VALID(diff));
    620 
    621 	for (p = ISC_LIST_HEAD(diff->tuples); p != NULL;
    622 	     p = ISC_LIST_NEXT(p, link))
    623 	{
    624 		length++;
    625 	}
    626 	if (length == 0) {
    627 		return ISC_R_SUCCESS;
    628 	}
    629 	v = isc_mem_cget(diff->mctx, length, sizeof(dns_difftuple_t *));
    630 	for (i = 0; i < length; i++) {
    631 		p = ISC_LIST_HEAD(diff->tuples);
    632 		v[i] = p;
    633 		ISC_LIST_UNLINK(diff->tuples, p, link);
    634 	}
    635 	INSIST(ISC_LIST_HEAD(diff->tuples) == NULL);
    636 	qsort(v, length, sizeof(v[0]), compare);
    637 	for (i = 0; i < length; i++) {
    638 		ISC_LIST_APPEND(diff->tuples, v[i], link);
    639 	}
    640 	isc_mem_cput(diff->mctx, v, length, sizeof(dns_difftuple_t *));
    641 	return ISC_R_SUCCESS;
    642 }
    643 
    644 /*
    645  * Create an rdataset containing the single RR of the given
    646  * tuple.  The caller must allocate the rdata, rdataset and
    647  * an rdatalist structure for it to refer to.
    648  */
    649 
    650 static void
    651 diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata,
    652 		      dns_rdatalist_t *rdl, dns_rdataset_t *rds) {
    653 	REQUIRE(DNS_DIFFTUPLE_VALID(t));
    654 	REQUIRE(rdl != NULL);
    655 	REQUIRE(rds != NULL);
    656 
    657 	dns_rdatalist_init(rdl);
    658 	rdl->type = t->rdata.type;
    659 	rdl->rdclass = t->rdata.rdclass;
    660 	rdl->ttl = t->ttl;
    661 	dns_rdataset_init(rds);
    662 	ISC_LINK_INIT(rdata, link);
    663 	dns_rdata_clone(&t->rdata, rdata);
    664 	ISC_LIST_APPEND(rdl->rdata, rdata, link);
    665 	dns_rdatalist_tordataset(rdl, rds);
    666 }
    667 
    668 isc_result_t
    669 dns_diff_print(const dns_diff_t *diff, FILE *file) {
    670 	isc_result_t result;
    671 	dns_difftuple_t *t;
    672 	char *mem = NULL;
    673 	unsigned int size = 2048;
    674 	const char *op = NULL;
    675 
    676 	REQUIRE(DNS_DIFF_VALID(diff));
    677 
    678 	int required_log_level = ISC_LOG_DEBUG(7);
    679 
    680 	/*
    681 	 * Logging requires allocating a buffer and some costly translation to
    682 	 * text. Avoid it if possible.
    683 	 */
    684 	if (isc_log_wouldlog(dns_lctx, required_log_level) || file != NULL) {
    685 		mem = isc_mem_get(diff->mctx, size);
    686 
    687 		for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
    688 		     t = ISC_LIST_NEXT(t, link))
    689 		{
    690 			isc_buffer_t buf;
    691 			isc_region_t r;
    692 
    693 			dns_rdatalist_t rdl;
    694 			dns_rdataset_t rds;
    695 			dns_rdata_t rd = DNS_RDATA_INIT;
    696 
    697 			diff_tuple_tordataset(t, &rd, &rdl, &rds);
    698 		again:
    699 			isc_buffer_init(&buf, mem, size);
    700 			result = dns_rdataset_totext(&rds, &t->name, false,
    701 						     false, &buf);
    702 
    703 			if (result == ISC_R_NOSPACE) {
    704 				isc_mem_put(diff->mctx, mem, size);
    705 				size += 1024;
    706 				mem = isc_mem_get(diff->mctx, size);
    707 				goto again;
    708 			}
    709 
    710 			if (result != ISC_R_SUCCESS) {
    711 				goto cleanup;
    712 			}
    713 			/*
    714 			 * Get rid of final newline.
    715 			 */
    716 			INSIST(buf.used >= 1 &&
    717 			       ((char *)buf.base)[buf.used - 1] == '\n');
    718 			buf.used--;
    719 
    720 			isc_buffer_usedregion(&buf, &r);
    721 			switch (t->op) {
    722 			case DNS_DIFFOP_EXISTS:
    723 				op = "exists";
    724 				break;
    725 			case DNS_DIFFOP_ADD:
    726 				op = "add";
    727 				break;
    728 			case DNS_DIFFOP_DEL:
    729 				op = "del";
    730 				break;
    731 			case DNS_DIFFOP_ADDRESIGN:
    732 				op = "add re-sign";
    733 				break;
    734 			case DNS_DIFFOP_DELRESIGN:
    735 				op = "del re-sign";
    736 				break;
    737 			}
    738 			if (file != NULL) {
    739 				fprintf(file, "%s %.*s\n", op, (int)r.length,
    740 					(char *)r.base);
    741 			} else {
    742 				isc_log_write(DIFF_COMMON_LOGARGS,
    743 					      required_log_level, "%s %.*s", op,
    744 					      (int)r.length, (char *)r.base);
    745 			}
    746 		}
    747 	}
    748 	result = ISC_R_SUCCESS;
    749 cleanup:
    750 	if (mem != NULL) {
    751 		isc_mem_put(diff->mctx, mem, size);
    752 	}
    753 	return result;
    754 }
    755