diff mbox

[08/29] qcow2-bitmap: delete bitmap from qcow2 after load

Message ID 1470668720-211300-9-git-send-email-vsementsov@virtuozzo.com (mailing list archive)
State New, archived
Headers show

Commit Message

Vladimir Sementsov-Ogievskiy Aug. 8, 2016, 3:04 p.m. UTC
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(+)

Comments

Kevin Wolf Aug. 11, 2016, 1:18 p.m. UTC | #1
Am 08.08.2016 um 17:04 hat Vladimir Sementsov-Ogievskiy geschrieben:
> 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>

The approach that you're taking (keeping the complete bitmap directory
in memory in its on-disk format) means that we can't keep pointers to a
QCow2BitmapHeader anywhere because modifying the directory would move it
around.

Did you consider using a different in-memory representation like a
normal list of structs? This would also allow you to use the normal list
iteration mechanisms (i.e. the existing *_FOREACH macros) instead of the
rather complex ones you need now.

For example, qcow2 snapshots use a different on-disk and in-memory
format and the on-disk format is only implemented in the actual
read/write functions, and anything else can deal with the simpler
in-memory structures.

Kevin
Vladimir Sementsov-Ogievskiy Aug. 30, 2016, 3:03 p.m. UTC | #2
On 11.08.2016 16:18, Kevin Wolf wrote:
> Am 08.08.2016 um 17:04 hat Vladimir Sementsov-Ogievskiy geschrieben:
>> 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>
> The approach that you're taking (keeping the complete bitmap directory
> in memory in its on-disk format) means that we can't keep pointers to a
> QCow2BitmapHeader anywhere because modifying the directory would move it
> around.
>
> Did you consider using a different in-memory representation like a
> normal list of structs? This would also allow you to use the normal list
> iteration mechanisms (i.e. the existing *_FOREACH macros) instead of the
> rather complex ones you need now.
>
> For example, qcow2 snapshots use a different on-disk and in-memory
> format and the on-disk format is only implemented in the actual
> read/write functions, and anything else can deal with the simpler
> in-memory structures.
>
> Kevin

Hmm. It was some kind of optimization (not to rewrite the whole 
directory when rewriting one entry).. But for now, after deciding to 
clear bitmap directory after reading bitmaps, it(my approach) obviously 
becomes unnecessary complication.. So, I will change this.
diff mbox

Patch

diff --git a/block/qcow2-bitmap.c b/block/qcow2-bitmap.c
index 280b7bf..e677c31 100644
--- a/block/qcow2-bitmap.c
+++ b/block/qcow2-bitmap.c
@@ -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;