X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=tools%2Fvirt-resize;h=8a473ca3fcf39047e1d67b94f60ae134d8935042;hp=1c4006ae50e8b442b2a925d8683eabedd4088074;hb=6d4815d3a4921219379bc9ec3cceead217668426;hpb=def627e4daf2d32cebf91f5e01f44a0b7e512129 diff --git a/tools/virt-resize b/tools/virt-resize old mode 100644 new mode 100755 index 1c4006a..8a473ca --- a/tools/virt-resize +++ b/tools/virt-resize @@ -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=[+/-][%]] [--expand /dev/sdaN] - [--shrink /dev/sdaN] [--ignore /dev/sdaN] [--delete /dev/sdaN] [...] - indisk outdisk + virt-resize [--resize /dev/sdaN=[+/-][%]] + [--expand /dev/sdaN] [--shrink /dev/sdaN] + [--ignore /dev/sdaN] [--delete /dev/sdaN] [...] indisk outdisk =head1 DESCRIPTION @@ -60,41 +63,62 @@ L and L, we recommend you go and read those manual pages first. -=head2 BASIC USAGE +=head1 EXAMPLES + +Copy C to C, 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 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 like this to find the disk image name: # virsh dumpxml guestname | xpath /domain/devices/disk/source Found 1 nodes: -- NODE -- -=item 2. Look at current sizing +=item 3. Look at current sizing Use L 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 to create a logical volume: +Or use L to create a logical volume: # lvcreate -L 10G -n lv_name vg_name @@ -111,9 +135,13 @@ Or use L 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 to disk image C I resizing or changing any existing partitions. If @@ -121,32 +149,37 @@ C is larger, then an extra, empty partition is created at the end of the disk covering the extra space. If C 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 section below). L 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 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 automatically resized. You can resize it -afterwards either using L (offline) or using commands -inside the guest (online resizing). - L 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, +L or L. 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 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 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, L and L. It is -also possible to do this offline (eg. for scripting changes) using -L. +=item 7. Resize LVs etc inside the guest + +(This can also be done offline using L) + +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) + +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 command use +C
) + =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 B the size of partitions that contain +You can only I 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 expanded. You will need -to expand the filesystem (or PV) to fit the extra space either using -L (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). 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 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 +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 and a root device +called C 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 to list the filesystems in +the guest. + +You can give this option multiple times, I 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 affect the output format. +See L. + +=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. + =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: $!"; - $_ = ; - 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 is the +boot partition and C 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 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 + > cat /boot/grub/device.map + # check the contents of this file are sensible or + # edit the file if necessary + > grub-install / /dev/vda + > exit + +For more flexible guest reconfiguration, including if you need to +specify other parameters to grub-install, use L. + +=head1 ALTERNATIVE TOOLS + +There are several proprietary tools for resizing partitions. We +won't mention any here. + +L 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 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 for details. + =head1 SEE ALSO L, @@ -956,13 +1494,20 @@ L, L, L, L, +L, L, +L, +L, +L, +L, +L, +L, L, L. =head1 AUTHOR -Richard W.M. Jones L +Richard W.M. Jones L =head1 COPYRIGHT