diff mbox

[RFC,V8,06/24] qcow2: Add the deduplication store.

Message ID 1371738392-9594-7-git-send-email-benoit@irqsave.net
State New
Headers show

Commit Message

Benoît Canet June 20, 2013, 2:26 p.m. UTC
The key value store is an SSD optimised store used to store deduplication's
QCowHashInfo.
It binds together the QCowJournal, the QCowLogStore and the QCowHashStore and
must be called be the deduplication code.

Signed-off-by: Benoit Canet <benoit@irqsave.net>
---
 block/Makefile.objs |    2 +-
 block/qcow2-store.c |  771 +++++++++++++++++++++++++++++++++++++++++++++++++++
 block/qcow2.h       |   20 +-
 3 files changed, 791 insertions(+), 2 deletions(-)
 create mode 100644 block/qcow2-store.c
diff mbox

Patch

diff --git a/block/Makefile.objs b/block/Makefile.objs
index cafd4f8..2d1a269 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -1,6 +1,6 @@ 
 block-obj-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o
 block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o
-block-obj-y += qcow2-journal.o qcow2-log-store.o qcow2-hash-store.o
+block-obj-y += qcow2-journal.o qcow2-log-store.o qcow2-hash-store.o qcow2-store.o
 block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
 block-obj-y += qed-check.o
 block-obj-y += vhdx.o
