Fix networking in the appliance.
[libguestfs.git] / tools / virt-resize
old mode 100644 (file)
new mode 100755 (executable)
index 1c4006a..8a473ca
@@ -20,6 +20,7 @@ use warnings;
 use strict;
 
 use Sys::Guestfs;
+use Sys::Guestfs::Lib qw(feature_available);
 use Fcntl qw(S_ISREG SEEK_SET);
 use POSIX qw(floor);
 use Pod::Usage;
@@ -32,6 +33,8 @@ $Data::Dumper::Sortkeys = 1;
 die __"virt-resize: sorry this program does not work on a 32 bit host\n"
     if ~1 == 4294967294;
 
+$| = 1;
+
 =encoding utf8
 
 =head1 NAME
@@ -40,9 +43,9 @@ virt-resize - Resize a virtual machine disk
 
 =head1 SYNOPSIS
 
- virt-resize [--resize /dev/sdaN=[+/-]<size>[%]] [--expand /dev/sdaN]
-   [--shrink /dev/sdaN] [--ignore /dev/sdaN] [--delete /dev/sdaN] [...]
-   indisk outdisk
+ virt-resize [--resize /dev/sdaN=[+/-]<size>[%]]
+   [--expand /dev/sdaN] [--shrink /dev/sdaN]
+   [--ignore /dev/sdaN] [--delete /dev/sdaN] [...] indisk outdisk
 
 =head1 DESCRIPTION
 
@@ -60,41 +63,62 @@ L<virt-list-filesystems(1)> and
 L<virt-df(1)>,
 we recommend you go and read those manual pages first.
 
-=head2 BASIC USAGE
+=head1 EXAMPLES
+
+Copy C<olddisk> to C<newdisk>, extending one of the guest's partitions
+to fill the extra 5GB of space.
+
+ truncate -r olddisk newdisk; truncate -s +5G newdisk
+ virt-list-partitions -lht olddisk
+ # Note "/dev/sda2" is a partition inside the "olddisk" file.
+ virt-resize --expand /dev/sda2 olddisk newdisk
+
+As above, but make the /boot partition 200MB bigger, while giving the
+remaining space to /dev/sda2:
+
+ virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 olddisk newdisk
+
+As above, but the output format will be uncompressed qcow2:
+
+ qemu-img create -f qcow2 newdisk.qcow2 15G
+ virt-resize --expand /dev/sda2 olddisk newdisk.qcow2
+
+=head1 DETAILED USAGE
 
-This describes the common case where you want to expand an image to
-give your guest more space.  Shrinking images is considerably more
-complicated (unfortunately).
+=head2 EXPANDING A VIRTUAL MACHINE DISK
 
 =over 4
 
-=item 1. Locate disk image
+=item 1. Shut down the virtual machine
 
