/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0.  If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file
 */

#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>

#include <isc/commandline.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>

#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/dbtable.h>
#include <dns/fixedname.h>
#include <dns/log.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/result.h>

#define MAXHOLD	    100
#define MAXVERSIONS 100

typedef struct dbinfo {
	dns_db_t *db;
	dns_dbversion_t *version;
	dns_dbversion_t *wversion;
	dns_dbversion_t *rversions[MAXVERSIONS];
	int rcount;
	dns_dbnode_t *hold_nodes[MAXHOLD];
	int hold_count;
	dns_dbiterator_t *dbiterator;
	dns_dbversion_t *iversion;
	int pause_every;
	bool ascending;
	ISC_LINK(struct dbinfo) link;
} dbinfo;

static isc_mem_t *mctx = NULL;
static char dbtype[128];
static dns_dbtable_t *dbtable;
static ISC_LIST(dbinfo) dbs;
static dbinfo *cache_dbi = NULL;
static int pause_every = 0;
static bool ascending = true;

static void
print_result(const char *message, isc_result_t result) {
	if (message == NULL) {
		message = "";
	}
	printf("%s%sresult %08x: %s\n", message, (*message == '\0') ? "" : " ",
	       result, isc_result_totext(result));
}

static void
print_rdataset(dns_name_t *name, dns_rdataset_t *rdataset) {
	isc_buffer_t text;
	char t[1000];
	isc_result_t result;
	isc_region_t r;

	isc_buffer_init(&text, t, sizeof(t));
	result = dns_rdataset_totext(rdataset, name, false, false, &text);
	isc_buffer_usedregion(&text, &r);
	if (result == ISC_R_SUCCESS) {
		printf("%.*s", (int)r.length, (char *)r.base);
	} else {
		print_result("", result);
	}
}

static void
print_rdatasets(dns_name_t *name, dns_rdatasetiter_t *rdsiter) {
	isc_result_t result;
	dns_rdataset_t rdataset;

	dns_rdataset_init(&rdataset);
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);
		print_rdataset(name, &rdataset);
		dns_rdataset_disassociate(&rdataset);
		result = dns_rdatasetiter_next(rdsiter);
	}
	if (result != ISC_R_NOMORE) {
		print_result("", result);
	}
}

static dbinfo *
select_db(char *origintext) {
	dns_fixedname_t forigin;
	dns_name_t *origin;
	isc_buffer_t source;
	size_t len;
	dbinfo *dbi;
	isc_result_t result;

	if (strcasecmp(origintext, "cache") == 0) {
		if (cache_dbi == NULL) {
			printf("the cache does not exist\n");
		}
		return (cache_dbi);
	}
	len = strlen(origintext);
	isc_buffer_init(&source, origintext, len);
	isc_buffer_add(&source, len);
	origin = dns_fixedname_initname(&forigin);
	result = dns_name_fromtext(origin, &source, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		print_result("bad name", result);
		return (NULL);
	}

	for (dbi = ISC_LIST_HEAD(dbs); dbi != NULL;
	     dbi = ISC_LIST_NEXT(dbi, link))
	{
		if (dns_name_compare(dns_db_origin(dbi->db), origin) == 0) {
			break;
		}
	}

	return (dbi);
}

