diff mbox

[[PATCH,v2] 1/1] vpc.c: Add VHD resize support

Message ID 1411120786-28764-1-git-send-email-lpetrut@cloudbasesolutions.com
State New
Headers show

Commit Message

Lucian Petrut Sept. 19, 2014, 9:59 a.m. UTC
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.

Signed-off-by: Lucian Petrut <lpetrut@cloudbasesolutions.com>
---
 block/vpc.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 184 insertions(+), 2 deletions(-)


Changes from previous version: fixed a few coding style nits
underlined by the checkpatch.pl script. I forgot to run this
script before submiting the first version, sorry about this.

Comments

Stefan Hajnoczi Sept. 22, 2014, 12:17 p.m. UTC | #1
On Fri, Sep 19, 2014 at 12:59:46PM +0300, Lucian Petrut wrote:
> Note that this patch assumes that all the data blocks are written
> right after the BAT.

Are there any known files out there where this is not the case?

Perhaps an error should be printed if a data block that requires moving
does not appear in pagetable[].  That's an indication that the image is
not laid out as expected.

> +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;
> +    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 (cpu_to_be32(footer->type) == VHD_DIFFERENCING) {

footer_buf is big-endian so this should be be32_to_cpu()

> +        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);

This expression doesn't just round up, it always adds an extra block.
Is this intentional?

I expected the numerator for rounding up to be:
  new_total_sectors + s->block_size / 512 - 1

> +
> +    if (cpu_to_be32(footer->type) == VHD_DYNAMIC) {

be32_to_cpu()

> +        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_index == 0) {

Is this condition correct?  I expected !buf.  What happens during
the second loop iteration when block_index == 1 (we freed buf at the end
of the first iteration).

If I'm reading the code correctly this results in a NULL pointer
dereference.  That would mean you never executed this code path.

Please create a qemu-iotests test case to exercise the code paths in
your patch.  There are many examples you can use as a starting point in
tests/qemu-iotests/.  The basic overview is here:
http://qemu-project.org/Documentation/QemuIoTests

> +                buf = g_malloc(s->block_size + s->bitmap_size);

Please use qemu_blockalign()/qemu_vfree() for disk data buffers.  If the
file is opened with O_DIRECT there are memory alignment constraints.
Using qemu_blockalign() avoids the need for a bounce buffer in
block/raw-posix.c.

(Even if there is no performance concern, it's a good habit to follow
consistently so that we do align in the cases where the performance does
matter.)

> +            }
> +
> +            ret = bdrv_pread(bs->file, block_offset, 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, buf,
> +                             s->block_size + s->bitmap_size);
> +            if (ret < 0) {
> +                goto out;
> +            }
> +
> +            for (index = 0; index < s->max_table_entries; index++) {
> +                if (s->pagetable[index] == block_offset / BDRV_SECTOR_SIZE) {
> +                    s->pagetable[index] = block_offset;

The if condition checks for block_offset / BDRV_SECTOR_SIZE, but the
assignment stores block_offset (without converting it to sectors).

Did you mean:

  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 = be32_to_cpu(new_block_offset /
> +                                            BDRV_SECTOR_SIZE);

cpu_to_be32()

> +                    ret = bdrv_pwrite_sync(bs->file, bat_offset, &bat_value, 4);
> +                    if (ret < 0) {
> +                        goto out;
> +                    }
> +                    break;
> +                }
> +            }
> +
> +            s->free_data_block_offset = new_block_offset + s->block_size +
> +                                        s->bitmap_size;
> +            g_free(buf);
> +            buf = NULL;
> +        }
> +
> +        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_sync(bs->file, offset, buf, 512);

You could just bdrv_pwrite() and bdrv_flush() after the loop to make
this faster.

> +            if (ret < 0) {
> +                goto out;
> +            }
> +            offset += 512;
> +        }
> +
> +        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 = be32_to_cpu(new_num_bat_entries);

cpu_to_be32()

> +        dyndisk_header->checksum = 0;
> +        dyndisk_header->checksum = be32_to_cpu(vpc_checksum(buf, 1024));

cpu_to_be32()

> +        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;

Don't we have to take into account the header for VHD_FIXED?

> +    }
> +
> +    footer->cyls = be16_to_cpu(cyls);

cpu_to_be16()

> +    footer->heads = heads;
> +    footer->secs_per_cyl = secs_per_cyl;
> +    footer->size = be64_to_cpu(new_total_sectors * BDRV_SECTOR_SIZE);

cpu_to_be64()

> +    footer->checksum = 0;
> +    footer->checksum = be32_to_cpu(vpc_checksum(s->footer_buf, HEADER_SIZE));

cpu_to_be32()

> +
> +    /*
> +     *  Rewrite the footer, copying to the image header in case of a
> +     *  dynamic vhd.
> +     */
> +    rewrite_footer(bs, (cpu_to_be32(footer->type) != VHD_FIXED));

be32_to_cpu()
Lucian Petrut Sept. 22, 2014, 1:24 p.m. UTC | #2
Thanks for reviewing this patch! I’ll send a new one, fixing the issues you’ve pointed out, along with some tests.