-Locate the disk image that you want to resize.  It could be in a local
-file or device.  If the guest is managed by libvirt, you can use
-C<virsh dumpxml> like this to find the disk image name:
+=item 2. Locate input disk image
+
+Locate the input disk image (ie. the file or device on the host
+containing the guest's disk).  If the guest is managed by libvirt, you
+can use C<virsh dumpxml> like this to find the disk image name:
 
  # virsh dumpxml guestname | xpath /domain/devices/disk/source
  Found 1 nodes:
  -- NODE --
  <source dev="/dev/vg/lv_guest" />
 
-=item 2. Look at current sizing
+=item 3. Look at current sizing
 
 Use L<virt-list-partitions(1)> to display the current partitions and
 sizes:
 
- # virt-list-partitions -lh /dev/vg/lv_guest
+ # virt-list-partitions -lht /dev/vg/lv_guest
  /dev/sda1 ext3 101.9M
  /dev/sda2 pv 7.9G
+ /dev/sda device 8.0G
 
 (This example is a virtual machine with an 8 GB disk which we would
 like to expand up to 10 GB).
 
-=item 3. Create destination disk
+=item 4. Create output disk
 
 Virt-resize cannot do in-place disk modifications.  You have to have
-space to store the resized destination disk.
+space to store the resized output disk.
 
 To store the resized disk image in a file, create a file of a suitable
 size:
@@ -102,7 +126,7 @@ size:
  # rm -f outdisk
  # truncate -s 10G outdisk
 
-Use L<lvcreate(1)> to create a logical volume:
+Or use L<lvcreate(1)> to create a logical volume:
 
  # lvcreate -L 10G -n lv_name vg_name
 
@@ -111,9 +135,13 @@ Or use L<virsh(1)> vol-create-as to create a libvirt storage volume:
  # virsh pool-list
  # virsh vol-create-as poolname newvol 10G
 
-=item 4. Resize
+=item 5. Resize
+
+virt-resize takes two mandatory parameters, the input disk (eg. device
+or file) and the output disk.  The output disk is the one created in
+the previous step.
 
- virt-resize indisk outdisk
virt-resize indisk outdisk
 
 This command just copies disk image C<indisk> to disk image C<outdisk>
 I<without> resizing or changing any existing partitions.  If
@@ -121,32 +149,37 @@ C<outdisk> is larger, then an extra, empty partition is created at the
 end of the disk covering the extra space.  If C<outdisk> is smaller,
 then it will give an error.
 
-To resize, you need to pass extra options (for the full list see the
+More realistically you'd want to expand existing partitions in the
+disk image by passing extra options (for the full list see the
 L</OPTIONS> section below).
 
 L</--expand> is the most useful option.  It expands the named
 partition within the disk to fill any extra space:
 
- virt-resize --expand /dev/sda2 indisk outdisk
virt-resize --expand /dev/sda2 indisk outdisk
 
 (In this case, an extra partition is I<not> created at the end of the
 disk, because there will be no unused space).
 
-If /dev/sda2 in the image contains a filesystem or LVM PV, then
-this content is B<not> automatically resized.  You can resize it
-afterwards either using L<guestfish(1)> (offline) or using commands
-inside the guest (online resizing).
-
 L</--resize> is the other commonly used option.  The following would
 increase the size of /dev/sda1 by 200M, and expand /dev/sda2
 to fill the rest of the available space:
 
- virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 \
-   indisk outdisk
+ # virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 \
+     indisk outdisk
+
+If the expanded partition in the image contains a filesystem or LVM
+PV, then if virt-resize knows how, it will resize the contents, the
+equivalent of calling a command such as L<pvresize(8)>,
+L<resize2fs(8)> or L<ntfsresize(8)>.  However virt-resize does not
+know how to resize some filesystems, so you would have to online
+resize them after booting the guest.  And virt-resize also does not
+resize anything inside an LVM PV, it just resizes the PV itself and
+leaves the user to resize any LVs inside that PV as desired.
 
 Other options are covered below.
 
-=item 5. Test
+=item 6. Test
 
 Thoroughly test the new disk image I<before> discarding the old one.
 
@@ -161,17 +194,66 @@ Then start up the domain with the new, resized disk:
 
  # virsh start guestname
 
-and check that it still works.
+and check that it still works.  See also the L</NOTES> section below
+for additional information.
 
-Note that to see the extra space in the guest, you may need to use
-guest commands to resize PVs, LVs and/or filesystems to fit the extra
-space available.  Three common guest commands for doing this for Linux
-guests are L<pvresize(8)>, L<lvresize(8)> and L<resize2fs(8)>.  It is
-also possible to do this offline (eg. for scripting changes) using
-L<guestfish(1)>.
+=item 7. Resize LVs etc inside the guest
+
+(This can also be done offline using L<guestfish(1)>)
+
+Once the guest has booted you should see the new space available, at
+least for filesystems that virt-resize knows how to resize, and for
+PVs.  The user may need to resize LVs inside PVs, and also resize
+filesystem types that virt-resize does not know how to expand.
 
 =back
 
+=head2 SHRINKING A VIRTUAL MACHINE DISK
+
+Shrinking is somewhat more complex than expanding, and only an
+overview is given here.
+
+Firstly virt-resize will not attempt to shrink any partition content
+(PVs, filesystems).  The user has to shrink content before passing the
+disk image to virt-resize, and virt-resize will check that the content
+has been shrunk properly.
+
+(Shrinking can also be done offline using L<guestfish(1)>)
+
+After shrinking PVs and filesystems, shut down the guest, and proceed
+with steps 3 and 4 above to allocate a new disk image.
+
+Then run virt-resize with any of the C<--shrink> and/or C<--resize>
+options.
+
+=head2 IGNORING OR DELETING PARTITIONS
+
+virt-resize also gives a convenient way to ignore or delete partitions
+when copying from the input disk to the output disk.  Ignoring a
+partition speeds up the copy where you don't care about the existing
+contents of a partition.  Deleting a partition removes it completely,
+but note that it also renumbers any partitions after the one which is
+deleted, which can leave some guests unbootable.
+
+=head2 QCOW2 AND NON-SPARSE RAW FORMATS
+
+If the input disk is in qcow2 format, then you may prefer that the
+output is in qcow2 format as well.  Alternately, virt-resize can
+convert the format on the fly.  The output format is simply determined
+by the format of the empty output container that you provide.  Thus to
+create qcow2 output, use:
+
+ qemu-img create [-c] -f qcow2 outdisk [size]
+
+instead of the truncate command (use C<-c> for a compressed disk).
+
+Similarly, to get non-sparse raw output use:
+
+ fallocate -l size outdisk
+
+(on older systems that don't have the L<fallocate(1)> command use
+C<dd if=/dev/zero of=outdisk bs=1M count=..>)
+
 =head1 OPTIONS
 
 =over 4
@@ -220,9 +302,11 @@ size; or as a relative number or percentage.  For example:
 
  --resize /dev/sda1=-10%
 
-You can increase the size of any partition.
+You can increase the size of any partition.  Virt-resize will expand
+the direct content of the partition if it knows how (see C<--expand>
+below).
 
-You can I<only> B<decrease> the size of partitions that contain
+You can only I<decrease> the size of partitions that contain
 filesystems or PVs which have already been shrunk.  Virt-resize will
 check this has been done before proceeding, or else will print an
 error (see also C<--resize-force>).
@@ -252,9 +336,37 @@ my $expand;
 Expand the named partition so it uses up all extra space (space left
 over after any other resize changes that you request have been done).
 
-Any filesystem inside the partition is I<not> expanded.  You will need
-to expand the filesystem (or PV) to fit the extra space either using
-L<guestfish(1)> (offline) or online guest tools.
+If virt-resize knows how, it will expand the direct content of the
+partition.  For example, if the partition is an LVM PV, it will expand
+the PV to fit (like calling L<pvresize(8)>).  Virt-resize leaves any
+other content it doesn't know about alone.
+
+Currently virt-resize can resize:
+
+=over 4
+
+=item *
+
+ext2, ext3 and ext4 filesystems when they are contained
+directly inside a partition.
+
+=item *
+
+NTFS filesystems contained directly in a partition, if libguestfs was
+compiled with support for NTFS.
+
+The filesystem must have been shut down consistently last time it was
+used.  Additionally, L<ntfsresize(8)> marks the resized filesystem as
+requiring a consistency check, so at the first boot after resizing
+Windows will check the disk.
+
+=item *
+
+LVM PVs (physical volumes).  However virt-resize does I<not>
+resize anything inside the PV.  The user will have to resize
+LVs as desired.
+
+=back
 
 Note that you cannot use C<--expand> and C<--shrink> together.
 
@@ -312,6 +424,34 @@ You can give this option multiple times.
 
 =cut
 
+my @lv_expand;
+
+=item B<--LV-expand logvol>
+
+This takes the logical volume and, as a final step, expands it to fill
+all the space available in its volume group.  A typical usage,
+assuming a Linux guest with a single PV C</dev/sda2> and a root device
+called C</dev/vg_guest/lv_root> would be:
+
+ virt-resize indisk outdisk \
+   --expand /dev/sda2 --LV-expand /dev/vg_guest/lv_root
+
+This would first expand the partition (and PV), and then expand the
+root device to fill the extra space in the PV.
+
+The contents of the LV are also resized if virt-resize knows how to do
+that.  You can stop virt-resize from trying to expand the content by
+using the option C<--no-expand-content>.
+
+Use L<virt-list-filesystems(1)> to list the filesystems in
+the guest.
+
+You can give this option multiple times, I<but> it doesn't
+make sense to do this unless the logical volumes you specify
+are all in different volume groups.
+
+=cut
+
 my $copy_boot_loader = 1;
 
 =item B<--no-copy-boot-loader>
@@ -342,6 +482,18 @@ partition will be created.
 
 =cut
 
+my $expand_content = 1;
+
+=item B<--no-expand-content>
+
+By default, virt-resize will try to expand the direct contents
+of partitions, if it knows how (see C<--expand> option above).
+
+If you give the C<--no-expand-content> option then virt-resize
+will not attempt this.
+
+=cut
+
 my $debug;
 
 =item B<-d> | B<--debug>
@@ -364,6 +516,36 @@ my $quiet;
 
 Don't print the summary.
 
+=cut
+
+my $format;
+
+=item B<--format> raw
+
+Specify the format of the input disk image.  If this flag is not
+given then it is auto-detected from the image itself.
+
+If working with untrusted raw-format guest disk images, you should
+ensure the format is always specified.
+
+Note that this option I<does not> affect the output format.
+See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
+
+=cut
+
+my $output_format;
+
+=item B<--output-format> raw
+
+Specify the format of the output disk image.  If this flag is not
+given then it is auto-detected from the image itself.
+
+If working with untrusted raw-format guest disk images, you should
+ensure the format is always specified.
+
+Note that you still need to create the output disk with the right
+format.  See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
+
 =back
 
 =cut
@@ -376,11 +558,15 @@ GetOptions ("help|?" => \$help,
             "shrink=s" => \$shrink,
             "ignore=s" => \@ignore,
             "delete=s" => \@delete,
+            "lv-expand=s" => \@lv_expand,
             "copy-boot-loader!" => \$copy_boot_loader,
             "extra-partition!" => \$extra_partition,
+            "expand-content!" => \$expand_content,
             "d|debug" => \$debug,
-            "n|dryrun" => \$dryrun,
+            "n|dryrun|dry-run" => \$dryrun,
             "q|quiet" => \$quiet,
+            "format=s" => \$format,
+            "output-format=s" => \$output_format,
     ) or pod2usage (2);
 pod2usage (1) if $help;
 if ($version) {
@@ -400,23 +586,88 @@ die __x("virt-resize: {file}: does not exist or is not readable\n", file => $inf
 die __x("virt-resize: {file}: does not exist or is not writable\nYou have to create the destination disk before running this program.\nPlease read the virt-resize(1) manpage for more information.\n", file => $outfile)
     unless -w $outfile;
 
-my @s;
-@s = stat $infile;
-my $insize = S_ISREG ($s[2]) ? $s[7] : host_blockdevsize ($infile);
-@s = stat $outfile;
-my $outsize = S_ISREG ($s[2]) ? $s[7] : host_blockdevsize ($outfile);
+# Add them to the handle and launch the appliance.
+my $g;
+launch_guestfs ();
+
+sub launch_guestfs
+{
+    $g = Sys::Guestfs->new ();
+    $g->set_trace (1) if $debug;
+    my @args = ($infile);
+    push @args, readonly => 1;
+    push @args, format => $format if defined $format;
+    $g->add_drive_opts (@args);
+    @args = ($outfile);
+    push @args, format => $output_format if defined $output_format;
+    $g->add_drive_opts (@args);
+    $g->set_progress_callback (\&progress_callback) unless $quiet;
+    $g->launch ();
+}
+
+my $sectsize = $g->blockdev_getss ("/dev/sdb");
+
+# Get the size in bytes of each disk.
+#
+# Originally we computed this by looking at the same of the host file,
+# but of course this failed for qcow2 images (RHBZ#633096).  The right
+# way to do it is with $g->blockdev_getsize64.
+my $insize = $g->blockdev_getsize64 ("/dev/sda");
+my $outsize = $g->blockdev_getsize64 ("/dev/sdb");
 
 if ($debug) {
     print "$infile size $insize bytes\n";
     print "$outfile size $outsize bytes\n";
 }
 
+# Create a partition table.
+#
+# We *must* do this before copying the bootloader across, and copying
+# the bootloader must be careful not to disturb this partition table
+# (RHBZ#633766).  There are two reasons for this:
+#
+# (1) The 'parted' library is stupid and broken.  In many ways.  In
+# this particular instance the stupid and broken bit is that it
+# overwrites the whole boot sector when initializating a partition
+# table.  (Upstream don't consider this obvious problem to be a bug).
+#
+# (2) GPT has a backup partition table located at the end of the disk.
+# It's non-movable, because the primary GPT contains fixed references
+# to both the size of the disk and the backup partition table at the
+# end.  This would be a problem for any resize that didn't either
+# carefully move the backup GPT (and rewrite those references) or
+# recreate the whole partition table from scratch.
+
+my $parttype;
+create_partition_table ();
+
+sub create_partition_table
+{
+    local $_;
+
+    $parttype = $g->part_get_parttype ("/dev/sda");
+    print "partition table type: $parttype\n" if $debug;
+
+    $g->part_init ("/dev/sdb", $parttype);
+}
+
+# In reality the number of sectors containing boot loader data will be
+# less than this (although Windows 7 defaults to putting the first
+# partition on sector 2048, and has quite a large boot loader).
+#
+# However make this large enough to be sure that we have copied over
+# the boot loader.  We could also do this by looking for the sector
+# offset of the first partition.
+#
+# It doesn't matter if we copy too much.
+my $max_bootloader = 4096 * 512;
+
 die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n",
         file => $infile, sz => $insize)
-    if $insize < 64 * 512;
+    if $insize < $max_bootloader;
 die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n",
         file => $outfile, sz => $outsize)
-    if $outsize < 64 * 512;
+    if $outsize < $max_bootloader;
 
 # Copy the boot loader across.
 do_copy_boot_loader () if $copy_boot_loader;
@@ -424,30 +675,30 @@ do_copy_boot_loader () if $copy_boot_loader;
 sub do_copy_boot_loader
 {
     print "copying boot loader ...\n" if $debug;
-    open IFILE, $infile or die "$infile: $!";
-    my $s;
-    my $r = sysread (IFILE, $s, 64 * 512) or die "$infile: $!";
-    die "$infile: short read" if $r < 64 * 512;
-    open OFILE, "+<$outfile" or die "$outfile: $!";
-    sysseek OFILE, 0, SEEK_SET or die "$outfile: seek: $!";
-    $r = syswrite (OFILE, $s, 64 * 512) or die "$outfile: $!";
-    die "$outfile: short write" if $r < 64 * 512;
-}
 
-# Add them to the handle and launch the appliance.
-my $g;
-launch_guestfs ();
+    # Don't disturb the partition table that we just wrote.
+    # https://secure.wikimedia.org/wikipedia/en/wiki/Master_Boot_Record
+    # https://secure.wikimedia.org/wikipedia/en/wiki/GUID_Partition_Table
 
-sub launch_guestfs
-{
-    $g = Sys::Guestfs->new ();
-    $g->set_trace (1) if $debug;
-    $g->add_drive_ro ($infile);
-    $g->add_drive ($outfile);
-    $g->launch ();
+    my $bootsect = $g->pread_device ("/dev/sda", 446, 0);
+    die __"virt-resize: short read" if length ($bootsect) < 446;
+
+    $g->pwrite_device ("/dev/sdb", $bootsect, 0);
+
+    my $start = 512;
+    if ($parttype eq "gpt") {
+        # XXX With 4K sectors does GPT just fit more entries in a
+        # sector, or does it always use 34 sectors?
+        $start = 17408;
+    }
+
+    my $loader = $g->pread_device ("/dev/sda", $max_bootloader, $start);
+    die __"virt-resize: short read" if length ($loader) < $max_bootloader;
+
+    $g->pwrite_device ("/dev/sdb", $loader, $start);
 }
 
-my $sectsize = $g->blockdev_getss ("/dev/sdb");
+my $to_be_expanded = 0;
 
 # Get the partitions on the source disk.
 my @partitions;
@@ -466,6 +717,8 @@ sub check_source_disk
 
         my %h = %$_;
         $h{name} = $name;
+        $h{bootable} = $g->part_get_bootable ("/dev/sda", $h{part_num});
+        eval { $h{mbr_id} = $g->part_get_mbr_id ("/dev/sda", $h{part_num}); };
         $partitions{$name} = \%h;
     }
 }
