From patchwork Wed Feb 17 15:28:57 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Sementsov-Ogievskiy X-Patchwork-Id: 8340281 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 166E79F399 for ; Wed, 17 Feb 2016 15:34:46 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id ED9E1203B7 for ; Wed, 17 Feb 2016 15:34:44 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A39F820357 for ; Wed, 17 Feb 2016 15:34:43 +0000 (UTC) Received: from localhost ([::1]:58689 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aW47m-00061m-Vq for patchwork-qemu-devel@patchwork.kernel.org; Wed, 17 Feb 2016 10:34:43 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:60113) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aW434-0006eP-Bc for qemu-devel@nongnu.org; Wed, 17 Feb 2016 10:29:52 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aW432-0008DK-9Z for qemu-devel@nongnu.org; Wed, 17 Feb 2016 10:29:50 -0500 Received: from mailhub.sw.ru ([195.214.232.25]:31848 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aW42y-0008C0-EL for qemu-devel@nongnu.org; Wed, 17 Feb 2016 10:29:48 -0500 Received: from kvm.sw.ru. ([10.28.8.145]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id u1HFTHCN009256; Wed, 17 Feb 2016 18:29:31 +0300 (MSK) From: Vladimir Sementsov-Ogievskiy To: qemu-devel@nongnu.org Date: Wed, 17 Feb 2016 18:28:57 +0300 Message-Id: <1455722949-17060-6-git-send-email-vsementsov@virtuozzo.com> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1455722949-17060-1-git-send-email-vsementsov@virtuozzo.com> References: <1455722949-17060-1-git-send-email-vsementsov@virtuozzo.com> X-detected-operating-system: by eggs.gnu.org: OpenBSD 3.x X-Received-From: 195.214.232.25 Cc: kwolf@redhat.com, vsementsov@virtuozzo.com, famz@redhat.com, qemu-block@nongnu.org, mreitz@redhat.com, stefanha@redhat.com, pbonzini@redhat.com, den@openvz.org, jsnow@redhat.com Subject: [Qemu-devel] [PATCH 05/17] qcow2-dirty-bitmap: add qcow2_bitmap_store() X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This function stores block dirty bitmap to qcow2. If the bitmap with the same name, size and granularity already exists, it will be rewritten, if the bitmap with the same name exists but granularity or size does not match, an error will be genrated. Signed-off-by: Vladimir Sementsov-Ogievskiy --- block/qcow2-dirty-bitmap.c | 443 +++++++++++++++++++++++++++++++++++++++++++++ block/qcow2.c | 1 + block/qcow2.h | 2 + include/block/block_int.h | 3 + 4 files changed, 449 insertions(+) diff --git a/block/qcow2-dirty-bitmap.c b/block/qcow2-dirty-bitmap.c index 5e38a3d..979e17e 100644 --- a/block/qcow2-dirty-bitmap.c +++ b/block/qcow2-dirty-bitmap.c @@ -74,6 +74,15 @@ static void bitmap_header_to_cpu(QCow2BitmapHeader *h) be32_to_cpus(&h->extra_data_size); } +static void bitmap_header_to_be(QCow2BitmapHeader *h) +{ + cpu_to_be64s(&h->bitmap_table_offset); + cpu_to_be32s(&h->bitmap_table_size); + cpu_to_be32s(&h->flags); + cpu_to_be16s(&h->name_size); + cpu_to_be32s(&h->extra_data_size); +} + static int calc_dir_entry_size(size_t name_size) { return align_offset(sizeof(QCow2BitmapHeader) + name_size, 8); @@ -84,6 +93,17 @@ static int dir_entry_size(QCow2BitmapHeader *h) return calc_dir_entry_size(h->name_size); } +static void directory_to_be(uint8_t *dir, size_t size) +{ + uint8_t *end = dir + size; + while (dir < end) { + QCow2BitmapHeader *h = (QCow2BitmapHeader *)dir; + dir += dir_entry_size(h); + + bitmap_header_to_be(h); + } +} + static int check_constraints(QCow2BitmapHeader *h, int cluster_size, uint64_t disk_size) { @@ -318,3 +338,426 @@ BdrvDirtyBitmap *qcow2_bitmap_load(BlockDriverState *bs, const char *name, return load_bitmap(bs, bm, errp); } + +static int update_header_sync(BlockDriverState *bs) +{ + int ret; + + ret = qcow2_update_header(bs); + if (ret < 0) { + return ret; + } + + ret = bdrv_flush(bs); + if (ret < 0) { + return ret; + } + + return 0; +} + +/* write bitmap directory from the state to new allocated clusters */ +static int64_t directory_write(BlockDriverState *bs, const uint8_t *dir, + size_t size) +{ + int ret = 0; + uint8_t *dir_be = NULL; + int64_t dir_offset = 0; + + dir_be = g_try_malloc(size); + if (dir_be == NULL) { + return -ENOMEM; + } + memcpy(dir_be, dir, size); + directory_to_be(dir_be, size); + + /* Allocate space for the new bitmap directory */ + dir_offset = qcow2_alloc_clusters(bs, size); + if (dir_offset < 0) { + ret = dir_offset; + goto out; + } + + /* The bitmap directory position has not yet been updated, so these + * clusters must indeed be completely free */ + ret = qcow2_pre_write_overlap_check(bs, 0, dir_offset, size); + if (ret < 0) { + goto out; + } + + ret = bdrv_pwrite(bs->file->bs, dir_offset, dir_be, size); + if (ret < 0) { + goto out; + } + +out: + g_free(dir_be); + + if (ret < 0) { + if (dir_offset > 0) { + qcow2_free_clusters(bs, dir_offset, size, QCOW2_DISCARD_ALWAYS); + } + + return ret; + } + + return dir_offset; +} + +static int directory_push_entry(BlockDriverState *bs, QCow2BitmapHeader *header) +{ + BDRVQcow2State *s = bs->opaque; + int ret; + int entry_size = dir_entry_size(header); + int64_t new_offset = 0, old_offset = 0; + uint64_t new_size = s->bitmap_directory_size + entry_size, old_size = 0; + void *p; + int64_t nb_sectors = bdrv_nb_sectors(bs); + + if (nb_sectors < 0) { + return nb_sectors; + } + + if (new_size > QCOW_MAX_DIRTY_BITMAP_DIRECTORY_SIZE) { + return -EINVAL; + } + + ret = check_constraints(header, s->cluster_size, + nb_sectors << BDRV_SECTOR_BITS); + if (ret < 0) { + return -EINVAL; + } + + old_offset = s->bitmap_directory_offset; + old_size = s->bitmap_directory_size; + + uint8_t *new_dir = g_try_malloc(new_size); + if (new_dir == NULL) { + return -ENOMEM; + } + memcpy(new_dir, s->bitmap_directory, s->bitmap_directory_size); + memcpy(new_dir + s->bitmap_directory_size, header, entry_size); + + new_offset = directory_write(bs, new_dir, new_size); + if (new_offset < 0) { + ret = new_offset; + goto fail; + } + + ret = bdrv_flush(bs); + if (ret < 0) { + goto fail; + } + + s->bitmap_directory_offset = new_offset; + s->bitmap_directory_size = new_size; + + ret = update_header_sync(bs); + if (ret < 0) { + goto fail; + } + + if (old_size) { + qcow2_free_clusters(bs, old_offset, old_size, QCOW2_DISCARD_ALWAYS); + } + + g_free(s->bitmap_directory); + s->bitmap_directory = new_dir; + + return 0; + +fail: + g_free(new_dir); + if (new_offset > 0) { + qcow2_free_clusters(bs, new_offset, new_size, QCOW2_DISCARD_ALWAYS); + s->bitmap_directory_offset = old_offset; + s->bitmap_directory_size = old_size; + } + + p = g_try_realloc(s->bitmap_directory, s->bitmap_directory_size); + if (p != NULL) { + s->bitmap_directory = p; + } + + return ret; +} + +/* store_bitmap() + * update bitmap table by storing bitmap to it. + * Bitmap table entries are assumed to be in big endian format + * On the error, the resulting bitmap table is valid for clearing, but + * may contain invalid bitmap */ +static int store_bitmap(BlockDriverState *bs, uint64_t *bitmap_table, + uint32_t bitmap_table_size, + const BdrvDirtyBitmap *bitmap) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + int cl_size = s->cluster_size; + uint32_t i, tab_size = 0; + uint64_t *tab; + uint32_t j; + + bdrv_dirty_bitmap_prepare_store(bitmap, s->cluster_size, NULL, &tab_size); + tab = g_try_malloc(tab_size * sizeof(tab[0])); + if (tab == NULL) { + return -ENOMEM; + } + + ret = bdrv_dirty_bitmap_prepare_store(bitmap, s->cluster_size, + tab, &tab_size); + if (ret != 0) { + g_free(tab); + return ret; + } + + for (i = 0, j = 0; i < bitmap_table_size; ++i) { + if (tab[i] <= 1) { + continue; + } + + for ( ; j < bitmap_table_size && bitmap_table[j] <= 1; ++j) { + ; + } + + if (j < bitmap_table_size) { + tab[i] = be64_to_cpu(bitmap_table[j]); + } else { + tab[i] = qcow2_alloc_clusters(bs, cl_size); + } + } + + for ( ; j < bitmap_table_size; ++j) { + uint64_t addr = be64_to_cpu(bitmap_table[j]); + if (addr <= 1) { + continue; + } + + qcow2_free_clusters(bs, addr, cl_size, QCOW2_DISCARD_ALWAYS); + bitmap_table[j] = 0; + } + + bdrv_dirty_bitmap_store(bitmap, bs->file->bs, tab, + bitmap_table_size, s->cluster_size); + + for (i = 0; i < bitmap_table_size; ++i) { + bitmap_table[i] = cpu_to_be64(tab[i]); + } + + g_free(tab); + + return 0; +} + +static int64_t alloc_zeroed_clusters(BlockDriverState *bs, uint64_t size) +{ + int ret = 0; + void *buf = NULL; + int64_t offset = qcow2_alloc_clusters(bs, size); + if (offset < 0) { + return offset; + } + + buf = g_try_malloc0(size); + if (buf == NULL) { + ret = -ENOMEM; + goto out; + } + + ret = qcow2_pre_write_overlap_check(bs, 0, offset, size); + if (ret < 0) { + goto out; + } + + ret = bdrv_pwrite(bs->file->bs, offset, buf, size); + if (ret < 0) { + goto out; + } + + ret = bdrv_flush(bs); + if (ret < 0) { + goto out; + } + +out: + g_free(buf); + + if (ret < 0) { + qcow2_free_clusters(bs, offset, size, QCOW2_DISCARD_ALWAYS); + return ret; + } + + return offset; +} + +static int directory_push(BlockDriverState *bs, const char *name, + uint64_t size, int granularity) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + int sector_granularity = granularity >> BDRV_SECTOR_BITS; + size_t name_size = strlen(name); + size_t entry_size = calc_dir_entry_size(name_size); + QCow2BitmapHeader *entry = g_malloc0(entry_size); + int64_t table_offset = 0; + + entry->granularity_bits = ctz32(granularity); + entry->type = BT_DIRTY_TRACKING_BITMAP; + entry->name_size = name_size; + memcpy(entry + 1, name, name_size); + + entry->bitmap_table_size = + size_to_clusters(s, (((size - 1) / sector_granularity) >> 3) + 1); + table_offset = alloc_zeroed_clusters(bs, entry->bitmap_table_size * + sizeof(uint64_t)); + if (table_offset < 0) { + ret = table_offset; + goto out; + } + entry->bitmap_table_offset = table_offset; + + ret = directory_push_entry(bs, entry); + if (ret < 0) { + goto out; + } + +out: + g_free(entry); + if (ret < 0 && table_offset > 0) { + qcow2_free_clusters(bs, table_offset, entry->bitmap_table_size * + sizeof(uint64_t), QCOW2_DISCARD_ALWAYS); + } + + return ret; +} + +static int bitmaps_push(BDRVQcow2State *s, const char *name, uint64_t offset) +{ + QCow2Bitmap *bm; + QCow2Bitmap *p; + + printf("dirty bitmaps push\n"); + p = g_try_renew(QCow2Bitmap, s->bitmaps, s->nb_bitmaps + 1); + if (p == NULL) { + return -ENOMEM; + } + s->bitmaps = p; + s->nb_bitmaps++; + + bm = s->bitmaps + s->nb_bitmaps - 1; + bm->name = g_strdup(name); + bm->offset = offset; + + + return 0; +} + +static void bitmaps_pop(BDRVQcow2State *s) +{ + QCow2Bitmap *p; + + if (s->nb_bitmaps == 0) { + return; + } + + p = g_try_renew(QCow2Bitmap, s->bitmaps, s->nb_bitmaps - 1); + if (p != NULL) { + s->bitmaps = p; + } + + s->nb_bitmaps--; +} + +/* if no id is provided, a new one is constructed */ +static int qcow2_bitmap_create(BlockDriverState *bs, const char *name, + uint64_t size, int granularity) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + + if (s->nb_bitmaps >= QCOW_MAX_DIRTY_BITMAPS) { + return -EFBIG; + } + + /* Check that the name is unique */ + if (find_bitmap_by_name(bs, name) != NULL) { + return -EEXIST; + } + + ret = bitmaps_push(s, name, s->bitmap_directory_size); + if (ret < 0) { + return ret; + } + + ret = directory_push(bs, name, size, granularity); + if (ret < 0) { + bitmaps_pop(s); + return ret; + } + + return 0; +} + +void qcow2_bitmap_store(BlockDriverState *bs, + const BdrvDirtyBitmap *bitmap, Error **errp) +{ + BDRVQcow2State *s = bs->opaque; + int ret = 0; + uint64_t *bitmap_table; + QCow2Bitmap *bm; + QCow2BitmapHeader *bmh; + const char *name = bdrv_dirty_bitmap_name(bitmap); + uint64_t size = bdrv_dirty_bitmap_size(bitmap); + int granularity = bdrv_dirty_bitmap_granularity(bitmap); + + /* find/create dirty bitmap */ + bm = find_bitmap_by_name(bs, name); + if (bm == NULL) { + ret = qcow2_bitmap_create(bs, name, size, granularity); + if (ret < 0) { + error_setg_errno(errp, ret, "Can't create dirty bitmap in qcow2."); + } + bm = s->bitmaps + s->nb_bitmaps - 1; + bmh = bitmap_header(s, bm); + } else { + bmh = bitmap_header(s, bm); + + if (granularity != (1U << bmh->granularity_bits)) { + error_setg(errp, + "The bitmap with same name (but other granularity) " + "already exists."); + return; + } + } + + bitmap_table = g_try_new(uint64_t, bmh->bitmap_table_size); + if (bitmap_table == NULL) { + error_setg(errp, "No memory."); + return; + } + ret = bdrv_pread(bs->file->bs, bmh->bitmap_table_offset, + bitmap_table, + bmh->bitmap_table_size * sizeof(uint64_t)); + if (ret < 0) { + error_setg_errno(errp, ret, "Can't read dirty bitmap table."); + goto finish; + } + + ret = store_bitmap(bs, bitmap_table, bmh->bitmap_table_size, + bitmap); + if (ret < 0) { + error_setg_errno(errp, ret, "Can't store bitmap table."); + goto finish; + } + + ret = bdrv_pwrite(bs->file->bs, bmh->bitmap_table_offset, + bitmap_table, + bmh->bitmap_table_size * sizeof(uint64_t)); + if (ret < 0) { + error_setg_errno(errp, ret, "Can't write dirty bitmap table."); + goto finish; + } + +finish: + g_free(bitmap_table); +} diff --git a/block/qcow2.c b/block/qcow2.c index fe53338..75ec4c0 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -3347,6 +3347,7 @@ BlockDriver bdrv_qcow2 = { .bdrv_get_specific_info = qcow2_get_specific_info, .bdrv_dirty_bitmap_load = qcow2_bitmap_load, + .bdrv_dirty_bitmap_store = qcow2_bitmap_store, .bdrv_save_vmstate = qcow2_save_vmstate, .bdrv_load_vmstate = qcow2_load_vmstate, diff --git a/block/qcow2.h b/block/qcow2.h index cc4c776..e4a517c 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -622,6 +622,8 @@ int qcow2_read_bitmaps(BlockDriverState *bs, Error **errp); BdrvDirtyBitmap *qcow2_bitmap_load(BlockDriverState *bs, const char *name, Error **errp); +void qcow2_bitmap_store(BlockDriverState *bs, const BdrvDirtyBitmap *bitmap, + Error **errp); /* qcow2-cache.c functions */ Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables); diff --git a/include/block/block_int.h b/include/block/block_int.h index 7c0290e..be8f8a3 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -217,6 +217,9 @@ struct BlockDriver { BdrvDirtyBitmap *(*bdrv_dirty_bitmap_load)(BlockDriverState *bs, const char *name, Error **errp); + void (*bdrv_dirty_bitmap_store)(BlockDriverState *bs, + const BdrvDirtyBitmap *bitmap, + Error **errp); int (*bdrv_save_vmstate)(BlockDriverState *bs, QEMUIOVector *qiov, int64_t pos);