qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [[PATCH v3] 1/1] vpc.c: Add VHD resize support


From: Lucian Petrut
Subject: [Qemu-devel] [[PATCH v3] 1/1] vpc.c: Add VHD resize support
Date: Wed, 24 Sep 2014 18:13:15 +0300

This patch introduces resize support for dynamic and fixed VHD
images. Note that differencing VHD images do not support this
operation.

In order to resize dynamic VHDs, the BAT region may need to be
extended. This may require moving the first data blocks, making
room for it to expand. This required updating the according
BAT entries for the moved blocks as well, as well as initializing
the new BAT entries.

In case of fixed VHDs, the only thing that needs to be done is
moving and updating the footer.

Note that this patch assumes that all the data blocks are written
right after the BAT. An error is returned if the data blocks are
layed on the image otherwise.

Tests covering this feature have been added as well.

Signed-off-by: Lucian Petrut <address@hidden>
---
Changes since the last version: Fixed the issues pointed out by
Stefan Hajnoczi, among which are: wrong conversions to/from BE,
switched to using qemu_blockalign when moving data blocks, fixed
BAT entries for the moved blocks.

Also, this patch includes tests covering this feature.


 block/vpc.c                | 200 ++++++++++++++++++++++++++++++++++++++++++++-
 tests/qemu-iotests/104     |  93 +++++++++++++++++++++
 tests/qemu-iotests/104.out |  36 ++++++++
 tests/qemu-iotests/group   |   1 +
 4 files changed, 328 insertions(+), 2 deletions(-)
 create mode 100755 tests/qemu-iotests/104
 create mode 100755 tests/qemu-iotests/104.out