@@ -511,6 +764,21 @@ sub examine_partition
     # that case user won't be allowed to shrink this partition except
     # by forcing it.
     $partitions{$part}->{fssize} = $fssize;
+
+    # Is it partition content that we know how to expand?
+    $partitions{$part}->{can_expand_content} = 0;
+    if ($expand_content) {
+        if ($type eq "LVM2_member") {
+            $partitions{$part}->{can_expand_content} = 1;
+            $partitions{$part}->{expand_content_method} = "pvresize";
+        } elsif ($type =~ /^ext[234]$/) {
+            $partitions{$part}->{can_expand_content} = 1;
+            $partitions{$part}->{expand_content_method} = "resize2fs";
+        } elsif ($type eq "ntfs" && feature_available ($g, "ntfsprogs")) {
+            $partitions{$part}->{can_expand_content} = 1;
+            $partitions{$part}->{expand_content_method} = "ntfsresize";
+        }
+    }
 }
 
 if ($debug) {
@@ -526,6 +794,50 @@ if ($debug) {
     }
 }
 
+# Examine the LVs (for --lv-expand option).
+my @lvs = $g->lvs ();
+my %lvs;
+examine_lv ($_) foreach @lvs;
+mark_lvs_to_expand ();
+
+sub examine_lv
+{
+    local $_ = shift;
+
+    $lvs{$_}->{name} = $_;
+
+    my $type = "unknown";
+    eval {
+        $type = $g->vfs_type ($_);
+    };
+    $lvs{$_}->{type} = $type;
+
+    if ($expand_content) {
+        if ($type =~ /^ext[234]$/) {
+            $lvs{$_}->{can_expand_content} = 1;
+            $lvs{$_}->{expand_content_method} = "resize2fs";
+        } elsif ($type eq "ntfs" && feature_available ($g, "ntfsprogs")) {
+            $lvs{$_}->{can_expand_content} = 1;
+            $lvs{$_}->{expand_content_method} = "ntfsresize";
+        }
+    }
+}
+
+sub mark_lvs_to_expand {
+    local $_;
+
+    foreach (@lv_expand) {
+        die __x("virt-resize: no logical volume called {n}\n",
+                n => $_)
+            unless exists $lvs{$_};
+
+        if ($lvs{$_}->{can_expand_content}) {
+            $lvs{$_}->{will_expand_content} = 1;
+            $to_be_expanded++;
+        }
+    }
+}
+
 sub find_partition
 {
     local $_ = shift;
@@ -646,6 +958,11 @@ sub mark_partition_for_resize
     }
 
     $partitions{$part}->{newsize} = $newsize;
