From 1f393e767a5abfadd708ee5865fb72177d448374 Mon Sep 17 00:00:00 2001
From: Jan Kadlec <jan.kadlec@nic.cz>
Date: Mon, 15 Jul 2013 13:09:29 +0200
Subject: [PATCH] Another partial changes.

---
 src/libknot/updates/changesets.c |  59 ++++++
 src/libknot/updates/changesets.h |  22 ++-
 src/libknot/updates/xfr-in.c     | 314 ++++++++++++-------------------
 src/libknot/updates/xfr-in.h     |   2 +-
 src/libknot/zone/zone-diff.c     |   2 -
 5 files changed, 196 insertions(+), 203 deletions(-)

diff --git a/src/libknot/updates/changesets.c b/src/libknot/updates/changesets.c
index 927e520dbc..183eaae711 100644
--- a/src/libknot/updates/changesets.c
+++ b/src/libknot/updates/changesets.c
@@ -155,6 +155,57 @@ int knot_changeset_add_rr(knot_changeset_t *chgs, knot_rrset_t *rr,
 	}
 }
 
+int knot_changes_add_rrset(knot_changes_t *ch, knot_rrset_t *rrset,
+                           knot_changes_part_t part)
+{
+	if (ch == NULL || rrset == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	knot_rr_node_t *rr_node =
+		ch->mem_ctx.alloc(ch->mem_ctx.ctx, sizeof(knot_rr_node_t));
+	if (rr_node == NULL) {
+		// This will not happen with mp_alloc, but allocator can change
+		ERR_ALLOC_FAILED;
+		return KNOT_ENOMEM;
+	}
+	rr_node->rr = rrset;
+
+	if (part == KNOT_CHANGES_NEW) {
+		add_tail(&ch->new_rrsets, (node *)rr_node);
+	} else {
+		assert(part == KNOT_CHANGES_OLD);
+		add_tail(&ch->old_rrsets, (node *)rr_node);
+	}
+
+	return KNOT_EOK;
+}
+
+int knot_changes_add_node(knot_changes_t *ch, knot_node_t *kn_node,
+                          knot_changes_part_t part)
+{
+	if (ch == NULL || kn_node == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Using the same allocator for node and rr's, sizes are equal.
+	knot_node_list_t *list_node =
+		ch->mem_ctx.alloc(ch->mem_ctx.ctx, sizeof(knot_node_list_t));
+	if (list_node == NULL) {
+		// This will not happen with mp_alloc, but allocator can change
+		ERR_ALLOC_FAILED;
+		return KNOT_ENOMEM;
+	}
+	list_node->node = kn_node;
+
+	if (part == KNOT_CHANGES_NORMAL_NODE) {
+		add_tail(&ch->old_nodes, (node *)list_node);
+	} else {
+		assert(part == KNOT_CHANGES_NSEC3_NODE);
+		add_tail(&ch->old_nsec3, (node *)list_node);
+	}
+}
+
 /*----------------------------------------------------------------------------*/
 
 void knot_changeset_store_soa(knot_rrset_t **chg_soa,
@@ -226,6 +277,14 @@ static void knot_free_changeset(knot_changeset_t *changeset)
 	free(changeset->data);
 }
 
+void knot_changes_free(knot_changes_t **changes)
+{
+	// Destroy mempool's data
+	mp_delete((struct mempool *)((*changes)->mem_ctx.ctx));
+	free(*changes);
+	*changes = NULL;
+}
+
 /*----------------------------------------------------------------------------*/
 
 void knot_free_changesets(knot_changesets_t **changesets)
diff --git a/src/libknot/updates/changesets.h b/src/libknot/updates/changesets.h
index da4f87cd2d..6e92e4e766 100644
--- a/src/libknot/updates/changesets.h
+++ b/src/libknot/updates/changesets.h
@@ -61,12 +61,18 @@ typedef struct knot_changeset {
 
 /*----------------------------------------------------------------------------*/
 
-/*! \brief Wrapper for oh-so-great BIRD lists. */
+/*! \brief Wrapper for oh-so-great BIRD lists. Storing: RRSet. */
 typedef struct knot_rr_node {
 	node n; /*!< List node. */
 	knot_rrset_t *rr; /*!< Actual usable data. */
 } knot_rr_node_t;
 
+/*! \brief Wrapper for oh-so-great BIRD lists. Storing: Node. */
+typedef struct knot_node_list {
+	node n; /*!< List node. */
+	knot_node_t *node; /*!< Actual usable data. */
+} knot_node_list_t;
+
 /*! \brief Partial changes done to zones - used for update/transfer rollback. */
 typedef struct {
 	/*!
@@ -99,7 +105,7 @@ typedef struct {
 typedef struct {
 	mm_ctx_t mem_ctx; /*!< Memory context - pool allocator. */
 	list sets; /*!< List of changesets. */
-	size_t count; /*!< Changeset coung. */
+	size_t count; /*!< Changeset count. */
 	knot_rrset_t *first_soa; /*!< First received SOA. */
 	uint32_t flags; /*!< DDNS / IXFR flags. */
 	knot_changes_t *changes; /*!< Partial changes. */
@@ -114,7 +120,9 @@ typedef enum {
 
 typedef enum {
 	KNOT_CHANGES_OLD,
-	KNOT_CHANGES_NEW
+	KNOT_CHANGES_NEW,
+	KNOT_CHANGES_NORMAL_NODE,
+	KNOT_CHANGES_NSEC3_NODE
 } knot_changes_part_t;
 
 /*----------------------------------------------------------------------------*/
@@ -150,12 +158,14 @@ int knot_changeset_is_empty(const knot_changeset_t *changeset);
 
 void knot_free_changesets(knot_changesets_t **changesets);
 
-int knot_changes_add_rr(knot_changes_t *ch, knot_rrset_t *rrset,
-                        knot_changes_part_t part);
-
 int knot_changes_add_rrset(knot_changes_t *ch, knot_rrset_t *rrset,
                            knot_changes_part_t part);
 
+int knot_changes_add_node(knot_changes_t *ch, knot_node_t *kn_node,
+                          knot_changes_part_t part);
+
+void knot_changes_free(knot_changes_t **changes);
+
 #endif /* _KNOT_CHANGESETS_H_ */
 
 /*! @} */
diff --git a/src/libknot/updates/xfr-in.c b/src/libknot/updates/xfr-in.c
index 1d58bf34fb..f7001a12f9 100644
--- a/src/libknot/updates/xfr-in.c
+++ b/src/libknot/updates/xfr-in.c
@@ -2159,65 +2159,25 @@ dbg_xfrin_exec_detail(
 
 /*----------------------------------------------------------------------------*/
 
-void xfrin_cleanup_successful_update(knot_changes_t **changes)
+void xfrin_cleanup_successful_update(knot_changes_t *changes)
 {
-	for (int i = 0; i < (*changes)->old_rrsets_count; ++i) {
-		//TODO temporary fix!
-		if ((*changes)->old_rrsets[i] == NULL) {
-			log_server_warning("NULL RRSet to be freed in DDNS!\n");
-			continue;
-		}
-		if ((*changes)->old_rrsets[i]->rdata_count == 0) {
-dbg_xfrin_exec_detail(
-			char *name = knot_dname_to_str((*changes)->old_rrsets[i]->owner);
-			dbg_xfrin_detail("Deleting old RRSet: %s type %u\n",
-					 name, (*changes)->old_rrsets[i]->type);
-			free(name);
-);
-			knot_rrset_free(&(*changes)->old_rrsets[i]);
-		}
-	}
-
-	// delete old RDATA
-	for (int i = 0; i < (*changes)->old_rdata_count; ++i) {
-		// RDATA are stored separately so do not delete the whole chain
-		knot_rrset_deep_free_no_sig(&(*changes)->old_rdata[i], 1, 1);
+	// Free old RRSets
+	knot_rr_node_t *rr_node = NULL;
+	WALK_LIST(rr_node, changes->old_rrsets) {
+		knot_rrset_t *rrset = rr_node->rr;
+		knot_rrset_deep_free_no_sig(&rrset, 1, 1);
 	}
 
-	// free the empty nodes
-	for (int i = 0; i < (*changes)->old_nodes_count; ++i) {
-dbg_xfrin_exec_detail(
-		char *name = knot_dname_to_str(
-				   knot_node_owner((*changes)->old_nodes[i]));
-		dbg_xfrin_detail("Deleting old empty node: %p, owner: %s\n",
-				 (*changes)->old_nodes[i], name);
-		free(name);
-);
-		knot_node_free(&(*changes)->old_nodes[i]);
+	// Free old nodes
+	knot_node_list_t *n_node = NULL;
+	WALK_LIST(n_node, changes->old_nodes) {
+		knot_node_free(&n_node->node);
 	}
 
-	// free empty NSEC3 nodes
-	for (int i = 0; i < (*changes)->old_nsec3_count; ++i) {
-dbg_xfrin_exec_detail(
-		char *name = knot_dname_to_str(
-				   knot_node_owner((*changes)->old_nsec3[i]));
-		dbg_xfrin_detail("Deleting old empty node: %p, owner: %s\n",
-				 (*changes)->old_nsec3[i], name);
-		free(name);
-);
-		knot_node_free(&(*changes)->old_nsec3[i]);
+	// Free old NSEC3 nodes
+	WALK_LIST(n_node, changes->old_nsec3) {
+		knot_node_free(&n_node->node);
 	}
-
-	// free allocated arrays of nodes and rrsets
-	free((*changes)->new_rrsets);
-	free((*changes)->new_rdata);
-	free((*changes)->old_nodes);
-	free((*changes)->old_nsec3);
-	free((*changes)->old_rrsets);
-	free((*changes)->old_rdata);
-
-	free((*changes));
-	*changes = NULL;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -2339,29 +2299,32 @@ static int xfrin_apply_remove(knot_zone_contents_t *contents,
 	 * RDATA may be removed from it.
 	 */
 	int ret = 0;
-	knot_node_t *node = NULL;
-	knot_rrset_t *rrset = NULL, *rrsigs = NULL;
-
+	knot_node_t *last_node = NULL;
+	knot_rrset_t *rrset = NULL;
+	knot_rrset_t *rrsigs = NULL;
 	int is_nsec3 = 0;
 
-	for (int i = 0; i < chset->remove_count; ++i) {
+	knot_rr_node_t *rr_node = NULL;
+	WALK_LIST(rr_node, chset->remove) {
+		knot_rrset_t *rr = rr_node->rr;
+		assert(rr); // No malformed changesets should get here
 dbg_xfrin_exec_verb(
 		char *name = knot_dname_to_str(
-			knot_rrset_owner(chset->remove[i]));
+			knot_rrset_owner(rr));
 		dbg_xfrin_verb("Removing RRSet: %s, type %u\n", name,
-			       knot_rrset_type(chset->remove[i]));
+			       knot_rrset_type(rr));
 		free(name);
 );
 dbg_xfrin_exec_detail(
-		knot_rrset_dump(chset->remove[i]);
+		knot_rrset_dump(rr);
 );
 
 		is_nsec3 = 0;
 
 		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(chset->remove[i]) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(chset->remove[i]) == KNOT_RRTYPE_RRSIG
-			&& knot_rrset_rdata_rrsig_type_covered(chset->remove[i])
+		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
+		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
+			&& knot_rrset_rdata_rrsig_type_covered(rr)
 			    == KNOT_RRTYPE_NSEC3))
 		{
 			dbg_xfrin_verb("Removed RRSet belongs to NSEC3 tree.\n");
@@ -2370,45 +2333,44 @@ dbg_xfrin_exec_detail(
 
 		// check if the old node is not the one we should use
 		dbg_xfrin_verb("Node:%p Owner: %p Node owner: %p\n",
-			       node, knot_rrset_owner(chset->remove[i]),
-			       knot_node_owner(node));
-		if (!node || knot_rrset_owner(chset->remove[i])
-			     != knot_node_owner(node)) {
+			       last_node, knot_rrset_owner(rr),
+			       knot_node_owner(last_node));
+		if (!last_node || knot_rrset_owner(rr)
+			     != knot_node_owner(last_node)) {
 			if (is_nsec3) {
-				node = knot_zone_contents_get_nsec3_node(
+				last_node = knot_zone_contents_get_nsec3_node(
 					    contents,
-					    knot_rrset_owner(chset->remove[i]));
+					    knot_rrset_owner(rr));
 			} else {
-				node = knot_zone_contents_get_node(contents,
-					    knot_rrset_owner(chset->remove[i]));
+				last_node = knot_zone_contents_get_node(contents,
+					    knot_rrset_owner(rr));
 			}
-			if (node == NULL) {
+			if (last_node == NULL) {
 				dbg_xfrin_verb("Node not found for RR to be "
 					       "removed!\n");
 				continue;
 			}
 		}
 
-		assert(node != NULL);
+		assert(last_node != NULL);
 
 		// first check if all RRSets should be removed
 		dbg_xfrin_verb("RRSet class to be removed=%u\n",
-			       knot_rrset_class(chset->remove[i]));
-		if (knot_rrset_class(chset->remove[i]) == KNOT_CLASS_ANY) {
+			       knot_rrset_class(rr));
+		if (knot_rrset_class(rr) == KNOT_CLASS_ANY) {
 			ret = xfrin_apply_remove_all_rrsets(
-				changes, node,
-				knot_rrset_type(chset->remove[i]), chset->flags);
-		} else if (knot_rrset_type(chset->remove[i])
+				changes, last_node,
+				knot_rrset_type(rr), chset->flags);
+		} else if (knot_rrset_type(rr)
 			   == KNOT_RRTYPE_RRSIG) {
 			// this should work also for UPDATE
-			ret = xfrin_apply_remove_rrsigs(changes,
-							chset->remove[i],
-							node, &rrset, &rrsigs);
+			ret = xfrin_apply_remove_rrsigs(changes, rr,
+							last_node, &rrset, &rrsigs);
 		} else {
 			// this should work also for UPDATE
 			ret = xfrin_apply_remove_normal(changes,
-							chset->remove[i],
-							node, &rrset,
+							rr,
+							last_node, &rrset,
 							chset->flags);
 		}
 
@@ -2431,30 +2393,32 @@ static int xfrin_apply_add(knot_zone_contents_t *contents,
                            knot_changes_t *changes)
 {
 	int ret = KNOT_EOK;
-	knot_node_t *node = NULL;
+	knot_node_t *last_node = NULL;
 	knot_rrset_t *rrset = NULL;
 	knot_rrset_t *rrsigs = NULL;
-
 	int is_nsec3 = 0;
 
-	for (int i = 0; i < chset->add_count; ++i) {
+	knot_rr_node_t *rr_node = NULL;
+	WALK_LIST(rr_node, chset->add) {
+		knot_rrset_t *rr = rr_node->rr;
+		assert(rr); // No malformed changesets should get here
 dbg_xfrin_exec_verb(
 		char *name = knot_dname_to_str(
-			knot_rrset_owner(chset->add[i]));
+			knot_rrset_owner(rr));
 		dbg_xfrin_verb("Adding RRSet: %s, type: %u\n", name,
-			       knot_rrset_type(chset->add[i]));
+			       knot_rrset_type(rr));
 		free(name);
 );
 dbg_xfrin_exec_detail(
-		knot_rrset_dump(chset->add[i]);
+		knot_rrset_dump(rr);
 );
 
 		is_nsec3 = 0;
 
 		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(chset->add[i]) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(chset->add[i]) == KNOT_RRTYPE_RRSIG
-			&& knot_rrset_rdata_rrsig_type_covered(chset->add[i])
+		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
+		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
+			&& knot_rrset_rdata_rrsig_type_covered(rr)
 			    == KNOT_RRTYPE_NSEC3))
 		{
 			dbg_xfrin_detail("This is NSEC3-related RRSet.\n");
@@ -2462,26 +2426,26 @@ dbg_xfrin_exec_detail(
 		}
 
 		// check if the old node is not the one we should use
-		if (!node || knot_rrset_owner(chset->add[i])
-			     != knot_node_owner(node)) {
+		if (!last_node || knot_rrset_owner(rr)
+			     != knot_node_owner(last_node)) {
 			dbg_xfrin_detail("Searching for node...\n");
 			if (is_nsec3) {
-				node = knot_zone_contents_get_nsec3_node(
-					       contents,
-					       knot_rrset_owner(chset->add[i]));
+				last_node = knot_zone_contents_get_nsec3_node(
+				            contents,
+				            knot_rrset_owner(rr));
 			} else {
-				node = knot_zone_contents_get_node(contents,
-					       knot_rrset_owner(chset->add[i]));
+				last_node = knot_zone_contents_get_node(contents,
+				            knot_rrset_owner(rr));
 			}
-			if (node == NULL) {
+			if (last_node == NULL) {
 				// create new node, connect it properly to the
 				// zone nodes
 				dbg_xfrin_detail("Node not found. Creating new."
 						 "\n");
-				node = xfrin_add_new_node(contents,
-							  chset->add[i],
+				last_node = xfrin_add_new_node(contents,
+							  rr,
 							  is_nsec3);
-				if (node == NULL) {
+				if (last_node == NULL) {
 					dbg_xfrin("Failed to create new node "
 						  "in zone.\n");
 					return KNOT_ERROR;
@@ -2489,16 +2453,16 @@ dbg_xfrin_exec_detail(
 			}
 		}
 
-		assert(node != NULL);
+		assert(last_node != NULL);
 
-		if (knot_rrset_type(chset->add[i]) == KNOT_RRTYPE_RRSIG) {
-			ret = xfrin_apply_add_rrsig(changes, chset->add[i],
-						    node, &rrset, &rrsigs,
+		if (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG) {
+			ret = xfrin_apply_add_rrsig(changes, rr, last_node,
+			                            &rrset, &rrsigs,
 						    contents);
 			assert(ret != KNOT_EOK);
 		} else {
-			ret = xfrin_apply_add_normal(changes, chset->add[i],
-						     node, &rrset, contents,
+			ret = xfrin_apply_add_normal(changes, rr,
+						     last_node, &rrset, contents,
 						     chset->flags);
 			assert(ret <= 3);
 		}
@@ -2515,35 +2479,19 @@ dbg_xfrin_exec_detail(
 				// the ADD RRSet was used, i.e. it should be
 				// removed from the changeset and saved in the
 				// list of new RRSets
-				ret = knot_changes_rrsets_reserve(
-					&changes->new_rrsets,
-					&changes->new_rrsets_count,
-					&changes->new_rrsets_allocated, 1);
+				ret = knot_changes_add_rrset(changes, rr,
+				                             KNOT_CHANGES_NEW);
 				if (ret != KNOT_EOK) {
-					dbg_xfrin("Failed to add old RRSet to "
-						  "list.\n");
 					return ret;
 				}
 
-				changes->new_rrsets[changes->new_rrsets_count++]
-					 = chset->add[i];
-
-				// the same goes for the RDATA
-				int count = 1;//knot_rrset_rdata_rr_count(chset->add[i]);
-
-				// connect the RDATA to the list of new RDATA
-
-				knot_changes_add_rdata(changes->new_rdata,
-					    &changes->new_rdata_count,
-					    chset->add[i]);
-
-				chset->add[i] = NULL;
+				rem_node((node *)rr_node);
 			} else if (ret == 2) {
 				// the copy of the RRSet was used, but it was
 				// already stored in the new RRSets list
 				// just delete the add RRSet, but without RDATA
 				// DNAMES as these were merged to the copied RRSet
-				knot_rrset_deep_free(&chset->add[i], 1, 0);
+				knot_rrset_deep_free(&rr, 1, 0);
 
 				// In this case, the RDATA does not have to be
 				// stored in the list of new RDATA, because
@@ -2553,7 +2501,7 @@ dbg_xfrin_exec_detail(
 				// the RRSet was used and both RRSet and RDATA
 				// were properly stored. Just clear the place
 				// in the changeset
-				chset->add[i] = NULL;
+				rem_node((node *)rr_node);
 			} else {
 				assert(0);
 			}
@@ -2630,7 +2578,8 @@ static int xfrin_apply_changeset(knot_zone_contents_t *contents,
 
 /*----------------------------------------------------------------------------*/
 
-static void xfrin_mark_empty(knot_node_t *node, void *data)
+static void xfrin_mark_empty_node(knot_node_t *node, void *data,
+                                  knot_changeset_part_t part)
 {
 	assert(node != NULL);
 	assert(data != NULL);
@@ -2639,16 +2588,13 @@ static void xfrin_mark_empty(knot_node_t *node, void *data)
 
 	if (knot_node_rrset_count(node) == 0
 	    && knot_node_children(node) == 0) {
-		int ret = knot_changes_nodes_reserve(&changes->old_nodes,
-						 &changes->old_nodes_count,
-						 &changes->old_nodes_allocated);
+		// Add node to changes.
+		int ret = knot_changes_add_node(changes, node, part);
 		if (ret != KNOT_EOK) {
-			/*! \todo Stop on error? */
 			return;
 		}
 
-		changes->old_nodes[changes->old_nodes_count++] = node;
-		// mark the node as empty
+		// Mark the node as empty.
 		knot_node_set_empty(node);
 
 		if (node->parent != NULL) {
@@ -2660,34 +2606,18 @@ static void xfrin_mark_empty(knot_node_t *node, void *data)
 			node->parent = NULL;
 		}
 	}
-	dbg_xfrin_detail("Space for nodes reserved, old node count = %d\n",
-			 changes->old_nodes_count);
 }
 
 /*----------------------------------------------------------------------------*/
 
-static void xfrin_mark_empty_nsec3(knot_node_t *node, void *data)
+static void xfrin_mark_empty(knot_node_t *node, void *data)
 {
-	assert(node != NULL);
-	assert(data != NULL);
-
-	knot_changes_t *changes = (knot_changes_t *)data;
-
-	if (knot_node_rrset_count(node) == 0
-	    && knot_node_children(node) == 0) {
-		changes->old_nsec3[changes->old_nsec3_count++] = node;
-		// mark the node as empty
-		knot_node_set_empty(node);
+	xfrin_mark_empty_node(node, data, KNOT_CHANGES_NORMAL_NODE);
+}
 
-		if (node->parent != NULL) {
-			assert(node->parent->children > 0);
-			--node->parent->children;
-			if (node->parent->wildcard_child == node) {
-				node->parent->wildcard_child = NULL;
-			}
-			node->parent = NULL;
-		}
-	}
+static void xfrin_mark_empty_nsec3(knot_node_t *node, void *data)
+{
+	xfrin_mark_empty_node(node, data, KNOT_CHANGES_NSEC3_NODE);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -2699,13 +2629,7 @@ static int xfrin_remove_empty_nodes(knot_zone_contents_t *contents,
 
 	dbg_xfrin("Removing empty nodes from zone.\n");
 
-	dbg_xfrin_verb("OLD NODES COUNT: %d\n", changes->old_nodes_count);
-	dbg_xfrin_verb("OLD NSEC3 NODES COUNT: %d\n", changes->old_nsec3_count);
-
 	// walk through the zone and select nodes to be removed
-	/* \note This function doesn't require order, but requires to be applied
-	 * on the leaves first and then on the their parent.
-	 */
 	ret = knot_zone_contents_tree_apply_inorder_reverse(contents,
 							    xfrin_mark_empty,
 							    (void *)changes);
@@ -2717,31 +2641,29 @@ static int xfrin_remove_empty_nodes(knot_zone_contents_t *contents,
 							 (void *)changes);
 	assert(ret == KNOT_EOK);
 
-	dbg_xfrin_verb("OLD NODES COUNT: %d\n", changes->old_nodes_count);
-	dbg_xfrin_verb("OLD NSEC3 NODES COUNT: %d\n", changes->old_nsec3_count);
-
-	// remove these nodes from both hash table and the tree
+	// Remove these nodes from zone tree.
 	knot_node_t *zone_node = NULL;
 
-	for (int i = 0; i < changes->old_nodes_count; ++i) {
+	knot_node_list_t *list_node = NULL;
+	WALK_LIST(list_node, changes->old_nodes) {
+		knot_node_t *node = list_node->node;
+		assert(node);
 		zone_node = NULL;
 
 dbg_xfrin_exec_detail(
-		char *name = knot_dname_to_str(knot_node_owner(
-						       changes->old_nodes[i]));
-		dbg_xfrin_detail("Old node #%d: %p, %s\n", i,
-				 changes->old_nodes[i], name);
+		char *name = knot_dname_to_str(knot_node_owner(node));
+		dbg_xfrin_detail("Old node: %p, %s\n",node, name);
 		free(name);
 );
 
 		ret = knot_zone_contents_remove_node(
-			contents, changes->old_nodes[i], &zone_node);
+			contents, node, &zone_node);
 
 		if (ret == KNOT_ENONODE) {
-			assert(knot_node_rrset_count(changes->old_nodes[i]) == 1);
-			assert(knot_node_rrset(changes->old_nodes[i],
+			assert(knot_node_rrset_count(node) == 1);
+			assert(knot_node_rrset(node,
 			                       KNOT_RRTYPE_RRSIG));
-			char *name = knot_dname_to_str(changes->old_nodes[i]->owner);
+			char *name = knot_dname_to_str(node->owner);
 			log_zone_warning("Ignoring extra RRSIG for %s!\n",
 			                 name);
 			free(name);
@@ -2749,27 +2671,27 @@ dbg_xfrin_exec_detail(
 			dbg_xfrin("Failed to remove node from zone!\n");
 			return ret;
 		}
-		assert(changes->old_nodes[i] == zone_node);
+		assert(node == zone_node);
 	}
 
 	// remove NSEC3 nodes
-	for (int i = 0; i < changes->old_nsec3_count; ++i) {
+	WALK_LIST(list_node, changes->old_nsec3) {
+		knot_node_t *node = list_node->node;
+		assert(node);
 		zone_node = NULL;
 
-		char *name = knot_dname_to_str(knot_node_owner(
-						       changes->old_nsec3[i]));
-		dbg_xfrin_detail("Old NSEC3 node #%d: %p, %s\n", i,
-				 changes->old_nsec3[i], name);
+		char *name = knot_dname_to_str(knot_node_owner(node));
+		dbg_xfrin_detail("Old NSEC3 node: %p, %s\n", node, name);
 		free(name);
 
 		ret = knot_zone_contents_remove_nsec3_node(
-			contents, changes->old_nsec3[i], &zone_node);
+			contents, node, &zone_node);
 
 		if (ret != KNOT_EOK) {
 			dbg_xfrin("Failed to remove NSEC3 node from zone!\n");
 			return KNOT_ENONODE;
 		}
-		assert(changes->old_nsec3[i] == zone_node);
+		assert(node == zone_node);
 	}
 
 	return KNOT_EOK;
@@ -2866,7 +2788,8 @@ int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
 				sizeof(knot_changes_t));
 	if (chgs == NULL) {
 		dbg_xfrin("Failed to allocate structure for changes!\n");
-		xfrin_rollback_update(old_contents, &contents_copy, &chgs);
+		xfrin_rollback_update(old_contents, &contents_copy, chgs);
+		knot_changes_free(&chgs);
 		return KNOT_ENOMEM;
 	}
 
@@ -2878,7 +2801,8 @@ int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
 	ret = xfrin_check_contents_copy(old_contents);
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Contents copy check failed!\n");
-		xfrin_rollback_update(old_contents, &contents_copy, &chgs);
+		xfrin_rollback_update(old_contents, &contents_copy, chgs);
+		knot_changes_free(&chgs);
 		return ret;
 	}
 
@@ -2975,12 +2899,13 @@ int xfrin_apply_changesets(knot_zone_t *zone,
 	dbg_xfrin("Applying changesets.\n");
 	dbg_xfrin_verb("Old contents apex: %p, new apex: %p\n",
 		       old_contents->apex, contents_copy->apex);
-	for (int i = 0; i < chsets->count; ++i) {
-		if ((ret = xfrin_apply_changeset(contents_copy, changes,
-						  &chsets->sets[i]))
-						  != KNOT_EOK) {
+	knot_changeset_t *set = NULL;
+	WALK_LIST(set, chsets->sets) {
+		ret = xfrin_apply_changeset(contents_copy, changes, set);
+		if (ret != KNOT_EOK) {
 			xfrin_rollback_update(old_contents,
-					       &contents_copy, &changes);
+					       &contents_copy, changes);
+			knot_changes_free(&changes);
 			dbg_xfrin("Failed to apply changesets to zone: "
 				  "%s\n", knot_strerror(ret));
 			return ret;
@@ -2997,7 +2922,8 @@ int xfrin_apply_changesets(knot_zone_t *zone,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 			  knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &contents_copy, &changes);
+		xfrin_rollback_update(old_contents, &contents_copy, changes);
+		knot_changes_free(&changes);
 		return ret;
 	}
 
diff --git a/src/libknot/updates/xfr-in.h b/src/libknot/updates/xfr-in.h
index 7763a2d721..909537b666 100644
--- a/src/libknot/updates/xfr-in.h
+++ b/src/libknot/updates/xfr-in.h
@@ -196,7 +196,7 @@ int xfrin_switch_zone(knot_zone_t *zone,
                       knot_zone_contents_t *new_contents,
                       int deep_free);
 
-void xfrin_cleanup_successful_update(knot_changes_t **changes);
+void xfrin_cleanup_successful_update(knot_changes_t *changes);
 
 void xfrin_rollback_update(knot_zone_contents_t *old_contents,
                            knot_zone_contents_t **new_contents,
diff --git a/src/libknot/zone/zone-diff.c b/src/libknot/zone/zone-diff.c
index 168933866f..8a91a69be4 100644
--- a/src/libknot/zone/zone-diff.c
+++ b/src/libknot/zone/zone-diff.c
@@ -1036,8 +1036,6 @@ int knot_zone_diff_create_changesets(const knot_zone_contents_t *z1,
 		return ret;
 	}
 
-	(*changesets)->count = 1;
-
 	dbg_zonediff("Changesets created successfully!\n");
 	dbg_zonediff_detail("Changeset dump:\n");
 dbg_zonediff_exec_detail(
-- 
GitLab