diff --git a/block/vpc.c b/block/vpc.c
index 055efc4..2641e09 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -417,7 +417,7 @@ static inline int64_t get_sector_offset(BlockDriverState 
*bs,
  *
  * Returns 0 on success and < 0 on error
  */
-static int rewrite_footer(BlockDriverState* bs)
+static int rewrite_footer(BlockDriverState *bs, bool update_header)
 {
     int ret;
     BDRVVPCState *s = bs->opaque;
@@ -427,6 +427,13 @@ static int rewrite_footer(BlockDriverState* bs)
     if (ret < 0)
         return ret;
 
+    if (update_header) {
+        ret = bdrv_pwrite_sync(bs->file, 0, s->footer_buf, HEADER_SIZE);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
     return 0;
 }
 
@@ -466,7 +473,7 @@ static int64_t alloc_block(BlockDriverState* bs, int64_t 
sector_num)
 
     // Write new footer (the old one will be overwritten)
     s->free_data_block_offset += s->block_size + s->bitmap_size;
-    ret = rewrite_footer(bs);
+    ret = rewrite_footer(bs, false);
     if (ret < 0)
         goto fail;
 
@@ -852,6 +859,194 @@ out:
     return ret;
 }
 
+
+static int vpc_truncate(BlockDriverState *bs, int64_t offset)
+{
+    BDRVVPCState *s = bs->opaque;
+    VHDFooter *footer = (VHDFooter *) s->footer_buf;
+    VHDDynDiskHeader *dyndisk_header;
+    void *buf = NULL, *block_buf = NULL;
+    int64_t new_total_sectors, old_bat_size, new_bat_size,
+            block_offset, new_block_offset, bat_offset;
+    int32_t bat_value, data_blocks_required;
+    int ret = 0;
+    uint16_t cyls = 0;
+    uint8_t heads = 0;
+    uint8_t secs_per_cyl = 0;
+    uint32_t new_num_bat_entries;
+    uint64_t index, block_index, new_bat_right_limit;
+
+    if (offset & 511) {
+        error_report("The new size must be a multiple of 512.");
+        return -EINVAL;
+    }
+
+    if (offset < bs->total_sectors * 512) {
+        error_report("Shrinking vhd images is not supported.");
+        return -ENOTSUP;
+    }
+
+    if (be32_to_cpu(footer->type) == VHD_DIFFERENCING) {
+        error_report("Resizing differencing vhd images is not supported.");
+        return -ENOTSUP;
+    }
+
+    old_bat_size = (s->max_table_entries * 4 + 511) & ~511;
+    new_total_sectors = offset / BDRV_SECTOR_SIZE;
+
+    for (index = 0; new_total_sectors > (int64_t)cyls * heads * secs_per_cyl;
+            index++) {
+        if (calculate_geometry(new_total_sectors + index, &cyls, &heads,
+                               &secs_per_cyl)) {
+            return -EFBIG;
+        }
+    }
+    new_total_sectors = (int64_t) cyls * heads * secs_per_cyl;
+    new_num_bat_entries = (new_total_sectors + s->block_size / 512) /
+                          (s->block_size / 512);
+
+    if (be32_to_cpu(footer->type) == VHD_DYNAMIC) {
+        new_bat_size = (new_num_bat_entries * 4 + 511) & ~511;
+        /* Number of blocks required for extending the BAT */
+        data_blocks_required = (new_bat_size - old_bat_size +
+                                s->block_size - 1) / s->block_size;
+        new_bat_right_limit = s->bat_offset + old_bat_size +
+                              data_blocks_required *
+                              (s->block_size + s->bitmap_size);
+
+        for (block_index = 0; block_index <
+                data_blocks_required; block_index++){
+            /*
+             * The BAT has to be extended. We'll have to move the first
+             * data block(s) to the end of the file, making room for the
+             * BAT to expand. Also, the BAT entries have to be updated for
+             * the moved blocks.
+             */
+
+            block_offset = s->bat_offset + old_bat_size +
+                           block_index * (s->block_size + s->bitmap_size);
+            if (block_offset >= s->free_data_block_offset) {
+                /*
+                * Do not allocate a new block for the BAT if no data blocks
+                * were previously allocated to the vhd image.
+                */
+                s->free_data_block_offset += (new_bat_size - old_bat_size);
+                break;
+            }
+
+            if (!block_buf) {
+                block_buf = qemu_try_blockalign(
+                    bs->file,
+                    s->block_size + s->bitmap_size);
+                if (block_buf == NULL) {
+                    ret = -ENOMEM;
+                    goto out;
+                }
+            }
+
+            ret = bdrv_pread(bs->file, block_offset, block_buf,
+                             s->block_size + s->bitmap_size);
+            if (ret < 0) {
+                goto out;
+            }
+
+            new_block_offset = s->free_data_block_offset < new_bat_right_limit 
?
+                               new_bat_right_limit : s->free_data_block_offset;
+            bdrv_pwrite_sync(bs->file, new_block_offset, block_buf,
+                             s->block_size + s->bitmap_size);
+            if (ret < 0) {
+                goto out;
+            }
+
+            bat_offset = 0;
+            for (index = 0; index < s->max_table_entries; index++) {
+                if (s->pagetable[index] == block_offset / BDRV_SECTOR_SIZE) {
+                    s->pagetable[index] = new_block_offset / BDRV_SECTOR_SIZE;
+                    bat_offset = s->bat_offset + (4 * index);
+                    bat_value = cpu_to_be32(new_block_offset /
+                                            BDRV_SECTOR_SIZE);
+                    ret = bdrv_pwrite_sync(bs->file, bat_offset, &bat_value, 
4);
+                    if (ret < 0) {
+                        goto out;
+                    }
+                    break;
+                }
+            }
+            if (!bat_offset) {
+                error_report("Invalid VHD BAT.");
+                ret = -EINVAL;
+                goto out;
+            }
+
+            s->free_data_block_offset = new_block_offset + s->block_size +
+                                        s->bitmap_size;
+        }
+
+        buf = g_malloc(512);
+        memset(buf, 0xFF, 512);
+
+        /* Extend the BAT */
+        offset = s->bat_offset + old_bat_size;
+        for (index = 0;
+                index < (new_bat_size - old_bat_size) / 512;
+                index++) {
+            ret = bdrv_pwrite(bs->file, offset, buf, 512);
+            if (ret < 0) {
+                goto out;
+            }
+            offset += 512;
+        }
+        bdrv_flush(bs);
+
+        g_free(buf);
+        buf = g_malloc(1024);
+
+        /* Update the Dynamic Disk Header */
+        ret = bdrv_pread(bs->file, 512, buf,
+                         1024);
+        if (ret < 0) {
+            goto out;
+        }
+
+        dyndisk_header = (VHDDynDiskHeader *) buf;
+        dyndisk_header->max_table_entries = cpu_to_be32(new_num_bat_entries);
+        dyndisk_header->checksum = 0;
+        dyndisk_header->checksum = cpu_to_be32(vpc_checksum(buf, 1024));
+        ret = bdrv_pwrite_sync(bs->file, 512, buf, 1024);
+        if (ret < 0) {
+            goto out;
+        }
+
+    } else {
+        s->free_data_block_offset = new_total_sectors * BDRV_SECTOR_SIZE;
+    }
+
+    footer->cyls = cpu_to_be16(cyls);
+    footer->heads = heads;
+    footer->secs_per_cyl = secs_per_cyl;
+    footer->size = cpu_to_be64(new_total_sectors * BDRV_SECTOR_SIZE);
+    footer->checksum = 0;
+    footer->checksum = cpu_to_be32(vpc_checksum(s->footer_buf, HEADER_SIZE));
+
+    /*
+     *  Rewrite the footer, copying to the image header in case of a
+     *  dynamic vhd.
+     */
+    rewrite_footer(bs, (be32_to_cpu(footer->type) != VHD_FIXED));
+    if (ret < 0) {
+        goto out;
+    }
+
+out:
+    if (buf) {
+        g_free(buf);
+    }
+    if (block_buf) {
+        qemu_vfree(block_buf);
+    }
+    return ret;
+}
+
 static int vpc_has_zero_init(BlockDriverState *bs)
 {
     BDRVVPCState *s = bs->opaque;
@@ -916,6 +1111,7 @@ static BlockDriver bdrv_vpc = {
 
     .bdrv_get_info          = vpc_get_info,
 
+    .bdrv_truncate          = vpc_truncate,
     .create_opts            = &vpc_create_opts,
     .bdrv_has_zero_init     = vpc_has_zero_init,
 };
diff --git a/tests/qemu-iotests/104 b/tests/qemu-iotests/104
new file mode 100755
index 0000000..ee14a51
--- /dev/null
+++ b/tests/qemu-iotests/104
@@ -0,0 +1,93 @@
+#!/bin/bash
+#
+# Resizing vhd images
+#
+# Copyright (C) 2014 Cloudbase Solutions Srl.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Note that VHD images can get larger than the allocated size because
+# of the image metadata size, this being considered by this test.
+# This test is based on test 025.
+#
+
+# creator
address@hidden
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1    # failure is the default!
+
+_cleanup()
+{
+    _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+. ./common.pattern
+
+_supported_fmt vpc
+_supported_proto file sheepdog rbd nfs
+_supported_os Linux
+
+echo "=== Creating image"
+echo
+small_size=$((128 * 1024 * 1024))
+big_size=$((384 * 1024 * 1024))
+_make_test_img $small_size
+
+echo
+echo "=== Writing whole image"
+io_pattern write 0 $small_size 0 1 0xc5
+_check_test_img
+
+echo
+echo "=== Resizing image"
+$QEMU_IO "$TEST_IMG" -c "truncate $big_size"
+_check_test_img
+
+echo
+echo "=== Verifying image size after reopen"
+$QEMU_IO -c "length" "$TEST_IMG" | sed -e "s/\.[0-9]*//"
+
+echo
+echo "=== Verifying resized image"
+io_pattern read 0 $small_size 0 1 0xc5
+io_pattern read $small_size $(($big_size - $small_size)) 0 1 0
+
+echo
+echo "=== Shrinking image"
+$QEMU_IO "$TEST_IMG" -c "truncate  $small_size"
+
+echo
+echo "=== Resizing to a new size that is not a sector size multiple"
+$QEMU_IO "$TEST_IMG" -c "truncate $(($big_size + 5))"
+
+echo
+echo "=== Resizing to a size bigger than supported by the vhd format."
+$QEMU_IO "$TEST_IMG" -c "truncate 3T"
+
+# TODO: Add tests for fixed VHD image resizing as soon as the patch set
+# which fixes vpc_probe is merged, in order to recognise fixed VHD images.
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/104.out b/tests/qemu-iotests/104.out
new file mode 100755
index 0000000..c54e9c3
--- /dev/null
+++ b/tests/qemu-iotests/104.out
@@ -0,0 +1,36 @@
+QA output created by 104
+=== Creating image
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728
+
+=== Writing whole image
+=== IO: pattern 0xc5
+wrote 134217728/134217728 bytes at offset 0
+128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+No errors were found on the image.
+
+=== Resizing image
+No errors were found on the image.
+
+=== Verifying image size after reopen
+384 MiB
+
+=== Verifying resized image
+=== IO: pattern 0xc5
+read 134217728/134217728 bytes at offset 0
+128 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+=== IO: pattern 0
+read 268435456/268435456 bytes at offset 134217728
+256 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+=== Shrinking image
+Shrinking vhd images is not supported.
+truncate: Operation not supported
+
+=== Resizing to a new size that is not a sector size multiple
+The new size must be a multiple of 512.
+truncate: Invalid argument
+
+=== Resizing to a size bigger than supported by the vhd format.
+truncate: File too large
+*** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 0920b28..377687e 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -104,3 +104,4 @@
 100 rw auto quick
 101 rw auto quick
 103 rw auto quick
+104 rw auto quick

reply via email to

[Prev in Thread] Current Thread [Next in Thread]