@@ -1,5 +1,5 @@
block-obj-y += raw_bsd.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.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
new file mode 100644
@@ -0,0 +1,514 @@
+/*
+ * 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 list 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;
+ struct {
+ uint32_t nb_dirty_bitmaps;
+ uint64_t dirty_bitmaps_offset;
+ } QEMU_PACKED header_data;
+ int64_t offset, dirty_bitmaps_offset = 0;
+ int ret;
+
+ /* compute the size of the dirty bitmaps */
+ 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 list */
+ 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 list 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 list */
+ 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 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;
+ }
+
+ QEMU_BUILD_BUG_ON(offsetof(QCowHeader, dirty_bitmaps_offset) !=
+ offsetof(QCowHeader, nb_dirty_bitmaps) +
+ sizeof(header_data.nb_dirty_bitmaps));
+
+ header_data.nb_dirty_bitmaps = cpu_to_be32(s->nb_dirty_bitmaps);
+ header_data.dirty_bitmaps_offset = cpu_to_be64(dirty_bitmaps_offset);
+
+ ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, nb_dirty_bitmaps),
+ &header_data, sizeof(header_data));
+ if (ret < 0) {
+ goto fail;
+ }
+
+ /* free the old dirty bitmap table */
+ qcow2_free_clusters(bs, s->dirty_bitmaps_offset, s->dirty_bitmaps_size,
+ QCOW2_DISCARD_ALWAYS);
+ s->dirty_bitmaps_offset = dirty_bitmaps_offset;
+ s->dirty_bitmaps_size = dirty_bitmaps_size;
+ 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;
+
+ /* 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);
+ }
+
+ 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);
+ 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;
+
+fail:
+ g_free(bm->name);
+ g_free(l1_table);
+
+ return ret;
+}
+
+int qcow2_dirty_bitmap_delete(BlockDriverState *bs,
+ const char *name,
+ Error **errp)
+{
+ BDRVQcowState *s = bs->opaque;
+ QCowDirtyBitmap bm;
+ int dirty_bitmap_index, ret = 0, i;
+ uint64_t *l1_table;
+
+ /* 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;
+ }
+
+ /*
+ * The dirty_bitmap is now unused, clean up. If we fail after this point, we
+ * won't recover but just leak clusters.
+ */
+ g_free(bm.name);
+
+ /*
+ * Now decrease the refcounts of clusters referenced by the dirty_bitmap and
+ * free the L1 table.
+ */
+ l1_table = g_try_new(uint64_t, bm.l1_size);
+ if (l1_table == NULL) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+ ret = bdrv_pread(bs->file, bm.l1_table_offset, l1_table,
+ s->l1_size * sizeof(uint64_t));
+ if (ret < 0) {
+ goto finish;
+ }
+
+ 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);
+
+finish:
+ g_free(l1_table);
+ return ret;
+}
@@ -570,6 +570,8 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
be32_to_cpus(&header.refcount_table_clusters);
be64_to_cpus(&header.snapshots_offset);
be32_to_cpus(&header.nb_snapshots);
+ be64_to_cpus(&header.dirty_bitmaps_offset);
+ be32_to_cpus(&header.nb_dirty_bitmaps);
if (header.magic != QCOW_MAGIC) {
error_setg(errp, "Image is not in qcow2 format");
@@ -892,6 +894,16 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
goto fail;
}
+ /* Internal bitmaps */
+ s->dirty_bitmaps_offset = header.dirty_bitmaps_offset;
+ s->nb_dirty_bitmaps = header.nb_dirty_bitmaps;
+
+ ret = qcow2_read_dirty_bitmaps(bs);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Could not read dirty bitmaps");
+ goto fail;
+ }
+
/* Clear unknown autoclear feature bits */
if (!bs->read_only && !(flags & BDRV_O_INCOMING) && s->autoclear_features) {
s->autoclear_features = 0;
@@ -994,6 +1006,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 */
@@ -1457,6 +1470,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)
@@ -1579,6 +1593,8 @@ int qcow2_update_header(BlockDriverState *bs)
.refcount_table_clusters = cpu_to_be32(refcount_table_clusters),
.nb_snapshots = cpu_to_be32(s->nb_snapshots),
.snapshots_offset = cpu_to_be64(s->snapshots_offset),
+ .nb_dirty_bitmaps = cpu_to_be32(s->nb_dirty_bitmaps),
+ .dirty_bitmaps_offset = cpu_to_be64(s->dirty_bitmaps_offset),
/* Version 3 fields */
.incompatible_features = cpu_to_be64(s->incompatible_features),
@@ -2123,6 +2139,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");
@@ -2888,6 +2910,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,
@@ -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) */
@@ -116,6 +119,9 @@ typedef struct QCowHeader {
uint32_t refcount_order;
uint32_t header_length;
+
+ uint32_t nb_dirty_bitmaps;
+ uint64_t dirty_bitmaps_offset;
} QEMU_PACKED QCowHeader;
typedef struct QEMU_PACKED QCowSnapshotHeader {
@@ -138,6 +144,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 +175,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;
@@ -254,6 +281,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;
@@ -558,6 +590,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);
@@ -204,6 +204,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,
Adds dirty-bitmaps feature to qcow2 format as specified in docs/specs/qcow2.txt Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@parallels.com> --- block/Makefile.objs | 2 +- block/qcow2-dirty-bitmap.c | 514 +++++++++++++++++++++++++++++++++++++++++++++ block/qcow2.c | 26 +++ block/qcow2.h | 48 +++++ include/block/block_int.h | 10 + 5 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 block/qcow2-dirty-bitmap.c