static void
list(dbinfo *dbi, char *seektext) {
	dns_fixedname_t fname;
	dns_name_t *name;
	dns_dbnode_t *node;
	dns_rdatasetiter_t *rdsiter;
	isc_result_t result;
	int i;
	size_t len;
	dns_fixedname_t fseekname;
	dns_name_t *seekname;
	isc_buffer_t source;

	name = dns_fixedname_initname(&fname);

	if (dbi->dbiterator == NULL) {
		INSIST(dbi->iversion == NULL);
		if (dns_db_iszone(dbi->db)) {
			if (dbi->version != NULL) {
				dns_db_attachversion(dbi->db, dbi->version,
						     &dbi->iversion);
			} else {
				dns_db_currentversion(dbi->db, &dbi->iversion);
			}
		}

		result = dns_db_createiterator(dbi->db, 0, &dbi->dbiterator);
		if (result == ISC_R_SUCCESS) {
			if (seektext != NULL) {
				len = strlen(seektext);
				isc_buffer_init(&source, seektext, len);
				isc_buffer_add(&source, len);
				seekname = dns_fixedname_initname(&fseekname);
				result = dns_name_fromtext(
					seekname, &source,
					dns_db_origin(dbi->db), 0, NULL);
				if (result == ISC_R_SUCCESS) {
					result = dns_dbiterator_seek(
						dbi->dbiterator, seekname);
				}
			} else if (dbi->ascending) {
				result = dns_dbiterator_first(dbi->dbiterator);
			} else {
				result = dns_dbiterator_last(dbi->dbiterator);
			}
		}
	} else {
		result = ISC_R_SUCCESS;
	}

	node = NULL;
	rdsiter = NULL;
	i = 0;
	while (result == ISC_R_SUCCESS) {
		result = dns_dbiterator_current(dbi->dbiterator, &node, name);
		if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) {
			break;
		}
		result = dns_db_allrdatasets(dbi->db, node, dbi->iversion, 0, 0,
					     &rdsiter);
		if (result != ISC_R_SUCCESS) {
			dns_db_detachnode(dbi->db, &node);
			break;
		}
		print_rdatasets(name, rdsiter);
		dns_rdatasetiter_destroy(&rdsiter);
		dns_db_detachnode(dbi->db, &node);
		if (dbi->ascending) {
			result = dns_dbiterator_next(dbi->dbiterator);
		} else {
			result = dns_dbiterator_prev(dbi->dbiterator);
		}
		i++;
		if (result == ISC_R_SUCCESS && i == dbi->pause_every) {
			printf("[more...]\n");
			result = dns_dbiterator_pause(dbi->dbiterator);
			if (result == ISC_R_SUCCESS) {
				return;
			}
		}
	}
	if (result != ISC_R_NOMORE) {
		print_result("", result);
	}

	dns_dbiterator_destroy(&dbi->dbiterator);
	if (dbi->iversion != NULL) {
		dns_db_closeversion(dbi->db, &dbi->iversion, false);
	}
}

static isc_result_t
load(const char *filename, const char *origintext, bool cache) {
	dns_fixedname_t forigin;
	dns_name_t *origin;
	isc_result_t result;
	isc_buffer_t source;
	size_t len;
	dbinfo *dbi;
	unsigned int i;

	dbi = isc_mem_get(mctx, sizeof(*dbi));

	dbi->db = NULL;
	dbi->version = NULL;
	dbi->wversion = NULL;
	for (i = 0; i < MAXVERSIONS; i++) {
		dbi->rversions[i] = NULL;
	}
	dbi->hold_count = 0;
	for (i = 0; i < MAXHOLD; i++) {
		dbi->hold_nodes[i] = NULL;
	}
	dbi->dbiterator = NULL;
	dbi->iversion = NULL;
	dbi->pause_every = pause_every;
	dbi->ascending = ascending;
	ISC_LINK_INIT(dbi, link);

	len = strlen(origintext);
	isc_buffer_constinit(&source, origintext, len);
	isc_buffer_add(&source, len);
	origin = dns_fixedname_initname(&forigin);
	result = dns_name_fromtext(origin, &source, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		isc_mem_put(mctx, dbi, sizeof(*dbi));
		return (result);
	}

	result = dns_db_create(mctx, dbtype, origin,
			       cache ? dns_dbtype_cache : dns_dbtype_zone,
			       dns_rdataclass_in, 0, NULL, &dbi->db);
	if (result != ISC_R_SUCCESS) {
		isc_mem_put(mctx, dbi, sizeof(*dbi));
		return (result);
	}

	printf("loading %s (%s)\n", filename, origintext);
	result = dns_db_load(dbi->db, filename, dns_masterformat_text, 0);
	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
		dns_db_detach(&dbi->db);
		isc_mem_put(mctx, dbi, sizeof(*dbi));
		return (result);
	}
	printf("loaded\n");

	if (cache) {
		INSIST(cache_dbi == NULL);
		dns_dbtable_adddefault(dbtable, dbi->db);
		cache_dbi = dbi;
	} else {
		result = dns_dbtable_add(dbtable, dbi->db);
		if (result != ISC_R_SUCCESS) {
			dns_db_detach(&dbi->db);
			isc_mem_put(mctx, dbi, sizeof(*dbi));
			return (result);
		}
	}
	ISC_LIST_APPEND(dbs, dbi, link);

	return (ISC_R_SUCCESS);
}