Please see my inline comments bellow.

Regards,
Lucian Petrut​

 >From: Stefan Hajnoczi

 >Sent: ‎Monday‎, ‎September‎ ‎22‎, ‎2014 ‎3‎:‎17‎ ‎PM

 >To: Petrut Lucian

 >Cc: qemu-devel@nongnu.org<mailto:qemu-devel@nongnu.org>, Lucian Petrut, jcody@redhat.com<mailto:jcody@redhat.com>, kwolf@redhat.com<mailto:kwolf@redhat.com>, sw@weilnetz.de<mailto:sw@weilnetz.de>, Alessandro Pilotti

 >

 >

 >On Fri, Sep 19, 2014 at 12:59:46PM +0300, Lucian Petrut wrote:

 >> Note that this patch assumes that all the data blocks are written

 >> right after the BAT.

 >Are there any known files out there where this is not the case?

 >Perhaps an error should be printed if a data block that requires moving

 >does not appear in pagetable[].  That's an indication that the image is

 >not laid out as expected.


You are right, a check should be added here. I’m not aware of parsers
that may have this behavior but better be on the safe side.

 >> +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;

 >> +    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 (cpu_to_be32(footer->type) == VHD_DIFFERENCING) {

 >footer_buf is big-endian so this should be be32_to_cpu()


My bad, I’ll fix the BE related issues.

 >> +        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);

 >This expression doesn't just round up, it always adds an extra block.

 >Is this intentional?

 >I expected the numerator for rounding up to be:

 >  new_total_sectors + s->block_size / 512 - 1


This was intended, it may just add an extra entry. Also, the number
of entries is calculated in the same way in the existing code which
creates dynamic VHDs, so I wanted to keep consistency with that
as well.

 >> +

 >> +    if (cpu_to_be32(footer->type) == VHD_DYNAMIC) {

 >be32_to_cpu()

 >> +        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_index == 0) {

 >Is this condition correct?  I expected !buf.  What happens during

 >the second loop iteration when block_index == 1 (we freed buf at the end

 >of the first iteration).


Whops, my bad. You are right, I should just use !buf. At first, I did not free
this at the end of iteration so I forgot to change this as well.

 >If I'm reading the code correctly this results in a NULL pointer

 >dereference.  That would mean you never executed this code path.

 >Please create a qemu-iotests test case to exercise the code paths in

 >your patch.  There are many examples you can use as a starting point in

 >tests/qemu-iotests/.  The basic overview is here:

 >http://qemu-project.org/Documentation/QemuIoTests


Thanks for the link, I’ll add a test for this.

 >> +                buf = g_malloc(s->block_size + s->bitmap_size);

 >Please use qemu_blockalign()/qemu_vfree() for disk data buffers.  If the

 >file is opened with O_DIRECT there are memory alignment constraints.

 >Using qemu_blockalign() avoids the need for a bounce buffer in

 >block/raw-posix.c.

 >(Even if there is no performance concern, it's a good habit to follow

 >consistently so that we do align in the cases where the performance does

 >matter.)


You are right, this would be much better.


 >> +            }

 >> +

 >> +            ret = bdrv_pread(bs->file, block_offset, 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, buf,

 >> +                             s->block_size + s->bitmap_size);

 >> +            if (ret < 0) {

 >> +                goto out;

 >> +            }

 >> +

 >> +            for (index = 0; index < s->max_table_entries; index++) {

 >> +                if (s->pagetable[index] == block_offset / BDRV_SECTOR_SIZE) {

 >> +                    s->pagetable[index] = block_offset;

 >The if condition checks for block_offset / BDRV_SECTOR_SIZE, but the

 >assignment stores block_offset (without converting it to sectors).

 >Did you mean:

 >  if (s->pagetable[index] == block_offset / BDRV_SECTOR_SIZE) {

 >      s->pagetable[index] = new_block_offset / BDRV_SECTOR_SIZE;


You are right here as well. I’ve actually done it this way but forgot to amend
the commit, thanks for pointing this out!

 >> +                    bat_offset = s->bat_offset + (4 * index);

 >> +                    bat_value = be32_to_cpu(new_block_offset /

 >> +                                            BDRV_SECTOR_SIZE);

 >cpu_to_be32()

 >> +                    ret = bdrv_pwrite_sync(bs->file, bat_offset, &bat_value, 4);

 >> +                    if (ret < 0) {

 >> +                        goto out;

 >> +                    }

 >> +                    break;

 >> +                }

 >> +            }

 >> +

 >> +            s->free_data_block_offset = new_block_offset + s->block_size +

 >> +                                        s->bitmap_size;

 >> +            g_free(buf);

 >> +            buf = NULL;

 >> +        }

 >> +

 >> +        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_sync(bs->file, offset, buf, 512);

 >You could just bdrv_pwrite() and bdrv_flush() after the loop to make

 >this faster.


