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