From 30869e23a183154810b381ea9968ae46814b997c Mon Sep 17 00:00:00 2001
From: Libor Peltan <libor.peltan@nic.cz>
Date: Mon, 23 Jan 2017 19:09:36 +0100
Subject: [PATCH] journal support for bootstrap changeset

---
 src/knot/journal/journal.c       | 214 +++++++++++++++++++++++++------
 src/knot/journal/journal.h       |  16 ++-
 src/knot/journal/serialization.c |  60 ++++++++-
 src/knot/journal/serialization.h |  20 ++-
 src/knot/updates/changesets.h    |   2 +-
 5 files changed, 266 insertions(+), 46 deletions(-)

diff --git a/src/knot/journal/journal.c b/src/knot/journal/journal.c
index efcb1b4ec0..79608f43d4 100644
--- a/src/knot/journal/journal.c
+++ b/src/knot/journal/journal.c
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2017 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
@@ -38,6 +38,7 @@
 #define MDKEY_GLOBAL_LAST_INSERTER_ZONE		"last_inserter_zone"
 #define MDKEY_PERZONE_OCCUPIED			"occupied"
 #define MDKEY_PERZONE_FLAGS			"flags"
+#define KEY_BOOTSTRAP_CHANGESET			"bootstrap"
 
 /*! \brief The number of unused bytes in DB key. */
 #define DB_KEY_UNUSED_ZERO (4)
@@ -47,10 +48,11 @@
 #define JOURNAL_HEADER_SIZE (32)
 
 enum {
-	LAST_FLUSHED_VALID  = 1 << 0, /* "last flush is valid" flag. */
-	SERIAL_TO_VALID     = 1 << 1, /* "last serial_to is valid" flag. */
-	MERGED_SERIAL_VALID = 1 << 2, /* "serial_from" of merged changeset. */
-	DIRTY_SERIAL_VALID  = 1 << 3, /* "dirty_serial" is present in the DB. */
+	LAST_FLUSHED_VALID   = 1 << 0, /* "last flush is valid" flag. */
+	SERIAL_TO_VALID      = 1 << 1, /* "last serial_to is valid" flag. */
+	MERGED_SERIAL_VALID  = 1 << 2, /* "serial_from" of merged changeset. */
+	DIRTY_SERIAL_VALID   = 1 << 3, /* "dirty_serial" is present in the DB. */
+	FIRST_SERIAL_INVALID = 1 << 4, /* "first_serial" is not valid. */
 };
 
 static bool journal_flush_allowed(journal_t *j) {
@@ -152,6 +154,8 @@ static void txn_init(txn_t *txn, knot_db_txn_t *db_txn, journal_t *j)
  *
  * Changeset:
  * | zone_name | \0 | unused zero 4B | (be32)serial_from | (be32)chunk_index |
+ *  or
+ * | zone_name | \0 | unused zero 4B | metadata_key | \0 | (be32)serial_from |
  *
  * Structure of the changeset:
  * | (be32)serial_to | (be32)#of_chunks | unused zero 24B | serialized_changeset...
@@ -190,6 +194,23 @@ static void txn_key_2u32(txn_t *txn, const knot_dname_t *zone, uint32_t key1, ui
 	       &key_be2, sizeof(uint32_t));
 }
 
