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 <vsement...@virtuozzo.com> --- 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 c9f7ef1..28ed309 100644 --- a/block/qcow2-dirty-bitmap.c +++ b/block/qcow2-dirty-bitmap.c @@ -76,6 +76,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); @@ -86,6 +95,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) { @@ -320,3 +340,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 5f54528..20d095b 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -3353,6 +3353,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 d0e3db8..7cd05e1 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -218,6 +218,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); -- 1.8.3.1