static void
unload_all(void) {
	dbinfo *dbi, *dbi_next;

	for (dbi = ISC_LIST_HEAD(dbs); dbi != NULL; dbi = dbi_next) {
		dbi_next = ISC_LIST_NEXT(dbi, link);
		if (dns_db_iszone(dbi->db)) {
			dns_dbtable_remove(dbtable, dbi->db);
		} else {
			INSIST(dbi == cache_dbi);
			dns_dbtable_removedefault(dbtable);
			cache_dbi = NULL;
		}
		dns_db_detach(&dbi->db);
		ISC_LIST_UNLINK(dbs, dbi, link);
		isc_mem_put(mctx, dbi, sizeof(*dbi));
	}
}

#define DBI_CHECK(dbi)                                                 \
	if ((dbi) == NULL) {                                           \
		printf("You must first select a database with !DB\n"); \
		continue;                                              \
	}

int
main(int argc, char *argv[]) {
	dns_db_t *db;
	dns_dbnode_t *node;
	isc_result_t result;
	dns_name_t name;
	dns_offsets_t offsets;
	size_t len;
	isc_buffer_t source, target;
	char s[1000];
	char b[255];
	dns_rdataset_t rdataset, sigrdataset;
	int ch;
	dns_rdatatype_t type = 1;
	bool printnode = false;
	bool addmode = false;
	bool delmode = false;
	bool holdmode = false;
	bool verbose = false;
	bool done = false;
	bool quiet = false;
	bool time_lookups = false;
	bool found_as;
	bool find_zonecut = false;
	bool noexact_zonecut = false;
	int i, v;
	dns_rdatasetiter_t *rdsiter;
	char t1[256];
	char t2[256];
	isc_buffer_t tb1, tb2;
	isc_region_t r1, r2;
	dns_fixedname_t foundname;
	dns_name_t *fname;
	unsigned int options = 0, zcoptions;
	isc_time_t start, finish;
	const char *origintext;
	dbinfo *dbi;
	dns_dbversion_t *version;
	const dns_name_t *origin;
	dns_trust_t trust = 0;
	unsigned int addopts;
	isc_log_t *lctx = NULL;
	size_t n;

	dns_result_register();

	isc_mem_create(&mctx);
	RUNTIME_CHECK(dns_dbtable_create(mctx, dns_rdataclass_in, &dbtable) ==
		      ISC_R_SUCCESS);

	snprintf(dbtype, sizeof(dbtype), "rbt");
	while ((ch = isc_commandline_parse(argc, argv, "c:d:t:z:P:Q:glpqvT")) !=
	       -1)
	{
		switch (ch) {
		case 'c':
			result = load(isc_commandline_argument, ".", true);
			if (result != ISC_R_SUCCESS) {
				printf("cache load(%s) %08x: %s\n",
				       isc_commandline_argument, result,
				       isc_result_totext(result));
			}
			break;
		case 'd':
			n = strlcpy(dbtype, isc_commandline_argument,
				    sizeof(dbtype));
			if (n >= sizeof(dbtype)) {
				fprintf(stderr, "bad db type '%s'\n",
					isc_commandline_argument);
				exit(1);
			}
			break;
		case 'g':
			options |= (DNS_DBFIND_GLUEOK |
				    DNS_DBFIND_VALIDATEGLUE);
			break;
		case 'l':
			isc_log_create(mctx, &lctx, NULL);
			isc_log_setcontext(lctx);
			dns_log_init(lctx);
			dns_log_setcontext(lctx);
			break;
		case 'q':
			quiet = true;
			verbose = false;
			break;
		case 'p':
			printnode = true;
			break;
		case 'P':
			pause_every = atoi(isc_commandline_argument);
			break;
		case 't':
			type = atoi(isc_commandline_argument);
			break;
		case 'T':
			time_lookups = true;
			break;
		case 'v':
			verbose = true;
			break;
		case 'z':
			origintext = strrchr(isc_commandline_argument, '/');
			if (origintext == NULL) {
				origintext = isc_commandline_argument;
			} else {
				origintext++; /* Skip '/'. */
			}
			result = load(isc_commandline_argument, origintext,
				      false);
			if (result != ISC_R_SUCCESS) {
				printf("zone load(%s) %08x: %s\n",
				       isc_commandline_argument, result,
				       isc_result_totext(result));
			}
			break;
		}
	}

	argc -= isc_commandline_index;
	argv += isc_commandline_index;
	POST(argv);

	if (argc != 0) {
		printf("ignoring trailing arguments\n");
	}

	/*
	 * Some final initialization...
	 */
	fname = dns_fixedname_initname(&foundname);
	dbi = NULL;
	origin = dns_rootname;
	version = NULL;

	if (time_lookups) {
		TIME_NOW(&start);
	}

	while (!done) {
		if (!quiet) {
			printf("\n");
		}
		if (fgets(s, sizeof(s), stdin) == NULL) {
			done = true;
			continue;
		}
		len = strlen(s);
		if (len > 0U && s[len - 1] == '\n') {
			s[len - 1] = '\0';
			len--;
		}
		if (verbose && dbi != NULL) {
			if (dbi->wversion != NULL) {
				printf("future version (%p)\n", dbi->wversion);
			}
			for (i = 0; i < dbi->rcount; i++) {
				if (dbi->rversions[i] != NULL) {
					printf("open version %d (%p)\n", i,
					       dbi->rversions[i]);
				}
			}
		}
		dns_name_init(&name, offsets);
		if (strcmp(s, "!R") == 0) {
			DBI_CHECK(dbi);
			if (dbi->rcount == MAXVERSIONS) {
				printf("too many open versions\n");
				continue;
			}
			dns_db_currentversion(dbi->db,
					      &dbi->rversions[dbi->rcount]);
			printf("opened version %d\n", dbi->rcount);
			dbi->version = dbi->rversions[dbi->rcount];
			version = dbi->version;
			dbi->rcount++;
			continue;
		} else if (strcmp(s, "!W") == 0) {
			DBI_CHECK(dbi);
			if (dbi->wversion != NULL) {
				printf("using existing future version\n");
				dbi->version = dbi->wversion;
				version = dbi->version;
				continue;
			}
			result = dns_db_newversion(dbi->db, &dbi->wversion);
			if (result != ISC_R_SUCCESS) {
				print_result("", result);
			} else {
				printf("newversion\n");
			}
			dbi->version = dbi->wversion;
			version = dbi->version;
			continue;
		} else if (strcmp(s, "!C") == 0) {
			DBI_CHECK(dbi);
			addmode = false;
			delmode = false;
			if (dbi->version == NULL) {
				continue;
			}
			if (dbi->version == dbi->wversion) {
				printf("closing future version\n");
				dbi->wversion = NULL;
			} else {
				for (i = 0; i < dbi->rcount; i++) {
					if (dbi->version == dbi->rversions[i]) {
						dbi->rversions[i] = NULL;
						printf("closing open version "
						       "%d\n",
						       i);
						break;
					}
				}
			}
			dns_db_closeversion(dbi->db, &dbi->version, true);
			version = NULL;
			continue;
		} else if (strcmp(s, "!X") == 0) {
			DBI_CHECK(dbi);
			addmode = false;
			delmode = false;
			if (dbi->version == NULL) {
				continue;
			}
			if (dbi->version == dbi->wversion) {
				printf("aborting future version\n");
				dbi->wversion = NULL;
			} else {
				for (i = 0; i < dbi->rcount; i++) {
					if (dbi->version == dbi->rversions[i]) {
						dbi->rversions[i] = NULL;
						printf("closing open version "
						       "%d\n",
						       i);
						break;
					}
				}
			}
			dns_db_closeversion(dbi->db, &dbi->version, false);
			version = NULL;
			continue;
		} else if (strcmp(s, "!A") == 0) {
			DBI_CHECK(dbi);
			delmode = false;
			if (addmode) {
				addmode = false;
			} else {
				addmode = true;
			}
			printf("addmode = %s\n", addmode ? "TRUE" : "FALSE");
			continue;
		} else if (strcmp(s, "!D") == 0) {
			DBI_CHECK(dbi);
			addmode = false;
			if (delmode) {
				delmode = false;
			} else {
				delmode = true;
			}
			printf("delmode = %s\n", delmode ? "TRUE" : "FALSE");
			continue;
		} else if (strcmp(s, "!H") == 0) {
			DBI_CHECK(dbi);
			if (holdmode) {
				holdmode = false;
			} else {
				holdmode = true;
			}
			printf("holdmode = %s\n", holdmode ? "TRUE" : "FALSE");
			continue;
		} else if (strcmp(s, "!HR") == 0) {
			DBI_CHECK(dbi);
			for (i = 0; i < dbi->hold_count; i++) {
				dns_db_detachnode(dbi->db, &dbi->hold_nodes[i]);
			}
			dbi->hold_count = 0;
			holdmode = false;
			printf("held nodes have been detached\n");
			continue;
		} else if (strcmp(s, "!VC") == 0) {
			DBI_CHECK(dbi);
			printf("switching to current version\n");
			dbi->version = NULL;
			version = NULL;
			continue;
		} else if (strstr(s, "!V") == s) {
			DBI_CHECK(dbi);
			v = atoi(&s[2]);
			if (v >= dbi->rcount || v < 0) {
				printf("unknown open version %d\n", v);
				continue;
			}
			if (dbi->rversions[v] == NULL) {
				printf("version %d is not open\n", v);
				continue;
			}
			printf("switching to open version %d\n", v);
			dbi->version = dbi->rversions[v];
			version = dbi->version;
			continue;
		} else if (strstr(s, "!TR") == s) {
			trust = (unsigned int)atoi(&s[3]);
			printf("trust level is now %u\n", (unsigned int)trust);
			continue;
		} else if (strstr(s, "!T") == s) {
			type = (unsigned int)atoi(&s[2]);
			printf("now searching for type %u\n", type);
			continue;
		} else if (strcmp(s, "!G") == 0) {
			if ((options & DNS_DBFIND_GLUEOK) != 0) {
				options &= ~DNS_DBFIND_GLUEOK;
			} else {
				options |= DNS_DBFIND_GLUEOK;
			}
			printf("glue ok = %s\n",
			       ((options & DNS_DBFIND_GLUEOK) != 0) ? "TRUE"
								    : "FALSE");
			continue;
		} else if (strcmp(s, "!GV") == 0) {
			if ((options & DNS_DBFIND_VALIDATEGLUE) != 0) {
				options &= ~DNS_DBFIND_VALIDATEGLUE;
			} else {
				options |= DNS_DBFIND_VALIDATEGLUE;
			}
			printf("validate glue = %s\n",
			       ((options & DNS_DBFIND_VALIDATEGLUE) != 0)
				       ? "TRUE"
				       : "FALSE");
			continue;
		} else if (strcmp(s, "!WC") == 0) {
			if ((options & DNS_DBFIND_NOWILD) != 0) {
				options &= ~DNS_DBFIND_NOWILD;
			} else {
				options |= DNS_DBFIND_NOWILD;
			}
			printf("wildcard matching = %s\n",
			       ((options & DNS_DBFIND_NOWILD) == 0) ? "TRUE"
								    : "FALSE");
			continue;
		} else if (strstr(s, "!LS ") == s) {
			DBI_CHECK(dbi);
			list(dbi, &s[4]);
			continue;
		} else if (strcmp(s, "!LS") == 0) {
			DBI_CHECK(dbi);
			list(dbi, NULL);
			continue;
		} else if (strstr(s, "!DU ") == s) {
			DBI_CHECK(dbi);
			result = dns_db_dump(dbi->db, dbi->version, s + 4);
			if (result != ISC_R_SUCCESS) {
				printf("\n");
				print_result("", result);
			}
			continue;
		} else if (strcmp(s, "!PN") == 0) {
			if (printnode) {
				printnode = false;
			} else {
				printnode = true;
			}
			printf("printnode = %s\n",
			       printnode ? "TRUE" : "FALSE");
			continue;
		} else if (strstr(s, "!P") == s) {
			DBI_CHECK(dbi);
			v = atoi(&s[2]);
			dbi->pause_every = v;
			continue;
		} else if (strcmp(s, "!+") == 0) {
			DBI_CHECK(dbi);
			dbi->ascending = true;
			continue;
		} else if (strcmp(s, "!-") == 0) {
			DBI_CHECK(dbi);
			dbi->ascending = false;
			continue;
		} else if (strcmp(s, "!DB") == 0) {
			dbi = NULL;
			origin = dns_rootname;
			version = NULL;
			printf("now searching all databases\n");
			continue;
		} else if (strncmp(s, "!DB ", 4) == 0) {
			dbi = select_db(s + 4);
			if (dbi != NULL) {
				db = dbi->db;
				origin = dns_db_origin(dbi->db);
				version = dbi->version;
				addmode = false;
				delmode = false;
				holdmode = false;
			} else {
				db = NULL;
				version = NULL;
				origin = dns_rootname;
				printf("database not found; "
				       "now searching all databases\n");
			}
			continue;
		} else if (strcmp(s, "!ZC") == 0) {
			if (find_zonecut) {
				find_zonecut = false;
			} else {
				find_zonecut = true;
			}
			printf("find_zonecut = %s\n",
			       find_zonecut ? "TRUE" : "FALSE");
			continue;
		} else if (strcmp(s, "!NZ") == 0) {
			if (noexact_zonecut) {
				noexact_zonecut = false;
			} else {
				noexact_zonecut = true;
			}
			printf("noexact_zonecut = %s\n",
			       noexact_zonecut ? "TRUE" : "FALSE");
			continue;
		}

		isc_buffer_init(&source, s, len);
		isc_buffer_add(&source, len);
		isc_buffer_init(&target, b, sizeof(b));
		result = dns_name_fromtext(&name, &source, origin, 0, &target);
		if (result != ISC_R_SUCCESS) {
			print_result("bad name: ", result);
			continue;
		}

		if (dbi == NULL) {
			zcoptions = 0;
			if (noexact_zonecut) {
				zcoptions |= DNS_DBTABLEFIND_NOEXACT;
			}
			db = NULL;
			result = dns_dbtable_find(dbtable, &name, zcoptions,
						  &db);
			if (result != ISC_R_SUCCESS &&
			    result != DNS_R_PARTIALMATCH)
			{
				if (!quiet) {
					printf("\n");
					print_result("", result);
				}
				continue;
			}
			isc_buffer_init(&tb1, t1, sizeof(t1));
			result = dns_name_totext(dns_db_origin(db), false,
						 &tb1);
			if (result != ISC_R_SUCCESS) {
				printf("\n");
				print_result("", result);
				dns_db_detach(&db);
				continue;
			}
			isc_buffer_usedregion(&tb1, &r1);
			printf("\ndatabase = %.*s (%s)\n", (int)r1.length,
			       r1.base, (dns_db_iszone(db)) ? "zone" : "cache");
		}
		node = NULL;
		dns_rdataset_init(&rdataset);
		dns_rdataset_init(&sigrdataset);

		if (find_zonecut && dns_db_iscache(db)) {
			zcoptions = options;
			if (noexact_zonecut) {
				zcoptions |= DNS_DBFIND_NOEXACT;
			}
			result = dns_db_findzonecut(db, &name, zcoptions, 0,
						    &node, fname, NULL,
						    &rdataset, &sigrdataset);
		} else {
			result = dns_db_find(db, &name, version, type, options,
					     0, &node, fname, &rdataset,
					     &sigrdataset);
		}

		if (!quiet) {
			if (dbi != NULL) {
				printf("\n");
			}
			print_result("", result);
		}

		found_as = false;
		switch (result) {
		case ISC_R_SUCCESS:
		case DNS_R_GLUE:
		case DNS_R_CNAME:
		case DNS_R_ZONECUT:
			break;
		case DNS_R_DNAME:
		case DNS_R_DELEGATION:
			found_as = true;
			break;
		case DNS_R_NXRRSET:
			if (dns_rdataset_isassociated(&rdataset)) {
				break;
			}
			if (dbi != NULL) {
				if (holdmode) {
					RUNTIME_CHECK(dbi->hold_count <
						      MAXHOLD);
					dbi->hold_nodes[dbi->hold_count++] =
						node;
					node = NULL;
				} else {
					dns_db_detachnode(db, &node);
				}
			} else {
				dns_db_detachnode(db, &node);
				dns_db_detach(&db);
			}
			continue;
		case DNS_R_NXDOMAIN:
			if (dns_rdataset_isassociated(&rdataset)) {
				break;
			}
			FALLTHROUGH;
		default:
			if (dbi == NULL) {
				dns_db_detach(&db);
			}
			if (quiet) {
				print_result("", result);
			}
			continue;
		}
		if (found_as && !quiet) {
			isc_buffer_init(&tb1, t1, sizeof(t1));
			isc_buffer_init(&tb2, t2, sizeof(t2));
			result = dns_name_totext(&name, false, &tb1);
			if (result != ISC_R_SUCCESS) {
				print_result("", result);
				dns_db_detachnode(db, &node);
				if (dbi == NULL) {
					dns_db_detach(&db);
				}
				continue;
			}
			result = dns_name_totext(fname, false, &tb2);
			if (result != ISC_R_SUCCESS) {
				print_result("", result);
				dns_db_detachnode(db, &node);
				if (dbi == NULL) {
					dns_db_detach(&db);
				}
				continue;
			}
			isc_buffer_usedregion(&tb1, &r1);
			isc_buffer_usedregion(&tb2, &r2);
			printf("found %.*s as %.*s\n", (int)r1.length, r1.base,
			       (int)r2.length, r2.base);
		}

		if (printnode) {
			dns_db_printnode(db, node, stdout);
		}

		if (!found_as && type == dns_rdatatype_any) {
			rdsiter = NULL;
			result = dns_db_allrdatasets(db, node, version, 0, 0,
						     &rdsiter);
			if (result == ISC_R_SUCCESS) {
				if (!quiet) {
					print_rdatasets(fname, rdsiter);
				}
				dns_rdatasetiter_destroy(&rdsiter);
			} else {
				print_result("", result);
			}
		} else {
			if (!quiet) {
				print_rdataset(fname, &rdataset);
			}
			if (dns_rdataset_isassociated(&sigrdataset)) {
				if (!quiet) {
					print_rdataset(fname, &sigrdataset);
				}
				dns_rdataset_disassociate(&sigrdataset);
			}
			if (dbi != NULL && addmode && !found_as) {
				rdataset.ttl++;
				rdataset.trust = trust;
				if (dns_db_iszone(db)) {
					addopts = DNS_DBADD_MERGE;
				} else {
					addopts = 0;
				}
				result = dns_db_addrdataset(db, node, version,
							    0, &rdataset,
							    addopts, NULL);
				if (result != ISC_R_SUCCESS) {
					print_result("", result);
				}
				if (printnode) {
					dns_db_printnode(db, node, stdout);
				}
			} else if (dbi != NULL && delmode && !found_as) {
				result = dns_db_deleterdataset(
					db, node, version, type, 0);
				if (result != ISC_R_SUCCESS) {
					print_result("", result);
				}
				if (printnode) {
					dns_db_printnode(db, node, stdout);
				}
			}
			dns_rdataset_disassociate(&rdataset);
		}

		if (dbi != NULL) {
			if (holdmode) {
				RUNTIME_CHECK(dbi->hold_count < MAXHOLD);
				dbi->hold_nodes[dbi->hold_count++] = node;
				node = NULL;
			} else {
				dns_db_detachnode(db, &node);
			}
		} else {
			dns_db_detachnode(db, &node);
			dns_db_detach(&db);
		}
	}

	if (time_lookups) {
		uint64_t usec;

		TIME_NOW(&finish);

		usec = isc_time_microdiff(&finish, &start);

		printf("elapsed time: %lu.%06lu seconds\n",
		       (unsigned long)(usec / 1000000),
		       (unsigned long)(usec % 1000000));
	}

	unload_all();

	dns_dbtable_detach(&dbtable);

	if (lctx != NULL) {
		isc_log_destroy(&lctx);
	}

	if (!quiet) {
		isc_mem_stats(mctx, stdout);
	}

	return (0);
}
