Auto loading bitmaps are bitmaps in Qcow2, with AUTO flag set. They are loaded at image open and becomes BdrvDirtyBitmap's for corresponding drive. These bitmaps are deleted from Qcow2 image after loading to avoid conflicts.
Extra data in bitmaps is not supported for now. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsement...@virtuozzo.com> --- block/qcow2-bitmap.c | 518 ++++++++++++++++++++++++++++++++++++++++++++++++++- block/qcow2.c | 5 + block/qcow2.h | 3 + 3 files changed, 525 insertions(+), 1 deletion(-) diff --git a/block/qcow2-bitmap.c b/block/qcow2-bitmap.c index cd18b07..760a047 100644 --- a/block/qcow2-bitmap.c +++ b/block/qcow2-bitmap.c @@ -25,6 +25,12 @@ * THE SOFTWARE. */ +#include "qemu/osdep.h" +#include "qapi/error.h" + +#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. */ @@ -37,11 +43,521 @@ #define BME_MAX_NAME_SIZE 1023 /* Bitmap directory entry flags */ -#define BME_RESERVED_FLAGS 0xffffffff +#define BME_RESERVED_FLAGS 0xfffffffd +#define BME_FLAG_AUTO (1U << 1) /* bits [1, 8] U [56, 63] are reserved */ #define BME_TABLE_ENTRY_RESERVED_MASK 0xff000000000001fe +#define for_each_bitmap_dir_entry_unsafe(entry, dir, size) \ + for (entry = (Qcow2BitmapDirEntry *)(dir); \ + entry < (Qcow2BitmapDirEntry *)((uint8_t *)(dir) + size); \ + entry = next_dir_entry(entry)) + +#define for_each_bitmap_dir_entry(entry, dir, size) \ + for (entry = (Qcow2BitmapDirEntry *)(dir); \ + assert(check_dir_iter(entry, (uint8_t *)(dir) + size)), \ + entry < (Qcow2BitmapDirEntry *)((uint8_t *)(dir) + size); \ + entry = next_dir_entry(entry)) + 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 inline void bitmap_dir_entry_to_cpu(Qcow2BitmapDirEntry *entry) +{ + be64_to_cpus(&entry->bitmap_table_offset); + be32_to_cpus(&entry->bitmap_table_size); + be32_to_cpus(&entry->flags); + be16_to_cpus(&entry->name_size); + 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 void bitmap_table_to_cpu(uint64_t *bitmap_table, size_t size) +{ + size_t i; + + for (i = 0; i < size; ++i) { + be64_to_cpus(&bitmap_table[i]); + } +} + +static inline int calc_dir_entry_size(size_t name_size, size_t extra_data_size) +{ + return align_offset(sizeof(Qcow2BitmapDirEntry) + + name_size + extra_data_size, 8); +} + +static inline int dir_entry_size(Qcow2BitmapDirEntry *entry) +{ + return calc_dir_entry_size(entry->name_size, entry->extra_data_size); +} + +static inline const char *dir_entry_name_notcstr(Qcow2BitmapDirEntry *entry) +{ + return (const char *)(entry + 1) + entry->extra_data_size; +} + +static inline int copy_dir_entry(void *dest, Qcow2BitmapDirEntry *entry) +{ + int sz = dir_entry_size(entry); + memcpy(dest, entry, sz); + return sz; +} + +static inline Qcow2BitmapDirEntry *next_dir_entry(Qcow2BitmapDirEntry *entry) +{ + return (Qcow2BitmapDirEntry *)((uint8_t *)entry + dir_entry_size(entry)); +} + +static inline bool check_dir_iter(Qcow2BitmapDirEntry *it, void *directory_end) +{ + return ((void *)it == directory_end) || + (((void *)(it + 1) <= directory_end) && + ((void *)next_dir_entry(it) <= directory_end)); +} + +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); + } +} + +static void clear_bitmap_table(BlockDriverState *bs, uint64_t *bitmap_table, + uint32_t bitmap_table_size) +{ + BDRVQcow2State *s = bs->opaque; + int cl_size = s->cluster_size; + int i; + + for (i = 0; i < bitmap_table_size; ++i) { + uint64_t addr = bitmap_table[i]; + if (addr <= 1) { + continue; + } + + qcow2_free_clusters(bs, addr, cl_size, QCOW2_DISCARD_ALWAYS); + bitmap_table[i] = 0; + } +} + +static int bitmap_table_load(BlockDriverState *bs, Qcow2BitmapDirEntry *entry, + uint64_t **table) +{ + int ret; + + *table = g_try_new(uint64_t, entry->bitmap_table_size); + if (*table == NULL) { + return -ENOMEM; + } + + ret = bdrv_pread(bs->file, entry->bitmap_table_offset, + *table, entry->bitmap_table_size * sizeof(uint64_t)); + if (ret < 0) { + g_free(*table); + *table = NULL; + return ret; + } + + bitmap_table_to_cpu(*table, entry->bitmap_table_size); + + return 0; +} + +static void do_free_bitmap_clusters(BlockDriverState *bs, + uint64_t table_offset, + uint32_t table_size, + uint64_t *bitmap_table) +{ + clear_bitmap_table(bs, bitmap_table, table_size); + + qcow2_free_clusters(bs, table_offset, table_size * sizeof(uint64_t), + QCOW2_DISCARD_ALWAYS); +} + +static int free_bitmap_clusters(BlockDriverState *bs, Qcow2BitmapDirEntry *entry) +{ + int ret; + uint64_t *bitmap_table; + + ret = bitmap_table_load(bs, entry, &bitmap_table); + if (ret < 0 || bitmap_table == NULL) { + return ret; + } + + do_free_bitmap_clusters(bs, entry->bitmap_table_offset, + entry->bitmap_table_size, bitmap_table); + + return 0; +} + +/* dirty sectors in cluster is a number of sectors in the image, corresponding + * to one cluster of bitmap data */ +static uint64_t dirty_sectors_in_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); +} + +static int load_bitmap_data(BlockDriverState *bs, + const uint64_t *dirty_bitmap_table, + uint32_t dirty_bitmap_table_size, + BdrvDirtyBitmap *bitmap) +{ + int ret = 0; + BDRVQcow2State *s = bs->opaque; + uint64_t sector, dsc; + uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap); + int cl_size = s->cluster_size; + uint8_t *buf = NULL; + uint32_t i, tab_size = + size_to_clusters(s, + bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size)); + + if (tab_size != dirty_bitmap_table_size) { + return -EINVAL; + } + + bdrv_clear_dirty_bitmap(bitmap, NULL); + + buf = g_malloc0(cl_size); + dsc = dirty_sectors_in_cluster(s, bitmap); + for (i = 0, sector = 0; i < tab_size; ++i, sector += dsc) { + uint64_t end = MIN(bm_size, sector + dsc); + uint64_t offset = dirty_bitmap_table[i]; + + if (offset == 1) { + bdrv_dirty_bitmap_deserialize_ones(bitmap, sector, end, false); + } else if (offset > 1) { + ret = bdrv_pread(bs->file, offset, buf, cl_size); + if (ret < 0) { + goto finish; + } + bdrv_dirty_bitmap_deserialize_part(bitmap, buf, sector, end, false); + } + } + ret = 0; + + bdrv_dirty_bitmap_deserialize_finish(bitmap); + +finish: + g_free(buf); + + return ret; +} + +static BdrvDirtyBitmap *load_bitmap(BlockDriverState *bs, + Qcow2BitmapDirEntry *entry, Error **errp) +{ + int ret; + uint64_t *bitmap_table = NULL; + uint32_t granularity; + BdrvDirtyBitmap *bitmap = NULL; + char *name = g_strndup(dir_entry_name_notcstr(entry), entry->name_size); + + ret = bitmap_table_load(bs, entry, &bitmap_table); + if (ret < 0) { + error_setg_errno(errp, -ret, + "Could not read bitmap_table table from image"); + goto fail; + } + + granularity = 1U << entry->granularity_bits; + bitmap = bdrv_create_dirty_bitmap(bs, granularity, name, errp); + if (bitmap == NULL) { + goto fail; + } + + ret = load_bitmap_data(bs, bitmap_table, entry->bitmap_table_size, + bitmap); + if (ret < 0) { + error_setg_errno(errp, -ret, "Could not read bitmap from image"); + goto fail; + } + + g_free(name); + g_free(bitmap_table); + return bitmap; + +fail: + g_free(name); + g_free(bitmap_table); + if (bitmap != NULL) { + bdrv_release_dirty_bitmap(bs, bitmap); + } + + return NULL; +} + +/* directory_read + * Read bitmaps directory from bs by @offset and @size. Convert it to cpu + * format from BE. + */ +static uint8_t *directory_read(BlockDriverState *bs, + uint64_t offset, uint64_t size, Error **errp) +{ + int ret; + uint8_t *dir, *dir_end; + Qcow2BitmapDirEntry *e; + + dir = g_try_malloc(size); + if (dir == NULL) { + error_setg(errp, "Can't allocate space for bitmap directory."); + return NULL; + } + dir_end = dir + size; + + ret = bdrv_pread(bs->file, offset, dir, size); + if (ret < 0) { + error_setg_errno(errp, -ret, "Can't read bitmap directory."); + goto fail; + } + + /* loop is safe because next entry offset is calculated after conversion to + * cpu format */ + for_each_bitmap_dir_entry_unsafe(e, dir, size) { + if ((uint8_t *)(e + 1) > dir_end) { + goto broken_dir; + } + + bitmap_dir_entry_to_cpu(e); + + if ((uint8_t *)next_dir_entry(e) > dir_end) { + goto broken_dir; + } + + if (e->extra_data_size != 0) { + error_setg(errp, "Can't load bitmap '%.*s' from '%s':" + "extra data not supported.", e->name_size, + dir_entry_name_notcstr(e), + bdrv_get_device_or_node_name(bs)); + goto fail; + } + } + + assert((uint8_t *)e == dir_end); + + return dir; + +broken_dir: + error_setg(errp, "Broken bitmap directory."); + +fail: + g_free(dir); + + return NULL; +} + +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); + bitmap_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, 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_update(BlockDriverState *bs, uint8_t *new_dir, + size_t new_size, uint32_t new_nb_bitmaps) +{ + BDRVQcow2State *s = bs->opaque; + int ret; + int64_t new_offset = 0; + uint64_t old_offset = s->bitmap_directory_offset; + uint64_t old_size = s->bitmap_directory_size; + uint32_t old_nb_bitmaps = s->nb_bitmaps; + uint64_t old_autocl = s->autoclear_features; + + if (new_size > QCOW_MAX_DIRTY_BITMAP_DIRECTORY_SIZE) { + return -EINVAL; + } + + if ((new_size == 0) != (new_nb_bitmaps == 0)) { + return -EINVAL; + } + + if (new_nb_bitmaps > 0) { + new_offset = directory_write(bs, new_dir, new_size); + if (new_offset < 0) { + return new_offset; + } + + ret = bdrv_flush(bs); + if (ret < 0) { + goto fail; + } + } + s->bitmap_directory_offset = new_offset; + s->bitmap_directory_size = new_size; + s->nb_bitmaps = new_nb_bitmaps; + + ret = update_header_sync(bs); + if (ret < 0) { + goto fail; + } + + if (old_size) { + qcow2_free_clusters(bs, old_offset, old_size, QCOW2_DISCARD_ALWAYS); + } + + return 0; + +fail: + 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; + s->nb_bitmaps = old_nb_bitmaps; + s->autoclear_features = old_autocl; + } + + return ret; +} + +int qcow2_read_bitmaps(BlockDriverState *bs, Error **errp) +{ + int ret; + BDRVQcow2State *s = bs->opaque; + uint8_t *dir, *new_dir, *new_pos; + uint64_t dir_size; + Qcow2BitmapDirEntry *e; + uint32_t new_nb_bitmaps = 0; + + if (s->nb_bitmaps == 0) { + /* No bitmaps - nothing to do */ + return 0; + } + + new_pos = new_dir = g_try_malloc(s->bitmap_directory_size); + if (new_dir == NULL) { + error_setg(errp, "Can't allocate space for bitmap directory."); + return -ENOMEM; + } + + dir_size = s->bitmap_directory_size; + dir = directory_read(bs, s->bitmap_directory_offset, + s->bitmap_directory_size, errp); + if (dir == NULL) { + ret = -EINVAL; + goto out; + } + + for_each_bitmap_dir_entry(e, dir, dir_size) { + uint32_t fl = e->flags; + + if (fl & BME_FLAG_AUTO) { + BdrvDirtyBitmap *bitmap = load_bitmap(bs, e, errp); + if (bitmap == NULL) { + ret = -EINVAL; + goto out; + } + } else { + /* leave bitmap in the image */ + new_pos += copy_dir_entry(new_pos, e); + new_nb_bitmaps++; + } + } + + if (!can_write(bs)) { + goto out; + } + + ret = directory_update(bs, new_dir, new_pos - new_dir, new_nb_bitmaps); + if (ret < 0) { + error_setg(errp, "Can't update bitmap directory."); + goto out; + } + + /* to be consistent, free bitmap only after successfull directory update */ + for_each_bitmap_dir_entry(e, dir, dir_size) { + uint32_t fl = e->flags; + + if (fl & BME_FLAG_AUTO) { + free_bitmap_clusters(bs, e); + } + } + +out: + g_free(dir); + g_free(new_dir); + + return ret; +} diff --git a/block/qcow2.c b/block/qcow2.c index 08c4ef9..02ec224 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -213,6 +213,11 @@ static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, s->bitmap_directory_size = bitmaps_ext.bitmap_directory_size; + ret = qcow2_read_bitmaps(bs, errp); + if (ret < 0) { + return ret; + } + #ifdef DEBUG_EXT printf("Qcow2: Got dirty bitmaps extension:" " offset=%" PRIu64 " nb_bitmaps=%" PRIu32 "\n", diff --git a/block/qcow2.h b/block/qcow2.h index c068b2c..482a29f 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -603,6 +603,9 @@ int qcow2_snapshot_load_tmp(BlockDriverState *bs, void qcow2_free_snapshots(BlockDriverState *bs); int qcow2_read_snapshots(BlockDriverState *bs); +/* qcow2-bitmap.c functions */ +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