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