diff --git a/block/qcow2-store.c b/block/qcow2-store.c
new file mode 100644
index 0000000..8e3fad5
--- /dev/null
+++ b/block/qcow2-store.c
@@ -0,0 +1,771 @@ 
+/*
+ * QCOW2 key value store for SSD storage
+ *
+ * Copyright (C) Nodalink, SARL. 2013
+ *
+ * Author:
+ *   Benoît Canet <benoit.canet@irqsave.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu-common.h"
+#include "block/block_int.h"
+#include "block/qcow2.h"
+
+/* The qcow2_store combine the qcow2_log_store with the qcow2_hash_store->
+ *
+ * insertions:
+ * Entries are inserted in the qcow2_log_store which delegate writing to disk
+ * to the qcow2_journal.
+ * When full the qcow2_log_store is frozen into an incarnation and given to the
+ * qcow2_hash_store-> A new qcow2_log store is then created
+ *
+ * deletions:
+ * Deletions are insertions with an additional flag set and a mandatory
+ * write flush of the journal: loosing an insertion is not a problem whereas
+ * loosing a deletion is a problem.
+ *
+ * queries:
+ * The log store is first queried and if the entry is not found in it the hash
+ * store is queried.
+ * When queried the hash store it will check for the presence of the
+ * QCowHashInfo in every incarnation starting from the youngest to the oldest.
+ *
+ * fifo:
+ * A user configurable setting allow the qcow2_store to act as a fifo.
+ * the oldest QCowHashInfos stored in it will be dropped an incarnation at once.
+ * Loosing the oldest QCowHashInfo won't corrupt the dedup metadata the only
+ * risk if to make to dedup ratio lower.
+ *
+ * algorithm:
+ * This code is an implementation of the first two stages of the SILT paper.
+ * The third stage was dropped to lower the complexity of the code
+ * The idea of behaving as a FIFO and the notion of incarnations comes from
+ * BufferHash and is handy to help reducing RAM consumption.
+ *
+ * SILT: www.cs.cmu.edu/~dga/papers/silt-sosp2011.pdf
+ * BufferHash: research.microsoft.com/en-us/people/sumann/bufferhash-nsdi10.pdf
+ */
+
+/* This function initialize a key value store
+ *
+ * @store: the store to initialize
+ * @order: the bit order
+ */
+static void qcow2_store_init(BlockDriverState *bs,
+                             QCowStore *store,
+                             uint32_t order)
+{
+    store->order = order;
+    qemu_co_mutex_init(&store->insert_lock);
+    qcow2_log_store_init(bs, &store->log_store, order);
+    qcow2_log_store_init(bs, &store->frozen_log_store, order);
+    qcow2_hash_store_init(&store->hash_store, order);
+}
+
+/* This function cleanup a key value store
+ *
+ * @store: the store to cleanup
+ */
+void qcow2_store_cleanup(QCowStore *store)
+{
+    qcow2_log_store_cleanup(&store->log_store);
+    qcow2_log_store_cleanup(&store->frozen_log_store);
+    qcow2_hash_store_cleanup(&store->hash_store);
+}
+
+/* This function look for a given hash info in the store
+ *
+ *
+ * @store:     the QCowStore to lookup into
+ * @hash_info: the QCowHashInfo to complete : at last the hash must be filled.
+ * @ret:       1 on successfull lookup, 0 if not found, -errno on error
+ */
+int qcow2_store_get(BlockDriverState *bs, QCowStore *store,
+                    QCowHashInfo *hash_info)
+{
+    bool found;
+    int ret = 0;
+
+    /* we do the same query from the youngest to the oldest store */
+
+    found = qcow2_log_store_get(&store->log_store, hash_info);
+
+    if (found) {
+        goto exit;
+    }
+
+    if (store->freezing) {
+         found = qcow2_log_store_get(&store->frozen_log_store, hash_info);
+    }
+
+    /* found or error */
+    if (found) {
+        goto exit;
+    }
+
+    ret = qcow2_hash_store_get(bs, &store->hash_store, hash_info);
+
+exit:
+    /* if nothing found or error */
+    if (!found && ret <= 0) {
+        return ret;
+    }
+
+    /* if hash info found but deleted from store return not found */
+    if (hash_info->first_logical_sect & QCOW_DEDUP_DELETED) {
+        return 0;
+    }
+
+    return 1;
+}
+
+/* this structure is used between the two following functions */
+typedef struct {
+    BlockDriverState *bs;
+    QCowStore *store;
+} QCowIncarnateArgs;
+
+/* This coroutine convert a log store into an incarnation
+ *
+ * While doing this convertion we take care of two things :
+ *
+ * -not allocating QCOW2 disk space while the guest is frozen
+ * -not reseting the freezing flag to false while the guest is frozen
+ *
+ * All operations in between these two steps can be done freely as they are only
+ * writes on an already allocated disk space area.
+ *
+ * As long a the freezing flag is set to true the target of the migration would
+ * restart the freeze cleanly.
+ *
+ * @opaque: the function arguments (bs, log_store and hash_store)
+ */
+static void coroutine_fn qcow2_store_co_incarnate(void *opaque)
+{
+    QCowIncarnateArgs *args = (QCowIncarnateArgs *) opaque;
+    int64_t ret = 0;
+    int64_t offset;
+    uint64_t filter_size;
+    uint64_t incarnation_size;
+    uint8_t *filter;
+
+    BlockDriverState *bs               = args->bs;
+    BDRVQcowState    *s                = bs->opaque;
+    QCowStore        *store            = args->store;
+    QCowLogStore     *frozen_log_store = &store->frozen_log_store;
+    QCowHashStore    *hash_store       = &store->hash_store;
+
+    /* avoid disk allocations while the guest is suspended or migrating */
+    co_sleep_ns(vm_clock, 1);
+
+    /* allocate a new incarnation disk space or get it from limbo */
+    offset = qcow2_hash_store_get_incarnation_offset(bs,
+                                                     hash_store);
+
+    if (offset < 0) {
+        /* save the errno so the rest of QCOW2 will know */
+        s->freeze_errno = offset;
+        return;
+    }
+
+    /* save new config */
+    ret = qcow2_store_save(bs, store);
+
+    if (ret < 0) {
+        goto deallocate_exit;
+    }
+
+    /* build the in ram filter and dump it to disk */
+    ret = qcow2_log_store_freeze_filter(bs,
+                                        frozen_log_store,
+                                        offset,
+                                        &filter);
+
+    if (ret < 0) {
+        goto deallocate_exit;
+    }
+
+    /* reorder the journal contained hashes and write them to disk */
+    filter_size = qcow2_hash_store_filter_size(hash_store->order);
+    ret = qcow2_log_store_freeze_hash_table(bs,
+                                            frozen_log_store,
+                                            offset + filter_size);
+
+    if (ret < 0) {
+        goto free_exit;
+    }
+
+    /* delegate the new frozen hash table to the hash store */
+    qcow2_hash_store_add_incarnation(bs,
+                                     hash_store,
+                                     filter,
+                                     offset);
+
+    /* reset the log store and clear journal  */
+    ret = qcow2_log_store_recycle(bs, &store->frozen_log_store);
+
+    if (ret < 0) {
+        goto exit;
+    }
+
+    /* we finished to incarnate the log -> remember it */
+    store->freezing = false;
+    /* avoid modifying the freezing flag while the guest is suspended or
+     * migrating
+     */
+    co_sleep_ns(vm_clock, 1);
+    /* save new config */
+    ret =  qcow2_store_save(bs, store);
+    if (ret < 0) {
+        goto free_exit;
+    }
+
+    free(args);
+    return;
+
+free_exit:
+    qemu_vfree(filter);
+deallocate_exit:
+    incarnation_size = qcow2_hash_store_incarnation_size(hash_store->order);
+    /* postpone deallocation if guest is suspended */
+    co_sleep_ns(vm_clock, 1);
+    /* FIXME: why freeing this does match well with limbo */
+    qcow2_free_clusters(bs, offset, incarnation_size);
+exit:
+    /* save the errno so the rest of QCOW2 will know */
+    s->freeze_errno = ret;
+    free(args);
+}
+
+/* This function start a coroutine convertion a log store into and incarnation
+ *
+ * As the function return void the coroutine store error status in
+ * s->freeze_errno
+ *
+ * @store:  the store to work on
+ */
+static void qcow2_store_incarnate(BlockDriverState *bs,
+                                  QCowStore *store)
+{
+    BDRVQcowState *s = bs->opaque;
+
+    QCowIncarnateArgs *args = g_new0(QCowIncarnateArgs, 1);
+    args->bs = bs;
+    args->store = store;
+
+    s->freeze_co = qemu_coroutine_create(qcow2_store_co_incarnate);
+    qemu_coroutine_enter(s->freeze_co, args);
+}
+
+/* This function freeze the current log store and create a new one
+ *
+ * This swap the two log store
+ *
+ * @store: the QCowStore we work with
+ */
+static int qcow2_store_swap_log_store(BlockDriverState *bs, QCowStore *store)
+{
+    QCowStore swap;
+    int ret = 0;
+
+    /* flush and stop the journal */
+    ret = qcow2_log_store_stop(bs, &store->log_store);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* backup frozen log store */
+    memcpy(&swap,
+           &store->frozen_log_store,
+           sizeof(QCowLogStore));
+
+    /* freeze current log store */
+    memcpy(&store->frozen_log_store,
+           &store->log_store,
+           sizeof(QCowLogStore));
+
+    /* finish the swap */
+    memcpy(&store->log_store,
+           &swap,
+           sizeof(QCowLogStore));
+
+    /* no parallel freeze can occur */
+    assert(!store->freezing);
+    store->freezing = true;
+
+    return 0;
+}
+
+/* this function start the freeze of the current log store into and incarnation
+ *
+ * @store:     the QCowStore to work on
+ * @hash_info: the hash_info to insert into the new log store
+ * @ret:       0 on succes, -errno on error
+ */
+static int qcow2_store_start_freeze(BlockDriverState *bs,
+                                    QCowStore *store,
+                                    QCowHashInfo *hash_info)
+{
+    int ret = 0;
+    bool need_save = false;
+
+    /* if we are already are freezing a log store wait for the operation to end
+     * before starting a new one
+     */
+    while (store->freezing) {
+        co_sleep_ns(rt_clock, QCOW2_STORE_CO_SLEEP_NS);
+    }
+
+    /* log store is full -> prepare the log store freeze and create new one */
+    ret = qcow2_store_swap_log_store(bs, store);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* save new configuration */
+    ret = qcow2_store_save(bs, store);
+
+    /* on error */
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* delegate the insert to the new log store */
+    ret = qcow2_log_store_insert(bs, &store->log_store, hash_info, &need_save);
+
+    /* on error */
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* ret == 1 case is not tested because the log store is new and can't
+     * possibly be full right now
+     */
+
+    /* start to incarnate the previous log store into a hash store
+     * This is the responsibility of the coroutine to get rid of the frozen log
+     * store and save the new configuration to disk.
+     */
+    store->freezing = true;
+    qcow2_store_incarnate(bs, store);
+
+    return 0;
+}
+
+/* This function insert a QCowHashInfo into the store
+ *
+ * @store:     The QCowStore to work with
+ * @hash_info: the QCowHashInfo to insert into the sore
+ * @ret:       0 on success, -errno on error
+ */
+int qcow2_store_insert(BlockDriverState *bs, QCowStore *store,
+                       QCowHashInfo *hash_info)
+{
+    int ret = 0;
+    bool need_save = false;
+
+    /* we take the insert_lock here to prevent concurents inserts to trigger
+     * multiple concurent log store freeze if the log store is full
+     */
+    qemu_co_mutex_lock(&store->insert_lock);
+
+    /* delegate the insert to the log store */
+    ret = qcow2_log_store_insert(bs, &store->log_store, hash_info, &need_save);
+
+    /* on error */
+    if (ret < 0) {
+        goto unlock_exit;
+    }
+
+    /* need_save is true only if case of success -> no need to check ret */
+    if (need_save) {
+        ret = qcow2_store_save(bs, store);
+    }
+
+    /* on success */
+    if (!ret) {
+        goto unlock_exit;
+    }
+
+    /* ret == 1 -> log store is full -> freeze in into an incarnation */
+    ret = qcow2_store_start_freeze(bs, store, hash_info);
+
+unlock_exit:
+    qemu_co_mutex_unlock(&store->insert_lock);
+    return ret;
+}
+
+/* This delegate a flush to the log store
+ *
+ * @store:     The QCowStore to work with
+ * @ret:       0 on success, -errno on error
+ */
+int qcow2_store_flush(BlockDriverState *bs, QCowStore *store)
+{
+    return qcow2_log_store_flush(bs, &store->log_store);
+}
+
+/* This function delete a QCowHashInfo from the store
+ *
+ * @store:     The QCowStore to work with
+ * @hash_info: the QCowHashInfo to insert into the sore
+ * @ret:       0 on success, -errno on error
+ */
+int qcow2_store_delete(BlockDriverState *bs, QCowStore *store,
+                       QCowHashInfo *hash_info)
+{
+    hash_info->first_logical_sect |= QCOW_DEDUP_DELETED;
+    return qcow2_store_insert(bs, store, hash_info);
+}
+
+/* this function compute the size of a dump of a store
+ *
+ * @store: the store to work on
+ * @ret:   the size of a dump
+ */
+static size_t qcow2_store_dump_size(QCowStore *store)
+{
+    return sizeof(store->order) + 1 +
+           qcow2_log_store_dump_size() * 2 + /* current + frozen log store */
+           qcow2_hash_store_dump_size(&store->hash_store);
+}
+
+/* this function dump a store in a buffer
+ *
+ * @buf:   the buffer to dump into
+ * @store: the store to dump
+ * @ret:   the size used for the dump
+ */
+static size_t qcow2_store_dump(uint8_t *buf, QCowStore *store)
+{
+    uint32_t *buf32 = (uint32_t *) buf;
+    size_t offset;
+
+    /* dump order */
+    buf32[0] = cpu_to_be32(store->order);
+    offset = sizeof(store->order);
+
+    /* dump freeze status */
+    buf[offset] = store->freezing;
+    offset++;
+
+    /* dump current log store */
+    offset += qcow2_log_store_dump(buf + offset,
+                                   &store->log_store);
+
+
+    /* dump frozen log store */
+    offset += qcow2_log_store_dump(buf + offset,
+                                   &store->frozen_log_store);
+
+    /* dump hash store */
+    offset += qcow2_hash_store_dump(buf + offset,
+                                    &store->hash_store);
+
+    return offset;
+}
+
+/* this function parse a buffer in a given store
+ *
+ * note: As the parse method is used only on startup it also init the store
+ *
+ * @store: the store to parse into
+ * @buf:   the buffer to parse from
+ * @size:  the size of the buffer
+ * @ret:   0 on success, -1 when buffer is too small to parse all data from
+ */
+static int qcow2_store_parse(BlockDriverState *bs,
+                             QCowStore *store,
+                             uint8_t *buf,
+                             uint32_t size)
+{
+    size_t offset;
+    uint32_t *buf32 = (uint32_t *) buf;
+    uint32_t order;
+    int ret;
+
+    /* parse order and init store */
+    order = be32_to_cpu(buf32[0]);
+    qcow2_store_init(bs, store, order);
+    offset = sizeof(store->order);
+
+    /* parse freeze status */
+    store->freezing = buf[offset];
+    offset++;
+
+    /* parse current log store */
+    offset += qcow2_log_store_parse(&store->log_store, buf + offset);
+
+    /* parse frozen log store i its not NULL */
+    offset += qcow2_log_store_parse(&store->frozen_log_store, buf + offset);
+
+    /* parse hash store */
+    ret = qcow2_hash_store_parse(&store->hash_store,
+                                 buf + offset,
+                                 buf + size);
+
+    /* buffer was too small -> cleanup and return error  */
+    if (ret == -1) {
+        /* free various buffers */
+        qcow2_store_cleanup(store);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int32_t qcow2_store_compute_order(BlockDriverState *bs,
+                                         uint64_t total_size)
+{
+    BDRVQcowState *s = bs->opaque;
+    uint64_t nb_clusters = total_size / s->cluster_size;
+    uint64_t nb_hash_per_incarnation = nb_clusters / QCOW2_NB_INCARNATION_GOAL;
+    return qcow2_log_store_compute_order(nb_hash_per_incarnation);
+}
+
+static int64_t qcow2_store_alloc_disk_conf(BlockDriverState *bs,
+                                           QCowStore *store,
+                                           size_t *psize)
+{
+    /* compute configuration space dump size * 2 with room for growth */
+    *psize = qcow2_store_dump_size(store) * 2;
+
+    /* allocate the store configuration space */
+    return qcow2_alloc_clusters(bs, *psize);
+}
+
+/* this function save a QCowStore to disk and update header if needeed
+ *
+ * The function handle the need to grow the on disk dump area
+ * The function does rewrite the QCOW2 header extension if needed.
+ *
+ * @store:  the store to save
+ * @ret:    0 on success, -errno on error
+ */
+int64_t qcow2_store_save(BlockDriverState *bs, QCowStore *store)
+{
+    BDRVQcowState *s = bs->opaque;
+    int64_t offset, old_offset = 0;
+    size_t size, old_size = 0;
+    bool alloc = false;
+    uint8_t *buf;
+    int ret = 0;
+
+    /* we use these temporary variable to commit atomically */
+    offset = s->dedup_conf_offset;
+    size = s->dedup_conf_size;
+
+    /* check if we need to grow on disk dump area */
+    if (size < qcow2_store_dump_size(store))  {
+        alloc = true;
+        old_offset = offset;
+        old_size = size;
+        offset = qcow2_store_alloc_disk_conf(bs, store, &size);
+
+        if (offset < 0) {
+            return offset;
+        }
+    }
+
+    /* allocate buffer */
+    buf = qemu_blockalign(bs, size);
+    memset(buf, 0, size);
+
+    /* serialize store config in buffer */
+    ret = qcow2_store_dump(buf, store);
+
+    if (ret < 0) {
+        goto free_dealloc_exit;
+    }
+
+    /* write the conf to disk */
+    ret = bdrv_pwrite(bs->file, offset, buf, size);
+
+    /* no error happened and we grew the config disk space -> save in header */
+    if (alloc && ret > 0) {
+        s->dedup_conf_offset = offset;
+        s->dedup_conf_size = size;
+        qcow2_update_header(bs);
+        /* do not free old disk space on creation */
+        if (old_size) {
+            qcow2_free_clusters(bs, old_offset, old_size);
+        }
+    }
+    ret = 0;
+
+free_dealloc_exit:
+    /* on alloc and error free newly allocated disk space */
+    if (alloc && ret < 0) {
+        qcow2_free_clusters(bs, offset, size);
+    }
+    qemu_vfree(buf);
+    return ret;
+}
+
+/* This function will allocate a journal, init the store and save the config
+ *
+ * this function will be used at QCOW2 image creation time
+ * This code don't bother cleaning up failure since error would make image
+ * creation fail. It just free memory.
+ *
+ * note: s->cluster_size must be filled with correct value for
+ *       qcow2_store_compute_order
+ *
+ * @store:      the store to create
+ * @total_size: the total size of the image
+ * @ret:        0 on success, -errno on error
+ */
+int64_t qcow2_store_create(BlockDriverState *bs,
+                           QCowStore *store,
+                           uint64_t total_size)
+{
+    BDRVQcowState *s = bs->opaque;
+    int ret = 0;
+
+    /* compute the bit order required to store the journal and hash tables */
+    uint32_t order = qcow2_store_compute_order(bs, total_size);
+
+    /* initialize the store state */
+    qcow2_store_init(bs, store, order);
+
+    /* create the journal */
+    ret = qcow2_log_store_create(bs, &store->log_store, order);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* create frozen log store we will swap at runtime */
+    ret = qcow2_log_store_create(bs, &store->frozen_log_store, order);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* set size to zero so the save function will start allocating
+     * and will not try to free anything
+     */
+    s->dedup_conf_size = 0;
+
+    /* save new config */
+    return qcow2_store_save(bs, store);
+}
+
+
+/* this function initialize a store and load it's configuration from disk
+ *
+ * note: the journal and the incarnations filters are not loaded from disk
+ *       this work is to be done later.
+ *
+ * @store:  the store to load the configuration into
+ * @ret:    0 on success, -errno on error, -1 if the on disk dump was invalid
+ */
+int qcow2_store_load(BlockDriverState *bs, QCowStore *store)
+{
+    BDRVQcowState *s = bs->opaque;
+    int ret = 0;
+
+    /* allocate read buffer */
+    uint8_t *buf = qemu_blockalign(bs, s->dedup_conf_size);
+
+    /* read conf from disk */
+    ret = bdrv_pread(bs->file,
+                     s->dedup_conf_offset,
+                     buf,
+                     s->dedup_conf_size);
+
+    if (ret < 0) {
+        goto free_exit;
+    }
+
+    /* parse the buffer into a store */
+    ret = qcow2_store_parse(bs, store, buf, s->dedup_conf_size);
+free_exit:
+    qemu_vfree(buf);
+
+    return ret;
+}
+
+/* this function load the in ram store data at startup
+ *
+ * It loads the current journal synchronously so after this the deduplication
+ * will be operational.
+ *
+ * @store: the store to work on
+ * @ret:   0 on succes, -errno on error
+ */
+int qcow2_store_start(BlockDriverState *bs, QCowStore *store)
+{
+    int ret = 0;
+
+    /* reload current journal */
+    ret = qcow2_log_store_rebuild_from_journal(bs, &store->log_store);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* resume freeze in progress */
+    if(store->freezing) {
+        ret = qcow2_log_store_rebuild_from_journal(bs,
+                                                   &store->frozen_log_store);
+
+        if (ret < 0) {
+            return ret;
+        }
+
+        /* do the incarnation in a coroutine */
+        qcow2_store_incarnate(bs, store);
+    }
+
+    /* load incarnations in ram filters in a coroutine */
+    qcow2_hash_store_load_filters(bs, &store->hash_store);
+
+    return 0;
+}
+
+int qcow2_store_forget(BlockDriverState *bs, QCowStore *store)
+{
+     int ret = 0;
+     store->freezing = false;
+
+     /* reset hash store */
+     qcow2_hash_store_forget_all_incarnations(&store->hash_store);
+
+     /* reset log store */
+     ret = qcow2_log_store_create(bs, &store->log_store, store->order);
+
+     if (ret < 0) {
+         return ret;
+     }
+
+     /* reset frozen log store */
+     ret = qcow2_log_store_create(bs, &store->frozen_log_store, store->order);
+
+     if (ret < 0) {
+         return ret;
+     }
+
+     /* save config to disk */
+     return qcow2_store_save(bs, store);
+}
diff --git a/block/qcow2.h b/block/qcow2.h
index d0037bf..c0dbfe3 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -226,9 +226,9 @@  typedef struct {
 
 typedef struct {
     uint32_t      order;
+    bool          freezing;          /* is a log store freeze in progress */
     QCowLogStore  log_store;         /* the current log store */
     QCowLogStore  frozen_log_store;  /* the log store to incarnate */
-    bool          freezing;          /* are we incarnating a log store */
     QCowHashStore hash_store;
     CoMutex       insert_lock;       /* used to prevent multiple freeze attempts
                                       * at the same time
@@ -699,4 +699,22 @@  size_t qcow2_hash_store_parse(QCowHashStore *store,
                                 uint8_t *buf,
                                 uint8_t *buf_end);
 
+/* qcow2-store.c functions (Should be called by dedup.c) */
+
+void qcow2_store_cleanup(QCowStore *store);
+int qcow2_store_get(BlockDriverState *bs, QCowStore *store,
+                    QCowHashInfo *hash_info);
+int qcow2_store_insert(BlockDriverState *bs, QCowStore *store,
+                       QCowHashInfo *hash_info);
+int qcow2_store_flush(BlockDriverState *bs, QCowStore *store);
+int qcow2_store_delete(BlockDriverState *bs, QCowStore *store,
+                       QCowHashInfo *hash_info);
+int64_t qcow2_store_save(BlockDriverState *bs, QCowStore *store);
+int64_t qcow2_store_create(BlockDriverState *bs,
+                           QCowStore *store,
+                           uint64_t total_size);
+int qcow2_store_load(BlockDriverState *bs, QCowStore *store);
+int qcow2_store_start(BlockDriverState *bs, QCowStore *store);
+int qcow2_store_forget(BlockDriverState *bs, QCowStore *store);
+
 #endif