Done.

 >> +            if (ret < 0) {

 >> +                goto out;

 >> +            }

 >> +            offset += 512;

 >> +        }

 >> +

 >> +        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 = be32_to_cpu(new_num_bat_entries);

 >cpu_to_be32()

 >> +        dyndisk_header->checksum = 0;

 >> +        dyndisk_header->checksum = be32_to_cpu(vpc_checksum(buf, 1024));

 >cpu_to_be32()

 >> +        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;

 >Don't we have to take into account the header for VHD_FIXED?


Fixed VHDs, unlike the other VHD types, don’t have a copy of the footer in the
file header, nor do they have the “dynamic vhd header”.

 >> +    }

 >> +

 >> +    footer->cyls = be16_to_cpu(cyls);

 >cpu_to_be16()

 >> +    footer->heads = heads;

 >> +    footer->secs_per_cyl = secs_per_cyl;

 >> +    footer->size = be64_to_cpu(new_total_sectors * BDRV_SECTOR_SIZE);

 >cpu_to_be64()

 >> +    footer->checksum = 0;

 >> +    footer->checksum = be32_to_cpu(vpc_checksum(s->footer_buf, HEADER_SIZE));

 >cpu_to_be32()

 >> +

 >> +    /*

 >> +     *  Rewrite the footer, copying to the image header in case of a

 >> +     *  dynamic vhd.

 >> +     */

 >> +    rewrite_footer(bs, (cpu_to_be32(footer->type) != VHD_FIXED));

 >be32_to_cpu()
Stefan Hajnoczi Sept. 23, 2014, 9:58 a.m. UTC | #3
On Mon, Sep 22, 2014 at 01:24:16PM +0000, Lucian Petrut wrote:
>  >> +    if (cpu_to_be32(footer->type) == VHD_DIFFERENCING) {
>  >footer_buf is big-endian so this should be be32_to_cpu()
> 
> My bad, I’ll fix the BE related issues.

The existing block/vpc.c code confuses beX_to_cpu() and cpu_to_beX().  I
have sent a patch to fix that ("[PATCH] vpc: fix beX_to_cpu() and
cpu_to_beX() confusion").

>  >> +        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);
>  >This expression doesn't just round up, it always adds an extra block.
>  >Is this intentional?
>  >I expected the numerator for rounding up to be:
>  >  new_total_sectors + s->block_size / 512 - 1
> 
> This was intended, it may just add an extra entry. Also, the number
> of entries is calculated in the same way in the existing code which
> creates dynamic VHDs, so I wanted to keep consistency with that
> as well.

Okay, if existing code already does it like this it makes sense to be
consistent.
diff mbox

Patch

diff --git a/block/vpc.c b/block/vpc.c
index 055efc4..6a13574 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,180 @@  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;
+    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 (cpu_to_be32(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 (cpu_to_be32(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_index == 0) {
+                buf = g_malloc(s->block_size + s->bitmap_size);
+            }
+
+            ret = bdrv_pread(bs->file, block_offset, 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, buf,
+                             s->block_size + s->bitmap_size);
+            if (ret < 0) {
+                goto out;
+            }
+
+            for (index = 0; index < s->max_table_entries; index++) {
+                if (s->pagetable[index] == block_offset / BDRV_SECTOR_SIZE) {
+                    s->pagetable[index] = block_offset;
+                    bat_offset = s->bat_offset + (4 * index);
+                    bat_value = be32_to_cpu(new_block_offset /
+                                            BDRV_SECTOR_SIZE);
+                    ret = bdrv_pwrite_sync(bs->file, bat_offset, &bat_value, 4);
+                    if (ret < 0) {
+                        goto out;
+                    }
+                    break;
+                }
+            }
+
+            s->free_data_block_offset = new_block_offset + s->block_size +
+                                        s->bitmap_size;
+            g_free(buf);
+            buf = NULL;
+        }
+
+        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_sync(bs->file, offset, buf, 512);
+            if (ret < 0) {
+                goto out;
+            }
+            offset += 512;
+        }
+
+        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 = be32_to_cpu(new_num_bat_entries);
+        dyndisk_header->checksum = 0;
+        dyndisk_header->checksum = be32_to_cpu(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 = be16_to_cpu(cyls);
+    footer->heads = heads;
+    footer->secs_per_cyl = secs_per_cyl;
+    footer->size = be64_to_cpu(new_total_sectors * BDRV_SECTOR_SIZE);
+    footer->checksum = 0;
+    footer->checksum = be32_to_cpu(vpc_checksum(s->footer_buf, HEADER_SIZE));
+
+    /*
+     *  Rewrite the footer, copying to the image header in case of a
+     *  dynamic vhd.
+     */
+    rewrite_footer(bs, (cpu_to_be32(footer->type) != VHD_FIXED));
+    if (ret < 0) {
+        goto out;
+    }
+
+out:
+    if (buf) {
+        g_free(buf);
+    }
+    return ret;
+}
+
 static int vpc_has_zero_init(BlockDriverState *bs)
 {
     BDRVVPCState *s = bs->opaque;
@@ -916,6 +1097,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,
 };