Adds qcow2_read_bitmaps, reading bitmap directory as specified in docs/specs/qcow2.txt
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsement...@virtuozzo.com> --- block/qcow2-dirty-bitmap.c | 163 +++++++++++++++++++++++++++++++++++++++++++++ block/qcow2.h | 10 +++ 2 files changed, 173 insertions(+) diff --git a/block/qcow2-dirty-bitmap.c b/block/qcow2-dirty-bitmap.c index 2c749ab..2e87acf 100644 --- a/block/qcow2-dirty-bitmap.c +++ b/block/qcow2-dirty-bitmap.c @@ -25,6 +25,9 @@ * THE SOFTWARE. */ +#include "block/block_int.h" +#include "block/qcow2.h" + /* NOTICE: BME here means Bitmaps Extension and used as a namespace for * _internal_ constants. Please do not use this _internal_ abbreviation for * other needs and/or outside of this file. */ @@ -45,3 +48,163 @@ typedef enum BitmapType { BT_DIRTY_TRACKING_BITMAP = 1 } BitmapType; + +void qcow2_free_bitmaps(BlockDriverState *bs) +{ + BDRVQcow2State *s = bs->opaque; + int i; + + for (i = 0; i < s->nb_bitmaps; i++) { + g_free(s->bitmaps[i].name); + } + g_free(s->bitmaps); + s->bitmaps = NULL; + s->nb_bitmaps = 0; + + g_free(s->bitmap_directory); + s->bitmap_directory = NULL; +} + +static void bitmap_header_to_cpu(QCow2BitmapHeader *h) +{ + be64_to_cpus(&h->bitmap_table_offset); + be32_to_cpus(&h->bitmap_table_size); + be32_to_cpus(&h->flags); + be16_to_cpus(&h->name_size); + be32_to_cpus(&h->extra_data_size); +} + +static int calc_dir_entry_size(size_t name_size) +{ + return align_offset(sizeof(QCow2BitmapHeader) + name_size, 8); +} + +static int dir_entry_size(QCow2BitmapHeader *h) +{ + return calc_dir_entry_size(h->name_size); +} + +static int check_constraints(QCow2BitmapHeader *h, int cluster_size, + uint64_t disk_size) +{ + uint64_t phys_bitmap_bytes = + (uint64_t)h->bitmap_table_size * cluster_size; + uint64_t max_virtual_bits = (phys_bitmap_bytes * 8) << h->granularity_bits; + + int fail = + (h->bitmap_table_offset % cluster_size) || + (h->bitmap_table_size > BME_MAX_TABLE_SIZE) || + (phys_bitmap_bytes > BME_MAX_PHYS_SIZE) || + (disk_size > max_virtual_bits) || + (h->granularity_bits > BME_MAX_GRANULARITY_BITS) || + (h->granularity_bits < BME_MIN_GRANULARITY_BITS) || + (h->flags & BME_RESERVED_FLAGS) || + (h->name_size > BME_MAX_NAME_SIZE) || + (h->type != BT_DIRTY_TRACKING_BITMAP); + + return fail ? -EINVAL : 0; +} + +static int directory_read(BlockDriverState *bs, Error **errp) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + QCow2Bitmap *bm, *end; + int64_t nb_sectors = bdrv_nb_sectors(bs); + size_t offset; + + if (nb_sectors < 0) { + error_setg(errp, "Can't calculate number of disk sectors."); + return nb_sectors; + } + + if (s->bitmap_directory != NULL) { + /* already read */ + error_setg(errp, "Try read bitmaps, when they are already read."); + return -EEXIST; + } + + s->bitmap_directory = g_try_malloc0(s->bitmap_directory_size); + if (s->bitmap_directory == NULL) { + error_setg(errp, "Can't allocate space for bitmap directory."); + return -ENOMEM; + } + + ret = bdrv_pread(bs->file->bs, + s->bitmap_directory_offset, + s->bitmap_directory, + s->bitmap_directory_size); + if (ret < 0) { + error_setg(errp, "Can't read bitmap directory."); + goto fail; + } + + offset = 0; + end = s->bitmaps + s->nb_bitmaps; + for (bm = s->bitmaps; bm < end; ++bm) { + QCow2BitmapHeader *h = + (QCow2BitmapHeader *)(s->bitmap_directory + offset); + + if (offset >= s->bitmap_directory_size) { + error_setg(errp, "Broken bitmap directory."); + goto fail; + } + + bitmap_header_to_cpu(h); + + ret = check_constraints(h, s->cluster_size, + nb_sectors << BDRV_SECTOR_BITS); + if (ret < 0) { + error_setg(errp, "Bitmap doesn't satisfy the constraints."); + goto fail; + } + + bm->offset = offset; + bm->name = g_strndup((char *)(h + 1), h->name_size); + + offset += dir_entry_size(h); + } + return 0; + +fail: + g_free(s->bitmap_directory); + s->bitmap_directory = NULL; + + return ret; +} + +int qcow2_read_bitmaps(BlockDriverState *bs, Error **errp) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + + if (s->bitmap_directory != NULL || s->bitmaps != NULL) { + /* already read */ + error_setg(errp, "Try read bitmaps, when they are already read."); + return -EEXIST; + } + + if (s->nb_bitmaps == 0) { + /* No bitmaps - nothing to do */ + return 0; + } + + s->bitmaps = g_try_new0(QCow2Bitmap, s->nb_bitmaps); + if (s->bitmaps == NULL) { + error_setg(errp, "Can't allocate space for qcow2 bitmaps."); + ret = -ENOMEM; + goto fail; + } + + ret = directory_read(bs, errp); + if (ret < 0) { + goto fail; + } + + return 0; + +fail: + qcow2_free_bitmaps(bs); + + return ret; +} diff --git a/block/qcow2.h b/block/qcow2.h index 3f7429e..48fb2a5 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -297,6 +297,12 @@ typedef struct BDRVQcow2State { unsigned int nb_snapshots; QCowSnapshot *snapshots; + uint64_t bitmap_directory_offset; + uint64_t bitmap_directory_size; + uint8_t *bitmap_directory; + unsigned int nb_bitmaps; + QCow2Bitmap *bitmaps; + int flags; int qcow_version; bool use_lazy_refcounts; @@ -610,6 +616,10 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs, void qcow2_free_snapshots(BlockDriverState *bs); int qcow2_read_snapshots(BlockDriverState *bs); +/* qcow2-dirty-bitmap.c functions */ +void qcow2_free_bitmaps(BlockDriverState *bs); +int qcow2_read_bitmaps(BlockDriverState *bs, Error **errp); + /* qcow2-cache.c functions */ Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables); int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c); -- 1.8.3.1