From nobody Wed Apr 16 07:36:58 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1499451675064654.4973931518958; Fri, 7 Jul 2017 11:21:15 -0700 (PDT) Received: from localhost ([::1]:58120 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dTXsO-00024C-PK for importer@patchew.org; Fri, 07 Jul 2017 14:21:13 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:43257) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dTWmM-0006RS-TK for qemu-devel@nongnu.org; Fri, 07 Jul 2017 13:10:57 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dTWmK-0000iW-CU for qemu-devel@nongnu.org; Fri, 07 Jul 2017 13:10:54 -0400 Received: from mx1.redhat.com ([209.132.183.28]:57598) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1dTWmD-0000Y0-4W; Fri, 07 Jul 2017 13:10:45 -0400 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 31A0883F3E; Fri, 7 Jul 2017 17:10:44 +0000 (UTC) Received: from noname.redhat.com (ovpn-117-34.ams2.redhat.com [10.36.117.34]) by smtp.corp.redhat.com (Postfix) with ESMTP id 465D661F52; Fri, 7 Jul 2017 17:10:43 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 31A0883F3E Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=kwolf@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com 31A0883F3E From: Kevin Wolf To: qemu-block@nongnu.org Date: Fri, 7 Jul 2017 19:08:28 +0200 Message-Id: <1499447335-6125-74-git-send-email-kwolf@redhat.com> In-Reply-To: <1499447335-6125-1-git-send-email-kwolf@redhat.com> References: <1499447335-6125-1-git-send-email-kwolf@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Fri, 07 Jul 2017 17:10:44 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PULL 073/100] qcow2: autoloading dirty bitmaps X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, qemu-devel@nongnu.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" From: Vladimir Sementsov-Ogievskiy 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 Reviewed-by: Max Reitz Reviewed-by: John Snow Message-id: 20170628120530.31251-12-vsementsov@virtuozzo.com Signed-off-by: Max Reitz --- 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 b8e472b..2c7b057 100644 --- a/block/qcow2-bitmap.c +++ b/block/qcow2-bitmap.c @@ -44,6 +44,8 @@ =20 /* Bitmap directory entry flags */ #define BME_RESERVED_FLAGS 0xfffffffcU +#define BME_FLAG_IN_USE (1U << 0) +#define BME_FLAG_AUTO (1U << 1) =20 /* 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 =3D 1 } BitmapType; =20 +static inline bool can_write(BlockDriverState *bs) +{ + return !bdrv_is_read_only(bs) && !(bdrv_get_flags(bs) & BDRV_O_INACTIV= E); +} + +static int update_header_sync(BlockDriverState *bs) +{ + int ret; + + ret =3D 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; } =20 +/* This function returns the number of disk sectors covered by a single qc= ow2 + * cluster of bitmap data. */ +static uint64_t sectors_covered_by_bitmap_cluster(const BDRVQcow2State *s, + const BdrvDirtyBitmap *b= itmap) +{ + uint32_t sector_granularity =3D + 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 =3D 0; + BDRVQcow2State *s =3D bs->opaque; + uint64_t sector, sbc; + uint64_t bm_size =3D bdrv_dirty_bitmap_size(bitmap); + uint8_t *buf =3D NULL; + uint64_t i, tab_size =3D + size_to_clusters(s, + bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size)); + + if (tab_size !=3D bitmap_table_size || tab_size > BME_MAX_TABLE_SIZE) { + return -EINVAL; + } + + buf =3D g_malloc(s->cluster_size); + sbc =3D sectors_covered_by_bitmap_cluster(s, bitmap); + for (i =3D 0, sector =3D 0; i < tab_size; ++i, sector +=3D sbc) { + uint64_t count =3D MIN(bm_size - sector, sbc); + uint64_t entry =3D bitmap_table[i]; + uint64_t offset =3D entry & BME_TABLE_ENTRY_OFFSET_MASK; + + assert(check_table_entry(entry, s->cluster_size) =3D=3D 0); + + if (offset =3D=3D 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 =3D 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 =3D 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 =3D NULL; + uint32_t granularity; + BdrvDirtyBitmap *bitmap =3D NULL; + + if (bm->flags & BME_FLAG_IN_USE) { + error_setg(errp, "Bitmap '%s' is in use", bm->name); + goto fail; + } + + ret =3D 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 =3D 1U << bm->granularity_bits; + bitmap =3D bdrv_create_dirty_bitmap(bs, granularity, bm->name, errp); + if (bitmap =3D=3D NULL) { + goto fail; + } + + ret =3D load_bitmap_data(bs, bitmap_table, bm->table.size, bitmap); + if (ret < 0) { + error_setg_errno(errp, -ret, "Could not read bitmap '%s' from imag= e", + bm->name); + goto fail; + } + + g_free(bitmap_table); + return bitmap; + +fail: + g_free(bitmap_table); + if (bitmap !=3D NULL) { + bdrv_release_dirty_bitmap(bs, bitmap); + } + + return NULL; +} + /* * Bitmap List */ @@ -164,6 +297,15 @@ static inline void bitmap_dir_entry_to_cpu(Qcow2Bitmap= DirEntry *entry) be32_to_cpus(&entry->extra_data_size); } =20 +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, Qcow2= BitmapDirEntry *entry) return fail ? -EINVAL : 0; } =20 +static inline void bitmap_directory_to_be(uint8_t *dir, size_t size) +{ + uint8_t *end =3D dir + size; + while (dir < end) { + Qcow2BitmapDirEntry *e =3D (Qcow2BitmapDirEntry *)dir; + dir +=3D 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; } =20 +static uint32_t bitmap_list_count(Qcow2BitmapList *bm_list) +{ + Qcow2Bitmap *bm; + uint32_t nb_bitmaps =3D 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: =20 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_lis= t, + uint64_t *offset, uint64_t *size, bool in_pla= ce) +{ + int ret; + uint8_t *dir; + int64_t dir_offset =3D 0; + uint64_t dir_size =3D 0; + Qcow2Bitmap *bm; + Qcow2BitmapDirEntry *e; + + QSIMPLEQ_FOREACH(bm, bm_list, entry) { + dir_size +=3D calc_dir_entry_size(strlen(bm->name), 0); + } + + if (dir_size =3D=3D 0 || dir_size > QCOW2_MAX_BITMAP_DIRECTORY_SIZE) { + return -EINVAL; + } + + if (in_place) { + if (*size !=3D dir_size || *offset =3D=3D 0) { + return -EINVAL; + } + + dir_offset =3D *offset; + } + + dir =3D g_try_malloc(dir_size); + if (dir =3D=3D NULL) { + return -ENOMEM; + } + + e =3D (Qcow2BitmapDirEntry *)dir; + QSIMPLEQ_FOREACH(bm, bm_list, entry) { + e->bitmap_table_offset =3D bm->table.offset; + e->bitmap_table_size =3D bm->table.size; + e->flags =3D bm->flags; + e->type =3D BT_DIRTY_TRACKING_BITMAP; + e->granularity_bits =3D bm->granularity_bits; + e->name_size =3D strlen(bm->name); + e->extra_data_size =3D 0; + memcpy(e + 1, bm->name, e->name_size); + + if (check_dir_entry(bs, e) < 0) { + ret =3D -EINVAL; + goto fail; + } + + e =3D next_dir_entry(e); + } + + bitmap_directory_to_be(dir, dir_size); + + if (!in_place) { + dir_offset =3D qcow2_alloc_clusters(bs, dir_size); + if (dir_offset < 0) { + ret =3D dir_offset; + goto fail; + } + } + + ret =3D qcow2_pre_write_overlap_check(bs, 0, dir_offset, dir_size); + if (ret < 0) { + goto fail; + } + + ret =3D bdrv_pwrite(bs->file, dir_offset, dir, dir_size); + if (ret < 0) { + goto fail; + } + + g_free(dir); + + if (!in_place) { + *size =3D dir_size; + *offset =3D 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 =3D bs->opaque; + int ret; + + if (!(s->autoclear_features & QCOW2_AUTOCLEAR_BITMAPS) || + bm_list =3D=3D NULL || QSIMPLEQ_EMPTY(bm_list) || + bitmap_list_count(bm_list) !=3D s->nb_bitmaps) + { + return -EINVAL; + } + + s->autoclear_features &=3D ~(uint64_t)QCOW2_AUTOCLEAR_BITMAPS; + ret =3D 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 =3D 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 remov= ed on + * qemu-img check */ + return ret; + } + + ret =3D update_header_sync(bs); + if (ret < 0) { + /* autoclear bit is cleared, so all leaked clusters would be remov= ed on + * qemu-img check */ + return ret; + } + + s->autoclear_features |=3D 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 upd= ated. + */ +} + +/* 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 **er= rp) +{ + BDRVQcow2State *s =3D bs->opaque; + Qcow2BitmapList *bm_list; + Qcow2Bitmap *bm; + GSList *created_dirty_bitmaps =3D NULL; + bool header_updated =3D false; + + if (s->nb_bitmaps =3D=3D 0) { + /* No bitmaps - nothing to do */ + return false; + } + + bm_list =3D bitmap_list_load(bs, s->bitmap_directory_offset, + s->bitmap_directory_size, errp); + if (bm_list =3D=3D NULL) { + return false; + } + + QSIMPLEQ_FOREACH(bm, bm_list, entry) { + if ((bm->flags & BME_FLAG_AUTO) && !(bm->flags & BME_FLAG_IN_USE))= { + BdrvDirtyBitmap *bitmap =3D load_bitmap(bs, bm, errp); + if (bitmap =3D=3D NULL) { + goto fail; + } + bm->flags |=3D BME_FLAG_IN_USE; + created_dirty_bitmaps =3D + g_slist_append(created_dirty_bitmaps, bitmap); + } + } + + if (created_dirty_bitmaps !=3D NULL) { + if (can_write(bs)) { + /* in_use flags must be updated */ + int ret =3D update_ext_header_and_dir_in_place(bs, bm_list); + if (ret < 0) { + error_setg_errno(errp, -ret, "Can't update bitmap director= y"); + goto fail; + } + header_updated =3D 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 8688fdd..8645b08 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1445,9 +1445,22 @@ static int qcow2_do_open(BlockDriverState *bs, QDict= *options, int flags, =20 /* Clear unknown autoclear feature bits */ update_header |=3D s->autoclear_features & ~QCOW2_AUTOCLEAR_MASK; - - if (update_header && !bs->read_only && !(flags & BDRV_O_INACTIVE)) { + update_header =3D + update_header && !bs->read_only && !(flags & BDRV_O_INACTIVE); + if (update_header) { s->autoclear_features &=3D QCOW2_AUTOCLEAR_MASK; + } + + if (qcow2_load_autoloading_dirty_bitmaps(bs, &local_err)) { + update_header =3D false; + } + if (local_err !=3D NULL) { + error_propagate(errp, local_err); + ret =3D -EINVAL; + goto fail; + } + + if (update_header) { ret =3D 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 3488648..886480b 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -645,4 +645,6 @@ void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *= c, void **table); int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *r= es, void **refcount_table, int64_t *refcount_table_size); +bool qcow2_load_autoloading_dirty_bitmaps(BlockDriverState *bs, Error **er= rp); + #endif --=20 1.8.3.1