zone_nsec3chain(dns_zone_t *zone) {
	const char *me = "zone_nsec3chain";
	dns_db_t *db = NULL;
	dns_dbnode_t *node = NULL;
	dns_dbversion_t *version = NULL;
	dns_diff_t _sig_diff;
	dns_diff_t nsec_diff;
	dns_diff_t nsec3_diff;
	dns_diff_t param_diff;
	dns__zonediff_t zonediff;
	dns_fixedname_t fixed;
	dns_fixedname_t nextfixed;
	dns_name_t *name, *nextname;
	dns_rdataset_t rdataset;
	dns_nsec3chain_t *nsec3chain = NULL, *nextnsec3chain;
	dns_nsec3chainlist_t cleanup;
	dst_key_t *zone_keys[DNS_MAXZONEKEYS];
	int32_t signatures;
	bool check_ksk, keyset_kskonly;
	bool delegation;
	bool first;
	isc_result_t result;
	isc_stdtime_t now, inception, soaexpire, expire;
	uint32_t jitter, sigvalidityinterval;
	unsigned int i;
	unsigned int nkeys = 0;
	uint32_t nodes;
	bool unsecure = false;
	bool seen_soa, seen_ns, seen_dname, seen_ds;
	bool seen_nsec, seen_nsec3, seen_rr;
	dns_rdatasetiter_t *iterator = NULL;
	bool buildnsecchain;
	bool updatensec = false;
	dns_rdatatype_t privatetype = zone->privatetype;

	ENTER;

	dns_rdataset_init(&rdataset);
	name = dns_fixedname_initname(&fixed);
	nextname = dns_fixedname_initname(&nextfixed);
	dns_diff_init(zone->mctx, &param_diff);
	dns_diff_init(zone->mctx, &nsec3_diff);
	dns_diff_init(zone->mctx, &nsec_diff);
	dns_diff_init(zone->mctx, &_sig_diff);
	zonediff_init(&zonediff, &_sig_diff);
	ISC_LIST_INIT(cleanup);

	/*
	 * Updates are disabled.  Pause for 5 minutes.
	 */
	if (zone->update_disabled) {
		result = ISC_R_FAILURE;
		goto failure;
	}

	ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
	/*
	 * This function is called when zone timer fires, after the latter gets
	 * set by zone_addnsec3chain().  If the action triggering the call to
	 * zone_addnsec3chain() is closely followed by a zone deletion request,
	 * it might turn out that the timer thread will not be woken up until
	 * after the zone is deleted by rmzone(), which calls dns_db_detach()
	 * for zone->db, causing the latter to become NULL.  Return immediately
	 * if that happens.
	 */
	if (zone->db != NULL) {
		dns_db_attach(zone->db, &db);
	}
	ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
	if (db == NULL) {
		return;
	}

	result = dns_db_newversion(db, &version);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:dns_db_newversion -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	isc_stdtime_get(&now);

	result = dns__zone_findkeys(zone, db, version, now, zone->mctx,
				    DNS_MAXZONEKEYS, zone_keys, &nkeys);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:dns__zone_findkeys -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
	inception = now - 3600;	/* Allow for clock skew. */
	soaexpire = now + sigvalidityinterval;

	/*
	 * Spread out signatures over time if they happen to be
	 * clumped.  We don't do this for each add_sigs() call as
	 * we still want some clustering to occur.
	 */
	if (sigvalidityinterval >= 3600U) {
		if (sigvalidityinterval > 7200U) {
			jitter = isc_random_uniform(3600);
		} else {
			jitter = isc_random_uniform(1200);
		}
		expire = soaexpire - jitter - 1;
	} else {
		expire = soaexpire - 1;
	}

	check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
	keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);

	/*
	 * We keep pulling nodes off each iterator in turn until
	 * we have no more nodes to pull off or we reach the limits
	 * for this quantum.
	 */
	nodes = zone->nodes;
	signatures = zone->signatures;
	LOCK_ZONE(zone);
	nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
	UNLOCK_ZONE(zone);
	first = true;

	if (nsec3chain != NULL) {
		nsec3chain->save_delete_nsec = nsec3chain->delete_nsec;
	}
	/*
	 * Generate new NSEC3 chains first.
	 *
	 * The following while loop iterates over nodes in the zone database,
	 * updating the NSEC3 chain by calling dns_nsec3_addnsec3() for each of
	 * them.  Once all nodes are processed, the "delete_nsec" field is
	 * consulted to check whether we are supposed to remove NSEC records
	 * from the zone database; if so, the database iterator is reset to
	 * point to the first node and the loop traverses all of them again,
	 * this time removing NSEC records.  If we hit a node which is obscured
	 * by a delegation or a DNAME, nodes are skipped over until we find one
	 * that is not obscured by the same obscuring name and then normal
	 * processing is resumed.
	 *
	 * The above is repeated until all requested NSEC3 chain changes are
	 * applied or when we reach the limits for this quantum, whichever
	 * happens first.
	 *
	 * Note that the "signatures" variable is only used here to limit the
	 * amount of work performed.  Actual DNSSEC signatures are only
	 * generated by dns__zone_updatesigs() calls later in this function.
	 */
	while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) {
		LOCK_ZONE(zone);
		nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link);

		ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read);
		if (nsec3chain->done || nsec3chain->db != zone->db) {
			ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link);
			ISC_LIST_APPEND(cleanup, nsec3chain, link);
		}
		ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
		UNLOCK_ZONE(zone);
		if (ISC_LIST_TAIL(cleanup) == nsec3chain)
			goto next_addchain;

		/*
		 * Possible future db.
		 */
		if (nsec3chain->db != db) {
			goto next_addchain;
		}

		if (NSEC3REMOVE(nsec3chain->nsec3param.flags))
			goto next_addchain;

		dns_dbiterator_current(nsec3chain->dbiterator, &node, name);

		if (nsec3chain->delete_nsec) {
			delegation = false;
			dns_dbiterator_pause(nsec3chain->dbiterator);
			CHECK(delete_nsec(db, version, node, name, &nsec_diff));
			goto next_addnode;
		}
		/*
		 * On the first pass we need to check if the current node
		 * has not been obscured.
		 */
		delegation = false;
		unsecure = false;
		if (first) {
			dns_fixedname_t ffound;
			dns_name_t *found;
			found = dns_fixedname_initname(&ffound);
			result = dns_db_find(db, name, version,
					     dns_rdatatype_soa,
					     DNS_DBFIND_NOWILD, 0, NULL, found,
					     NULL, NULL);
			if ((result == DNS_R_DELEGATION ||
			    result == DNS_R_DNAME) &&
			    !dns_name_equal(name, found)) {
				/*
				 * Remember the obscuring name so that
				 * we skip all obscured names.
				 */
				dns_name_copy(found, name, NULL);
				delegation = true;
				goto next_addnode;
			}
		}

		/*
		 * Check to see if this is a bottom of zone node.
		 */
		result = dns_db_allrdatasets(db, node, version, 0, &iterator);
		if (result == ISC_R_NOTFOUND) {
			/* Empty node? */
			goto next_addnode;
		}
		if (result != ISC_R_SUCCESS) {
			goto failure;
		}

		seen_soa = seen_ns = seen_dname = seen_ds = seen_nsec = false;
		for (result = dns_rdatasetiter_first(iterator);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(iterator))
		{
			dns_rdatasetiter_current(iterator, &rdataset);
			INSIST(rdataset.type != dns_rdatatype_nsec3);
			if (rdataset.type == dns_rdatatype_soa) {
				seen_soa = true;
			} else if (rdataset.type == dns_rdatatype_ns) {
				seen_ns = true;
			} else if (rdataset.type == dns_rdatatype_dname) {
				seen_dname = true;
			} else if (rdataset.type == dns_rdatatype_ds) {
				seen_ds = true;
			} else if (rdataset.type == dns_rdatatype_nsec) {
				seen_nsec = true;
			}
			dns_rdataset_disassociate(&rdataset);
		}
		dns_rdatasetiter_destroy(&iterator);
		/*
		 * Is there a NSEC chain than needs to be cleaned up?
		 */
		if (seen_nsec) {
			nsec3chain->seen_nsec = true;
		}
		if (seen_ns && !seen_soa && !seen_ds) {
			unsecure = true;
		}
		if ((seen_ns && !seen_soa) || seen_dname) {
			delegation = true;
		}

		/*
		 * Process one node.
		 */
		dns_dbiterator_pause(nsec3chain->dbiterator);
		result = dns_nsec3_addnsec3(db, version, name,
					    &nsec3chain->nsec3param,
					    zone->minimum, unsecure,
					    &nsec3_diff);
		if (result != ISC_R_SUCCESS) {
			dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:"
				   "dns_nsec3_addnsec3 -> %s",
				   dns_result_totext(result));
			goto failure;
		}

		/*
		 * Treat each call to dns_nsec3_addnsec3() as if it's cost is
		 * two signatures.  Additionally there will, in general, be
		 * two signature generated below.
		 *
		 * If we are only changing the optout flag the cost is half
		 * that of the cost of generating a completely new chain.
		 */
		signatures -= 4;

		/*
		 * Go onto next node.
		 */
 next_addnode:
		first = false;
		dns_db_detachnode(db, &node);
		do {
			result = dns_dbiterator_next(nsec3chain->dbiterator);

			if (result == ISC_R_NOMORE && nsec3chain->delete_nsec) {
				dns_dbiterator_pause(nsec3chain->dbiterator);
				CHECK(fixup_nsec3param(db, version, nsec3chain,
						       false, privatetype,
						       &param_diff));
				LOCK_ZONE(zone);
				ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
						link);
				UNLOCK_ZONE(zone);
				ISC_LIST_APPEND(cleanup, nsec3chain, link);
				goto next_addchain;
			}
			if (result == ISC_R_NOMORE) {
				dns_dbiterator_pause(nsec3chain->dbiterator);
				if (nsec3chain->seen_nsec) {
					CHECK(fixup_nsec3param(db, version,
							       nsec3chain,
							       true,
							       privatetype,
							       &param_diff));
					nsec3chain->delete_nsec = true;
					goto same_addchain;
				}
				CHECK(fixup_nsec3param(db, version, nsec3chain,
						       false, privatetype,
						       &param_diff));
				LOCK_ZONE(zone);
				ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
						link);
				UNLOCK_ZONE(zone);
				ISC_LIST_APPEND(cleanup, nsec3chain, link);
				goto next_addchain;
			} else if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:"
					   "dns_dbiterator_next -> %s",
					   dns_result_totext(result));
				goto failure;
			} else if (delegation) {
				dns_dbiterator_current(nsec3chain->dbiterator,
						       &node, nextname);
				dns_db_detachnode(db, &node);
				if (!dns_name_issubdomain(nextname, name))
					break;
			} else {
				break;
			}
		} while (1);
		continue;

 same_addchain:
		CHECK(dns_dbiterator_first(nsec3chain->dbiterator));
		first = true;
		continue;

 next_addchain:
		dns_dbiterator_pause(nsec3chain->dbiterator);
		nsec3chain = nextnsec3chain;
		first = true;
		if (nsec3chain != NULL) {
			nsec3chain->save_delete_nsec = nsec3chain->delete_nsec;
		}
	}

	if (nsec3chain != NULL) {
		goto skip_removals;
	}

	/*
	 * Process removals.
	 *
	 * This is a counterpart of the above while loop which takes care of
	 * removing an NSEC3 chain.  It starts with determining whether the
	 * zone needs to switch from NSEC3 to NSEC; if so, it first builds an
	 * NSEC chain by iterating over all nodes in the zone database and only
	 * then goes on to remove NSEC3 records be iterating over all nodes
	 * again and calling deletematchingnsec3() for each of them; otherwise,
	 * it starts removing NSEC3 records immediately.  Rules for processing
	 * obscured nodes and interrupting work are the same as for the while
	 * loop above.
	 */
	LOCK_ZONE(zone);
	nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
	UNLOCK_ZONE(zone);
	first = true;
	buildnsecchain = false;
	while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) {
		LOCK_ZONE(zone);
		nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link);
		UNLOCK_ZONE(zone);

		if (nsec3chain->db != db) {
			goto next_removechain;
		}

		if (!NSEC3REMOVE(nsec3chain->nsec3param.flags)) {
			goto next_removechain;
		}

		/*
		 * Work out if we need to build a NSEC chain as a consequence
		 * of removing this NSEC3 chain.
		 */
		if (first && !updatensec &&
		    (nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_NONSEC) == 0)
		{
			result = need_nsec_chain(db, version,
						 &nsec3chain->nsec3param,
						 &buildnsecchain);
			if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:"
					   "need_nsec_chain -> %s",
					   dns_result_totext(result));
				goto failure;
			}
		}

		if (first) {
			dnssec_log(zone, ISC_LOG_DEBUG(3),
				   "zone_nsec3chain:buildnsecchain = %u\n",
				   buildnsecchain);
		}

		dns_dbiterator_current(nsec3chain->dbiterator, &node, name);
		delegation = false;

		if (!buildnsecchain) {
			/*
			 * Delete the NSEC3PARAM record matching this chain.
			 */
			if (first) {
				result = fixup_nsec3param(db, version,
							  nsec3chain,
							  true, privatetype,
							  &param_diff);
				if (result != ISC_R_SUCCESS) {
					dnssec_log(zone, ISC_LOG_ERROR,
						   "zone_nsec3chain:"
						   "fixup_nsec3param -> %s",
						   dns_result_totext(result));
					goto failure;
				}
			}

			/*
			 * Delete the NSEC3 records.
			 */
			result = deletematchingnsec3(db, version, node, name,
						     &nsec3chain->nsec3param,
						     &nsec3_diff);
			if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:"
					   "deletematchingnsec3 -> %s",
					   dns_result_totext(result));
				goto failure;
			}
			goto next_removenode;
		}

		if (first) {
			dns_fixedname_t ffound;
			dns_name_t *found;
			found = dns_fixedname_initname(&ffound);
			result = dns_db_find(db, name, version,
					     dns_rdatatype_soa,
					     DNS_DBFIND_NOWILD, 0, NULL, found,
					     NULL, NULL);
			if ((result == DNS_R_DELEGATION ||
			     result == DNS_R_DNAME) &&
			    !dns_name_equal(name, found))
			{
				/*
				 * Remember the obscuring name so that
				 * we skip all obscured names.
				 */
				dns_name_copy(found, name, NULL);
				delegation = true;
				goto next_removenode;
			}
		}

		/*
		 * Check to see if this is a bottom of zone node.
		 */
		result = dns_db_allrdatasets(db, node, version, 0, &iterator);
		if (result == ISC_R_NOTFOUND) {
			/* Empty node? */
			goto next_removenode;
		}
		if (result != ISC_R_SUCCESS) {
			goto failure;
		}

		seen_soa = seen_ns = seen_dname = seen_nsec3 =
			   seen_nsec = seen_rr = false;
		for (result = dns_rdatasetiter_first(iterator);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(iterator))
		{
			dns_rdatasetiter_current(iterator, &rdataset);
			if (rdataset.type == dns_rdatatype_soa) {
				seen_soa = true;
			} else if (rdataset.type == dns_rdatatype_ns) {
				seen_ns = true;
			} else if (rdataset.type == dns_rdatatype_dname) {
				seen_dname = true;
			} else if (rdataset.type == dns_rdatatype_nsec) {
				seen_nsec = true;
			} else if (rdataset.type == dns_rdatatype_nsec3) {
				seen_nsec3 = true;
			} else if (rdataset.type != dns_rdatatype_rrsig) {
				seen_rr = true;
			}
			dns_rdataset_disassociate(&rdataset);
		}
		dns_rdatasetiter_destroy(&iterator);

		if (!seen_rr || seen_nsec3 || seen_nsec) {
			goto next_removenode;
		}
		if ((seen_ns && !seen_soa) || seen_dname) {
			delegation = true;
		}

		/*
		 * Add a NSEC record except at the origin.
		 */
		if (!dns_name_equal(name, dns_db_origin(db))) {
			dns_dbiterator_pause(nsec3chain->dbiterator);
			CHECK(add_nsec(db, version, name, node, zone->minimum,
				       delegation, &nsec_diff));
			signatures--;
		}

 next_removenode:
		first = false;
		dns_db_detachnode(db, &node);
		do {
			result = dns_dbiterator_next(nsec3chain->dbiterator);
			if (result == ISC_R_NOMORE && buildnsecchain) {
				/*
				 * The NSEC chain should now be built.
				 * We can now remove the NSEC3 chain.
				 */
				updatensec = true;
				goto same_removechain;
			}
			if (result == ISC_R_NOMORE) {
				LOCK_ZONE(zone);
				ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
						link);
				UNLOCK_ZONE(zone);
				ISC_LIST_APPEND(cleanup, nsec3chain, link);
				dns_dbiterator_pause(nsec3chain->dbiterator);
				result = fixup_nsec3param(db, version,
							  nsec3chain, false,
							  privatetype,
							  &param_diff);
				if (result != ISC_R_SUCCESS) {
					dnssec_log(zone, ISC_LOG_ERROR,
						   "zone_nsec3chain:"
						   "fixup_nsec3param -> %s",
						   dns_result_totext(result));
					goto failure;
				}
				goto next_removechain;
			} else if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:"
					   "dns_dbiterator_next -> %s",
					   dns_result_totext(result));
				goto failure;
			} else if (delegation) {
				dns_dbiterator_current(nsec3chain->dbiterator,
						       &node, nextname);
				dns_db_detachnode(db, &node);
				if (!dns_name_issubdomain(nextname, name)) {
					break;
				}
			} else {
				break;
			}
		} while (1);
		continue;

 same_removechain:
		CHECK(dns_dbiterator_first(nsec3chain->dbiterator));
		buildnsecchain = false;
		first = true;
		continue;

 next_removechain:
		dns_dbiterator_pause(nsec3chain->dbiterator);
		nsec3chain = nextnsec3chain;
		first = true;
	}

 skip_removals:
	/*
	 * We may need to update the NSEC/NSEC3 records for the zone apex.
	 */
	if (!ISC_LIST_EMPTY(param_diff.tuples)) {
		bool rebuild_nsec = false,
			      rebuild_nsec3 = false;
		result = dns_db_getoriginnode(db, &node);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);
		result = dns_db_allrdatasets(db, node, version, 0, &iterator);
		if (result != ISC_R_SUCCESS) {
			dnssec_log(zone, ISC_LOG_ERROR,
				   "zone_nsec3chain:dns_db_allrdatasets -> %s",
				   dns_result_totext(result));
			goto failure;
		}
		for (result = dns_rdatasetiter_first(iterator);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(iterator))
		{
			dns_rdatasetiter_current(iterator, &rdataset);
			if (rdataset.type == dns_rdatatype_nsec) {
				rebuild_nsec = true;
			} else if (rdataset.type == dns_rdatatype_nsec3param) {
				rebuild_nsec3 = true;
			}
			dns_rdataset_disassociate(&rdataset);
		}
		dns_rdatasetiter_destroy(&iterator);
		dns_db_detachnode(db, &node);

		if (rebuild_nsec) {
			if (nsec3chain != NULL) {
				dns_dbiterator_pause(nsec3chain->dbiterator);
			}

			result = updatesecure(db, version, &zone->origin,
					      zone->minimum, true,
					      &nsec_diff);
			if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:updatesecure -> %s",
					   dns_result_totext(result));
				goto failure;
			}
		}

		if (rebuild_nsec3) {
			if (nsec3chain != NULL) {
				dns_dbiterator_pause(nsec3chain->dbiterator);
			}

			result = dns_nsec3_addnsec3s(db, version,
						     dns_db_origin(db),
						     zone->minimum, false,
						     &nsec3_diff);
			if (result != ISC_R_SUCCESS) {
				dnssec_log(zone, ISC_LOG_ERROR,
					   "zone_nsec3chain:"
					   "dns_nsec3_addnsec3s -> %s",
					   dns_result_totext(result));
				goto failure;
			}
		}
	}

	/*
	 * Add / update signatures for the NSEC3 records.
	 */
	if (nsec3chain != NULL) {
		dns_dbiterator_pause(nsec3chain->dbiterator);
	}
	result = dns__zone_updatesigs(&nsec3_diff, db, version, zone_keys,
				      nkeys, zone, inception, expire, 0, now,
				      check_ksk, keyset_kskonly, &zonediff);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:dns__zone_updatesigs -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	/*
	 * We have changed the NSEC3PARAM or private RRsets
	 * above so we need to update the signatures.
	 */
	result = dns__zone_updatesigs(&param_diff, db, version, zone_keys,
				      nkeys, zone, inception, expire, 0, now,
				      check_ksk, keyset_kskonly, &zonediff);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:dns__zone_updatesigs -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	if (updatensec) {
		result = updatesecure(db, version, &zone->origin,
				      zone->minimum, false, &nsec_diff);
		if (result != ISC_R_SUCCESS) {
			dnssec_log(zone, ISC_LOG_ERROR,
				   "zone_nsec3chain:updatesecure -> %s",
				   dns_result_totext(result));
			goto failure;
		}
	}

	result = dns__zone_updatesigs(&nsec_diff, db, version, zone_keys,
				      nkeys, zone, inception, expire, 0, now,
				      check_ksk, keyset_kskonly, &zonediff);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:dns__zone_updatesigs -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	/*
	 * If we made no effective changes to the zone then we can just
	 * cleanup otherwise we need to increment the serial.
	 */
	if (ISC_LIST_EMPTY(zonediff.diff->tuples)) {
		/*
		 * No need to call dns_db_closeversion() here as it is
		 * called with commit = true below.
		 */
		goto done;
	}

	result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa,
			  &zonediff, zone_keys, nkeys, now, false);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:del_sigs -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	result = update_soa_serial(db, version, zonediff.diff, zone->mctx,
				   zone->updatemethod);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:update_soa_serial -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	result = add_sigs(db, version, &zone->origin, dns_rdatatype_soa,
			  zonediff.diff, zone_keys, nkeys, zone->mctx,
			  inception, soaexpire, check_ksk, keyset_kskonly);
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR,
			   "zone_nsec3chain:add_sigs -> %s",
			   dns_result_totext(result));
		goto failure;
	}

	/* Write changes to journal file. */
	CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_nsec3chain"));

	LOCK_ZONE(zone);
	zone_needdump(zone, DNS_DUMP_DELAY);
	DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
	UNLOCK_ZONE(zone);

 done:
	/*
	 * Pause all iterators so that dns_db_closeversion() can succeed.
	 */
	LOCK_ZONE(zone);
	for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
	     nsec3chain != NULL;
	     nsec3chain = ISC_LIST_NEXT(nsec3chain, link))
	{
		dns_dbiterator_pause(nsec3chain->dbiterator);
	}
	UNLOCK_ZONE(zone);

	/*
	 * Everything has succeeded. Commit the changes.
	 * Unconditionally commit as zonediff.offline not checked above.
	 */
	dns_db_closeversion(db, &version, true);

	/*
	 * Everything succeeded so we can clean these up now.
	 */
	nsec3chain = ISC_LIST_HEAD(cleanup);
	while (nsec3chain != NULL) {
		ISC_LIST_UNLINK(cleanup, nsec3chain, link);
		dns_db_detach(&nsec3chain->db);
		dns_dbiterator_destroy(&nsec3chain->dbiterator);
		isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
		nsec3chain = ISC_LIST_HEAD(cleanup);
	}

	set_resigntime(zone);

 failure:
	if (result != ISC_R_SUCCESS) {
		dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain: %s",
			   dns_result_totext(result));
	}

	/*
	 * On error roll back the current nsec3chain.
	 */
	if (result != ISC_R_SUCCESS && nsec3chain != NULL) {
		if (nsec3chain->done) {
			dns_db_detach(&nsec3chain->db);
			dns_dbiterator_destroy(&nsec3chain->dbiterator);
			isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
		} else {
			result = dns_dbiterator_first(nsec3chain->dbiterator);
			RUNTIME_CHECK(result == ISC_R_SUCCESS);
			dns_dbiterator_pause(nsec3chain->dbiterator);
			nsec3chain->delete_nsec = nsec3chain->save_delete_nsec;
		}
	}

	/*
	 * Rollback the cleanup list.
	 */
	nsec3chain = ISC_LIST_TAIL(cleanup);
	while (nsec3chain != NULL) {
		ISC_LIST_UNLINK(cleanup, nsec3chain, link);
		if (nsec3chain->done) {
			dns_db_detach(&nsec3chain->db);
			dns_dbiterator_destroy(&nsec3chain->dbiterator);
			isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain);
		} else {
			LOCK_ZONE(zone);
			ISC_LIST_PREPEND(zone->nsec3chain, nsec3chain, link);
			UNLOCK_ZONE(zone);
			result = dns_dbiterator_first(nsec3chain->dbiterator);
			RUNTIME_CHECK(result == ISC_R_SUCCESS);
			dns_dbiterator_pause(nsec3chain->dbiterator);
			nsec3chain->delete_nsec = nsec3chain->save_delete_nsec;
		}
		nsec3chain = ISC_LIST_TAIL(cleanup);
	}

	LOCK_ZONE(zone);
	for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain);
	     nsec3chain != NULL;
	     nsec3chain = ISC_LIST_NEXT(nsec3chain, link))
	{
		dns_dbiterator_pause(nsec3chain->dbiterator);
	}
	UNLOCK_ZONE(zone);

	dns_diff_clear(&param_diff);
	dns_diff_clear(&nsec3_diff);
	dns_diff_clear(&nsec_diff);
	dns_diff_clear(&_sig_diff);

	if (iterator != NULL) {
		dns_rdatasetiter_destroy(&iterator);
	}

	for (i = 0; i < nkeys; i++) {
		dst_key_free(&zone_keys[i]);
	}

	if (node != NULL) {
		dns_db_detachnode(db, &node);
	}
	if (version != NULL) {
		dns_db_closeversion(db, &version, false);
		dns_db_detach(&db);
	} else if (db != NULL) {
		dns_db_detach(&db);
	}

	LOCK_ZONE(zone);
	if (ISC_LIST_HEAD(zone->nsec3chain) != NULL) {
		isc_interval_t interval;
		if (zone->update_disabled || result != ISC_R_SUCCESS) {
			isc_interval_set(&interval, 60, 0);	  /* 1 minute */
		} else {
			isc_interval_set(&interval, 0, 10000000); /* 10 ms */
		}
		isc_time_nowplusinterval(&zone->nsec3chaintime, &interval);
	} else {
		isc_time_settoepoch(&zone->nsec3chaintime);
	}
	UNLOCK_ZONE(zone);

	INSIST(version == NULL);
}