qemu-block
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]