Merge from vendor branch LIBARCHIVE:
[dragonfly.git] / contrib / bind-9.2.4rc7 / lib / dns / diff.c
1 /*
2  * Copyright (C) 2004  Internet Systems Consortium, Inc. ("ISC")
3  * Copyright (C) 2000-2002  Internet Software Consortium.
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15  * PERFORMANCE OF THIS SOFTWARE.
16  */
17
18 /* $Id: diff.c,v 1.4.2.3 2004/03/09 06:11:01 marka Exp $ */
19
20 #include <config.h>
21
22 #include <stdlib.h>
23
24 #include <isc/buffer.h>
25 #include <isc/file.h>
26 #include <isc/mem.h>
27 #include <isc/string.h>
28 #include <isc/util.h>
29
30 #include <dns/db.h>
31 #include <dns/diff.h>
32 #include <dns/log.h>
33 #include <dns/rdatalist.h>
34 #include <dns/rdataset.h>
35 #include <dns/result.h>
36
37 #define CHECK(op) \
38         do { result = (op);                                     \
39                 if (result != ISC_R_SUCCESS) goto failure;      \
40         } while (0)
41
42 #define DIFF_COMMON_LOGARGS \
43         dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF
44
45 static dns_rdatatype_t
46 rdata_covers(dns_rdata_t *rdata) {
47         return (rdata->type == dns_rdatatype_sig ?
48                 dns_rdata_covers(rdata) : 0);
49 }
50
51 isc_result_t
52 dns_difftuple_create(isc_mem_t *mctx,
53                      dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
54                      dns_rdata_t *rdata, dns_difftuple_t **tp)
55 {
56         dns_difftuple_t *t;
57         unsigned int size;
58         unsigned char *datap;
59
60         REQUIRE(tp != NULL && *tp == NULL);
61
62         /*
63          * Create a new tuple.  The variable-size wire-format name data and
64          * rdata immediately follow the dns_difftuple_t structure
65          * in memory.
66          */
67         size = sizeof(*t) + name->length + rdata->length;
68         t = isc_mem_allocate(mctx, size);
69         if (t == NULL)
70                 return (ISC_R_NOMEMORY);
71         t->mctx = mctx;
72         t->op = op;
73
74         datap = (unsigned char *)(t + 1);
75
76         memcpy(datap, name->ndata, name->length);
77         dns_name_init(&t->name, NULL);
78         dns_name_clone(name, &t->name);
79         t->name.ndata = datap;
80         datap += name->length;
81
82         t->ttl = ttl;
83
84         memcpy(datap, rdata->data, rdata->length);
85         dns_rdata_init(&t->rdata);
86         dns_rdata_clone(rdata, &t->rdata);
87         t->rdata.data = datap;
88         datap += rdata->length;
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         REQUIRE(DNS_DIFFTUPLE_VALID(t));
104         dns_name_invalidate(&t->name);
105         t->magic = 0;
106         isc_mem_free(t->mctx, t);
107         *tp = NULL;
108 }
109
110 isc_result_t
111 dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp) {
112         return (dns_difftuple_create(orig->mctx, orig->op, &orig->name,
113                                      orig->ttl, &orig->rdata, copyp));
114 }
115
116 void
117 dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff) {
118         diff->mctx = mctx;
119         ISC_LIST_INIT(diff->tuples);
120         diff->magic = DNS_DIFF_MAGIC;
121 }
122
123 void
124 dns_diff_clear(dns_diff_t *diff) {
125         dns_difftuple_t *t;
126         REQUIRE(DNS_DIFF_VALID(diff));
127         while ((t = ISC_LIST_HEAD(diff->tuples)) != NULL) {
128                 ISC_LIST_UNLINK(diff->tuples, t, link);
129                 dns_difftuple_free(&t);
130         }
131         ENSURE(ISC_LIST_EMPTY(diff->tuples));
132 }
133
134 void
135 dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuplep)
136 {
137         ISC_LIST_APPEND(diff->tuples, *tuplep, link);
138         *tuplep = NULL;
139 }
140
141 /* XXX this is O(N) */
142
143 void
144 dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep)
145 {
146         dns_difftuple_t *ot, *next_ot;
147
148         REQUIRE(DNS_DIFF_VALID(diff));
149         REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep));
150
151         /*
152          * Look for an existing tuple with the same owner name,
153          * rdata, and TTL.   If we are doing an addition and find a
154          * deletion or vice versa, remove both the old and the
155          * new tuple since they cancel each other out (assuming
156          * that we never delete nonexistent data or add existing
157          * data).
158          *
159          * If we find an old update of the same kind as
160          * the one we are doing, there must be a programming
161          * error.  We report it but try to continue anyway.
162          */
163         for (ot = ISC_LIST_HEAD(diff->tuples); ot != NULL;
164              ot = next_ot)
165         {
166                 next_ot = ISC_LIST_NEXT(ot, link);
167                 if (dns_name_equal(&ot->name, &(*tuplep)->name) &&
168                     dns_rdata_compare(&ot->rdata, &(*tuplep)->rdata) == 0 &&
169                     ot->ttl == (*tuplep)->ttl)
170                 {
171                         ISC_LIST_UNLINK(diff->tuples, ot, link);
172                         if ((*tuplep)->op == ot->op) {
173                                 UNEXPECTED_ERROR(__FILE__, __LINE__,
174                                          "unexpected non-minimal diff");
175                         } else {
176                                 dns_difftuple_free(tuplep);
177                         }
178                         dns_difftuple_free(&ot);
179                         break;
180                 }
181         }
182
183         if (*tuplep != NULL) {
184                 ISC_LIST_APPEND(diff->tuples, *tuplep, link);
185                 *tuplep = NULL;
186         }
187
188         ENSURE(*tuplep == NULL);
189 }
190
191 isc_result_t
192 dns_diff_apply(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver)
193 {
194         dns_difftuple_t *t;
195         dns_dbnode_t *node = NULL;
196         isc_result_t result;
197
198         REQUIRE(DNS_DIFF_VALID(diff));
199         REQUIRE(DNS_DB_VALID(db));
200
201         t = ISC_LIST_HEAD(diff->tuples);
202         while (t != NULL) {
203                 dns_name_t *name;
204
205                 INSIST(node == NULL);
206                 name = &t->name;
207                 /*
208                  * Find the node.
209                  * We create the node if it does not exist.
210                  * This will cause an empty node to be created if the diff
211                  * contains a deletion of an RR at a nonexistent name,
212                  * but such diffs should never be created in the first
213                  * place.
214                  */
215                 node = NULL;
216                 CHECK(dns_db_findnode(db, name, ISC_TRUE, &node));
217
218                 while (t != NULL && dns_name_equal(&t->name, name)) {
219                         dns_rdatatype_t type, covers;
220                         dns_diffop_t op;
221                         dns_rdatalist_t rdl;
222                         dns_rdataset_t rds;
223
224                         op = t->op;
225                         type = t->rdata.type;
226                         covers = rdata_covers(&t->rdata);
227
228                         /*
229                          * Collect a contiguous set of updates with
230                          * the same operation (add/delete) and RR type
231                          * into a single rdatalist so that the
232                          * database rrset merging/subtraction code
233                          * can work more efficiently than if each
234                          * RR were merged into / subtracted from
235                          * the database separately.
236                          *
237                          * This is done by linking rdata structures from the
238                          * diff into "rdatalist".  This uses the rdata link
239                          * field, not the diff link field, so the structure
240                          * of the diff itself is not affected.
241                          */
242
243                         rdl.type = type;
244                         rdl.covers = covers;
245                         rdl.rdclass = t->rdata.rdclass;
246                         rdl.ttl = t->ttl;
247                         ISC_LIST_INIT(rdl.rdata);
248                         ISC_LINK_INIT(&rdl, link);
249
250                         while (t != NULL &&
251                                dns_name_equal(&t->name, name) &&
252                                t->op == op &&
253                                t->rdata.type == type &&
254                                rdata_covers(&t->rdata) == covers)
255                         {
256                                 if (t->ttl != rdl.ttl) {
257                                         isc_log_write(DIFF_COMMON_LOGARGS,
258                                                 ISC_LOG_WARNING,
259                                                 "TTL differs in rdataset, "
260                                                 "adjusting %lu -> %lu",
261                                                 (unsigned long) t->ttl,
262                                                 (unsigned long) rdl.ttl);
263                                 }
264                                 ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
265                                 t = ISC_LIST_NEXT(t, link);
266                         }
267
268                         /*
269                          * Convert the rdatalist into a rdataset.
270                          */
271                         dns_rdataset_init(&rds);
272                         CHECK(dns_rdatalist_tordataset(&rdl, &rds));
273                         rds.trust = dns_trust_ultimate;
274
275                         /*
276                          * Merge the rdataset into the database.
277                          */
278                         if (op == DNS_DIFFOP_ADD) {
279                                 result = dns_db_addrdataset(db, node, ver,
280                                                             0, &rds,
281                                                             DNS_DBADD_MERGE|
282                                                             DNS_DBADD_EXACT|
283                                                             DNS_DBADD_EXACTTTL,
284                                                             NULL);
285                         } else if (op == DNS_DIFFOP_DEL) {
286                                 result = dns_db_subtractrdataset(db, node, ver,
287                                                                &rds,
288                                                                DNS_DBSUB_EXACT,
289                                                                NULL);
290                         } else {
291                                 INSIST(0);
292                         }
293                         if (result == DNS_R_UNCHANGED) {
294                                 /*
295                                  * This will not happen when executing a
296                                  * dynamic update, because that code will
297                                  * generate strictly minimal diffs.
298                                  * It may happen when receiving an IXFR
299                                  * from a server that is not as careful.
300                                  * Issue a warning and continue.
301                                  */
302                                 isc_log_write(DIFF_COMMON_LOGARGS,
303                                               ISC_LOG_WARNING,
304                                               "update with no effect");
305                         } else if (result == ISC_R_SUCCESS ||
306                                    result == DNS_R_NXRRSET) {
307                                 /*
308                                  * OK.
309                                  */
310                         } else {
311                                 CHECK(result);
312                         }
313                 }
314                 dns_db_detachnode(db, &node);
315         }
316         return (ISC_R_SUCCESS);
317
318  failure:
319         if (node != NULL)
320                 dns_db_detachnode(db, &node);
321         return (result);
322 }
323
324 /* XXX this duplicates lots of code in dns_diff_apply(). */
325
326 isc_result_t
327 dns_diff_load(dns_diff_t *diff, dns_addrdatasetfunc_t addfunc,
328               void *add_private)
329 {
330         dns_difftuple_t *t;
331         isc_result_t result;
332
333         REQUIRE(DNS_DIFF_VALID(diff));
334
335         t = ISC_LIST_HEAD(diff->tuples);
336         while (t != NULL) {
337                 dns_name_t *name;
338
339                 name = &t->name;
340                 while (t != NULL && dns_name_equal(&t->name, name)) {
341                         dns_rdatatype_t type, covers;
342                         dns_diffop_t op;
343                         dns_rdatalist_t rdl;
344                         dns_rdataset_t rds;
345
346                         op = t->op;
347                         type = t->rdata.type;
348                         covers = rdata_covers(&t->rdata);
349
350                         rdl.type = type;
351                         rdl.covers = covers;
352                         rdl.rdclass = t->rdata.rdclass;
353                         rdl.ttl = t->ttl;
354                         ISC_LIST_INIT(rdl.rdata);
355                         ISC_LINK_INIT(&rdl, link);
356
357                         while (t != NULL && dns_name_equal(&t->name, name) &&
358                                t->op == op && t->rdata.type == type &&
359                                rdata_covers(&t->rdata) == covers)
360                         {
361                                 ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
362                                 t = ISC_LIST_NEXT(t, link);
363                         }
364
365                         /*
366                          * Convert the rdatalist into a rdataset.
367                          */
368                         dns_rdataset_init(&rds);
369                         CHECK(dns_rdatalist_tordataset(&rdl, &rds));
370                         rds.trust = dns_trust_ultimate;
371
372                         INSIST(op == DNS_DIFFOP_ADD);
373                         result = (*addfunc)(add_private, name, &rds);
374                         if (result == DNS_R_UNCHANGED) {
375                                 isc_log_write(DIFF_COMMON_LOGARGS,
376                                               ISC_LOG_WARNING,
377                                               "update with no effect");
378                         } else if (result == ISC_R_SUCCESS ||
379                                    result == DNS_R_NXRRSET) {
380                                 /*
381                                  * OK.
382                                  */
383                         } else {
384                                 CHECK(result);
385                         }
386                 }
387         }
388         result = ISC_R_SUCCESS;
389  failure:
390         return (result);
391 }
392
393 /*
394  * XXX uses qsort(); a merge sort would be more natural for lists,
395  * and perhaps safer wrt thread stack overflow.
396  */
397 isc_result_t
398 dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) {
399         unsigned int length = 0;
400         unsigned int i;
401         dns_difftuple_t **v;
402         dns_difftuple_t *p;
403         REQUIRE(DNS_DIFF_VALID(diff));
404
405         for (p = ISC_LIST_HEAD(diff->tuples);
406              p != NULL;
407              p = ISC_LIST_NEXT(p, link))
408                 length++;
409         if (length == 0)
410                 return (ISC_R_SUCCESS);
411         v = isc_mem_get(diff->mctx, length * sizeof(dns_difftuple_t *));
412         if (v == NULL)
413                 return (ISC_R_NOMEMORY);
414         i = 0;
415         for (i = 0; i < length; i++) {
416                 p = ISC_LIST_HEAD(diff->tuples);
417                 v[i] = p;
418                 ISC_LIST_UNLINK(diff->tuples, p, link);
419         }
420         INSIST(ISC_LIST_HEAD(diff->tuples) == NULL);
421         qsort(v, length, sizeof(v[0]), compare);
422         for (i = 0; i < length; i++) {
423                 ISC_LIST_APPEND(diff->tuples, v[i], link);
424         }
425         isc_mem_put(diff->mctx, v, length * sizeof(dns_difftuple_t *));
426         return (ISC_R_SUCCESS);
427 }
428
429
430 /*
431  * Create an rdataset containing the single RR of the given
432  * tuple.  The caller must allocate the the rdata, rdataset and
433  * an rdatalist structure for it to refer to.
434  */
435
436 static isc_result_t
437 diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata,
438                       dns_rdatalist_t *rdl, dns_rdataset_t *rds)
439 {
440         REQUIRE(DNS_DIFFTUPLE_VALID(t));
441         REQUIRE(rdl != NULL);
442         REQUIRE(rds != NULL);
443
444         rdl->type = t->rdata.type;
445         rdl->rdclass = t->rdata.rdclass;
446         rdl->ttl = t->ttl;
447         ISC_LIST_INIT(rdl->rdata);
448         ISC_LINK_INIT(rdl, link);
449         dns_rdataset_init(rds);
450         ISC_LINK_INIT(rdata, link);
451         dns_rdata_clone(&t->rdata, rdata);
452         ISC_LIST_APPEND(rdl->rdata, rdata, link);
453         return (dns_rdatalist_tordataset(rdl, rds));
454 }
455
456 isc_result_t
457 dns_diff_print(dns_diff_t *diff, FILE *file) {
458         isc_result_t result;
459         dns_difftuple_t *t;
460         char *mem = NULL;
461         unsigned int size = 2048;
462
463         REQUIRE(DNS_DIFF_VALID(diff));
464
465         mem = isc_mem_get(diff->mctx, size);
466         if (mem == NULL)
467                 return (ISC_R_NOMEMORY);
468
469         for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
470              t = ISC_LIST_NEXT(t, link))
471         {
472                 isc_buffer_t buf;
473                 isc_region_t r;
474
475                 dns_rdatalist_t rdl;
476                 dns_rdataset_t rds;
477                 dns_rdata_t rd = DNS_RDATA_INIT;
478
479                 result = diff_tuple_tordataset(t, &rd, &rdl, &rds);
480                 if (result != ISC_R_SUCCESS) {
481                         UNEXPECTED_ERROR(__FILE__, __LINE__,
482                                          "diff_tuple_tordataset failed: %s",
483                                          dns_result_totext(result));
484                         result =  ISC_R_UNEXPECTED;
485                         goto cleanup;
486                 }
487  again:
488                 isc_buffer_init(&buf, mem, size);
489                 result = dns_rdataset_totext(&rds, &t->name,
490                                              ISC_FALSE, ISC_FALSE, &buf);
491
492                 if (result == ISC_R_NOSPACE) {
493                         isc_mem_put(diff->mctx, mem, size);
494                         size += 1024;
495                         mem = isc_mem_get(diff->mctx, size);
496                         if (mem == NULL) {
497                                 result = ISC_R_NOMEMORY;
498                                 goto cleanup;
499                         }
500                         goto again;
501                 }
502
503                 if (result != ISC_R_SUCCESS)
504                         goto cleanup;
505                 /*
506                  * Get rid of final newline.
507                  */
508                 INSIST(buf.used >= 1 &&
509                        ((char *) buf.base)[buf.used-1] == '\n');
510                 buf.used--;
511
512                 isc_buffer_usedregion(&buf, &r);
513                 if (file != NULL)
514                         fprintf(file, "%s %.*s\n",
515                                 t->op == DNS_DIFFOP_ADD ?  "add" : "del",
516                                 (int) r.length, (char *) r.base);
517                 else
518                         isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_DEBUG(7),
519                                       "%s %.*s",
520                                       t->op == DNS_DIFFOP_ADD ?  "add" : "del",
521                                       (int) r.length, (char *) r.base);
522         }
523         result = ISC_R_SUCCESS;
524  cleanup:
525         if (mem != NULL)
526                 isc_mem_put(diff->mctx, mem, size);
527         return (result);
528 }