[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH 2/8] qcow2: add dirty-bitmaps feature
From: |
John Snow |
Subject: |
Re: [Qemu-devel] [PATCH 2/8] qcow2: add dirty-bitmaps feature |
Date: |
Thu, 11 Jun 2015 19:04:23 -0400 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.7.0 |
On 06/08/2015 11:21 AM, Vladimir Sementsov-Ogievskiy wrote:
> From: Vladimir Sementsov-Ogievskiy <address@hidden>
>
> Adds dirty-bitmaps feature to qcow2 format as specified in
> docs/specs/qcow2.txt
>
> Signed-off-by: Vladimir Sementsov-Ogievskiy <address@hidden>
> Signed-off-by: Vladimir Sementsov-Ogievskiy <address@hidden>
> ---
> block/Makefile.objs | 2 +-
> block/qcow2-dirty-bitmap.c | 503
> +++++++++++++++++++++++++++++++++++++++++++++
> block/qcow2.c | 56 +++++
> block/qcow2.h | 50 +++++
> include/block/block_int.h | 10 +
> 5 files changed, 620 insertions(+), 1 deletion(-)
> create mode 100644 block/qcow2-dirty-bitmap.c
>
> diff --git a/block/Makefile.objs b/block/Makefile.objs
> index 0d8c2a4..bff12b4 100644
> --- a/block/Makefile.objs
> +++ b/block/Makefile.objs
> @@ -1,5 +1,5 @@
> block-obj-y += raw_bsd.o qcow.o vdi.o vmdk.o cloop.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.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o
> qcow2-cache.o qcow2-dirty-bitmap.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-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
> diff --git a/block/qcow2-dirty-bitmap.c b/block/qcow2-dirty-bitmap.c
> new file mode 100644
> index 0000000..bc0167c
> --- /dev/null
> +++ b/block/qcow2-dirty-bitmap.c
> @@ -0,0 +1,503 @@
> +/*
> + * Dirty bitmpas for the QCOW version 2 format
> + *
> + * Copyright (c) 2014-2015 Vladimir Sementsov-Ogievskiy
> + *
> + * This file is derived from qcow2-snapshot.c, original copyright:
> + * Copyright (c) 2004-2006 Fabrice Bellard
> + *
> + * 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"
> +
> +void qcow2_free_dirty_bitmaps(BlockDriverState *bs)
> +{
> + BDRVQcowState *s = bs->opaque;
> + int i;
> +
> + for (i = 0; i < s->nb_dirty_bitmaps; i++) {
> + g_free(s->dirty_bitmaps[i].name);
> + }
> + g_free(s->dirty_bitmaps);
> + s->dirty_bitmaps = NULL;
> + s->nb_dirty_bitmaps = 0;
> +}
> +
> +int qcow2_read_dirty_bitmaps(BlockDriverState *bs)
> +{
> + BDRVQcowState *s = bs->opaque;
> + QCowDirtyBitmapHeader h;
> + QCowDirtyBitmap *bm;
> + int i, name_size;
> + int64_t offset;
> + int ret;
> +
> + if (!s->nb_dirty_bitmaps) {
> + s->dirty_bitmaps = NULL;
> + s->dirty_bitmaps_size = 0;
> + return 0;
> + }
> +
> + offset = s->dirty_bitmaps_offset;
> + s->dirty_bitmaps = g_new0(QCowDirtyBitmap, s->nb_dirty_bitmaps);
> +
> + for (i = 0; i < s->nb_dirty_bitmaps; i++) {
> + /* Read statically sized part of the dirty_bitmap header */
> + offset = align_offset(offset, 8);
> + ret = bdrv_pread(bs->file, offset, &h, sizeof(h));
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + offset += sizeof(h);
> + bm = s->dirty_bitmaps + i;
> + bm->l1_table_offset = be64_to_cpu(h.l1_table_offset);
> + bm->l1_size = be32_to_cpu(h.l1_size);
> + bm->bitmap_granularity = be32_to_cpu(h.bitmap_granularity);
> + bm->bitmap_size = be64_to_cpu(h.bitmap_size);
> +
> + name_size = be16_to_cpu(h.name_size);
> +
> + /* Read dirty_bitmap name */
> + bm->name = g_malloc(name_size + 1);
> + ret = bdrv_pread(bs->file, offset, bm->name, name_size);
> + if (ret < 0) {
> + goto fail;
> + }
> + offset += name_size;
> + bm->name[name_size] = '\0';
> +
> + if (offset - s->dirty_bitmaps_offset > QCOW_MAX_DIRTY_BITMAPS_SIZE) {
> + ret = -EFBIG;
> + goto fail;
> + }
> + }
> +
> + assert(offset - s->dirty_bitmaps_offset <= INT_MAX);
> + s->dirty_bitmaps_size = offset - s->dirty_bitmaps_offset;
> + return 0;
> +
> +fail:
> + qcow2_free_dirty_bitmaps(bs);
> + return ret;
> +}
> +
> +/* Add at the end of the file a new table of dirty bitmaps */
> +static int qcow2_write_dirty_bitmaps(BlockDriverState *bs)
> +{
> + BDRVQcowState *s = bs->opaque;
> + QCowDirtyBitmap *bm;
> + QCowDirtyBitmapHeader h;
> + int i, name_size, dirty_bitmaps_size;
> + int64_t offset, dirty_bitmaps_offset = 0;
> + int ret;
> +
> + int old_dirty_bitmaps_size = s->dirty_bitmaps_size;
> + int64_t old_dirty_bitmaps_offset = s->dirty_bitmaps_offset;
> +
> + /* Compute the size of the dirty bitmaps table */
> + offset = 0;
> + for (i = 0; i < s->nb_dirty_bitmaps; i++) {
> + bm = s->dirty_bitmaps + i;
> + offset = align_offset(offset, 8);
> + offset += sizeof(h);
> + offset += strlen(bm->name);
> +
> + if (offset > QCOW_MAX_DIRTY_BITMAPS_SIZE) {
> + ret = -EFBIG;
> + goto fail;
> + }
> + }
> +
> + assert(offset <= INT_MAX);
> + dirty_bitmaps_size = offset;
> +
> + /* Allocate space for the new dirty bitmap table */
> + dirty_bitmaps_offset = qcow2_alloc_clusters(bs, dirty_bitmaps_size);
> + offset = dirty_bitmaps_offset;
> + if (offset < 0) {
> + ret = offset;
> + goto fail;
> + }
> + ret = bdrv_flush(bs);
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + /* The dirty bitmap table position has not yet been updated, so these
> + * clusters must indeed be completely free */
> + ret = qcow2_pre_write_overlap_check(bs, 0, offset, dirty_bitmaps_size);
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + /* Write all dirty bitmaps to the new table */
> + for (i = 0; i < s->nb_dirty_bitmaps; i++) {
> + bm = s->dirty_bitmaps + i;
> + memset(&h, 0, sizeof(h));
> + h.l1_table_offset = cpu_to_be64(bm->l1_table_offset);
> + h.l1_size = cpu_to_be32(bm->l1_size);
> + h.bitmap_granularity = cpu_to_be32(bm->bitmap_granularity);
> + h.bitmap_size = cpu_to_be64(bm->bitmap_size);
> +
> + name_size = strlen(bm->name);
> + assert(name_size <= UINT16_MAX);
> + h.name_size = cpu_to_be16(name_size);
> + offset = align_offset(offset, 8);
> +
> + ret = bdrv_pwrite(bs->file, offset, &h, sizeof(h));
> + if (ret < 0) {
> + goto fail;
> + }
> + offset += sizeof(h);
> +
> + ret = bdrv_pwrite(bs->file, offset, bm->name, name_size);
> + if (ret < 0) {
> + goto fail;
> + }
> + offset += name_size;
> + }
> +
> + /*
> + * Update the header extension to point to the new dirty bitmap table.
> This
> + * requires the new table and its refcounts to be stable on disk.
> + */
> + ret = bdrv_flush(bs);
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + s->dirty_bitmaps_offset = dirty_bitmaps_offset;
> + s->dirty_bitmaps_size = dirty_bitmaps_size;
> + ret = qcow2_update_header(bs);
> + if (ret < 0) {
> + fprintf(stderr, "Could not update qcow2 header\n");
> + goto fail;
> + }
> +
> + /* Free old dirty bitmap table */
> + qcow2_free_clusters(bs, old_dirty_bitmaps_offset, old_dirty_bitmaps_size,
> + QCOW2_DISCARD_ALWAYS);
> + return 0;
> +
> +fail:
> + if (dirty_bitmaps_offset > 0) {
> + qcow2_free_clusters(bs, dirty_bitmaps_offset, dirty_bitmaps_size,
> + QCOW2_DISCARD_ALWAYS);
> + }
> + return ret;
> +}
> +
> +static int find_dirty_bitmap_by_name(BlockDriverState *bs,
> + const char *name)
> +{
> + BDRVQcowState *s = bs->opaque;
> + int i;
> +
> + for (i = 0; i < s->nb_dirty_bitmaps; i++) {
> + if (!strcmp(s->dirty_bitmaps[i].name, name)) {
> + return i;
> + }
> + }
> +
> + return -1;
> +}
> +
> +uint8_t *qcow2_dirty_bitmap_load(BlockDriverState *bs,
> + const char *name, uint64_t size,
> + int granularity)
> +{
> + BDRVQcowState *s = bs->opaque;
> + int i, dirty_bitmap_index, ret;
> + uint64_t offset;
> + QCowDirtyBitmap *bm;
> + uint64_t *l1_table;
> + uint8_t *buf;
> +
> + dirty_bitmap_index = find_dirty_bitmap_by_name(bs, name);
> + if (dirty_bitmap_index < 0) {
> + return NULL;
> + }
> + bm = &s->dirty_bitmaps[dirty_bitmap_index];
> +
> + if (size != bm->bitmap_size || granularity != bm->bitmap_granularity) {
> + return NULL;
> + }
> +
> + l1_table = g_malloc(bm->l1_size * sizeof(uint64_t));
> + ret = bdrv_pread(bs->file, bm->l1_table_offset, l1_table,
> + bm->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + buf = g_malloc0(bm->l1_size * s->cluster_size);
> + for (i = 0; i < bm->l1_size; ++i) {
> + offset = be64_to_cpu(l1_table[i]);
> + if (!(offset & 1)) {
> + ret = bdrv_pread(bs->file, offset, buf + i * s->cluster_size,
> + s->cluster_size);
> + if (ret < 0) {
> + goto fail;
> + }
> + }
> + }
> +
> + g_free(l1_table);
> + return buf;
> +
> +fail:
> + g_free(l1_table);
> + return NULL;
> +}
> +
> +int qcow2_dirty_bitmap_store(BlockDriverState *bs, uint8_t *buf,
> + const char *name, uint64_t size,
> + int granularity)
> +{
> + BDRVQcowState *s = bs->opaque;
> + int cl_size = s->cluster_size;
> + int i, dirty_bitmap_index, ret = 0, n;
> + uint64_t *l1_table;
> + QCowDirtyBitmap *bm;
> + uint64_t buf_size;
> + uint8_t *p;
> + int sector_granularity = granularity >> BDRV_SECTOR_BITS;
> +
> + /* find/create dirty bitmap */
> + dirty_bitmap_index = find_dirty_bitmap_by_name(bs, name);
> + if (dirty_bitmap_index >= 0) {
> + bm = s->dirty_bitmaps + dirty_bitmap_index;
> +
> + if (size != bm->bitmap_size ||
> + granularity != bm->bitmap_granularity) {
> + qcow2_dirty_bitmap_delete(bs, name, NULL);
> + dirty_bitmap_index = -1;
> + }
> + }
> + if (dirty_bitmap_index < 0) {
> + qcow2_dirty_bitmap_create(bs, name, size, granularity);
> + dirty_bitmap_index = s->nb_dirty_bitmaps - 1;
> + }
> + bm = s->dirty_bitmaps + dirty_bitmap_index;
I catch a segfault right around here if I do the following:
./x86_64-softmmu/qemu-system-x86_64 --dirty-bitmap
file=bitmaps.qcow2,name=bitmap0,drive=drive0 -drive
if=none,file=hda.qcow2,id=drive0 -device ide-hd,drive=drive0
hda.qcow2 and bitmaps.qcow2 are both empty files, but bitmaps.qcow2 has
a size of '0'.
Then when I click close in the QEMU GTK frontend, we hit a segfault when
trying to close because s->dirty_bitmaps is NULL, because it appears as
if we've never actually tried to add the (empty) bitmap to the (empty) file.
Your iotest works, but I am not actually sure why, because I don't
actually know how to *create* a persistent bitmap. I thought that the
-dirty-bitmap CLI would create one in the file specified with file=, but
it apparently only creates an in-memory bitmap and sets the file
pointer, but never initializes any of these structures. Then, when we go
to close, it gets confused and everything breaks a bit.
> +
> + /* read l1 table */
> + l1_table = g_malloc(bm->l1_size * sizeof(uint64_t));
> + ret = bdrv_pread(bs->file, bm->l1_table_offset, l1_table,
> + bm->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + goto finish;
> + }
> +
> + buf_size = (((size - 1) / sector_granularity) >> 3) + 1;
> + buf_size = align_offset(buf_size, 4);
> + n = buf_size / cl_size;
> + p = buf;
> + for (i = 0; i < bm->l1_size; ++i) {
> + uint64_t addr = be64_to_cpu(l1_table[i]) & ~511;
> + int write_size = (i == n ? (buf_size % cl_size) : cl_size);
> +
> + if (buffer_is_zero(p, write_size)) {
> + if (addr) {
> + qcow2_free_clusters(bs, addr, cl_size,
> + QCOW2_DISCARD_ALWAYS);
> + }
> + l1_table[i] = cpu_to_be64(1);
> + } else {
> + if (!addr) {
> + addr = qcow2_alloc_clusters(bs, cl_size);
> + l1_table[i] = cpu_to_be64(addr);
> + }
> +
> + ret = bdrv_pwrite(bs->file, addr, p, write_size);
> + if (ret < 0) {
> + goto finish;
> + }
> + }
> +
> + p += cl_size;
> + }
> +
> + ret = bdrv_pwrite(bs->file, bm->l1_table_offset, l1_table,
> + bm->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + goto finish;
> + }
> +
> +finish:
> + g_free(l1_table);
> + return ret;
> +}
> +/* if no id is provided, a new one is constructed */
> +int qcow2_dirty_bitmap_create(BlockDriverState *bs, const char *name,
> + uint64_t size, int granularity)
> +{
> + BDRVQcowState *s = bs->opaque;
> + QCowDirtyBitmap *new_dirty_bitmap_list = NULL;
> + QCowDirtyBitmap *old_dirty_bitmap_list = NULL;
> + QCowDirtyBitmap sn1, *bm = &sn1;
> + int i, ret;
> + uint64_t *l1_table = NULL;
> + int64_t l1_table_offset;
> + int sector_granularity = granularity >> BDRV_SECTOR_BITS;
> +
> + if (s->nb_dirty_bitmaps >= QCOW_MAX_DIRTY_BITMAPS) {
> + return -EFBIG;
> + }
> +
> + memset(bm, 0, sizeof(*bm));
> +
> + /* Check that the ID is unique */
> + if (find_dirty_bitmap_by_name(bs, name) >= 0) {
> + return -EEXIST;
> + }
> +
> + /* Populate bm with passed data */
> + bm->name = g_strdup(name);
> + bm->bitmap_granularity = granularity;
> + bm->bitmap_size = size;
> +
> + bm->l1_size =
> + size_to_clusters(s, (((size - 1) / sector_granularity) >> 3) + 1);
> + l1_table_offset =
> + qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t));
> + if (l1_table_offset < 0) {
> + ret = l1_table_offset;
> + goto fail;
> + }
> + bm->l1_table_offset = l1_table_offset;
> +
> + l1_table = g_try_new(uint64_t, bm->l1_size);
> + if (l1_table == NULL) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + /* initialize with zero clusters */
> + for (i = 0; i < s->l1_size; i++) {
> + l1_table[i] = cpu_to_be64(1);
bm->l1_size here in my crash output is just "1",
but s->l1_size is 16, so we crash all over this array.
I assume you meant bm->l1_size here. This is a good case to make against
calling everything "L1."
> + }
> +
> + ret = qcow2_pre_write_overlap_check(bs, 0, bm->l1_table_offset,
> + s->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + ret = bdrv_pwrite(bs->file, bm->l1_table_offset, l1_table,
> + s->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + g_free(l1_table);
I can also catch a segfault here by doing something like this:
./x86_64-softmmu/qemu-system-x86_64 -drive
if=none,format=qcow2,cache=writethrough,file=hda.qcow2,id=drive0
-dirty-bitmap name=bitmap,drive=drive0
Trying to mimick your iotest which does not use an external bitmap file
-- it uses the implicit self-storage.
In this case, hda.qcow2 is still empty (but sized as 8GB) and I try to
quit before any writes occur.
freeing l1_table here causes memory corruption and even valgrind goes
down in flames:
==13284== Invalid write of size 8
==13284== at 0x53A15E: qcow2_dirty_bitmap_create
(qcow2-dirty-bitmap.c:406)
==13284== by 0x539D15: qcow2_dirty_bitmap_store
(qcow2-dirty-bitmap.c:307)
==13284== by 0x505F27: bdrv_store_dirty_bitmap (block.c:3176)
==13284== by 0x50306D: bdrv_close (block.c:1739)
==13284== by 0x5032FF: bdrv_close_all (block.c:1797)
==13284== by 0x3049DC: main (vl.c:4577)
==13284== Address 0x239b7978 is 0 bytes after a block of size 8 alloc'd
==13284== at 0x4A06BCF: malloc (vg_replace_malloc.c:296)
==13284== by 0x300111: malloc_and_trace (vl.c:2706)
==13284== by 0x62B954E: g_try_malloc (gmem.c:242)
==13284== by 0x53A11E: qcow2_dirty_bitmap_create
(qcow2-dirty-bitmap.c:398)
==13284== by 0x539D15: qcow2_dirty_bitmap_store
(qcow2-dirty-bitmap.c:307)
==13284== by 0x505F27: bdrv_store_dirty_bitmap (block.c:3176)
==13284== by 0x50306D: bdrv_close (block.c:1739)
==13284== by 0x5032FF: bdrv_close_all (block.c:1797)
==13284== by 0x3049DC: main (vl.c:4577)
==13284==
--13284-- VALGRIND INTERNAL ERROR: Valgrind received a signal 11
(SIGSEGV) - exiting
--13284-- si_code=80; Faulting address: 0x0; sp: 0x8090a1de0
valgrind: the 'impossible' happened:
Killed by fatal signal
> + l1_table = NULL;
> +
> + /* Append the new dirty bitmap to the dirty bitmap list */
> + new_dirty_bitmap_list = g_new(QCowDirtyBitmap, s->nb_dirty_bitmaps + 1);
> + if (s->dirty_bitmaps) {
> + memcpy(new_dirty_bitmap_list, s->dirty_bitmaps,
> + s->nb_dirty_bitmaps * sizeof(QCowDirtyBitmap));
> + old_dirty_bitmap_list = s->dirty_bitmaps;
> + }
> + s->dirty_bitmaps = new_dirty_bitmap_list;
> + s->dirty_bitmaps[s->nb_dirty_bitmaps++] = *bm;
> +
> + ret = qcow2_write_dirty_bitmaps(bs);
> + if (ret < 0) {
> + g_free(s->dirty_bitmaps);
> + s->dirty_bitmaps = old_dirty_bitmap_list;
> + s->nb_dirty_bitmaps--;
> + goto fail;
> + }
> +
> + g_free(old_dirty_bitmap_list);
> +
> + return 0;
> +
Disk is 8GiB, 16,777,216 sectors, and bm->bitmap_size matches that.
> +fail:
> + g_free(bm->name);
> + g_free(l1_table);
> +
> + return ret;
> +}
> +
> +static int qcow2_dirty_bitmap_free_clusters(BlockDriverState *bs,
> + QCowDirtyBitmap *bm)
> +{
> + BDRVQcowState *s = bs->opaque;
> + int ret, i;
> + uint64_t *l1_table = g_new(uint64_t, bm->l1_size);
> +
> + ret = bdrv_pread(bs->file, bm->l1_table_offset, l1_table,
> + bm->l1_size * sizeof(uint64_t));
> + if (ret < 0) {
> + g_free(l1_table);
> + return ret;
> + }
> +
> + for (i = 0; i < bm->l1_size; ++i) {
> + uint64_t addr = be64_to_cpu(l1_table[i]);
> + qcow2_free_clusters(bs, addr, s->cluster_size, QCOW2_DISCARD_ALWAYS);
> + }
> +
> + qcow2_free_clusters(bs, bm->l1_table_offset, bm->l1_size *
> sizeof(uint64_t),
> + QCOW2_DISCARD_ALWAYS);
> +
> + g_free(l1_table);
> + return 0;
> +}
> +
> +int qcow2_dirty_bitmap_delete(BlockDriverState *bs,
> + const char *name,
> + Error **errp)
> +{
> + BDRVQcowState *s = bs->opaque;
> + QCowDirtyBitmap bm;
> + int dirty_bitmap_index, ret = 0;
> +
> + /* Search the dirty_bitmap */
> + dirty_bitmap_index = find_dirty_bitmap_by_name(bs, name);
> + if (dirty_bitmap_index < 0) {
> + error_setg(errp, "Can't find the dirty bitmap");
> + return -ENOENT;
> + }
> + bm = s->dirty_bitmaps[dirty_bitmap_index];
> +
> + /* Remove it from the dirty_bitmap list */
> + memmove(s->dirty_bitmaps + dirty_bitmap_index,
> + s->dirty_bitmaps + dirty_bitmap_index + 1,
> + (s->nb_dirty_bitmaps - dirty_bitmap_index - 1) * sizeof(bm));
> + s->nb_dirty_bitmaps--;
> + ret = qcow2_write_dirty_bitmaps(bs);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret,
> + "Failed to remove dirty bitmap"
> + " from dirty bitmap list");
> + return ret;
> + }
> +
> + qcow2_dirty_bitmap_free_clusters(bs, &bm);
> + g_free(bm.name);
> +
> + return ret;
> +}
> diff --git a/block/qcow2.c b/block/qcow2.c
> index b9a72e3..406e55d 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -61,6 +61,7 @@ typedef struct {
> #define QCOW2_EXT_MAGIC_END 0
> #define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
> #define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
> +#define QCOW2_EXT_MAGIC_DIRTY_BITMAPS 0x23852875
>
> static int qcow2_probe(const uint8_t *buf, int buf_size, const char
> *filename)
> {
> @@ -90,6 +91,7 @@ static int qcow2_read_extensions(BlockDriverState *bs,
> uint64_t start_offset,
> QCowExtension ext;
> uint64_t offset;
> int ret;
> + Qcow2DirtyBitmapHeaderExt dirty_bitmaps_ext;
>
> #ifdef DEBUG_EXT
> printf("qcow2_read_extensions: start=%ld end=%ld\n", start_offset,
> end_offset);
> @@ -160,6 +162,33 @@ static int qcow2_read_extensions(BlockDriverState *bs,
> uint64_t start_offset,
> }
> break;
>
> + case QCOW2_EXT_MAGIC_DIRTY_BITMAPS:
> + ret = bdrv_pread(bs->file, offset, &dirty_bitmaps_ext, ext.len);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret, "ERROR: dirty_bitmaps_ext: "
> + "Could not read ext header");
> + return ret;
> + }
> +
> + be64_to_cpus(&dirty_bitmaps_ext.dirty_bitmaps_offset);
> + be32_to_cpus(&dirty_bitmaps_ext.nb_dirty_bitmaps);
> +
> + s->dirty_bitmaps_offset = dirty_bitmaps_ext.dirty_bitmaps_offset;
> + s->nb_dirty_bitmaps = dirty_bitmaps_ext.nb_dirty_bitmaps;
> +
> + ret = qcow2_read_dirty_bitmaps(bs);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret, "Could not read dirty bitmaps");
> + return ret;
> + }
> +
> +#ifdef DEBUG_EXT
> + printf("Qcow2: Got dirty bitmaps extension:"
> + " offset=%" PRIu64 " nb_bitmaps=%" PRIu32 "\n",
> + s->dirty_bitmaps_offset, s->nb_dirty_bitmaps);
> +#endif
> + break;
> +
> default:
> /* unknown magic - save it in case we need to rewrite the header
> */
> {
> @@ -1000,6 +1029,7 @@ static int qcow2_open(BlockDriverState *bs, QDict
> *options, int flags,
> g_free(s->unknown_header_fields);
> cleanup_unknown_header_ext(bs);
> qcow2_free_snapshots(bs);
> + qcow2_free_dirty_bitmaps(bs);
> qcow2_refcount_close(bs);
> qemu_vfree(s->l1_table);
> /* else pre-write overlap checks in cache_destroy may crash */
> @@ -1466,6 +1496,7 @@ static void qcow2_close(BlockDriverState *bs)
> qemu_vfree(s->cluster_data);
> qcow2_refcount_close(bs);
> qcow2_free_snapshots(bs);
> + qcow2_free_dirty_bitmaps(bs);
> }
>
> static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
> @@ -1667,6 +1698,21 @@ int qcow2_update_header(BlockDriverState *bs)
> buf += ret;
> buflen -= ret;
>
> + if (s->nb_dirty_bitmaps > 0) {
> + Qcow2DirtyBitmapHeaderExt dirty_bitmaps_header = {
> + .nb_dirty_bitmaps = cpu_to_be32(s->nb_dirty_bitmaps),
> + .dirty_bitmaps_offset = cpu_to_be64(s->dirty_bitmaps_offset)
> + };
> + ret = header_ext_add(buf, QCOW2_EXT_MAGIC_DIRTY_BITMAPS,
> + &dirty_bitmaps_header,
> sizeof(dirty_bitmaps_header),
> + buflen);
> + if (ret < 0) {
> + goto fail;
> + }
> + buf += ret;
> + buflen -= ret;
> + }
> +
> /* Keep unknown header extensions */
> QLIST_FOREACH(uext, &s->unknown_header_ext, next) {
> ret = header_ext_add(buf, uext->magic, uext->data, uext->len,
> buflen);
> @@ -2176,6 +2222,12 @@ static int qcow2_truncate(BlockDriverState *bs,
> int64_t offset)
> return -ENOTSUP;
> }
>
> + /* cannot proceed if image has dirty_bitmaps */
> + if (s->nb_dirty_bitmaps) {
> + error_report("Can't resize an image which has dirty bitmaps");
> + return -ENOTSUP;
> + }
> +
> /* shrinking is currently not supported */
> if (offset < bs->total_sectors * 512) {
> error_report("qcow2 doesn't support shrinking images yet");
> @@ -2952,6 +3004,10 @@ BlockDriver bdrv_qcow2 = {
> .bdrv_get_info = qcow2_get_info,
> .bdrv_get_specific_info = qcow2_get_specific_info,
>
> + .bdrv_dirty_bitmap_load = qcow2_dirty_bitmap_load,
> + .bdrv_dirty_bitmap_store = qcow2_dirty_bitmap_store,
> + .bdrv_dirty_bitmap_delete = qcow2_dirty_bitmap_delete,
> +
> .bdrv_save_vmstate = qcow2_save_vmstate,
> .bdrv_load_vmstate = qcow2_load_vmstate,
>
> diff --git a/block/qcow2.h b/block/qcow2.h
> index 422b825..24beee0 100644
> --- a/block/qcow2.h
> +++ b/block/qcow2.h
> @@ -39,6 +39,7 @@
>
> #define QCOW_MAX_CRYPT_CLUSTERS 32
> #define QCOW_MAX_SNAPSHOTS 65536
> +#define QCOW_MAX_DIRTY_BITMAPS 65536
>
> /* 8 MB refcount table is enough for 2 PB images at 64k cluster size
> * (128 GB for 512 byte clusters, 2 EB for 2 MB clusters) */
> @@ -52,6 +53,8 @@
> * space for snapshot names and IDs */
> #define QCOW_MAX_SNAPSHOTS_SIZE (1024 * QCOW_MAX_SNAPSHOTS)
>
> +#define QCOW_MAX_DIRTY_BITMAPS_SIZE (1024 * QCOW_MAX_DIRTY_BITMAPS)
> +
> /* indicate that the refcount of the referenced cluster is exactly one. */
> #define QCOW_OFLAG_COPIED (1ULL << 63)
> /* indicate that the cluster is compressed (they never have the copied flag)
> */
> @@ -138,6 +141,19 @@ typedef struct QEMU_PACKED QCowSnapshotHeader {
> /* name follows */
> } QCowSnapshotHeader;
>
> +typedef struct QEMU_PACKED QCowDirtyBitmapHeader {
> + /* header is 8 byte aligned */
> + uint64_t l1_table_offset;
> +
> + uint32_t l1_size;
> + uint32_t bitmap_granularity;
> +
> + uint64_t bitmap_size;
> + uint16_t name_size;
> +
> + /* name follows */
> +} QCowDirtyBitmapHeader;
> +
> typedef struct QEMU_PACKED QCowSnapshotExtraData {
> uint64_t vm_state_size_large;
> uint64_t disk_size;
> @@ -156,6 +172,14 @@ typedef struct QCowSnapshot {
> uint64_t vm_clock_nsec;
> } QCowSnapshot;
>
> +typedef struct QCowDirtyBitmap {
> + uint64_t l1_table_offset;
> + uint32_t l1_size;
> + char *name;
> + int bitmap_granularity;
> + uint64_t bitmap_size;
> +} QCowDirtyBitmap;
> +
> struct Qcow2Cache;
> typedef struct Qcow2Cache Qcow2Cache;
>
> @@ -218,6 +242,11 @@ typedef uint64_t Qcow2GetRefcountFunc(const void
> *refcount_array,
> typedef void Qcow2SetRefcountFunc(void *refcount_array,
> uint64_t index, uint64_t value);
>
> +typedef struct Qcow2DirtyBitmapHeaderExt {
> + uint32_t nb_dirty_bitmaps;
> + uint64_t dirty_bitmaps_offset;
> +} QEMU_PACKED Qcow2DirtyBitmapHeaderExt;
> +
> typedef struct BDRVQcowState {
> int cluster_bits;
> int cluster_size;
> @@ -259,6 +288,11 @@ typedef struct BDRVQcowState {
> unsigned int nb_snapshots;
> QCowSnapshot *snapshots;
>
> + uint64_t dirty_bitmaps_offset;
> + int dirty_bitmaps_size;
> + unsigned int nb_dirty_bitmaps;
> + QCowDirtyBitmap *dirty_bitmaps;
> +
> int flags;
> int qcow_version;
> bool use_lazy_refcounts;
> @@ -570,6 +604,22 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs,
> void qcow2_free_snapshots(BlockDriverState *bs);
> int qcow2_read_snapshots(BlockDriverState *bs);
>
> +/* qcow2-dirty-bitmap.c functions */
> +int qcow2_dirty_bitmap_store(BlockDriverState *bs, uint8_t *buf,
> + const char *name, uint64_t size,
> + int granularity);
> +uint8_t *qcow2_dirty_bitmap_load(BlockDriverState *bs,
> + const char *name, uint64_t size,
> + int granularity);
> +int qcow2_dirty_bitmap_create(BlockDriverState *bs, const char *name,
> + uint64_t size, int granularity);
> +int qcow2_dirty_bitmap_delete(BlockDriverState *bs,
> + const char *name,
> + Error **errp);
> +
> +void qcow2_free_dirty_bitmaps(BlockDriverState *bs);
> +int qcow2_read_dirty_bitmaps(BlockDriverState *bs);
> +
> /* qcow2-cache.c functions */
> Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables);
> int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c);
> diff --git a/include/block/block_int.h b/include/block/block_int.h
> index db29b74..88855b4 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -206,6 +206,16 @@ struct BlockDriver {
> int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi);
> ImageInfoSpecific *(*bdrv_get_specific_info)(BlockDriverState *bs);
>
> + int (*bdrv_dirty_bitmap_store)(BlockDriverState *bs, uint8_t *buf,
> + const char *name, uint64_t size,
> + int granularity);
> + uint8_t *(*bdrv_dirty_bitmap_load)(BlockDriverState *bs,
> + const char *name, uint64_t size,
> + int granularity);
> + int (*bdrv_dirty_bitmap_delete)(BlockDriverState *bs,
> + const char *name,
> + Error **errp);
> +
> int (*bdrv_save_vmstate)(BlockDriverState *bs, QEMUIOVector *qiov,
> int64_t pos);
> int (*bdrv_load_vmstate)(BlockDriverState *bs, uint8_t *buf,
>
In light of this, some "sanity" tests that test cases like no writes,
empty bitmaps, empty files, etc I think will be appropriate.
- [Qemu-devel] [PATCH 7/8] qemu: command line option for dirty bitmaps, (continued)
Re: [Qemu-devel] [PATCH 2/8] qcow2: add dirty-bitmaps feature,
John Snow <=
Re: [Qemu-devel] [PATCH 2/8] qcow2: add dirty-bitmaps feature, John Snow, 2015/06/12
Re: [Qemu-devel] [PATCH v2 RFC 0/8] block: persistent dirty bitmaps, Stefan Hajnoczi, 2015/06/10
Re: [Qemu-devel] [PATCH v2 RFC 0/8] block: persistent dirty bitmaps, Stefan Hajnoczi, 2015/06/11
Re: [Qemu-devel] [PATCH v2 RFC 0/8] block: persistent dirty bitmaps, John Snow, 2015/06/12