+
+    if ($partitions{$part}->{can_expand_content} && $bigger) {
+        $partitions{$part}->{will_expand_content} = 1;
+        $to_be_expanded++;
+    }
 }
 
 # Handle --expand and --shrink.
@@ -676,7 +993,11 @@ sub calculate_surplus
 
     # We need some overhead for partitioning.  Worst case would be for
     # EFI partitioning + massive per-partition alignment.
-    my $overhead = $sectsize * (2 * 64 + (64 * (@partitions + 1)) + 128);
+    my $overhead = $sectsize * (
+        2 * 64 +                   # GPT start and end
+        (64 * (@partitions + 1))   # Maximum alignment
+        ) +
+        ($max_bootloader - 64 * 512); # boot loader
 
     my $required = 0;
     foreach (@partitions) {
@@ -730,18 +1051,35 @@ sub print_summary
 
     foreach my $part (@partitions) {
         if ($partitions{$part}->{ignore}) {
-            print __x("{p}: partition will be ignored", p => $part);
+            print __x("{p}: partition will be ignored\n", p => $part);
         } elsif ($partitions{$part}->{delete}) {
-            print __x("{p}: partition will be deleted", p => $part);
+            print __x("{p}: partition will be deleted\n", p => $part);
         } elsif ($partitions{$part}->{newsize}) {
-            print __x("{p}: partition will be resized from {oldsize} to {newsize}",
+            print __x("{p}: partition will be resized from {oldsize} to {newsize}\n",
                       p => $part,
                       oldsize => human_size ($partitions{$part}->{part_size}),
                       newsize => human_size ($partitions{$part}->{newsize}));
+            if ($partitions{$part}->{will_expand_content}) {
+                print __x("{p}: content will be expanded using the '{meth}' method\n",
+                          p => $part,
+                          meth => $partitions{$part}->{expand_content_method});
+            }
         } else {
-            print __x("{p}: partition will be left alone", p => $part);
+            print __x("{p}: partition will be left alone\n", p => $part);
+        }
+    }
+
+    foreach my $lv (@lv_expand) {
+        print __x("{n}: LV will be expanded to maximum size\n",
+                  n => $lv);
+    }
+
+    foreach my $lv (@lvs) {
+        if ($lvs{$lv}->{will_expand_content}) {
+            print __x("{n}: content will be expanded using the '{meth}' method\n",
+                      n => $lv,
+                      meth => $lvs{$lv}->{expand_content_method});
         }
-        print "\n"
     }
 
     if ($surplus > 0) {
@@ -768,26 +1106,21 @@ exit 0 if $dryrun;
 
 # Repartition the target disk.
 my $nextpart = 1;
-my $parttype;
 repartition ();
 
 sub repartition
 {
     local $_;
 
-    if ($copy_boot_loader) {
-        $parttype = $g->part_get_parttype ("/dev/sdb");
-        print "partition table type: $parttype\n" if $debug;
-    } else {
-        # Didn't copy over the initial boot loader, so we need
-        # to make a new partition type here.
-        $parttype = "efi";
-    }
+    # Work out where to start the first partition.
+    die __"virt-resize: source disk does not have a first partition\n"
+        unless exists ($partitions{"/dev/sda1"});
+    my $start = $partitions{"/dev/sda1"}->{part_start} / $sectsize;
 
-    # Delete any existing partitions on the destination disk.
-    $g->part_init ("/dev/sdb", $parttype);
+    # Align to 64.
+    $start = ($start + 63) & ~63;
 
-    my $start = 64;
+    print "starting to partition from $start\n" if $debug;
 
     # Create the new partitions.
     foreach my $part (@partitions) {
@@ -803,9 +1136,18 @@ sub repartition
             }
 
             # Create it.
-            my ($target, $end) = add_partition ($start, $size);
+            my ($target, $end, $part_num) = add_partition ($start, $size);
             $partitions{$part}->{target} = $target;
 
+            if ($partitions{$part}->{bootable}) {
+                $g->part_set_bootable ("/dev/sdb", $part_num, 1);
+            }
+
+            if ($partitions{$part}->{mbr_id}) {
+                $g->part_set_mbr_id ("/dev/sdb", $part_num,
+                                     $partitions{$part}->{mbr_id});
+            }
+
             # Start of next partition + alignment.
             $start = $end + 1;
             $start = ($start + 63) & ~63;
@@ -825,26 +1167,26 @@ sub add_partition
     my $start = shift;
     my $size = shift;
 
-    my ($target, $end);
+    my ($target, $end, $part_num);
 
     if ($nextpart <= 3 || $parttype ne "msdos") {
         $target = "/dev/sdb$nextpart";
         $end = $start + $size - 1;
         $g->part_add ("/dev/sdb", "primary", $start, $end);
-        $nextpart++;
+        $part_num = $nextpart++;
     } else {
         if ($nextpart == 4) {
             $g->part_add ("/dev/sdb", "extended", $start, -1);
-            $nextpart++;
+            $part_num = $nextpart++;
             $start += 64;
         }
         $target = "/dev/sdb$nextpart";
         $end = $start + $size - 1;
         $g->part_add ("/dev/sdb", "logical", $start, $end);
-        $nextpart++;
+        $part_num = $nextpart++;
     }
 
-    return ($target, $end);
+    return ($target, $end, $part_num);
 }
 
 # Copy over the data.
@@ -866,21 +1208,147 @@ sub copy_data
                 }
 
                 if (!$quiet && !$debug) {
-                    local $| = 1;
-                    print "Copying $part ...";
+                    print __x("Copying {p} ...\n", p => $part);
                 }
 
                 $g->copy_size ($part, $target,
                                $newsize < $oldsize ? $newsize : $oldsize);
+            }
+        }
+    }
+}
 
