@@ -61,6 +61,11 @@ 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_header_to_cpu(QCow2BitmapHeader *h)
{
be64_to_cpus(&h->bitmap_table_offset);
@@ -70,6 +75,15 @@ static inline void bitmap_header_to_cpu(QCow2BitmapHeader *h)
be32_to_cpus(&h->extra_data_size);
}
+static inline 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 inline void bitmap_table_to_cpu(uint64_t *bitmap_table, size_t size)
{
size_t i;
@@ -94,6 +108,17 @@ static inline QCow2BitmapHeader *next_dir_entry(QCow2BitmapHeader *entry)
return (QCow2BitmapHeader *)((uint8_t *)entry + dir_entry_size(entry));
}
+static inline void bitmap_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);
+ }
+}
+
/* directory_read
* Read bitmaps directory from bs by @offset and @size. Convert it to cpu
* format from BE.
@@ -174,6 +199,35 @@ static QCow2BitmapHeader *find_bitmap_by_name(BlockDriverState *bs,
return NULL;
}
+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 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);
+}
+
/* 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,
@@ -255,6 +309,165 @@ static int bitmap_table_load(BlockDriverState *bs, QCow2BitmapHeader *bmh,
return 0;
}
+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);
+ }
+
+ g_free(s->bitmap_directory);
+ s->bitmap_directory = new_dir;
+
+ 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;
+}
+
+static int directory_del(BlockDriverState *bs, QCow2BitmapHeader *bmh)
+{
+ int ret;
+ BDRVQcow2State *s = bs->opaque;
+ uint8_t *new_dir = NULL;
+
+ size_t sz1 = (uint8_t *)bmh - s->bitmap_directory;
+ size_t sz2 = dir_entry_size(bmh);
+ size_t sz3 = s->bitmap_directory_size - sz1 - sz2;
+
+ uint64_t new_size = sz1 + sz3;
+
+ if (new_size > 0) {
+ new_dir = g_try_malloc(new_size);
+ if (new_dir == NULL) {
+ return -ENOMEM;
+ }
+ memcpy(new_dir, s->bitmap_directory, sz1);
+ memcpy(new_dir + sz1, s->bitmap_directory + sz1 + sz2, sz3);
+ }
+
+ ret = directory_update(bs, new_dir, new_size, s->nb_bitmaps - 1);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ g_free(new_dir);
+
+ return ret;
+}
+
static BdrvDirtyBitmap *load_bitmap(BlockDriverState *bs,
QCow2BitmapHeader *bmh, Error **errp)
{
@@ -284,6 +497,20 @@ static BdrvDirtyBitmap *load_bitmap(BlockDriverState *bs,
goto fail;
}
+ if (can_write(bs)) {
+ uint64_t off = bmh->bitmap_table_offset;
+ uint32_t sz = bmh->bitmap_table_size;
+
+ ret = directory_del(bs, bmh);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "Could not free bitmap after read.");
+ goto fail;
+ }
+
+ do_free_bitmap_clusters(bs, off, sz, bitmap_table);
+ }
+
g_free(name);
g_free(bitmap_table);
return bitmap;
If we load bitmap for r/w bds, it's data in the image should be considered inconsistent from this point. Therefore it is safe to remove it from the image. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- block/qcow2-bitmap.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+)