From 80afc68f7784868d68f544df4be0e9f9711323a5 Mon Sep 17 00:00:00 2001 From: Andreas Gustafsson Date: Fri, 27 Aug 1999 18:30:59 +0000 Subject: [PATCH] create diffs from axfr; numerous bug fixes --- bin/named/xfrin.c | 534 +++++++++++++++++++++++++++++++++++++--------- lib/dns/xfrin.c | 534 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 872 insertions(+), 196 deletions(-) diff --git a/bin/named/xfrin.c b/bin/named/xfrin.c index ccc0784674..b034f909b1 100644 --- a/bin/named/xfrin.c +++ b/bin/named/xfrin.c @@ -15,13 +15,13 @@ * SOFTWARE. */ - /* $Id: xfrin.c,v 1.4 1999/08/25 10:52:57 gson Exp $ */ + /* $Id: xfrin.c,v 1.5 1999/08/27 18:30:59 gson Exp $ */ #include #include #include -#include +#include #include #include @@ -96,7 +96,7 @@ typedef enum { struct xfrin_ctx { isc_mem_t *mctx; - dns_view_t *view; /* XXX */ + ns_dbinfo_t *dbi; /* XXX */ isc_task_t *task; isc_timer_t *timer; @@ -113,13 +113,13 @@ struct xfrin_ctx { * dns_rdatatype_ixfr). The actual transfer type * may differ due to IXFR->AXFR fallback. */ - dns_rdatatype_t reqtype; + dns_rdatatype_t reqtype; isc_sockaddr_t sockaddr; isc_socket_t *socket; /* Buffer for IXFR/AXFR request message */ - isc_buffer_t qbuffer; + isc_buffer_t qbuffer; unsigned char qbuffer_data[512]; /* Incoming reply TCP message */ @@ -129,7 +129,7 @@ struct xfrin_ctx { dns_db_t *db; dns_db_t *olddb; dns_dbversion_t *ver; - dns_diff_t diff; /* Pending database changes */ + dns_diff_t diff; /* Pending database changes */ int difflen; /* Number of pending tuples */ xfrin_state_t state; @@ -148,7 +148,7 @@ struct xfrin_ctx { } axfr; struct { - isc_uint32_t request_serial; + isc_uint32_t request_serial; isc_uint32_t end_serial; dns_journal_t *journal; @@ -162,7 +162,7 @@ struct xfrin_ctx { static dns_result_t xfrin_create(isc_mem_t *mctx, - dns_view_t *view, + ns_dbinfo_t *dbi, dns_db_t *db, isc_task_t *task, isc_socketmgr_t *socketmgr, @@ -194,6 +194,7 @@ static dns_result_t xfr_rr(xfrin_ctx_t *xfr, dns_name_t *name, void xfrin_start(xfrin_ctx_t *xfr); static void xfrin_connect_done(isc_task_t *task, isc_event_t *event); +static dns_result_t xfrin_send_request(xfrin_ctx_t *xfr); static void xfrin_send_done(isc_task_t *task, isc_event_t *event); static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event); static void xfrin_recv_done(isc_task_t *task, isc_event_t *event); @@ -205,7 +206,16 @@ static isc_boolean_t maybe_free(xfrin_ctx_t *xfr); static void xfrin_fail(xfrin_ctx_t *xfr, isc_result_t result, char *msg); static dns_result_t render(dns_message_t *msg, isc_buffer_t *buf); +static dns_result_t +diff_databases(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + char *journal_filename); + /**************************************************************************/ +/* + * AXFR handling + */ static dns_result_t axfr_init(xfrin_ctx_t *xfr) { @@ -235,7 +245,7 @@ axfr_makedb(xfrin_ctx_t *xfr, dns_db_t **dbp) { 0, NULL, /* XXX guess */ dbp)); } - + static dns_result_t axfr_putdata(xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) @@ -265,50 +275,82 @@ axfr_apply(xfrin_ctx_t *xfr) { return (result); } +/* + * Install the newly AXFR'ed database, and generate diffs if + * configured to do so. + * + * XXX Temporary code for testing only. This should all be + * part of the zone object functionality, and shared between + * AXFR and reloading of manually maintained master files. + */ + static dns_result_t -axfr_commit(xfrin_ctx_t *xfr) { - dns_result_t result; - CHECK(axfr_apply(xfr)); - CHECK(dns_db_endload(xfr->db, &xfr->axfr.add_private)); -#if 0 - CHECK(zone_replace_db(zone, xfr->db)); -#else - /* XXX Temporary code for testing only. */ - { - ns_dbinfo_t *dbi; - dns_dbversion_t *ver; - - dbi = isc_mem_get(ns_g_mctx, sizeof *dbi); - RUNTIME_CHECK(dbi != NULL); - dbi->path = "tky.hut.fi"; - dbi->origin = NULL; - dbi->iscache = ISC_FALSE; - dbi->db = NULL; - dns_db_attach(xfr->db, &dbi->db); - dbi->view = NULL; - dns_view_attach(xfr->view, &dbi->view); - ISC_LINK_INIT(dbi, link); - ISC_LIST_APPEND(ns_g_dbs, dbi, link); +install_new_db(xfrin_ctx_t *xfr) +{ + dns_dbversion_t *ver; + isc_result_t result; - if (xfr->olddb != NULL) - dns_dbtable_remove(xfr->view->dbtable, xfr->olddb); - - CHECK(dns_dbtable_add(xfr->view->dbtable, dbi->db)); + ver = NULL; + dns_db_currentversion(xfr->db, &ver); + /* + * XXX should be configurable per-zone with + * somehting like "journal-from-axfr yes"? + * + * The initial version of a slave zone is always dumped; + * subsequent versions may be journalled instead. + */ + if (xfr->olddb != NULL && 1) { + printf("generating diffs\n"); + CHECK(diff_databases(xfr->mctx, + xfr->db, ver, + xfr->olddb, NULL /* XXX */, + "journal")); + } else { + printf("dumping new version\n"); + /* XXX should use temporary file and rename */ + CHECK(dns_db_dump(xfr->db, ver, xfr->dbi->path)); + printf("unlinking journal\n"); (void) unlink("journal"); /* XXX filename */ - - ver = NULL; - dns_db_currentversion(dbi->db, &ver); - CHECK(dns_db_dump(dbi->db, ver, dbi->path)); - dns_db_closeversion(dbi->db, &ver, ISC_FALSE); } -#endif - result = DNS_R_SUCCESS; + dns_db_closeversion(xfr->db, &ver, ISC_FALSE); + + /* Replace the database (XXX atomically). */ + printf("replacing database..."); + if (xfr->olddb != NULL) + dns_dbtable_remove(xfr->dbi->view->dbtable, xfr->olddb); + CHECK(dns_dbtable_add(xfr->dbi->view->dbtable, xfr->db)); + + if (xfr->dbi->db) + dns_db_detach(&xfr->dbi->db); + dns_db_attach(xfr->db, &xfr->dbi->db); + + printf("done\n"); + result = ISC_R_SUCCESS; + failure: return (result); } + +static dns_result_t +axfr_commit(xfrin_ctx_t *xfr) { + dns_result_t result; + + CHECK(axfr_apply(xfr)); + CHECK(dns_db_endload(xfr->db, &xfr->axfr.add_private)); + CHECK(install_new_db(xfr)); + + result = ISC_R_SUCCESS; + failure: + printf("axfr_commit returns status %s\n", isc_result_totext(result)); + return (result); +} + /**************************************************************************/ +/* + * IXFR handling + */ static dns_result_t ixfr_init(xfrin_ctx_t *xfr) { @@ -328,10 +370,10 @@ ixfr_putdata(xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { dns_result_t result; - dns_difftuple_t *tuple = NULL; + dns_difftuple_t *tuple = NULL; CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, &tuple)); - dns_diff_append(&xfr->diff, &tuple); + dns_diff_append(&xfr->diff, &tuple); if (++xfr->difflen > 100) CHECK(ixfr_apply(xfr)); result = DNS_R_SUCCESS; @@ -358,7 +400,7 @@ ixfr_apply(xfrin_ctx_t *xfr) { static dns_result_t ixfr_commit(xfrin_ctx_t *xfr) { - dns_result_t result; + dns_result_t result; ixfr_apply(xfr); if (xfr->ver != NULL) { /* XXX enter ready-to-commit state here */ @@ -371,6 +413,9 @@ ixfr_commit(xfrin_ctx_t *xfr) { } /**************************************************************************/ +/* + * Common AXFR/IXFR protocol code + */ /* * Handle a single incoming resource record according to the current @@ -401,7 +446,7 @@ xfr_rr(xfrin_ctx_t *xfr, printf("requested %u, master has %u, not updating\n", xfr->ixfr.request_serial, xfr->end_serial); FAIL(DNS_R_UPTODATE); - } + } xfr->state = XFRST_FIRSTDATA; break; @@ -417,7 +462,7 @@ xfr_rr(xfrin_ctx_t *xfr, CHECK(axfr_init(xfr)); xfr->state = XFRST_AXFR; } - goto redo; + goto redo; case XFRST_IXFR_DELSOA: INSIST(rdata->type == dns_rdatatype_soa); @@ -444,7 +489,7 @@ xfr_rr(xfrin_ctx_t *xfr, case XFRST_IXFR_ADD: if (rdata->type == dns_rdatatype_soa) { isc_uint32_t soa_serial = dns_soa_getserial(rdata); - ixfr_commit(xfr); + CHECK(ixfr_commit(xfr)); if (soa_serial == xfr->end_serial) { xfr->state = XFRST_END; break; @@ -459,7 +504,7 @@ xfr_rr(xfrin_ctx_t *xfr, case XFRST_AXFR: CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); if (rdata->type == dns_rdatatype_soa) { - axfr_commit(xfr); + CHECK(axfr_commit(xfr)); xfr->state = XFRST_END; break; } @@ -476,49 +521,66 @@ xfr_rr(xfrin_ctx_t *xfr, return (result); } -void -xfrin_test(dns_view_t *view) { - dns_name_t name; - isc_region_t region; +static void +xfrin_test_dbi(ns_dbinfo_t *dbi) { + dns_name_t *name; isc_task_t *task; xfrin_ctx_t *xfr; dns_result_t result; - unsigned char dom[] = "\003tky\003hut\002fi"; + dns_fixedname_t forigin; + isc_buffer_t forigin_buf; dns_db_t *db; dns_rdatatype_t xfrtype; + unsigned int len; - printf("Testing zone transfer...\n"); - - region.base = dom; - region.length = sizeof(dom); - dns_name_init(&name, NULL); - dns_name_fromregion(&name, ®ion); + printf("attempting zone transfer of zone \"%s\"...\n", dbi->origin); + + len = strlen(dbi->origin); + isc_buffer_init(&forigin_buf, dbi->origin, len, ISC_BUFFERTYPE_TEXT); + isc_buffer_add(&forigin_buf, len); + dns_fixedname_init(&forigin); + name = dns_fixedname_name(&forigin); + RUNTIME_CHECK(dns_name_fromtext(name, &forigin_buf, dns_rootname, + ISC_FALSE, NULL) == DNS_R_SUCCESS); task = NULL; RUNTIME_CHECK(isc_task_create(ns_g_taskmgr, ns_g_mctx, 0, &task) == DNS_R_SUCCESS); db = NULL; - result = dns_dbtable_find(view->dbtable, &name, &db); + result = dns_dbtable_find(dbi->view->dbtable, name, &db); if (result == DNS_R_NOTFOUND) { printf("no database exists, trying to create with axfr\n"); - xfrtype = dns_rdatatype_axfr; + xfrtype = dns_rdatatype_axfr; } else { printf("database exists, trying ixfr\n"); xfrtype = dns_rdatatype_ixfr; } xfrin_create(ns_g_mctx, - view, + dbi, db, task, ns_g_socketmgr, - &name, + name, dns_rdataclass_in, xfrtype, - "194.100.32.81", 53, &xfr); + dbi->master, 53, &xfr); xfrin_start(xfr); } +void +xfrin_test(void) { + ns_dbinfo_t *dbi; + for (dbi = ISC_LIST_HEAD(ns_g_dbs); + dbi != NULL; + dbi = ISC_LIST_NEXT(dbi, link)) { + if (dbi->isslave) + xfrin_test_dbi(dbi); + } +} + + + static void xfrin_cleanup(xfrin_ctx_t *xfr) { printf("end of transfer - destroying task %p\n", xfr->task); isc_socket_cancel(xfr->socket, xfr->task, ISC_SOCKSHUT_ALL); /* XXX? */ @@ -539,7 +601,7 @@ xfrin_fail(xfrin_ctx_t *xfr, isc_result_t result, char *msg) { dns_result_t xfrin_create(isc_mem_t *mctx, - dns_view_t *view, + ns_dbinfo_t *dbi, dns_db_t *db, isc_task_t *task, isc_socketmgr_t *socketmgr, @@ -559,7 +621,7 @@ xfrin_create(isc_mem_t *mctx, if (xfr == NULL) return (DNS_R_NOMEMORY); xfr->mctx = mctx; - xfr->view = view; + xfr->dbi = dbi; xfr->task = task; xfr->timer = NULL; xfr->socketmgr = socketmgr; @@ -591,7 +653,7 @@ xfrin_create(isc_mem_t *mctx, /* ixfr.request_serial */ /* ixfr.end_serial */ xfr->ixfr.journal = NULL; - + xfr->axfr.add_func = NULL; xfr->axfr.add_private = NULL; @@ -604,7 +666,6 @@ xfrin_create(isc_mem_t *mctx, NULL, &interval, task, xfrin_timeout, xfr, &xfr->timer)); - ina.s_addr = inet_addr(addrstr); isc_sockaddr_fromin(&xfr->sockaddr, &ina, port); @@ -652,30 +713,47 @@ render(dns_message_t *msg, isc_buffer_t *buf) { } /* - * A connection has been established. Build an *XFR request - * and send its length prefix. + * A connection has been established. */ static void xfrin_connect_done(isc_task_t *task, isc_event_t *event) { isc_socket_connev_t *cev = (isc_socket_connev_t *) event; xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - isc_region_t region; - isc_region_t lregion; - dns_rdataset_t qrdataset; - dns_message_t *msg = NULL; dns_result_t result; - unsigned char length[2]; - - dns_rdatalist_t soardl; - dns_rdataset_t soards; - dns_difftuple_t *soatuple = NULL; - task = task; /* Unused */ INSIST(event->type == ISC_SOCKEVENT_CONNECT); printf("connected\n"); CHECK(cev->result); + dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg); + xfr->tcpmsg_valid = ISC_TRUE; + + CHECK(xfrin_send_request(xfr)); + + isc_event_free(&event); + return; + + failure: + isc_event_free(&event); + xfrin_fail(xfr, result, "connecting"); +} + +/* + * Build an *XFR request and send its length prefix. + */ +static dns_result_t +xfrin_send_request(xfrin_ctx_t *xfr) { + dns_result_t result; + isc_region_t region; + isc_region_t lregion; + dns_rdataset_t qrdataset; + dns_message_t *msg = NULL; + unsigned char length[2]; + dns_rdatalist_t soardl; + dns_rdataset_t soards; + dns_difftuple_t *soatuple = NULL; + dns_rdataset_init(&qrdataset); dns_rdataset_makequestion(&qrdataset, xfr->rdclass, xfr->reqtype); ISC_LIST_INIT(xfr->name.list); @@ -692,7 +770,7 @@ xfrin_connect_done(isc_task_t *task, isc_event_t *event) { dns_db_createsoatuple(xfr->db, ver, xfr->mctx, DNS_DIFFOP_EXISTS, &soatuple); xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata); - dns_db_closeversion(xfr->db, &ver, ISC_FALSE); + dns_db_closeversion(xfr->db, &ver, ISC_FALSE); printf("requesting IXFR for serial %u\n", xfr->ixfr.request_serial); @@ -732,21 +810,16 @@ xfrin_connect_done(isc_task_t *task, isc_event_t *event) { lregion.length = 2; CHECK(isc_socket_send(xfr->socket, &lregion, xfr->task, xfrin_sendlen_done, xfr)); - isc_event_free(&event); - return; + return (DNS_R_SUCCESS); failure: - isc_event_free(&event); if (soatuple != NULL) dns_difftuple_free(&soatuple); - if (event != NULL) - isc_event_free(&event); - xfrin_fail(xfr, result, "connecting"); - + return (result); } /* XXX there should be library support for sending DNS TCP messages */ - + static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event) { @@ -786,8 +859,6 @@ xfrin_send_done(isc_task_t *task, isc_event_t *event) printf("send done\n"); CHECK(sev->result); - dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg); - xfr->tcpmsg_valid = ISC_TRUE; CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task, xfrin_recv_done, xfr)); xfr->recvs++; @@ -825,6 +896,18 @@ xfrin_recv_done(isc_task_t *task, isc_event_t *ev) { CHECK(dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg)); CHECK(dns_message_parse(msg, &tcpmsg->buffer, ISC_TRUE)); + + if (msg->rcode != dns_rcode_noerror) { + result = ISC_RESULTCLASS_DNSRCODE + msg->rcode; /* XXX */ + if (xfr->reqtype == dns_rdatatype_axfr) + FAIL(result); + printf("got %s, retrying with AXFR\n", + isc_result_totext(result)); + dns_message_destroy(&msg); + xfr->reqtype = dns_rdatatype_axfr; + CHECK(xfrin_send_request(xfr)); + return; + } for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER); result == DNS_R_SUCCESS; @@ -865,14 +948,14 @@ xfrin_recv_done(isc_task_t *task, isc_event_t *ev) { failure: if (msg != NULL) - dns_message_destroy(&msg); + dns_message_destroy(&msg); xfrin_fail(xfr, result, "receving responses"); } static void xfrin_timeout(isc_task_t *task, isc_event_t *event) { xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - task = task; /* Unused */ + task = task; /* Unused */ INSIST(event->type == ISC_TIMEREVENT_IDLE); isc_event_free(&event); xfrin_fail(xfr, ISC_R_TIMEDOUT, "giving up"); @@ -881,9 +964,9 @@ xfrin_timeout(isc_task_t *task, isc_event_t *event) { static void xfrin_shutdown(isc_task_t *task, isc_event_t *event) { xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - task = task; /* Unused */ + task = task; /* Unused */ INSIST(event->type == ISC_TASKEVENT_SHUTDOWN); - isc_event_free(&event); + isc_event_free(&event); printf("xfrin_shutdown task=%p\n", task); xfr->tasks--; maybe_free(xfr); @@ -902,7 +985,7 @@ maybe_free(xfrin_ctx_t *xfr) { if (xfr->ixfr.journal != NULL) dns_journal_destroy(&xfr->ixfr.journal); - + if (xfr->axfr.add_private != NULL) (void) dns_db_endload(xfr->db, &xfr->axfr.add_private); @@ -926,3 +1009,258 @@ maybe_free(xfrin_ctx_t *xfr) { printf("xfrin_shutdown done\n"); return (ISC_TRUE); } + +/**************************************************************************/ +/* + * Generating diffs from databases + */ + +/* + * Construct a diff containing all the RRs at the current name of the + * database iterator 'dbit' in database 'db', version 'ver'. + * Set '*name' to the current name, and append the diff to 'diff'. + * All new tuples will have the operation 'op'. + * + * Requires: 'name' must have buffer large enough to hold the name. + * Typically, a dns_fixedname_t would be used. + */ +static dns_result_t +get_name_diff(dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, + dns_dbiterator_t *dbit, dns_name_t *name, dns_diffop_t op, + dns_diff_t *diff) +{ + dns_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_difftuple_t *tuple = NULL; + + result = dns_dbiterator_current(dbit, &node, name); + if (result != DNS_R_SUCCESS) + return (result); + + result = dns_db_allrdatasets(db, node, ver, now, &rdsiter); + if (result != DNS_R_SUCCESS) + goto cleanup_node; + + for (result = dns_rdatasetiter_first(rdsiter); + result == DNS_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + for (result = dns_rdataset_first(&rdataset); + result == DNS_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata; + dns_rdataset_current(&rdataset, &rdata); + result = dns_difftuple_create(diff->mctx, op, name, + rdataset.ttl, &rdata, + &tuple); + if (result != DNS_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + goto cleanup_iterator; + } + dns_diff_append(diff, &tuple); + } + dns_rdataset_disassociate(&rdataset); + if (result != DNS_R_NOMORE) + goto cleanup_iterator; + } + if (result != DNS_R_NOMORE) + goto cleanup_iterator; + + result = DNS_R_SUCCESS; + + cleanup_iterator: + dns_rdatasetiter_destroy(&rdsiter); + + cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/* + * Comparison function for use by dns_diff_subtract when sorting + * the diffs to be subtracted. The sort keys are the rdata type + * and the rdata itself. The owner name is ignored, because + * it is known to be the same for all tuples. + */ +static int +rdata_order(const void *av, const void *bv) +{ + dns_difftuple_t * const *ap = av; + dns_difftuple_t * const *bp = bv; + dns_difftuple_t *a = *ap; + dns_difftuple_t *b = *bp; + int r; + r = (b->rdata.type - a->rdata.type); + if (r != 0) + return (r); + r = dns_rdata_compare(&a->rdata, &b->rdata); + return (r); +} + +static dns_result_t +dns_diff_subtract(dns_diff_t diff[2], dns_diff_t *r) +{ + dns_result_t result; + dns_difftuple_t *p[2]; + int i, t; + CHECK(dns_diff_sort(&diff[0], rdata_order)); + CHECK(dns_diff_sort(&diff[1], rdata_order)); + + for (;;) { + p[0] = ISC_LIST_HEAD(diff[0].tuples); + p[1] = ISC_LIST_HEAD(diff[1].tuples); + if (p[0] == NULL && p[1] == NULL) + break; + + for (i = 0; i < 2; i++) + if (p[!i] == NULL) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + ISC_LIST_APPEND(r->tuples, p[i], link); + goto next; + } + t = rdata_order(&p[0], &p[1]); + if (t < 0) { + ISC_LIST_UNLINK(diff[0].tuples, p[0], link); + ISC_LIST_APPEND(r->tuples, p[0], link); + goto next; + } + if (t > 0) { + ISC_LIST_UNLINK(diff[1].tuples, p[1], link); + ISC_LIST_APPEND(r->tuples, p[1], link); + goto next; + } + INSIST(t == 0); + /* Identical RRs in both databases; skip them both. */ + for (i = 0; i < 2; i++) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + dns_difftuple_free(&p[i]); + } + next: ; + } + result = ISC_R_SUCCESS; + failure: + return (result); +} + +/* + * Compare the databases 'dba' and 'dbb' and generate a journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. + */ + +static dns_result_t +diff_databases(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + char *journal_filename) +{ + dns_db_t *db[2]; + dns_dbversion_t *ver[2]; + dns_dbiterator_t *dbit[2] = { NULL, NULL }; + isc_boolean_t have[2] = { ISC_FALSE, ISC_FALSE }; + dns_fixedname_t fixname[2]; + dns_result_t result, itresult[2]; + dns_diff_t diff[2], resultdiff; + int i, t; + dns_journal_t *journal = NULL; + + db[0] = dba, db[1] = dbb; + ver[0] = dbvera, ver[1] = dbverb; + + dns_diff_init(mctx, &diff[0]); + dns_diff_init(mctx, &diff[1]); + dns_diff_init(mctx, &resultdiff); + + dns_fixedname_init(&fixname[0]); + dns_fixedname_init(&fixname[1]); + + CHECK(dns_journal_open(mctx, journal_filename, ISC_TRUE, &journal)); + + CHECK(dns_db_createiterator(db[0], ISC_FALSE, &dbit[0])); + CHECK(dns_db_createiterator(db[1], ISC_FALSE, &dbit[1])); + + itresult[0] = dns_dbiterator_first(dbit[0]); + itresult[1] = dns_dbiterator_first(dbit[1]); + + for (;;) { + for (i = 0; i < 2; i++) { + if (! have[i] && itresult[i] == DNS_R_SUCCESS) { + CHECK(get_name_diff(db[i], ver[i], 0, dbit[i], + dns_fixedname_name(&fixname[i]), + i == 0 ? + DNS_DIFFOP_ADD : + DNS_DIFFOP_DEL, + &diff[i])); + itresult[i] = dns_dbiterator_next(dbit[i]); + have[i] = ISC_TRUE; + } + } + + if (! have[0] && ! have[1]) { + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + break; + } + + for (i = 0; i < 2; i++) { + if (! have[!i]) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[i].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[i].tuples)); + have[i] = ISC_FALSE; + goto next; + } + } + + t = dns_name_compare(dns_fixedname_name(&fixname[0]), + dns_fixedname_name(&fixname[1])); + if (t < 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[0].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + have[0] = ISC_FALSE; + continue; + } + if (t > 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[1].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[1] = ISC_FALSE; + continue; + } + INSIST(t == 0); + CHECK(dns_diff_subtract(diff, &resultdiff)); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[0] = have[1] = ISC_FALSE; + next: ; + } + if (itresult[0] != DNS_R_NOMORE) + FAIL(itresult[0]); + if (itresult[1] != DNS_R_NOMORE) + FAIL(itresult[1]); + + if (ISC_LIST_EMPTY(resultdiff.tuples)) { + printf("no changes\n"); + } else { + CHECK(dns_journal_write_transaction(journal, &resultdiff)); + } + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + dns_diff_clear(&resultdiff); + + failure: + dns_dbiterator_destroy(&dbit[0]); + dns_dbiterator_destroy(&dbit[1]); + dns_journal_destroy(&journal); + return (result); +} diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index ccc0784674..b034f909b1 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -15,13 +15,13 @@ * SOFTWARE. */ - /* $Id: xfrin.c,v 1.4 1999/08/25 10:52:57 gson Exp $ */ + /* $Id: xfrin.c,v 1.5 1999/08/27 18:30:59 gson Exp $ */ #include #include #include -#include +#include #include #include @@ -96,7 +96,7 @@ typedef enum { struct xfrin_ctx { isc_mem_t *mctx; - dns_view_t *view; /* XXX */ + ns_dbinfo_t *dbi; /* XXX */ isc_task_t *task; isc_timer_t *timer; @@ -113,13 +113,13 @@ struct xfrin_ctx { * dns_rdatatype_ixfr). The actual transfer type * may differ due to IXFR->AXFR fallback. */ - dns_rdatatype_t reqtype; + dns_rdatatype_t reqtype; isc_sockaddr_t sockaddr; isc_socket_t *socket; /* Buffer for IXFR/AXFR request message */ - isc_buffer_t qbuffer; + isc_buffer_t qbuffer; unsigned char qbuffer_data[512]; /* Incoming reply TCP message */ @@ -129,7 +129,7 @@ struct xfrin_ctx { dns_db_t *db; dns_db_t *olddb; dns_dbversion_t *ver; - dns_diff_t diff; /* Pending database changes */ + dns_diff_t diff; /* Pending database changes */ int difflen; /* Number of pending tuples */ xfrin_state_t state; @@ -148,7 +148,7 @@ struct xfrin_ctx { } axfr; struct { - isc_uint32_t request_serial; + isc_uint32_t request_serial; isc_uint32_t end_serial; dns_journal_t *journal; @@ -162,7 +162,7 @@ struct xfrin_ctx { static dns_result_t xfrin_create(isc_mem_t *mctx, - dns_view_t *view, + ns_dbinfo_t *dbi, dns_db_t *db, isc_task_t *task, isc_socketmgr_t *socketmgr, @@ -194,6 +194,7 @@ static dns_result_t xfr_rr(xfrin_ctx_t *xfr, dns_name_t *name, void xfrin_start(xfrin_ctx_t *xfr); static void xfrin_connect_done(isc_task_t *task, isc_event_t *event); +static dns_result_t xfrin_send_request(xfrin_ctx_t *xfr); static void xfrin_send_done(isc_task_t *task, isc_event_t *event); static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event); static void xfrin_recv_done(isc_task_t *task, isc_event_t *event); @@ -205,7 +206,16 @@ static isc_boolean_t maybe_free(xfrin_ctx_t *xfr); static void xfrin_fail(xfrin_ctx_t *xfr, isc_result_t result, char *msg); static dns_result_t render(dns_message_t *msg, isc_buffer_t *buf); +static dns_result_t +diff_databases(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + char *journal_filename); + /**************************************************************************/ +/* + * AXFR handling + */ static dns_result_t axfr_init(xfrin_ctx_t *xfr) { @@ -235,7 +245,7 @@ axfr_makedb(xfrin_ctx_t *xfr, dns_db_t **dbp) { 0, NULL, /* XXX guess */ dbp)); } - + static dns_result_t axfr_putdata(xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) @@ -265,50 +275,82 @@ axfr_apply(xfrin_ctx_t *xfr) { return (result); } +/* + * Install the newly AXFR'ed database, and generate diffs if + * configured to do so. + * + * XXX Temporary code for testing only. This should all be + * part of the zone object functionality, and shared between + * AXFR and reloading of manually maintained master files. + */ + static dns_result_t -axfr_commit(xfrin_ctx_t *xfr) { - dns_result_t result; - CHECK(axfr_apply(xfr)); - CHECK(dns_db_endload(xfr->db, &xfr->axfr.add_private)); -#if 0 - CHECK(zone_replace_db(zone, xfr->db)); -#else - /* XXX Temporary code for testing only. */ - { - ns_dbinfo_t *dbi; - dns_dbversion_t *ver; - - dbi = isc_mem_get(ns_g_mctx, sizeof *dbi); - RUNTIME_CHECK(dbi != NULL); - dbi->path = "tky.hut.fi"; - dbi->origin = NULL; - dbi->iscache = ISC_FALSE; - dbi->db = NULL; - dns_db_attach(xfr->db, &dbi->db); - dbi->view = NULL; - dns_view_attach(xfr->view, &dbi->view); - ISC_LINK_INIT(dbi, link); - ISC_LIST_APPEND(ns_g_dbs, dbi, link); +install_new_db(xfrin_ctx_t *xfr) +{ + dns_dbversion_t *ver; + isc_result_t result; - if (xfr->olddb != NULL) - dns_dbtable_remove(xfr->view->dbtable, xfr->olddb); - - CHECK(dns_dbtable_add(xfr->view->dbtable, dbi->db)); + ver = NULL; + dns_db_currentversion(xfr->db, &ver); + /* + * XXX should be configurable per-zone with + * somehting like "journal-from-axfr yes"? + * + * The initial version of a slave zone is always dumped; + * subsequent versions may be journalled instead. + */ + if (xfr->olddb != NULL && 1) { + printf("generating diffs\n"); + CHECK(diff_databases(xfr->mctx, + xfr->db, ver, + xfr->olddb, NULL /* XXX */, + "journal")); + } else { + printf("dumping new version\n"); + /* XXX should use temporary file and rename */ + CHECK(dns_db_dump(xfr->db, ver, xfr->dbi->path)); + printf("unlinking journal\n"); (void) unlink("journal"); /* XXX filename */ - - ver = NULL; - dns_db_currentversion(dbi->db, &ver); - CHECK(dns_db_dump(dbi->db, ver, dbi->path)); - dns_db_closeversion(dbi->db, &ver, ISC_FALSE); } -#endif - result = DNS_R_SUCCESS; + dns_db_closeversion(xfr->db, &ver, ISC_FALSE); + + /* Replace the database (XXX atomically). */ + printf("replacing database..."); + if (xfr->olddb != NULL) + dns_dbtable_remove(xfr->dbi->view->dbtable, xfr->olddb); + CHECK(dns_dbtable_add(xfr->dbi->view->dbtable, xfr->db)); + + if (xfr->dbi->db) + dns_db_detach(&xfr->dbi->db); + dns_db_attach(xfr->db, &xfr->dbi->db); + + printf("done\n"); + result = ISC_R_SUCCESS; + failure: return (result); } + +static dns_result_t +axfr_commit(xfrin_ctx_t *xfr) { + dns_result_t result; + + CHECK(axfr_apply(xfr)); + CHECK(dns_db_endload(xfr->db, &xfr->axfr.add_private)); + CHECK(install_new_db(xfr)); + + result = ISC_R_SUCCESS; + failure: + printf("axfr_commit returns status %s\n", isc_result_totext(result)); + return (result); +} + /**************************************************************************/ +/* + * IXFR handling + */ static dns_result_t ixfr_init(xfrin_ctx_t *xfr) { @@ -328,10 +370,10 @@ ixfr_putdata(xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { dns_result_t result; - dns_difftuple_t *tuple = NULL; + dns_difftuple_t *tuple = NULL; CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, &tuple)); - dns_diff_append(&xfr->diff, &tuple); + dns_diff_append(&xfr->diff, &tuple); if (++xfr->difflen > 100) CHECK(ixfr_apply(xfr)); result = DNS_R_SUCCESS; @@ -358,7 +400,7 @@ ixfr_apply(xfrin_ctx_t *xfr) { static dns_result_t ixfr_commit(xfrin_ctx_t *xfr) { - dns_result_t result; + dns_result_t result; ixfr_apply(xfr); if (xfr->ver != NULL) { /* XXX enter ready-to-commit state here */ @@ -371,6 +413,9 @@ ixfr_commit(xfrin_ctx_t *xfr) { } /**************************************************************************/ +/* + * Common AXFR/IXFR protocol code + */ /* * Handle a single incoming resource record according to the current @@ -401,7 +446,7 @@ xfr_rr(xfrin_ctx_t *xfr, printf("requested %u, master has %u, not updating\n", xfr->ixfr.request_serial, xfr->end_serial); FAIL(DNS_R_UPTODATE); - } + } xfr->state = XFRST_FIRSTDATA; break; @@ -417,7 +462,7 @@ xfr_rr(xfrin_ctx_t *xfr, CHECK(axfr_init(xfr)); xfr->state = XFRST_AXFR; } - goto redo; + goto redo; case XFRST_IXFR_DELSOA: INSIST(rdata->type == dns_rdatatype_soa); @@ -444,7 +489,7 @@ xfr_rr(xfrin_ctx_t *xfr, case XFRST_IXFR_ADD: if (rdata->type == dns_rdatatype_soa) { isc_uint32_t soa_serial = dns_soa_getserial(rdata); - ixfr_commit(xfr); + CHECK(ixfr_commit(xfr)); if (soa_serial == xfr->end_serial) { xfr->state = XFRST_END; break; @@ -459,7 +504,7 @@ xfr_rr(xfrin_ctx_t *xfr, case XFRST_AXFR: CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); if (rdata->type == dns_rdatatype_soa) { - axfr_commit(xfr); + CHECK(axfr_commit(xfr)); xfr->state = XFRST_END; break; } @@ -476,49 +521,66 @@ xfr_rr(xfrin_ctx_t *xfr, return (result); } -void -xfrin_test(dns_view_t *view) { - dns_name_t name; - isc_region_t region; +static void +xfrin_test_dbi(ns_dbinfo_t *dbi) { + dns_name_t *name; isc_task_t *task; xfrin_ctx_t *xfr; dns_result_t result; - unsigned char dom[] = "\003tky\003hut\002fi"; + dns_fixedname_t forigin; + isc_buffer_t forigin_buf; dns_db_t *db; dns_rdatatype_t xfrtype; + unsigned int len; - printf("Testing zone transfer...\n"); - - region.base = dom; - region.length = sizeof(dom); - dns_name_init(&name, NULL); - dns_name_fromregion(&name, ®ion); + printf("attempting zone transfer of zone \"%s\"...\n", dbi->origin); + + len = strlen(dbi->origin); + isc_buffer_init(&forigin_buf, dbi->origin, len, ISC_BUFFERTYPE_TEXT); + isc_buffer_add(&forigin_buf, len); + dns_fixedname_init(&forigin); + name = dns_fixedname_name(&forigin); + RUNTIME_CHECK(dns_name_fromtext(name, &forigin_buf, dns_rootname, + ISC_FALSE, NULL) == DNS_R_SUCCESS); task = NULL; RUNTIME_CHECK(isc_task_create(ns_g_taskmgr, ns_g_mctx, 0, &task) == DNS_R_SUCCESS); db = NULL; - result = dns_dbtable_find(view->dbtable, &name, &db); + result = dns_dbtable_find(dbi->view->dbtable, name, &db); if (result == DNS_R_NOTFOUND) { printf("no database exists, trying to create with axfr\n"); - xfrtype = dns_rdatatype_axfr; + xfrtype = dns_rdatatype_axfr; } else { printf("database exists, trying ixfr\n"); xfrtype = dns_rdatatype_ixfr; } xfrin_create(ns_g_mctx, - view, + dbi, db, task, ns_g_socketmgr, - &name, + name, dns_rdataclass_in, xfrtype, - "194.100.32.81", 53, &xfr); + dbi->master, 53, &xfr); xfrin_start(xfr); } +void +xfrin_test(void) { + ns_dbinfo_t *dbi; + for (dbi = ISC_LIST_HEAD(ns_g_dbs); + dbi != NULL; + dbi = ISC_LIST_NEXT(dbi, link)) { + if (dbi->isslave) + xfrin_test_dbi(dbi); + } +} + + + static void xfrin_cleanup(xfrin_ctx_t *xfr) { printf("end of transfer - destroying task %p\n", xfr->task); isc_socket_cancel(xfr->socket, xfr->task, ISC_SOCKSHUT_ALL); /* XXX? */ @@ -539,7 +601,7 @@ xfrin_fail(xfrin_ctx_t *xfr, isc_result_t result, char *msg) { dns_result_t xfrin_create(isc_mem_t *mctx, - dns_view_t *view, + ns_dbinfo_t *dbi, dns_db_t *db, isc_task_t *task, isc_socketmgr_t *socketmgr, @@ -559,7 +621,7 @@ xfrin_create(isc_mem_t *mctx, if (xfr == NULL) return (DNS_R_NOMEMORY); xfr->mctx = mctx; - xfr->view = view; + xfr->dbi = dbi; xfr->task = task; xfr->timer = NULL; xfr->socketmgr = socketmgr; @@ -591,7 +653,7 @@ xfrin_create(isc_mem_t *mctx, /* ixfr.request_serial */ /* ixfr.end_serial */ xfr->ixfr.journal = NULL; - + xfr->axfr.add_func = NULL; xfr->axfr.add_private = NULL; @@ -604,7 +666,6 @@ xfrin_create(isc_mem_t *mctx, NULL, &interval, task, xfrin_timeout, xfr, &xfr->timer)); - ina.s_addr = inet_addr(addrstr); isc_sockaddr_fromin(&xfr->sockaddr, &ina, port); @@ -652,30 +713,47 @@ render(dns_message_t *msg, isc_buffer_t *buf) { } /* - * A connection has been established. Build an *XFR request - * and send its length prefix. + * A connection has been established. */ static void xfrin_connect_done(isc_task_t *task, isc_event_t *event) { isc_socket_connev_t *cev = (isc_socket_connev_t *) event; xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - isc_region_t region; - isc_region_t lregion; - dns_rdataset_t qrdataset; - dns_message_t *msg = NULL; dns_result_t result; - unsigned char length[2]; - - dns_rdatalist_t soardl; - dns_rdataset_t soards; - dns_difftuple_t *soatuple = NULL; - task = task; /* Unused */ INSIST(event->type == ISC_SOCKEVENT_CONNECT); printf("connected\n"); CHECK(cev->result); + dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg); + xfr->tcpmsg_valid = ISC_TRUE; + + CHECK(xfrin_send_request(xfr)); + + isc_event_free(&event); + return; + + failure: + isc_event_free(&event); + xfrin_fail(xfr, result, "connecting"); +} + +/* + * Build an *XFR request and send its length prefix. + */ +static dns_result_t +xfrin_send_request(xfrin_ctx_t *xfr) { + dns_result_t result; + isc_region_t region; + isc_region_t lregion; + dns_rdataset_t qrdataset; + dns_message_t *msg = NULL; + unsigned char length[2]; + dns_rdatalist_t soardl; + dns_rdataset_t soards; + dns_difftuple_t *soatuple = NULL; + dns_rdataset_init(&qrdataset); dns_rdataset_makequestion(&qrdataset, xfr->rdclass, xfr->reqtype); ISC_LIST_INIT(xfr->name.list); @@ -692,7 +770,7 @@ xfrin_connect_done(isc_task_t *task, isc_event_t *event) { dns_db_createsoatuple(xfr->db, ver, xfr->mctx, DNS_DIFFOP_EXISTS, &soatuple); xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata); - dns_db_closeversion(xfr->db, &ver, ISC_FALSE); + dns_db_closeversion(xfr->db, &ver, ISC_FALSE); printf("requesting IXFR for serial %u\n", xfr->ixfr.request_serial); @@ -732,21 +810,16 @@ xfrin_connect_done(isc_task_t *task, isc_event_t *event) { lregion.length = 2; CHECK(isc_socket_send(xfr->socket, &lregion, xfr->task, xfrin_sendlen_done, xfr)); - isc_event_free(&event); - return; + return (DNS_R_SUCCESS); failure: - isc_event_free(&event); if (soatuple != NULL) dns_difftuple_free(&soatuple); - if (event != NULL) - isc_event_free(&event); - xfrin_fail(xfr, result, "connecting"); - + return (result); } /* XXX there should be library support for sending DNS TCP messages */ - + static void xfrin_sendlen_done(isc_task_t *task, isc_event_t *event) { @@ -786,8 +859,6 @@ xfrin_send_done(isc_task_t *task, isc_event_t *event) printf("send done\n"); CHECK(sev->result); - dns_tcpmsg_init(xfr->mctx, xfr->socket, &xfr->tcpmsg); - xfr->tcpmsg_valid = ISC_TRUE; CHECK(dns_tcpmsg_readmessage(&xfr->tcpmsg, xfr->task, xfrin_recv_done, xfr)); xfr->recvs++; @@ -825,6 +896,18 @@ xfrin_recv_done(isc_task_t *task, isc_event_t *ev) { CHECK(dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg)); CHECK(dns_message_parse(msg, &tcpmsg->buffer, ISC_TRUE)); + + if (msg->rcode != dns_rcode_noerror) { + result = ISC_RESULTCLASS_DNSRCODE + msg->rcode; /* XXX */ + if (xfr->reqtype == dns_rdatatype_axfr) + FAIL(result); + printf("got %s, retrying with AXFR\n", + isc_result_totext(result)); + dns_message_destroy(&msg); + xfr->reqtype = dns_rdatatype_axfr; + CHECK(xfrin_send_request(xfr)); + return; + } for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER); result == DNS_R_SUCCESS; @@ -865,14 +948,14 @@ xfrin_recv_done(isc_task_t *task, isc_event_t *ev) { failure: if (msg != NULL) - dns_message_destroy(&msg); + dns_message_destroy(&msg); xfrin_fail(xfr, result, "receving responses"); } static void xfrin_timeout(isc_task_t *task, isc_event_t *event) { xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - task = task; /* Unused */ + task = task; /* Unused */ INSIST(event->type == ISC_TIMEREVENT_IDLE); isc_event_free(&event); xfrin_fail(xfr, ISC_R_TIMEDOUT, "giving up"); @@ -881,9 +964,9 @@ xfrin_timeout(isc_task_t *task, isc_event_t *event) { static void xfrin_shutdown(isc_task_t *task, isc_event_t *event) { xfrin_ctx_t *xfr = (xfrin_ctx_t *) event->arg; - task = task; /* Unused */ + task = task; /* Unused */ INSIST(event->type == ISC_TASKEVENT_SHUTDOWN); - isc_event_free(&event); + isc_event_free(&event); printf("xfrin_shutdown task=%p\n", task); xfr->tasks--; maybe_free(xfr); @@ -902,7 +985,7 @@ maybe_free(xfrin_ctx_t *xfr) { if (xfr->ixfr.journal != NULL) dns_journal_destroy(&xfr->ixfr.journal); - + if (xfr->axfr.add_private != NULL) (void) dns_db_endload(xfr->db, &xfr->axfr.add_private); @@ -926,3 +1009,258 @@ maybe_free(xfrin_ctx_t *xfr) { printf("xfrin_shutdown done\n"); return (ISC_TRUE); } + +/**************************************************************************/ +/* + * Generating diffs from databases + */ + +/* + * Construct a diff containing all the RRs at the current name of the + * database iterator 'dbit' in database 'db', version 'ver'. + * Set '*name' to the current name, and append the diff to 'diff'. + * All new tuples will have the operation 'op'. + * + * Requires: 'name' must have buffer large enough to hold the name. + * Typically, a dns_fixedname_t would be used. + */ +static dns_result_t +get_name_diff(dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, + dns_dbiterator_t *dbit, dns_name_t *name, dns_diffop_t op, + dns_diff_t *diff) +{ + dns_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_difftuple_t *tuple = NULL; + + result = dns_dbiterator_current(dbit, &node, name); + if (result != DNS_R_SUCCESS) + return (result); + + result = dns_db_allrdatasets(db, node, ver, now, &rdsiter); + if (result != DNS_R_SUCCESS) + goto cleanup_node; + + for (result = dns_rdatasetiter_first(rdsiter); + result == DNS_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + for (result = dns_rdataset_first(&rdataset); + result == DNS_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata; + dns_rdataset_current(&rdataset, &rdata); + result = dns_difftuple_create(diff->mctx, op, name, + rdataset.ttl, &rdata, + &tuple); + if (result != DNS_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + goto cleanup_iterator; + } + dns_diff_append(diff, &tuple); + } + dns_rdataset_disassociate(&rdataset); + if (result != DNS_R_NOMORE) + goto cleanup_iterator; + } + if (result != DNS_R_NOMORE) + goto cleanup_iterator; + + result = DNS_R_SUCCESS; + + cleanup_iterator: + dns_rdatasetiter_destroy(&rdsiter); + + cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/* + * Comparison function for use by dns_diff_subtract when sorting + * the diffs to be subtracted. The sort keys are the rdata type + * and the rdata itself. The owner name is ignored, because + * it is known to be the same for all tuples. + */ +static int +rdata_order(const void *av, const void *bv) +{ + dns_difftuple_t * const *ap = av; + dns_difftuple_t * const *bp = bv; + dns_difftuple_t *a = *ap; + dns_difftuple_t *b = *bp; + int r; + r = (b->rdata.type - a->rdata.type); + if (r != 0) + return (r); + r = dns_rdata_compare(&a->rdata, &b->rdata); + return (r); +} + +static dns_result_t +dns_diff_subtract(dns_diff_t diff[2], dns_diff_t *r) +{ + dns_result_t result; + dns_difftuple_t *p[2]; + int i, t; + CHECK(dns_diff_sort(&diff[0], rdata_order)); + CHECK(dns_diff_sort(&diff[1], rdata_order)); + + for (;;) { + p[0] = ISC_LIST_HEAD(diff[0].tuples); + p[1] = ISC_LIST_HEAD(diff[1].tuples); + if (p[0] == NULL && p[1] == NULL) + break; + + for (i = 0; i < 2; i++) + if (p[!i] == NULL) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + ISC_LIST_APPEND(r->tuples, p[i], link); + goto next; + } + t = rdata_order(&p[0], &p[1]); + if (t < 0) { + ISC_LIST_UNLINK(diff[0].tuples, p[0], link); + ISC_LIST_APPEND(r->tuples, p[0], link); + goto next; + } + if (t > 0) { + ISC_LIST_UNLINK(diff[1].tuples, p[1], link); + ISC_LIST_APPEND(r->tuples, p[1], link); + goto next; + } + INSIST(t == 0); + /* Identical RRs in both databases; skip them both. */ + for (i = 0; i < 2; i++) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + dns_difftuple_free(&p[i]); + } + next: ; + } + result = ISC_R_SUCCESS; + failure: + return (result); +} + +/* + * Compare the databases 'dba' and 'dbb' and generate a journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. + */ + +static dns_result_t +diff_databases(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + char *journal_filename) +{ + dns_db_t *db[2]; + dns_dbversion_t *ver[2]; + dns_dbiterator_t *dbit[2] = { NULL, NULL }; + isc_boolean_t have[2] = { ISC_FALSE, ISC_FALSE }; + dns_fixedname_t fixname[2]; + dns_result_t result, itresult[2]; + dns_diff_t diff[2], resultdiff; + int i, t; + dns_journal_t *journal = NULL; + + db[0] = dba, db[1] = dbb; + ver[0] = dbvera, ver[1] = dbverb; + + dns_diff_init(mctx, &diff[0]); + dns_diff_init(mctx, &diff[1]); + dns_diff_init(mctx, &resultdiff); + + dns_fixedname_init(&fixname[0]); + dns_fixedname_init(&fixname[1]); + + CHECK(dns_journal_open(mctx, journal_filename, ISC_TRUE, &journal)); + + CHECK(dns_db_createiterator(db[0], ISC_FALSE, &dbit[0])); + CHECK(dns_db_createiterator(db[1], ISC_FALSE, &dbit[1])); + + itresult[0] = dns_dbiterator_first(dbit[0]); + itresult[1] = dns_dbiterator_first(dbit[1]); + + for (;;) { + for (i = 0; i < 2; i++) { + if (! have[i] && itresult[i] == DNS_R_SUCCESS) { + CHECK(get_name_diff(db[i], ver[i], 0, dbit[i], + dns_fixedname_name(&fixname[i]), + i == 0 ? + DNS_DIFFOP_ADD : + DNS_DIFFOP_DEL, + &diff[i])); + itresult[i] = dns_dbiterator_next(dbit[i]); + have[i] = ISC_TRUE; + } + } + + if (! have[0] && ! have[1]) { + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + break; + } + + for (i = 0; i < 2; i++) { + if (! have[!i]) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[i].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[i].tuples)); + have[i] = ISC_FALSE; + goto next; + } + } + + t = dns_name_compare(dns_fixedname_name(&fixname[0]), + dns_fixedname_name(&fixname[1])); + if (t < 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[0].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + have[0] = ISC_FALSE; + continue; + } + if (t > 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[1].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[1] = ISC_FALSE; + continue; + } + INSIST(t == 0); + CHECK(dns_diff_subtract(diff, &resultdiff)); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[0] = have[1] = ISC_FALSE; + next: ; + } + if (itresult[0] != DNS_R_NOMORE) + FAIL(itresult[0]); + if (itresult[1] != DNS_R_NOMORE) + FAIL(itresult[1]); + + if (ISC_LIST_EMPTY(resultdiff.tuples)) { + printf("no changes\n"); + } else { + CHECK(dns_journal_write_transaction(journal, &resultdiff)); + } + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + dns_diff_clear(&resultdiff); + + failure: + dns_dbiterator_destroy(&dbit[0]); + dns_dbiterator_destroy(&dbit[1]); + dns_journal_destroy(&journal); + return (result); +}