-                if (!$quiet && !$debug) {
-                    print " done\n"
+# After copying the data over we must shut down and restart the
+# appliance in order to expand the content.  The reason for this may
+# not be obvious, but it's because otherwise we'll have duplicate VGs
+# (the old VG(s) and the new VG(s)) which breaks LVM.
+#
+# The restart is only required if we're going to expand something.
+
+if ($to_be_expanded > 0) {
+    restart_appliance ();
+    expand_partitions ();
+    expand_lvs ();
+    expand_lvs_content ();
+}
+
+sub restart_appliance
+{
+    # Sync disk and exit.
+    $g->umount_all ();
+    $g->sync ();
+    undef $g;
+
+    $g = Sys::Guestfs->new ();
+    $g->set_trace (1) if $debug;
+    my @args = ($outfile);
+    push @args, format => $output_format if defined $output_format;
+    $g->add_drive_opts (@args);
+    $g->launch ();
+
+    # Target partitions have changed from /dev/sdb to /dev/sda,
+    # so change them.
+    foreach my $part (@partitions)
+    {
+        my $target = $partitions{$part}->{target};
+        if ($target) {
+            if ($target =~ m{/dev/(.)db(.*)}) {
+                $partitions{$part}->{target} = "/dev/$1da$2";
+            } else {
+                die "internal error: unexpected partition target: $target";
+            }
+        }
+    }
+}
+
+sub expand_partitions
+{
+    foreach my $part (@partitions)
+    {
+        unless ($partitions{$part}->{ignore}) {
+            my $target = $partitions{$part}->{target};
+            if ($target) {
+                # Expand if requested.
+                if ($partitions{$part}->{will_expand_content}) {
+                    if (!$quiet && !$debug) {
+                        print __x("Expanding {p} using the '{meth}' method\n",
+                                  p => $part,
+                                  meth => $partitions{$part}->{expand_content_method});
+                    }
+                    expand_target_partition ($part)
                 }
             }
         }
     }
 }
 
+sub expand_target_partition
+{
+    local $_;
+    my $part = shift;
+
+    # Assertions.
+    die unless $part;
+    die unless $partitions{$part}->{can_expand_content};
+    die unless $partitions{$part}->{will_expand_content};
+    die unless $partitions{$part}->{expand_content_method};
+    die unless $partitions{$part}->{target};
+    die unless $expand_content;
+
+    my $target = $partitions{$part}->{target};
+    my $method = $partitions{$part}->{expand_content_method};
+    if ($method eq "pvresize") {
+        $g->pvresize ($target);
+    }
+    elsif ($method eq "resize2fs") {
+        $g->e2fsck_f ($target);
+        $g->resize2fs ($target);
+    }
+    elsif ($method eq "ntfsresize") {
+        $g->ntfsresize ($target);
+    }
+    else {
+        die "internal error: unknown method: $method";
+    }
+}
+
+sub expand_lvs
+{
+    local $_;
+
+    foreach (@lv_expand) {
+        $g->lvresize_free ($_, 100);
+    }
+}
+
+sub expand_lvs_content
+{
+    local $_;
+
+    foreach (@lvs) {
+        if ($lvs{$_}->{will_expand_content}) {
+            my $method = $lvs{$_}->{expand_content_method};
+            if (!$quiet && !$debug) {
+                print __x("Expanding {p} using the '{meth}' method\n",
+                          p => $_, meth => $method);
+                    }
+            if ($method eq "resize2fs") {
+                $g->e2fsck_f ($_);
+                $g->resize2fs ($_);
+            } elsif ($method eq "ntfsresize") {
+                $g->ntfsresize ($_);
+            } else {
+                die "internal error: unknown method: $method";
+            }
+        }
+    }
+}
+
+# Sync disk and exit.
+$g->umount_all ();
+$g->sync ();
+undef $g;
+
 exit 0;
 
 sub sizebytes
@@ -920,19 +1388,6 @@ sub human_size
     }
 }
 
