summaryrefslogtreecommitdiffstats
path: root/src/knot/updates/zone-update.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/updates/zone-update.c')
-rw-r--r--src/knot/updates/zone-update.c895
1 files changed, 895 insertions, 0 deletions
diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c
new file mode 100644
index 0000000..7905a33
--- /dev/null
+++ b/src/knot/updates/zone-update.c
@@ -0,0 +1,895 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "knot/common/log.h"
+#include "knot/dnssec/zone-events.h"
+#include "knot/updates/zone-update.h"
+#include "knot/zone/serial.h"
+#include "knot/zone/zone-diff.h"
+#include "contrib/mempattern.h"
+#include "contrib/trim.h"
+#include "contrib/ucw/lists.h"
+#include "contrib/ucw/mempool.h"
+
+#include <urcu.h>
+
+static int init_incremental(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents, bool deep_copy)
+{
+ if (old_contents == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = changeset_init(&update->change, zone->name);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (deep_copy) {
+ update->new_cont_deep_copy = true;
+ update->new_cont = old_contents;
+ } else {
+ update->new_cont_deep_copy = false;
+ ret = apply_prepare_zone_copy(old_contents, &update->new_cont);
+ if (ret != KNOT_EOK) {
+ changeset_clear(&update->change);
+ return ret;
+ }
+ }
+
+ uint32_t apply_flags = update->flags & UPDATE_STRICT ? APPLY_STRICT : 0;
+ apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
+
+ /* Copy base SOA RR. */
+ update->change.soa_from =
+ node_create_rrset(old_contents->apex, KNOT_RRTYPE_SOA);
+ if (update->change.soa_from == NULL) {
+ zone_contents_free(update->new_cont);
+ changeset_clear(&update->change);
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
+}
+
+static int init_full(zone_update_t *update, zone_t *zone)
+{
+ update->new_cont = zone_contents_new(zone->name);
+ if (update->new_cont == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ update->new_cont_deep_copy = true;
+
+ apply_init_ctx(update->a_ctx, update->new_cont, 0);
+
+ return KNOT_EOK;
+}
+
+static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
+{
+ /* SOA possible only within apex. */
+ if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
+ return KNOT_EDENIED;
+ }
+
+ knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+ zone_node_t *n = contents->apex;
+ int ret = zone_contents_remove_rr(contents, &old_soa, &n);
+ if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
+ return ret;
+ }
+
+ ret = zone_contents_add_rr(contents, rr, &n);
+ if (ret == KNOT_ETTL) {
+ return KNOT_EOK;
+ }
+
+ return ret;
+}
+
+int init_base(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents,
+ zone_update_flags_t flags)
+{
+ if (update == NULL || zone == NULL || (old_contents == NULL && (flags & UPDATE_INCREMENTAL))) {
+ return KNOT_EINVAL;
+ }
+
+ memset(update, 0, sizeof(*update));
+ update->zone = zone;
+
+ mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
+ update->flags = flags;
+
+ update->a_ctx = calloc(1, sizeof(*update->a_ctx));
+ if (update->a_ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = KNOT_EINVAL;
+ if (flags & UPDATE_INCREMENTAL) {
+ ret = init_incremental(update, zone, old_contents, flags & UPDATE_JOURNAL);
+ } else if (flags & UPDATE_FULL) {
+ ret = init_full(update, zone);
+ }
+ if (ret != KNOT_EOK) {
+ free(update->a_ctx);
+ }
+
+ return ret;
+}
+
+/* ------------------------------- API -------------------------------------- */
+
+int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
+{
+ return init_base(update, zone, zone->contents, flags);
+}
+
+int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
+ zone_contents_t *new_cont, zone_update_flags_t flags, bool ignore_dnssec)
+{
+ if (update == NULL || zone == NULL || new_cont == NULL ||
+ !(flags & UPDATE_INCREMENTAL) || (flags & UPDATE_FULL)) {
+ return KNOT_EINVAL;
+ }
+
+ changeset_t diff;
+ int ret = changeset_init(&diff, zone->name);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = zone_contents_diff(old_cont, new_cont, &diff, ignore_dnssec);
+ if (ret != KNOT_EOK && ret != KNOT_ENODIFF && ret != KNOT_ESEMCHECK) {
+ changeset_clear(&diff);
+ return ret;
+ }
+
+ // True if nonempty changes were made but the serial
+ // remained the same and has to be incremented.
+ bool diff_semcheck = (ret == KNOT_ESEMCHECK);
+
+ ret = init_base(update, zone, old_cont, flags);
+ if (ret != KNOT_EOK) {
+ changeset_clear(&diff);
+ return ret;
+ }
+
+ ret = zone_update_apply_changeset(update, &diff);
+ changeset_clear(&diff);
+ if (ret != KNOT_EOK) {
+ zone_update_clear(update);
+ return ret;
+ }
+
+ if (diff_semcheck) {
+ ret = zone_update_increment_soa(update, conf());
+ if (ret != KNOT_EOK) {
+ zone_update_clear(update);
+ return ret;
+ }
+ log_zone_info(zone->name, "automatic SOA serial increment");
+ }
+
+ return KNOT_EOK;
+}
+
+int zone_update_from_contents(zone_update_t *update, zone_t *zone_without_contents,
+ zone_contents_t *new_cont, zone_update_flags_t flags)
+{
+ if (update == NULL || zone_without_contents == NULL || new_cont == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(update, 0, sizeof(*update));
+ update->zone = zone_without_contents;
+
+ mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
+ update->flags = flags;
+
+ update->new_cont = new_cont;
+ update->new_cont_deep_copy = true;
+
+ update->a_ctx = calloc(1, sizeof(*update->a_ctx));
+ if (update->a_ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ if (flags & UPDATE_INCREMENTAL) {
+ int ret = changeset_init(&update->change, zone_without_contents->name);
+ if (ret != KNOT_EOK) {
+ free(update->a_ctx);
+ return ret;
+ }
+
+ update->change.soa_from = node_create_rrset(new_cont->apex, KNOT_RRTYPE_SOA);
+ if (update->change.soa_from == NULL) {
+ changeset_clear(&update->change);
+ free(update->a_ctx);
+ return KNOT_ENOMEM;
+ }
+ }
+
+ uint32_t apply_flags = update->flags & UPDATE_STRICT ? APPLY_STRICT : 0;
+ apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
+
+ return KNOT_EOK;
+}
+
+const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
+{
+ if (update == NULL || dname == NULL) {
+ return NULL;
+ }
+
+ return zone_contents_find_node(update->new_cont, dname);
+}
+
+const zone_node_t *zone_update_get_apex(zone_update_t *update)
+{
+ if (update == NULL) {
+ return NULL;
+ }
+
+ return zone_update_get_node(update, update->zone->name);
+}
+
+uint32_t zone_update_current_serial(zone_update_t *update)
+{
+ const zone_node_t *apex = zone_update_get_apex(update);
+ if (apex != NULL) {
+ return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA)->rdata);
+ } else {
+ return 0;
+ }
+}
+
+const knot_rdataset_t *zone_update_from(zone_update_t *update)
+{
+ if (update == NULL) {
+ return NULL;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ const zone_node_t *apex = update->zone->contents->apex;
+ return node_rdataset(apex, KNOT_RRTYPE_SOA);
+ }
+
+ return NULL;
+}
+
+const knot_rdataset_t *zone_update_to(zone_update_t *update)
+{
+ if (update == NULL) {
+ return NULL;
+ }
+
+ if (update->flags & UPDATE_FULL) {
+ const zone_node_t *apex = update->new_cont->apex;
+ return node_rdataset(apex, KNOT_RRTYPE_SOA);
+ } else if (update->flags & UPDATE_INCREMENTAL) {
+ if (update->change.soa_to == NULL) {
+ return NULL;
+ }
+ return &update->change.soa_to->rrs;
+ }
+
+ return NULL;
+}
+
+void zone_update_clear(zone_update_t *update)
+{
+ if (update == NULL) {
+ return;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ /* Revert any changes on error, do nothing on success. */
+ if (update->new_cont_deep_copy) {
+ update_cleanup(update->a_ctx);
+ zone_contents_deep_free(update->new_cont);
+ } else {
+ update_rollback(update->a_ctx);
+ update_free_zone(update->new_cont);
+ }
+ changeset_clear(&update->change);
+ } else if (update->flags & UPDATE_FULL) {
+ assert(update->new_cont_deep_copy);
+ zone_contents_deep_free(update->new_cont);
+ }
+ free(update->a_ctx);
+ mp_delete(update->mm.ctx);
+ memset(update, 0, sizeof(*update));
+}
+
+int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
+{
+ if (update == NULL || rrset == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ int ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (rrset->type == KNOT_RRTYPE_SOA) {
+ /* replace previous SOA */
+ ret = apply_replace_soa(update->a_ctx, &update->change);
+ if (ret != KNOT_EOK) {
+ changeset_remove_addition(&update->change, rrset);
+ }
+ return ret;
+ }
+
+ ret = apply_add_rr(update->a_ctx, rrset);
+ if (ret != KNOT_EOK) {
+ changeset_remove_addition(&update->change, rrset);
+ return ret;
+ }
+
+ return KNOT_EOK;
+ } else if (update->flags & UPDATE_FULL) {
+ if (rrset->type == KNOT_RRTYPE_SOA) {
+ /* replace previous SOA */
+ return replace_soa(update->new_cont, rrset);
+ }
+
+ zone_node_t *n = NULL;
+ int ret = zone_contents_add_rr(update->new_cont, rrset, &n);
+ if (ret == KNOT_ETTL) {
+ char buff[KNOT_DNAME_TXT_MAXLEN + 1];
+ char *owner = knot_dname_to_str(buff, rrset->owner, sizeof(buff));
+ if (owner == NULL) {
+ owner = "";
+ }
+ char type[16] = { '\0' };
+ knot_rrtype_to_string(rrset->type, type, sizeof(type));
+ log_zone_notice(update->new_cont->apex->owner,
+ "TTL mismatch, owner %s, type %s, "
+ "TTL set to %u", owner, type, rrset->ttl);
+ return KNOT_EOK;
+ }
+
+ return ret;
+ } else {
+ return KNOT_EINVAL;
+ }
+}
+
+int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
+{
+ if (update == NULL || rrset == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (rrset->type == KNOT_RRTYPE_SOA) {
+ /* SOA is replaced with addition */
+ return KNOT_EOK;
+ }
+
+ ret = apply_remove_rr(update->a_ctx, rrset);
+ if (ret != KNOT_EOK) {
+ changeset_remove_removal(&update->change, rrset);
+ return ret;
+ }
+
+ return KNOT_EOK;
+ } else if (update->flags & UPDATE_FULL) {
+ zone_node_t *n = NULL;
+ knot_rrset_t *rrs_copy = knot_rrset_copy(rrset, &update->mm);
+ int ret = zone_contents_remove_rr(update->new_cont, rrs_copy, &n);
+ knot_rrset_free(rrs_copy, &update->mm);
+ return ret;
+ } else {
+ return KNOT_EINVAL;
+ }
+}
+
+int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
+{
+ if (update == NULL || owner == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ /* Remove the RRSet from the original node */
+ const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
+ if (node != NULL) {
+ knot_rrset_t rrset = node_rrset(node, type);
+ if (rrset.owner == NULL) {
+ return KNOT_ENOENT;
+ }
+ int ret = changeset_add_removal(&update->change, &rrset,
+ CHANGESET_CHECK);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (type == KNOT_RRTYPE_SOA) {
+ /* SOA is replaced with addition */
+ return KNOT_EOK;
+ }
+
+ ret = apply_remove_rr(update->a_ctx, &rrset);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ } else {
+ return KNOT_ENONODE;
+ }
+ } else if (update->flags & UPDATE_FULL) {
+ /* Remove the RRSet from the non-synthesized new node */
+ const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
+ if (node == NULL) {
+ return KNOT_ENONODE;
+ }
+
+ knot_rrset_t rrset = node_rrset(node, type);
+ int ret = zone_update_remove(update, &rrset);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
+{
+ if (update == NULL || owner == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ /* Remove all RRSets from the new node */
+ const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
+ if (node != NULL) {
+ size_t rrset_count = node->rrset_count;
+ for (int i = 0; i < rrset_count; ++i) {
+ knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
+ int ret = changeset_add_removal(&update->change, &rrset,
+ CHANGESET_CHECK);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (rrset.type == KNOT_RRTYPE_SOA) {
+ /* SOA is replaced with addition */
+ continue;
+ }
+
+ ret = apply_remove_rr(update->a_ctx, &rrset);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+ } else {
+ return KNOT_ENONODE;
+ }
+ } else if (update->flags & UPDATE_FULL) {
+ /* Remove all RRSets from the non-synthesized new node */
+ const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
+ if (node == NULL) {
+ return KNOT_ENONODE;
+ }
+
+ size_t rrset_count = node->rrset_count;
+ for (int i = 0; i < rrset_count; ++i) {
+ knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
+ int ret = zone_update_remove(update, &rrset);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int zone_update_apply_changeset(zone_update_t *update, const changeset_t *changes)
+{
+ int ret = KNOT_EOK;
+ if (update->flags & UPDATE_INCREMENTAL) {
+ ret = changeset_merge(&update->change, changes, CHANGESET_CHECK_CANCELOUT);
+ }
+ if (ret == KNOT_EOK) {
+ ret = apply_changeset_directly(update->a_ctx, changes);
+ }
+ return ret;
+}
+
+int zone_update_apply_changeset_fix(zone_update_t *update, changeset_t *changes)
+{
+ int ret = changeset_cancelout(changes);
+ if (ret == KNOT_EOK) {
+ ret = changeset_preapply_fix(update->new_cont, changes);
+ }
+ if (ret == KNOT_EOK) {
+ ret = zone_update_apply_changeset(update, changes);
+ }
+ return ret;
+}
+
+int zone_update_apply_changeset_reverse(zone_update_t *update, const changeset_t *changes)
+{
+ changeset_t reverse;
+ reverse.remove = changes->add;
+ reverse.add = changes->remove;
+ reverse.soa_from = changes->soa_to;
+ reverse.soa_to = changes->soa_from;
+ return zone_update_apply_changeset(update, &reverse);
+}
+
+static int set_new_soa(zone_update_t *update, unsigned serial_policy)
+{
+ assert(update);
+
+ knot_rrset_t *soa_cpy = node_create_rrset(zone_update_get_apex(update),
+ KNOT_RRTYPE_SOA);
+ if (soa_cpy == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = zone_update_remove(update, soa_cpy);
+ if (ret != KNOT_EOK) {
+ knot_rrset_free(soa_cpy, NULL);
+ return ret;
+ }
+
+ uint32_t old_serial = knot_soa_serial(soa_cpy->rrs.rdata);
+ uint32_t new_serial = serial_next(old_serial, serial_policy);
+ if (serial_compare(old_serial, new_serial) != SERIAL_LOWER) {
+ log_zone_warning(update->zone->name, "updated SOA serial is lower "
+ "than current, serial %u -> %u",
+ old_serial, new_serial);
+ ret = KNOT_ESOAINVAL;
+ } else {
+ knot_soa_serial_set(soa_cpy->rrs.rdata, new_serial);
+
+ ret = zone_update_add(update, soa_cpy);
+ }
+ knot_rrset_free(soa_cpy, NULL);
+
+ return ret;
+}
+
+int zone_update_increment_soa(zone_update_t *update, conf_t *conf)
+{
+ if (update == NULL || conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
+ return set_new_soa(update, conf_opt(&val));
+}
+
+static int commit_incremental(conf_t *conf, zone_update_t *update,
+ zone_contents_t **contents_out)
+{
+ assert(update);
+ assert(contents_out);
+
+ if (changeset_empty(&update->change)) {
+ changeset_clear(&update->change);
+ if (update->zone->contents == NULL || update->new_cont_deep_copy) {
+ *contents_out = update->new_cont;
+ }
+ return KNOT_EOK;
+ }
+
+ zone_contents_t *new_contents = update->new_cont;
+ int ret = KNOT_EOK;
+ if (zone_update_to(update) == NULL) {
+ /* No SOA in the update, create one according to the current policy */
+ ret = zone_update_increment_soa(update, conf);
+ if (ret != KNOT_EOK) {
+ zone_update_clear(update);
+ return ret;
+ }
+ }
+
+ ret = apply_finalize(update->a_ctx);
+ if (ret != KNOT_EOK) {
+ zone_update_clear(update);
+ return ret;
+ }
+
+ /* Write changes to journal if all went well. */
+ conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
+ if (conf_opt(&val) != JOURNAL_CONTENT_NONE) {
+ ret = zone_change_store(conf, update->zone, &update->change);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ *contents_out = new_contents;
+
+ return KNOT_EOK;
+}
+
+static int commit_full(conf_t *conf, zone_update_t *update, zone_contents_t **contents_out)
+{
+ assert(update);
+ assert(contents_out);
+
+ /* Check if we have SOA. We might consider adding full semantic check here.
+ * But if we wanted full sem-check I'd consider being it controlled by a flag
+ * - to enable/disable it on demand. */
+ if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
+ return KNOT_ESEMCHECK;
+ }
+
+ int ret = zone_contents_adjust_full(update->new_cont);
+ if (ret != KNOT_EOK) {
+ zone_update_clear(update);
+ return ret;
+ }
+
+ /* Store new zone contents in journal. */
+ conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
+ unsigned content = conf_opt(&val);
+ if (content == JOURNAL_CONTENT_ALL) {
+ ret = zone_in_journal_store(conf, update->zone, update->new_cont);
+ } else if (content != JOURNAL_CONTENT_NONE) { // zone_in_journal_store does this automatically
+ ret = zone_changes_clear(conf, update->zone);
+ }
+
+ *contents_out = update->new_cont;
+
+ return ret;
+}
+
+/*! \brief Routine for calling call_rcu() easier way.
+ *
+ * Consider moving elsewhere, as it has no direct relation to zone-update.
+ */
+typedef struct {
+ struct rcu_head rcuhead;
+ void (*callback)(void *);
+ void *ctx;
+ bool free_ctx;
+} callrcu_wrapper_t;
+
+static void callrcu_wrapper_cb(struct rcu_head *param)
+{
+ callrcu_wrapper_t *wrap = (callrcu_wrapper_t *)param;
+ wrap->callback(wrap->ctx);
+ if (wrap->free_ctx) {
+ free(wrap->ctx);
+ }
+ free(wrap);
+
+ // Trim extra heap.
+ mem_trim();
+}
+
+/* NOTE: Does nothing if not enough memory. */
+static void callrcu_wrapper(void *ctx, void *callback, bool free_ctx)
+{
+ callrcu_wrapper_t *wrap = calloc(1, sizeof(callrcu_wrapper_t));
+ if (wrap != NULL) {
+ wrap->callback = callback;
+ wrap->ctx = ctx;
+ wrap->free_ctx = free_ctx;
+ call_rcu((struct rcu_head *)wrap, callrcu_wrapper_cb);
+ }
+}
+
+int zone_update_commit(conf_t *conf, zone_update_t *update)
+{
+ if (conf == NULL || update == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = KNOT_EOK;
+ zone_contents_t *new_contents = NULL;
+ if (update->flags & UPDATE_INCREMENTAL) {
+ ret = commit_incremental(conf, update, &new_contents);
+ } else {
+ ret = commit_full(conf, update, &new_contents);
+ }
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* If there is anything to change. */
+ if (new_contents == NULL) {
+ return KNOT_EOK;
+ }
+
+ /* Check the zone size. */
+ conf_val_t val = conf_zone_get(conf, C_MAX_ZONE_SIZE, update->zone->name);
+ size_t size_limit = conf_int(&val);
+
+ if (new_contents->size > size_limit) {
+ /* Recoverable error. */
+ return KNOT_EZONESIZE;
+ }
+
+ /* Check if the zone was re-signed upon zone load to ensure proper flush
+ * even if the SOA serial wasn't incremented by re-signing. */
+ val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
+ bool dnssec = conf_bool(&val);
+ if (!changeset_empty(&update->change) && dnssec) {
+ update->zone->zonefile.resigned = true;
+ }
+
+ /* Switch zone contents. */
+ zone_contents_t *old_contents;
+ old_contents = zone_switch_contents(update->zone, new_contents);
+
+ /* Sync RCU. */
+ if (update->flags & UPDATE_FULL) {
+ assert(update->new_cont_deep_copy);
+ callrcu_wrapper(old_contents, zone_contents_deep_free, false);
+ } else if (update->flags & UPDATE_INCREMENTAL) {
+ if (update->new_cont_deep_copy) {
+ callrcu_wrapper(old_contents, zone_contents_deep_free, false);
+ } else {
+ callrcu_wrapper(old_contents, update_free_zone, false);
+ }
+ changeset_clear(&update->change);
+ }
+ callrcu_wrapper(update->a_ctx, update_cleanup, true);
+ update->a_ctx = NULL;
+ update->new_cont = NULL;
+
+ /* Sync zonefile immediately if configured. */
+ val = conf_zone_get(conf, C_ZONEFILE_SYNC, update->zone->name);
+ if (conf_int(&val) == 0) {
+ zone_events_schedule_now(update->zone, ZONE_EVENT_FLUSH);
+ }
+
+ return KNOT_EOK;
+}
+
+static int iter_init_tree_iters(zone_update_iter_t *it, zone_update_t *update,
+ bool nsec3)
+{
+ /* Set zone iterator. */
+ zone_contents_t *_contents = update->new_cont;
+
+ /* Begin iteration. We can safely assume _contents is a valid pointer. */
+ zone_tree_t *tree = nsec3 ? _contents->nsec3_nodes : _contents->nodes;
+ it->tree_it = trie_it_begin(tree);
+ if (it->tree_it == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
+
+ return KNOT_EOK;
+}
+
+static int iter_get_next_node(zone_update_iter_t *it)
+{
+ trie_it_next(it->tree_it);
+ if (trie_it_finished(it->tree_it)) {
+ trie_it_free(it->tree_it);
+ it->tree_it = NULL;
+ it->cur_node = NULL;
+ return KNOT_ENOENT;
+ }
+
+ it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
+
+ return KNOT_EOK;
+}
+
+static int iter_init(zone_update_iter_t *it, zone_update_t *update, const bool nsec3)
+{
+ memset(it, 0, sizeof(*it));
+
+ it->update = update;
+ it->nsec3 = nsec3;
+ int ret = iter_init_tree_iters(it, update, nsec3);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ it->cur_node = (zone_node_t *)(*trie_it_val(it->tree_it));
+
+ return KNOT_EOK;
+}
+
+int zone_update_iter(zone_update_iter_t *it, zone_update_t *update)
+{
+ if (it == NULL || update == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return iter_init(it, update, false);
+}
+
+int zone_update_iter_nsec3(zone_update_iter_t *it, zone_update_t *update)
+{
+ if (it == NULL || update == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (update->flags & UPDATE_FULL) {
+ if (update->new_cont->nsec3_nodes == NULL) {
+ /* No NSEC3 tree. */
+ return KNOT_ENOENT;
+ }
+ } else {
+ if (update->change.add->nsec3_nodes == NULL &&
+ update->change.remove->nsec3_nodes == NULL) {
+ /* No NSEC3 changes. */
+ return KNOT_ENOENT;
+ }
+ }
+
+ return iter_init(it, update, true);
+}
+
+int zone_update_iter_next(zone_update_iter_t *it)
+{
+ if (it == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (it->tree_it != NULL) {
+ int ret = iter_get_next_node(it);
+ if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+const zone_node_t *zone_update_iter_val(zone_update_iter_t *it)
+{
+ if (it != NULL) {
+ return it->cur_node;
+ } else {
+ return NULL;
+ }
+}
+
+void zone_update_iter_finish(zone_update_iter_t *it)
+{
+ if (it == NULL) {
+ return;
+ }
+
+ trie_it_free(it->tree_it);
+}
+
+bool zone_update_no_change(zone_update_t *update)
+{
+ if (update == NULL) {
+ return true;
+ }
+
+ if (update->flags & UPDATE_INCREMENTAL) {
+ return changeset_empty(&update->change);
+ } else {
+ /* This branch does not make much sense and FULL update will most likely
+ * be a change every time anyway, just return false. */
+ return false;
+ }
+}