+static void txn_key_str_u32(txn_t *txn, const knot_dname_t *zone, const char *key1, uint32_t key2)
+{
+	size_t zone_size = 0;
+	if (zone != NULL) zone_size = knot_dname_size(zone);
+	txn->key.len = zone_size + DB_KEY_UNUSED_ZERO + strlen(key1) + 1 + sizeof(uint32_t);
+	if (txn->key.len > 512) {
+		txn->ret = KNOT_ERROR;
+		return;
+	}
+	if (zone != NULL) memcpy(txn->key.data, zone, zone_size);
+	memset(txn->key.data + zone_size, 0, DB_KEY_UNUSED_ZERO);
+	strcpy(txn->key.data + zone_size + DB_KEY_UNUSED_ZERO, key1);
+	uint32_t key_be2 = htobe32(key2);
+	memcpy(txn->key.data + zone_size + DB_KEY_UNUSED_ZERO + strlen(key1) + 1,
+	       &key_be2, sizeof(uint32_t));
+}
+
 static int txn_cmpkey(txn_t *txn, knot_db_val_t *key2)
 {
 	if (txn->key.len != key2->len) {
@@ -462,7 +483,7 @@ static bool md_flag(txn_t *txn, int flag)
 /*! \brief Marks metadata as flushed */
 static void md_flush(txn_t *txn)
 {
-	if (md_flag(txn, SERIAL_TO_VALID)) {
+	if (md_flag(txn, SERIAL_TO_VALID) && !md_flag(txn, FIRST_SERIAL_INVALID)) {
 		txn->shadow_md.last_flushed = txn->shadow_md.last_serial;
 		txn->shadow_md.flags |= LAST_FLUSHED_VALID;
 	}
@@ -586,30 +607,31 @@ typedef struct {
 	void *iter_context;	// anything to send to the callback by the caller of iterate(), untouched by iterate()
 } iteration_ctx_t;
 
+typedef int (*iteration_cb_t)(iteration_ctx_t *ctx);
+
 /*!
  * \brief Move iter to next changeset chunk.
  *
  * Try optimisticly fast move to next DB item. But the changeset can be out of order,
  * so if we don't succeed (different serial or end of DB), we lookup next serial slowly.
  */
-static void get_iter_next(txn_t *txn, uint32_t expect_serial, int expect_chunk)
+
+static void get_iter_next(iteration_ctx_t *ctx, iteration_cb_t key_cb)
 {
 	knot_db_val_t other_key = { 0 };
 
-	txn_check(txn);
-	txn_iter_next(txn);
-	txn_iter_key(txn, &other_key);
-	txn_key_2u32(txn, txn->j->zone, expect_serial, (uint32_t)expect_chunk);
-	if (txn->ret == KNOT_ENOENT ||
-	    (txn->ret == KNOT_EOK && txn_cmpkey(txn, &other_key) != 0)) {
-			txn_iter_seek(txn);
+	txn_check(ctx->txn);
+	txn_iter_next(ctx->txn);
+	txn_iter_key(ctx->txn, &other_key);
+	key_cb(ctx);
+	if (ctx->txn->ret == KNOT_ENOENT ||
+	    (ctx->txn->ret == KNOT_EOK && txn_cmpkey(ctx->txn, &other_key) != 0)) {
+		txn_iter_seek(ctx->txn);
 	}
 }
 
-typedef int (*iteration_cb_t)(iteration_ctx_t *ctx);
-
 static int iterate(journal_t *j, txn_t *_txn, iteration_cb_t cb, int method,
-                   void *iter_context, uint32_t first, uint32_t last)
+                   void *iter_context, uint32_t first, uint32_t last, iteration_cb_t key_cb)
 {
 	reuse_txn(txn, j, _txn, true);
 
@@ -625,7 +647,7 @@ static int iterate(journal_t *j, txn_t *_txn, iteration_cb_t cb, int method,
 
 	txn_iter_begin(txn);
 
-	txn_key_2u32(txn, j->zone, ctx.serial, ctx.chunk_index);
+	key_cb(&ctx);
 	txn_iter_seek(txn);
 
 	ctx.val = &txn->val;
@@ -671,7 +693,7 @@ static int iterate(journal_t *j, txn_t *_txn, iteration_cb_t cb, int method,
 			ctx.chunk_index++;
 		}
 
-		get_iter_next(txn, ctx.serial, ctx.chunk_index);
+		get_iter_next(&ctx, key_cb);
 	}
 
 	if (vals != NULL) {
@@ -684,6 +706,12 @@ static int iterate(journal_t *j, txn_t *_txn, iteration_cb_t cb, int method,
 	txn_ret(txn);
 }
 
+static int normal_iterkeycb(iteration_ctx_t *ctx)
+{
+	txn_key_2u32(ctx->txn, ctx->txn->j->zone, ctx->serial, ctx->chunk_index);
+	return KNOT_EOK;
+}
+
 /*
  * ***************************** PART IV ******************************
  *
@@ -694,7 +722,7 @@ static int iterate(journal_t *j, txn_t *_txn, iteration_cb_t cb, int method,
 
 /*! \brief Deserialize changeset from chunks (in vals) */
 static int vals_to_changeset(knot_db_val_t *vals, int nvals,
-                             const knot_dname_t *zone_name, changeset_t **ch)
+                             const knot_dname_t *zone_name, changeset_t **ch, bool is_bootstrap)
 {
 	uint8_t *valps[nvals];
 	size_t vallens[nvals];
@@ -708,7 +736,8 @@ static int vals_to_changeset(knot_db_val_t *vals, int nvals,
 		return KNOT_ENOMEM;
 	}
 
-	int ret = changeset_deserialize(t_ch, valps, vallens, nvals);
+	int ret = is_bootstrap ? changeset_deserialize_bootstrap(t_ch, valps, vallens, nvals)
+	                       : changeset_deserialize(t_ch, valps, vallens, nvals);
 
 	if (ret != KNOT_EOK) {
 		changeset_free(t_ch);
@@ -725,7 +754,7 @@ static int load_one_itercb(iteration_ctx_t *ctx)
 		return KNOT_EINVAL;
 	}
 
-	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch);
+	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch, false);
 	if (ret == KNOT_EOK) *targ = ch;
 	return ret;
 }
@@ -735,7 +764,7 @@ static int load_list_itercb(iteration_ctx_t *ctx)
 	changeset_t *ch = NULL;
 	list_t *chlist = *(list_t **) ctx->iter_context;
 
-	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch);
+	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch, false);
 
 	if (ret == KNOT_EOK) {
 		add_tail(chlist, &ch->n);
@@ -748,7 +777,7 @@ static int load_one(journal_t *j, txn_t *_txn, uint32_t serial, changeset_t **ch
 {
 	reuse_txn(txn, j, _txn, false);
 	changeset_t *rch = NULL;
-	iterate(j, txn, load_one_itercb, JOURNAL_ITERATION_CHANGESETS, &rch, serial, serial);
+	iterate(j, txn, load_one_itercb, JOURNAL_ITERATION_CHANGESETS, &rch, serial, serial, normal_iterkeycb);
 	unreuse_txn(txn, _txn);
 	if (txn_finished_ok(txn)) {
 		if (rch == NULL) txn->ret = KNOT_ENOENT;
@@ -775,7 +804,6 @@ static int load_merged_changeset(journal_t *j, txn_t *_txn, changeset_t **mch,
 	txn_ret(txn);
 }
 
-/*! \brief API: load all changesets since "from" serial into dst. */
 int journal_load_changesets(journal_t *j, list_t *dst, uint32_t from)
 {
 	if (j == NULL || j->db == NULL || dst == NULL) return KNOT_EINVAL;
@@ -791,12 +819,72 @@ int journal_load_changesets(journal_t *j, list_t *dst, uint32_t from)
 	}
 
 	uint32_t ls = txn->shadow_md.last_serial;
-	iterate(j, txn, load_list_itercb, JOURNAL_ITERATION_CHANGESETS, &dst, from, ls);
+	iterate(j, txn, load_list_itercb, JOURNAL_ITERATION_CHANGESETS, &dst, from,
+	        ls, normal_iterkeycb);
 	txn_commit(txn);
 
 	txn_ret(txn);
 }
 
+int load_bootstrap_iterkeycb(iteration_ctx_t *ctx)
+{
+	txn_key_str_u32(ctx->txn, ctx->txn->j->zone, KEY_BOOTSTRAP_CHANGESET, ctx->chunk_index);
+	return KNOT_EOK;
+}
+
+static int load_bootstrap_itercb(iteration_ctx_t *ctx)
+{
+	changeset_t *ch = NULL, **targ = ctx->iter_context;
+	if (*targ != NULL) {
+		return KNOT_EINVAL;
+	}
+
+	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch, true);
+	if (ret == KNOT_EOK) *targ = ch;
+	return ret;
+}
+
+static int load_bootstrap_changeset(journal_t *j, txn_t *_txn, changeset_t **ch)
+{
+	reuse_txn(txn, j, _txn, false);
+	changeset_t *rch = NULL;
+	iterate(j, txn, load_bootstrap_itercb, JOURNAL_ITERATION_CHANGESETS, &rch,
+	        0, 0, load_bootstrap_iterkeycb);
+	unreuse_txn(txn, _txn);
+	if (txn_finished_ok(txn)) {
+		if (rch == NULL) txn->ret = KNOT_ENOENT;
+		else *ch = rch;
+	}
+	txn_ret(txn);
+}
+
+int journal_load_bootstrap(journal_t *j, list_t *dst)
+{
+	if (j == NULL || j->db == NULL || dst == NULL) return KNOT_EINVAL;
+
+	local_txn_t(txn, j);
+	txn_begin(txn, false);
+
+	changeset_t *bch = NULL;
+	load_bootstrap_changeset(j, txn, &bch);
+	if (bch == NULL) {
+		txn->ret = KNOT_ENOENT;
+		goto jlb_end;
+	}
+	add_tail(dst, &bch->n);
+	uint32_t from = knot_soa_serial(&bch->soa_to->rrs);
+
+	uint32_t ls = txn->shadow_md.last_serial;
+	iterate(j, txn, load_list_itercb, JOURNAL_ITERATION_CHANGESETS, &dst,
+	        from, ls, normal_iterkeycb);
+	if (txn->ret == KNOT_ENOENT) {
+		txn->ret = KNOT_EOK;
+	}
+jlb_end:
+	txn_commit(txn);
+	txn_ret(txn);
+}
+
 /*
  * ***************************** PART V *******************************
  *
@@ -842,7 +930,8 @@ static int del_upto_itercb(iteration_ctx_t *ctx)
  * Please ensure (dbfirst == j->metadata.first_serial) */
 static int delete_upto(journal_t *j, txn_t *txn, uint32_t dbfirst, uint32_t last)
 {
-	return iterate(j, txn, del_upto_itercb, JOURNAL_ITERATION_CHUNKS, NULL, dbfirst, last);
+	return iterate(j, txn, del_upto_itercb, JOURNAL_ITERATION_CHUNKS, NULL,
+	               dbfirst, last, normal_iterkeycb);
 }
 
 static int delete_merged_changeset(journal_t *j, txn_t *t)
@@ -866,7 +955,7 @@ static int drop_journal(journal_t *j, txn_t *_txn)
 	if (md_flag(txn, MERGED_SERIAL_VALID)) {
 		delete_merged_changeset(j, txn);
 	}
-	if (md_flag(txn, SERIAL_TO_VALID)) {
+	if (md_flag(txn, SERIAL_TO_VALID) && !md_flag(txn, FIRST_SERIAL_INVALID)) {
 		delete_upto(j, txn, txn->shadow_md.first_serial, txn->shadow_md.last_serial);
 	}
 	unreuse_txn(txn, _txn);
@@ -924,7 +1013,7 @@ static int delete_tofree(journal_t *j, txn_t *_txn, size_t to_be_freed, size_t *
 	}
 	delete_status_t ds = { .freed_approx = 0, .to_be_freed = to_be_freed };
 	iterate(j, txn, del_tofree_itercb, JOURNAL_ITERATION_CHUNKS, &ds,
-	        txn->shadow_md.first_serial, txn->shadow_md.last_serial);
+	        txn->shadow_md.first_serial, txn->shadow_md.last_serial, normal_iterkeycb);
 	unreuse_txn(txn, _txn);
 
 	if (txn_finished_ok(txn)) *really_freed = ds.freed_approx;
@@ -976,7 +1065,7 @@ static int delete_count(journal_t *j, txn_t *_txn, size_t to_be_deleted, size_t
 	}
 	delete_status_t ds = { .freed_approx = 0, .to_be_freed = to_be_deleted };
 	iterate(j, txn, del_count_itercb, JOURNAL_ITERATION_CHUNKS, &ds,
-	        txn->shadow_md.first_serial, txn->shadow_md.last_serial);
+	        txn->shadow_md.first_serial, txn->shadow_md.last_serial, normal_iterkeycb);
 	unreuse_txn(txn, _txn);
 
 	if (txn_finished_ok(txn)) *really_deleted = ds.freed_approx;
@@ -1016,7 +1105,7 @@ static int merge_itercb(iteration_ctx_t *ctx)
 {
 	changeset_t *ch = NULL, *mch = *(changeset_t **)ctx->iter_context;
 
-	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch);
+	int ret = vals_to_changeset(ctx->val, ctx->chunk_count, ctx->txn->j->zone, &ch, false);
 	if (ret == KNOT_EOK) {
 		ret = changeset_merge(mch, ch);
 		changeset_free(ch);
@@ -1050,7 +1139,7 @@ static int merge_unflushed_changesets(journal_t *j, txn_t *_txn, changeset_t **m
 	from = knot_soa_serial(&(*mch)->soa_to->rrs);
 
 	txn->ret = iterate(j, txn, merge_itercb, JOURNAL_ITERATION_CHANGESETS,
-	                   mch, from, txn->shadow_md.last_serial);
+	                   mch, from, txn->shadow_md.last_serial, normal_iterkeycb);
 
 m_u_ch_end:
 	unreuse_txn(txn, _txn);
@@ -1158,8 +1247,10 @@ static int store_changesets(journal_t *j, list_t *changesets)
 
 	// PART 4: continuity and duplicity check
 	changeset_t * chs_head = (HEAD(*changesets));
-	uint32_t serial = knot_soa_serial(&chs_head->soa_from->rrs);
-	if (md_flag(txn, SERIAL_TO_VALID) && serial_compare(txn->shadow_md.last_serial_to, serial) != 0) {
+	bool is_bootstrap = (chs_head->soa_from == NULL);
+	uint32_t serial = is_bootstrap ? 0 : knot_soa_serial(&chs_head->soa_from->rrs);
+	if (!is_bootstrap && md_flag(txn, SERIAL_TO_VALID) &&
+	    serial_compare(txn->shadow_md.last_serial_to, serial) != 0) {
 		log_zone_warning(j->zone, "discontinuity in chages history (%u -> %u), dropping older changesets",
 		                 txn->shadow_md.last_serial_to, serial);
 		try_flush
@@ -1168,7 +1259,9 @@ static int store_changesets(journal_t *j, list_t *changesets)
 	}
 	WALK_LIST(ch, *changesets) {
 		uint32_t serial_to = knot_soa_serial(&ch->soa_to->rrs);
-		if (inserting_merged && ch == TAIL(*changesets)) {
+		bool is_this_bootstrap = (is_bootstrap && ch == HEAD(*changesets));
+		bool is_this_merged = (inserting_merged && ch == TAIL(*changesets));
+		if (is_this_bootstrap || is_this_merged) {
 			continue;
 		}
 		txn_key_2u32(txn, j->zone, serial_to, 0);
@@ -1206,7 +1299,9 @@ static int store_changesets(journal_t *j, list_t *changesets)
 			break;
 		}
 
-		uint32_t serial = knot_soa_serial(&ch->soa_from->rrs);
+		bool is_this_bootstrap = is_bootstrap && (ch == HEAD(*changesets));
+		bool is_this_merged = (inserting_merged && ch == TAIL(*changesets));
+		uint32_t serial = is_this_bootstrap ? 0 : knot_soa_serial(&ch->soa_from->rrs);
 		uint32_t serial_to = knot_soa_serial(&ch->soa_to->rrs);
 
 		for (int i = 0; i < chunks; i++) {
@@ -1218,7 +1313,12 @@ static int store_changesets(journal_t *j, list_t *changesets)
 	// PART 6: inserting vals into db
 		for (int i = 0; i < chunks; i++) {
 			if (txn->ret != KNOT_EOK) break;
-			txn_key_2u32(txn, j->zone, serial, i);
+			if (is_this_bootstrap) {
+				txn_key_str_u32(txn, j->zone, KEY_BOOTSTRAP_CHANGESET, i);
+			}
+			else {
+				txn_key_2u32(txn, j->zone, serial, i);
+			}
 			txn->val = vals[i];
 			txn_insert(txn);
 			inserted_size += (vals+i)->len;
@@ -1236,14 +1336,22 @@ static int store_changesets(journal_t *j, list_t *changesets)
 		if (txn->ret != KNOT_EOK) {
 			break;
 		}
-		if (inserting_merged && ch == TAIL(*changesets)) {
+		if (is_this_merged) {
 			txn->shadow_md.flags |= MERGED_SERIAL_VALID;
 			txn->shadow_md.merged_serial = serial;
 		}
-		else {
+		else if (is_this_bootstrap) {
 			if (!md_flag(txn, SERIAL_TO_VALID)) {
+				txn->shadow_md.flags |= FIRST_SERIAL_INVALID;
+				txn->shadow_md.last_serial_to = serial_to;
+			}
+			txn->shadow_md.flags |= SERIAL_TO_VALID;
+		}
+		else {
+			if (!md_flag(txn, SERIAL_TO_VALID) || md_flag(txn, FIRST_SERIAL_INVALID)) {
 				txn->shadow_md.first_serial = serial;
 			}
+			txn->shadow_md.flags &= ~FIRST_SERIAL_INVALID;
 			txn->shadow_md.flags |= SERIAL_TO_VALID;
 			txn->shadow_md.last_serial = serial;
 			txn->shadow_md.last_serial_to = serial_to;
@@ -1716,6 +1824,34 @@ int journal_check(journal_t *j, journal_check_level warn_level)
 		goto check_merged;
 	}
 
+	if (md_flag(txn, FIRST_SERIAL_INVALID)) {
+		jch_info("there is just the bootstrap changeset in journal");
+		ret = load_bootstrap_changeset(j, txn, &ch);
+		if (ret != KNOT_EOK) {
+			jch_warn("can't read bootstrap changeset (%s)", knot_strerror(ret));
+		}
+		else {
+			changeset_free(ch);
+		}
+		goto check_merged;
+	}
+	else {
+		ret = load_bootstrap_changeset(j, txn, &ch);
+		switch (ret) {
+		case KNOT_EOK:
+			sto = knot_soa_serial(&ch->soa_to->rrs);
+			jch_info("bootstrap changeset loaded, sto %u", sto);
+			changeset_free(ch);
+			break;
+		case KNOT_ENOENT:
+			txn->ret = KNOT_EOK;
+			break;
+		default:
+			jch_info("failed to read bootstrap changeset (%s)", knot_strerror(ret));
+			break;
+		}
+	}
+
 	ret = load_one(j, txn, txn->shadow_md.first_serial, &ch);
 	if (ret != KNOT_EOK) {
 		jch_warn("can't read first changeset %u (%s)",
diff --git a/src/knot/journal/journal.h b/src/knot/journal/journal.h
index e21e6c7307..e7105e2ea9 100644
--- a/src/knot/journal/journal.h
+++ b/src/knot/journal/journal.h
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2017 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
@@ -111,7 +111,7 @@ int journal_open(journal_t *j, journal_db_t **db, const knot_dname_t *zone_name)
 void journal_close(journal_t *journal);
 
 /*!
- * \brief Load changesets from journal.
+ * \brief Load changesets from journal since "from" serial.
  *
  * \param journal  Journal to load from.
  * \param dst      Store changesets here.
@@ -123,6 +123,18 @@ void journal_close(journal_t *journal);
  */
 int journal_load_changesets(journal_t *journal, list_t *dst, uint32_t from);
 
+/*!
+ * \brief Load changesets from journal, starting with bootstrap changeset.
+ *
+ * \param journal  Journal to load from.
+ * \param dst      Store changesets here, starting with bootstrap changeset.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval KNOT_ENOENT when there is no bootstrap changeset.
+ * \return < KNOT_EOK on other error.
+ */
+int journal_load_bootstrap(journal_t *j, list_t *dst);
+
 /*!
  * \brief Store changesets in journal.
  *
diff --git a/src/knot/journal/serialization.c b/src/knot/journal/serialization.c
index 38fa302cc5..d9fb37b33c 100644
--- a/src/knot/journal/serialization.c
+++ b/src/knot/journal/serialization.c
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2017 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
@@ -211,6 +211,11 @@ int changeset_serialize(const changeset_t *ch, uint8_t *dst_chunks[],
 	wire_ctx_t wire = wire_ctx_init(dst_chunks[0], chunk_size);
 	size_t cur_chunk = 0;
 
+	if (ch->soa_from == NULL) {
+		// serializing bootstrap changeset
+		goto serialize_to; // note: it & ret & rrset are uninitialized here, we don't care
+	}
+
 	// Serialize SOA 'from'.
 	int ret = serialize_rrset_chunks(&wire, ch->soa_from, dst_chunks, chunk_size,
 	                                 chunks_count, chunks_real_sizes, &cur_chunk);
@@ -237,6 +242,7 @@ int changeset_serialize(const changeset_t *ch, uint8_t *dst_chunks[],
 	}
 	changeset_iter_clear(&it);
 
+serialize_to:
 	// Serialize SOA 'to'.
 	ret = serialize_rrset_chunks(&wire, ch->soa_to, dst_chunks, chunk_size,
 	                             chunks_count, chunks_real_sizes, &cur_chunk);
@@ -282,7 +288,8 @@ int changeset_deserialize(changeset_t *ch, uint8_t *src_chunks[],
 
 	// Deserialize SOA 'from'.
 	knot_rrset_t rrset;
-	int ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes, chunks_count, &cur_chunk);
+	int ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes,
+	                                   chunks_count, &cur_chunk);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -298,7 +305,8 @@ int changeset_deserialize(changeset_t *ch, uint8_t *src_chunks[],
 	bool in_remove_section = true;
 	while (cur_chunk < chunks_count - 1 || wire_ctx_available(&wire) > 0) {
 		// Parse next RRSet.
-		ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes, chunks_count, &cur_chunk);
+		ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes,
+		                               chunks_count, &cur_chunk);
 		if (ret != KNOT_EOK) {
 			break;
 		}
@@ -330,3 +338,49 @@ int changeset_deserialize(changeset_t *ch, uint8_t *src_chunks[],
 
 	return wire.error;
 }
+
+int changeset_deserialize_bootstrap(changeset_t *ch, uint8_t *src_chunks[],
+                                    const size_t *chunks_sizes, size_t chunks_count)
+{
+	if (ch == NULL || src_chunks == NULL || chunks_sizes == NULL ||
+	    chunks_count == 0) {
+		return KNOT_EINVAL;
+	}
+
+	size_t cur_chunk = 0;
+	wire_ctx_t wire = wire_ctx_init_const(src_chunks[0], chunks_sizes[0]);
+
+	// Deserialize SOA 'to'.
+	knot_rrset_t rrset;
+	int ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes,
+	                                   chunks_count, &cur_chunk);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	assert(rrset.type == KNOT_RRTYPE_SOA);
+
+	ch->soa_from = NULL;
+	ch->soa_to = knot_rrset_copy(&rrset, NULL);
+	knot_rrset_clear(&rrset, NULL);
+	if (ch->soa_to == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	// Read remaining RRSets.
+	while (cur_chunk < chunks_count - 1 || wire_ctx_available(&wire) > 0) {
+		// Parse next RRSet.
+		ret = deserialize_rrset_chunks(&wire, &rrset, src_chunks, chunks_sizes,
+		                               chunks_count, &cur_chunk);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+		assert(rrset.type != KNOT_RRTYPE_SOA);
+		ret = changeset_add_addition(ch, &rrset, 0);
+		knot_rrset_clear(&rrset, NULL);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	}
+
+	return wire.error;
+}
diff --git a/src/knot/journal/serialization.h b/src/knot/journal/serialization.h
index 0558bd72c5..63a2b80928 100644
--- a/src/knot/journal/serialization.h
+++ b/src/knot/journal/serialization.h
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2017 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
@@ -57,3 +57,21 @@ int changeset_serialize(const changeset_t *ch, uint8_t *dst_chunks[],
  */
 int changeset_deserialize(changeset_t *ch, uint8_t *src_chunks[],
                           const size_t *chunks_sizes, size_t chunks_count);
+
+/*!
+ * \brief Deserializes bootstrap changeset
+ *
+ * This is like changeset_deserialize(), but expects a bootstrap changeset (no soa_from and no
+ * 'rem' section) in src_chunks.
+ *
+ * There is no changeset_serialize_botstrap: simply run changeset_serialize() with ch->soa_from = NULL.
+ *
+ * \param[out] ch            The changeset.
+ * \param[in]  src_chunks    The chunks to deserialize.
+ * \param[in]  chunks_sizes  The size of each chunk.
+ * \param[in]  chunks_count  The number of chunks.
+ *
+ * \retval KNOT_E*
+ */
+int changeset_deserialize_bootstrap(changeset_t *ch, uint8_t *src_chunks[],
+                                    const size_t *chunks_sizes, size_t chunks_count);
diff --git a/src/knot/updates/changesets.h b/src/knot/updates/changesets.h
index e38e41cc92..2f591af224 100644
--- a/src/knot/updates/changesets.h
+++ b/src/knot/updates/changesets.h
@@ -41,7 +41,7 @@ typedef struct {
 	knot_rrset_t *soa_to;     /*!< Destination SOA. */
 	zone_contents_t *add;     /*!< Change additions. */
 	zone_contents_t *remove;  /*!< Change removals. */
-	size_t size;              /*!< Size of serialized changeset. */
+	size_t size;              /*!< Size of serialized changeset. \todo Remove after old_journal removal! */
 	uint8_t *data;            /*!< Serialized changeset. */
 } changeset_t;
 
-- 
GitLab