-# Return the size in bytes of a HOST block device.
-sub host_blockdevsize
-{
-    local $_;
-    my $dev = shift;
-
-    open BD, "PATH=/usr/sbin:/sbin:\$PATH blockdev --getsize64 $dev |"
-        or die "blockdev: $!";
-    $_ = <BD>;
-    chomp $_;
-    $_;
-}
-
 # The reverse of device name translation, see
 # BLOCK DEVICE NAMING in guestfs(3).
 sub canonicalize
@@ -945,6 +1400,89 @@ sub canonicalize
     $_;
 }
 
+# Not as sophisticated as the guestfish progress bar, because
+# I intend to use an external library for this at some point (XXX).
+sub progress_callback
+{
+    my $proc_nr = shift;
+    my $serial = shift;
+    my $position = shift;
+    my $total = shift;
+
+    my $ratio = $position / $total;
+    if ($ratio < 0) { $ratio = 0 }
+    elsif ($ratio > 1) { $ratio = 1 }
+
+    my $dots = int ($ratio * 76);
+
+    print "[", "#"x$dots, "-"x(76-$dots), "]\r";
+    print "\n" if $ratio == 1;
+}
+
+=head1 NOTES
+
+=head2 "Partition 1 does not end on cylinder boundary."
+
+Virt-resize aligns partitions to multiples of 64 sectors.  Usually
+this means the partitions will not be aligned to the ancient CHS
+geometry.  However CHS geometry is meaningless for disks manufactured
+since the early 1990s, and doubly so for virtual hard drives.
+Alignment of partitions to cylinders is not required by any modern
+operating system.
+
+=head2 RESIZING WINDOWS VIRTUAL MACHINES
+
+In Windows Vista and later versions, Microsoft switched to using a
+separate boot partition.  In these VMs, typically C</dev/sda1> is the
+boot partition and C</dev/sda2> is the main (C:) drive.  We have not
+had any luck resizing the boot partition.  Doing so seems to break the
+guest completely.  However expanding the second partition (ie. C:
+drive) should work.
+
+Windows may initiate a lengthy "chkdsk" on first boot after a resize,
+if NTFS partitions have been expanded.  This is just a safety check
+and (unless it find errors) is nothing to worry about.
+
+=head2 GUEST BOOT STUCK AT "GRUB"
+
+If a Linux guest does not boot after resizing, and the boot is stuck
+after printing C<GRUB> on the console, try reinstalling grub.  This
+sometimes happens on older (RHEL 5-era) guests, for reasons we don't
+fully understand, although we think is to do with partition alignment.
+
+ guestfish -i -a newdisk
+ ><fs> cat /boot/grub/device.map
+ # check the contents of this file are sensible or
+ # edit the file if necessary
+ ><fs> grub-install / /dev/vda
+ ><fs> exit
+
+For more flexible guest reconfiguration, including if you need to
+specify other parameters to grub-install, use L<virt-rescue(1)>.
+
+=head1 ALTERNATIVE TOOLS
+
+There are several proprietary tools for resizing partitions.  We
+won't mention any here.
+
+L<parted(8)> and its graphical shell gparted can do some types of
+resizing operations on disk images.  They can resize and move
+partitions, but I don't think they can do anything with the contents,
+and they certainly don't understand LVM.
+
+L<guestfish(1)> can do everything that virt-resize can do and a lot
+more, but at a much lower level.  You will probably end up
+hand-calculating sector offsets, which is something that virt-resize
+was designed to avoid.  If you want to see the guestfish-equivalent
+commands that virt-resize runs, use the C<--debug> flag.
+
+=head1 SHELL QUOTING
+
+Libvirt guest names can contain arbitrary characters, some of which
+have meaning to the shell such as C<#> and space.  You may need to
+quote or escape these characters on the command line.  See the shell
+manual page L<sh(1)> for details.
+
 =head1 SEE ALSO
 
 L<virt-list-partitions(1)>,
@@ -956,13 +1494,20 @@ L<lvm(8)>,
 L<pvresize(8)>,
 L<lvresize(8)>,
 L<resize2fs(8)>,
+L<ntfsresize(8)>,
 L<virsh(1)>,
+L<parted(8)>,
+L<truncate(1)>,
+L<fallocate(1)>,
+L<grub(8)>,
+L<grub-install(8)>,
+L<virt-rescue(1)>,
 L<Sys::Guestfs(3)>,
 L<http://libguestfs.org/>.
 
 =head1 AUTHOR
 
-Richard W.M. Jones L<http://et.redhat.com/~rjones/>
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
 
 =head1 COPYRIGHT