[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-block] [Qemu-devel] [PATCH v20 11/30] qcow2: autoloading dirty
From: |
John Snow |
Subject: |
Re: [Qemu-block] [Qemu-devel] [PATCH v20 11/30] qcow2: autoloading dirty bitmaps |
Date: |
Fri, 2 Jun 2017 17:48:55 -0400 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.1.0 |
On 06/02/2017 07:21 AM, Vladimir Sementsov-Ogievskiy wrote:
> Auto loading bitmaps are bitmaps in Qcow2, with the AUTO flag set. They
> are loaded when the image is opened and become BdrvDirtyBitmaps for the
> corresponding drive.
>
> Extra data in bitmaps is not supported for now.
>
> Signed-off-by: Vladimir Sementsov-Ogievskiy <address@hidden>
> Reviewed-by: Max Reitz <address@hidden>
Reviewed-by: John Snow <address@hidden>
> ---
> block/qcow2-bitmap.c | 389
> +++++++++++++++++++++++++++++++++++++++++++++++++++
> block/qcow2.c | 17 ++-
> block/qcow2.h | 2 +
> 3 files changed, 406 insertions(+), 2 deletions(-)
>
> diff --git a/block/qcow2-bitmap.c b/block/qcow2-bitmap.c
> index b8e472b3e8..2c7b057e21 100644
> --- a/block/qcow2-bitmap.c
> +++ b/block/qcow2-bitmap.c
> @@ -44,6 +44,8 @@
>
> /* Bitmap directory entry flags */
> #define BME_RESERVED_FLAGS 0xfffffffcU
> +#define BME_FLAG_IN_USE (1U << 0)
> +#define BME_FLAG_AUTO (1U << 1)
>
> /* bits [1, 8] U [56, 63] are reserved */
> #define BME_TABLE_ENTRY_RESERVED_MASK 0xff000000000001feULL
> @@ -85,6 +87,23 @@ typedef enum BitmapType {
> BT_DIRTY_TRACKING_BITMAP = 1
> } BitmapType;
>
> +static inline bool can_write(BlockDriverState *bs)
> +{
> + return !bdrv_is_read_only(bs) && !(bdrv_get_flags(bs) & BDRV_O_INACTIVE);
> +}
> +
> +static int update_header_sync(BlockDriverState *bs)
> +{
> + int ret;
> +
> + ret = qcow2_update_header(bs);
> + if (ret < 0) {
> + return ret;
> + }
> +
> + return bdrv_flush(bs);
> +}
> +
> static int check_table_entry(uint64_t entry, int cluster_size)
> {
> uint64_t offset;
> @@ -146,6 +165,120 @@ fail:
> return ret;
> }
>
> +/* This function returns the number of disk sectors covered by a single qcow2
> + * cluster of bitmap data. */
> +static uint64_t sectors_covered_by_bitmap_cluster(const BDRVQcow2State *s,
> + const BdrvDirtyBitmap
> *bitmap)
> +{
> + uint32_t sector_granularity =
> + bdrv_dirty_bitmap_granularity(bitmap) >> BDRV_SECTOR_BITS;
> +
> + return (uint64_t)sector_granularity * (s->cluster_size << 3);
> +}
> +
> +/* load_bitmap_data
> + * @bitmap_table entries must satisfy specification constraints.
> + * @bitmap must be cleared */
> +static int load_bitmap_data(BlockDriverState *bs,
> + const uint64_t *bitmap_table,
> + uint32_t bitmap_table_size,
> + BdrvDirtyBitmap *bitmap)
> +{
> + int ret = 0;
> + BDRVQcow2State *s = bs->opaque;
> + uint64_t sector, sbc;
> + uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
> + uint8_t *buf = NULL;
> + uint64_t i, tab_size =
> + size_to_clusters(s,
> + bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size));
> +
> + if (tab_size != bitmap_table_size || tab_size > BME_MAX_TABLE_SIZE) {
> + return -EINVAL;
> + }
> +
> + buf = g_malloc(s->cluster_size);
> + sbc = sectors_covered_by_bitmap_cluster(s, bitmap);
> + for (i = 0, sector = 0; i < tab_size; ++i, sector += sbc) {
> + uint64_t count = MIN(bm_size - sector, sbc);
> + uint64_t entry = bitmap_table[i];
> + uint64_t offset = entry & BME_TABLE_ENTRY_OFFSET_MASK;
> +
> + assert(check_table_entry(entry, s->cluster_size) == 0);
> +
> + if (offset == 0) {
> + if (entry & BME_TABLE_ENTRY_FLAG_ALL_ONES) {
> + bdrv_dirty_bitmap_deserialize_ones(bitmap, sector, count,
> + false);
> + } else {
> + /* No need to deserialize zeros because the dirty bitmap is
> + * already cleared */
> + }
> + } else {
> + ret = bdrv_pread(bs->file, offset, buf, s->cluster_size);
> + if (ret < 0) {
> + goto finish;
> + }
> + bdrv_dirty_bitmap_deserialize_part(bitmap, buf, sector, count,
> + false);
> + }
> + }
> + ret = 0;
> +
> + bdrv_dirty_bitmap_deserialize_finish(bitmap);
> +
> +finish:
> + g_free(buf);
> +
> + return ret;
> +}
> +
> +static BdrvDirtyBitmap *load_bitmap(BlockDriverState *bs,
> + Qcow2Bitmap *bm, Error **errp)
> +{
> + int ret;
> + uint64_t *bitmap_table = NULL;
> + uint32_t granularity;
> + BdrvDirtyBitmap *bitmap = NULL;
> +
> + if (bm->flags & BME_FLAG_IN_USE) {
> + error_setg(errp, "Bitmap '%s' is in use", bm->name);
> + goto fail;
> + }
> +
> + ret = bitmap_table_load(bs, &bm->table, &bitmap_table);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret,
> + "Could not read bitmap_table table from image for "
> + "bitmap '%s'", bm->name);
> + goto fail;
> + }
> +
> + granularity = 1U << bm->granularity_bits;
> + bitmap = bdrv_create_dirty_bitmap(bs, granularity, bm->name, errp);
> + if (bitmap == NULL) {
> + goto fail;
> + }
> +
> + ret = load_bitmap_data(bs, bitmap_table, bm->table.size, bitmap);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret, "Could not read bitmap '%s' from image",
> + bm->name);
> + goto fail;
> + }
> +
> + g_free(bitmap_table);
> + return bitmap;
> +
> +fail:
> + g_free(bitmap_table);
> + if (bitmap != NULL) {
> + bdrv_release_dirty_bitmap(bs, bitmap);
> + }
> +
> + return NULL;
> +}
> +
> /*
> * Bitmap List
> */
> @@ -164,6 +297,15 @@ static inline void
> bitmap_dir_entry_to_cpu(Qcow2BitmapDirEntry *entry)
> be32_to_cpus(&entry->extra_data_size);
> }
>
> +static inline void bitmap_dir_entry_to_be(Qcow2BitmapDirEntry *entry)
> +{
> + cpu_to_be64s(&entry->bitmap_table_offset);
> + cpu_to_be32s(&entry->bitmap_table_size);
> + cpu_to_be32s(&entry->flags);
> + cpu_to_be16s(&entry->name_size);
> + cpu_to_be32s(&entry->extra_data_size);
> +}
> +
> static inline int calc_dir_entry_size(size_t name_size, size_t
> extra_data_size)
> {
> return align_offset(sizeof(Qcow2BitmapDirEntry) +
> @@ -224,6 +366,17 @@ static int check_dir_entry(BlockDriverState *bs,
> Qcow2BitmapDirEntry *entry)
> return fail ? -EINVAL : 0;
> }
>
> +static inline void bitmap_directory_to_be(uint8_t *dir, size_t size)
> +{
> + uint8_t *end = dir + size;
> + while (dir < end) {
> + Qcow2BitmapDirEntry *e = (Qcow2BitmapDirEntry *)dir;
> + dir += dir_entry_size(e);
> +
> + bitmap_dir_entry_to_be(e);
> + }
> +}
> +
> /*
> * Bitmap List public functions
> */
> @@ -258,6 +411,18 @@ static Qcow2BitmapList *bitmap_list_new(void)
> return bm_list;
> }
>
> +static uint32_t bitmap_list_count(Qcow2BitmapList *bm_list)
> +{
> + Qcow2Bitmap *bm;
> + uint32_t nb_bitmaps = 0;
> +
> + QSIMPLEQ_FOREACH(bm, bm_list, entry) {
> + nb_bitmaps++;
> + }
> +
> + return nb_bitmaps;
> +}
> +
> /* bitmap_list_load
> * Get bitmap list from qcow2 image. Actually reads bitmap directory,
> * checks it and convert to bitmap list.
> @@ -437,3 +602,227 @@ out:
>
> return ret;
> }
> +
> +/* bitmap_list_store
> + * Store bitmap list to qcow2 image as a bitmap directory.
> + * Everything is checked.
> + */
> +static int bitmap_list_store(BlockDriverState *bs, Qcow2BitmapList *bm_list,
> + uint64_t *offset, uint64_t *size, bool in_place)
> +{
> + int ret;
> + uint8_t *dir;
> + int64_t dir_offset = 0;
> + uint64_t dir_size = 0;
> + Qcow2Bitmap *bm;
> + Qcow2BitmapDirEntry *e;
> +
> + QSIMPLEQ_FOREACH(bm, bm_list, entry) {
> + dir_size += calc_dir_entry_size(strlen(bm->name), 0);
> + }
> +
> + if (dir_size == 0 || dir_size > QCOW2_MAX_BITMAP_DIRECTORY_SIZE) {
> + return -EINVAL;
> + }
> +
> + if (in_place) {
> + if (*size != dir_size || *offset == 0) {
> + return -EINVAL;
> + }
> +
> + dir_offset = *offset;
> + }
> +
> + dir = g_try_malloc(dir_size);
> + if (dir == NULL) {
> + return -ENOMEM;
> + }
> +
> + e = (Qcow2BitmapDirEntry *)dir;
> + QSIMPLEQ_FOREACH(bm, bm_list, entry) {
> + e->bitmap_table_offset = bm->table.offset;
> + e->bitmap_table_size = bm->table.size;
> + e->flags = bm->flags;
> + e->type = BT_DIRTY_TRACKING_BITMAP;
> + e->granularity_bits = bm->granularity_bits;
> + e->name_size = strlen(bm->name);
> + e->extra_data_size = 0;
> + memcpy(e + 1, bm->name, e->name_size);
> +
> + if (check_dir_entry(bs, e) < 0) {
> + ret = -EINVAL;
> + goto fail;
> + }
> +
> + e = next_dir_entry(e);
> + }
> +
> + bitmap_directory_to_be(dir, dir_size);
> +
> + if (!in_place) {
> + dir_offset = qcow2_alloc_clusters(bs, dir_size);
> + if (dir_offset < 0) {
> + ret = dir_offset;
> + goto fail;
> + }
> + }
> +
> + ret = qcow2_pre_write_overlap_check(bs, 0, dir_offset, dir_size);
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + ret = bdrv_pwrite(bs->file, dir_offset, dir, dir_size);
> + if (ret < 0) {
> + goto fail;
> + }
> +
> + g_free(dir);
> +
> + if (!in_place) {
> + *size = dir_size;
> + *offset = dir_offset;
> + }
> +
> + return 0;
> +
> +fail:
> + g_free(dir);
> +
> + if (!in_place && dir_offset > 0) {
> + qcow2_free_clusters(bs, dir_offset, dir_size, QCOW2_DISCARD_OTHER);
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * Bitmap List end
> + */
> +
> +static int update_ext_header_and_dir_in_place(BlockDriverState *bs,
> + Qcow2BitmapList *bm_list)
> +{
> + BDRVQcow2State *s = bs->opaque;
> + int ret;
> +
> + if (!(s->autoclear_features & QCOW2_AUTOCLEAR_BITMAPS) ||
> + bm_list == NULL || QSIMPLEQ_EMPTY(bm_list) ||
> + bitmap_list_count(bm_list) != s->nb_bitmaps)
> + {
> + return -EINVAL;
> + }
> +
> + s->autoclear_features &= ~(uint64_t)QCOW2_AUTOCLEAR_BITMAPS;
> + ret = update_header_sync(bs);
> + if (ret < 0) {
> + /* Two variants are possible here:
> + * 1. Autoclear flag is dropped, all bitmaps will be lost.
> + * 2. Autoclear flag is not dropped, old state is left.
> + */
> + return ret;
> + }
> +
> + /* autoclear bit is not set, so we can safely update bitmap directory */
> +
> + ret = bitmap_list_store(bs, bm_list, &s->bitmap_directory_offset,
> + &s->bitmap_directory_size, true);
> + if (ret < 0) {
> + /* autoclear bit is cleared, so all leaked clusters would be removed
> on
> + * qemu-img check */
> + return ret;
> + }
> +
> + ret = update_header_sync(bs);
> + if (ret < 0) {
> + /* autoclear bit is cleared, so all leaked clusters would be removed
> on
> + * qemu-img check */
> + return ret;
> + }
> +
> + s->autoclear_features |= QCOW2_AUTOCLEAR_BITMAPS;
> + return update_header_sync(bs);
> + /* If final update_header_sync() fails, two variants are possible:
> + * 1. Autoclear flag is not set, all bitmaps will be lost.
> + * 2. Autoclear flag is set, header and directory are successfully
> updated.
> + */
> +}
> +
> +/* for g_slist_foreach for GSList of BdrvDirtyBitmap* elements */
> +static void release_dirty_bitmap_helper(gpointer bitmap,
> + gpointer bs)
> +{
> + bdrv_release_dirty_bitmap(bs, bitmap);
> +}
> +
> +/* for g_slist_foreach for GSList of BdrvDirtyBitmap* elements */
> +static void set_readonly_helper(gpointer bitmap, gpointer value)
> +{
> + bdrv_dirty_bitmap_set_readonly(bitmap, (bool)value);
> +}
> +
> +/* qcow2_load_autoloading_dirty_bitmaps()
> + * Return value is a hint for caller: true means that the Qcow2 header was
> + * updated. (false doesn't mean that the header should be updated by the
> + * caller, it just means that updating was not needed or the image cannot be
> + * written to).
> + * On failure the function returns false.
> + */
> +bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **errp)
> +{
> + BDRVQcow2State *s = bs->opaque;
> + Qcow2BitmapList *bm_list;
> + Qcow2Bitmap *bm;
> + GSList *created_dirty_bitmaps = NULL;
> + bool header_updated = false;
> +
> + if (s->nb_bitmaps == 0) {
> + /* No bitmaps - nothing to do */
> + return false;
> + }
> +
> + bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
> + s->bitmap_directory_size, errp);
> + if (bm_list == NULL) {
> + return false;
> + }
> +
> + QSIMPLEQ_FOREACH(bm, bm_list, entry) {
> + if ((bm->flags & BME_FLAG_AUTO) && !(bm->flags & BME_FLAG_IN_USE)) {
> + BdrvDirtyBitmap *bitmap = load_bitmap(bs, bm, errp);
> + if (bitmap == NULL) {
> + goto fail;
> + }
> + bm->flags |= BME_FLAG_IN_USE;
> + created_dirty_bitmaps =
> + g_slist_append(created_dirty_bitmaps, bitmap);
> + }
> + }
> +
> + if (created_dirty_bitmaps != NULL) {
> + if (can_write(bs)) {
> + /* in_use flags must be updated */
> + int ret = update_ext_header_and_dir_in_place(bs, bm_list);
> + if (ret < 0) {
> + error_setg_errno(errp, -ret, "Can't update bitmap
> directory");
> + goto fail;
> + }
> + header_updated = true;
> + } else {
> + g_slist_foreach(created_dirty_bitmaps, set_readonly_helper,
> + (gpointer)true);
> + }
> + }
> +
> + g_slist_free(created_dirty_bitmaps);
> + bitmap_list_free(bm_list);
> +
> + return header_updated;
> +
> +fail:
> + g_slist_foreach(created_dirty_bitmaps, release_dirty_bitmap_helper, bs);
> + g_slist_free(created_dirty_bitmaps);
> + bitmap_list_free(bm_list);
> +
> + return false;
> +}
> diff --git a/block/qcow2.c b/block/qcow2.c
> index 28a16789af..a70d284b75 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -1239,9 +1239,22 @@ static int qcow2_do_open(BlockDriverState *bs, QDict
> *options, int flags,
>
> /* Clear unknown autoclear feature bits */
> update_header |= s->autoclear_features & ~QCOW2_AUTOCLEAR_MASK;
> -
> - if (update_header && !bs->read_only && !(flags & BDRV_O_INACTIVE)) {
> + update_header =
> + update_header && !bs->read_only && !(flags & BDRV_O_INACTIVE);
> + if (update_header) {
> s->autoclear_features &= QCOW2_AUTOCLEAR_MASK;
> + }
> +
> + if (qcow2_load_autoloading_dirty_bitmaps(bs, &local_err)) {
> + update_header = false;
> + }
> + if (local_err != NULL) {
> + error_propagate(errp, local_err);
> + ret = -EINVAL;
> + goto fail;
> + }
> +
> + if (update_header) {
> ret = qcow2_update_header(bs);
> if (ret < 0) {
> error_setg_errno(errp, -ret, "Could not update qcow2 header");
> diff --git a/block/qcow2.h b/block/qcow2.h
> index f4a1f4cd78..67c61de008 100644
> --- a/block/qcow2.h
> +++ b/block/qcow2.h
> @@ -629,4 +629,6 @@ void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c,
> void **table);
> int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
> void **refcount_table,
> int64_t *refcount_table_size);
> +bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error
> **errp);
> +
> #endif
>
--
—js
- Re: [Qemu-block] [PATCH v20 13/30] block: new bdrv_reopen_bitmaps_rw interface, (continued)
[Qemu-block] [PATCH v20 04/30] tests: add hbitmap iter test, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 23/30] qmp: add persistent flag to block-dirty-bitmap-add, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 01/30] specs/qcow2: fix bitmap granularity qemu-specific note, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 24/30] qmp: add autoload parameter to block-dirty-bitmap-add, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 19/30] qcow2: add persistent dirty bitmaps support, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 11/30] qcow2: autoloading dirty bitmaps, Vladimir Sementsov-Ogievskiy, 2017/06/02
- Re: [Qemu-block] [Qemu-devel] [PATCH v20 11/30] qcow2: autoloading dirty bitmaps,
John Snow <=
[Qemu-block] [PATCH v20 06/30] block/dirty-bitmap: add deserialize_ones func, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 05/30] block: fix bdrv_dirty_bitmap_granularity signature, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 12/30] block: refactor bdrv_reopen_commit, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 16/30] block: bdrv_close: release bitmaps after drv->bdrv_close, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 18/30] block/dirty-bitmap: add bdrv_dirty_bitmap_next(), Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 28/30] qcow2: add .bdrv_remove_persistent_dirty_bitmap, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 29/30] qmp: block-dirty-bitmap-remove: remove persistent, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 07/30] qcow2-refcount: rename inc_refcounts() and make it public, Vladimir Sementsov-Ogievskiy, 2017/06/02
[Qemu-block] [PATCH v20 21/30] block: add bdrv_can_store_new_dirty_bitmap, Vladimir Sementsov-Ogievskiy, 2017/06/02