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 . +# +# 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 +owner=lpetrut@cloudbasesolutions.com + +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