X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;ds=sidebyside;f=tools%2Fvirt-resize;h=a70492e846acb9e60f96a11ac5064122a85b0106;hb=HEAD;hp=1e8a6c7b4a9f44a153f7e3bf18ce9e0db4a80b46;hpb=fbc2555903be8c88ad9430d871cf0d27c8fded1e;p=libguestfs.git diff --git a/tools/virt-resize b/tools/virt-resize deleted file mode 100755 index 1e8a6c7..0000000 --- a/tools/virt-resize +++ /dev/null @@ -1,1526 +0,0 @@ -#!/usr/bin/perl -w -# virt-resize -# Copyright (C) 2010 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -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; -use Getopt::Long; -use Data::Dumper; -use Locale::TextDomain 'libguestfs'; - -$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 - -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 - -=head1 DESCRIPTION - -Virt-resize is a tool which can resize a virtual machine disk, making -it larger or smaller overall, and resizing or deleting any partitions -contained within. - -Virt-resize B resize disk images in-place. Virt-resize -B be used on live virtual machines - for consistent -results, shut the virtual machine down before resizing it. - -If you are not familiar with the associated tools: -L and L, we recommend you go and read -those manual pages first. - -=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-filesystems --long --h --all -a 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 - -=head2 EXPANDING A VIRTUAL MACHINE DISK - -=over 4 - -=item 1. Shut down the virtual machine - -=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 3. Look at current sizing - -Use L to display the current partitions and -sizes: - - # virt-filesystems --long --parts --blkdevs -h -a /dev/vg/lv_guest - Name Type Size Parent - /dev/sda1 partition 101M /dev/sda - /dev/sda2 partition 7.9G /dev/sda - /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 4. Create output disk - -Virt-resize cannot do in-place disk modifications. You have to have -space to store the resized output disk. - -To store the resized disk image in a file, create a file of a suitable -size: - - # rm -f outdisk - # truncate -s 10G outdisk - -Or use L to create a logical volume: - - # lvcreate -L 10G -n lv_name vg_name - -Or use L vol-create-as to create a libvirt storage volume: - - # virsh pool-list - # virsh vol-create-as poolname newvol 10G - -=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 - -This command just copies disk image C to disk image C -I resizing or changing any existing partitions. If -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. - -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 - -(In this case, an extra partition is I created at the end of the -disk, because there will be no unused space). - -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 - -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 6. Test - -Thoroughly test the new disk image I discarding the old one. - -If you are using libvirt, edit the XML to point at the new disk: - - # virsh edit guestname - -Change Esource ...E, see -L - -Then start up the domain with the new, resized disk: - - # virsh start guestname - -and check that it still works. See also the L section below -for additional information. - -=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 - -=cut - -my $help; - -=item B<--help> - -Display help. - -=cut - -my $version; - -=item B<--version> - -Display version number and exit. - -=cut - -my @resize; - -=item B<--resize part=size> - -Resize the named partition (expanding or shrinking it) so that it has -the given size. - -C can be expressed as an absolute number followed by -b/K/M/G/T/P/E to mean bytes, Kilobytes, Megabytes, Gigabytes, -Terabytes, Petabytes or Exabytes; or as a percentage of the current -size; or as a relative number or percentage. For example: - - --resize /dev/sda2=10G - - --resize /dev/sda4=90% - - --resize /dev/sda2=+1G - - --resize /dev/sda2=-200M - - --resize /dev/sda1=+128K - - --resize /dev/sda1=+10% - - --resize /dev/sda1=-10% - -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 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>). - -You can give this option multiple times. - -=cut - -my @resize_force; - -=item B<--resize-force part=size> - -This is the same as C<--resize> except that it will let you decrease -the size of any partition. Generally this means you will lose any -data which was at the end of the partition you shrink, but you may not -care about that (eg. if shrinking an unused partition, or if you can -easily recreate it such as a swap partition). - -See also the C<--ignore> option. - -=cut - -my $expand; - -=item B<--expand part> - -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). - -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. - -=cut - -my $shrink; - -=item B<--shrink part> - -Shrink the named partition until the overall disk image fits in the -destination. The named partition B contain a filesystem or PV -which has already been shrunk using another tool (eg. L -or other online tools). Virt-resize will check this and give an error -if it has not been done. - -The amount by which the overall disk must be shrunk (after carrying -out all other operations requested by the user) is called the -"deficit". For example, a straight copy (assume no other operations) -from a 5GB disk image to a 4GB disk image results in a 1GB deficit. -In this case, virt-resize would give an error unless the user -specified a partition to shrink and that partition had more than a -gigabyte of free space. - -Note that you cannot use C<--expand> and C<--shrink> together. - -=cut - -my @ignore; - -=item B<--ignore part> - -Ignore the named partition. Effectively this means the partition is -allocated on the destination disk, but the content is not copied -across from the source disk. The content of the partition will be -blank (all zero bytes). - -You can give this option multiple times. - -=cut - -my @delete; - -=item B<--delete part> - -Delete the named partition. It would be more accurate to describe -this as "don't copy it over", since virt-resize doesn't do in-place -changes and the original disk image is left intact. - -Note that when you delete a partition, then anything contained in the -partition is also deleted. Furthermore, this causes any partitions -that come after to be I, which can easily make your guest -unbootable. - -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> - -By default, virt-resize copies over some sectors at the start of the -disk (up to the beginning of the first partition). Commonly these -sectors contain the Master Boot Record (MBR) and the boot loader, and -are required in order for the guest to boot correctly. - -If you specify this flag, then this initial copy is not done. You may -need to reinstall the boot loader in this case. - -=cut - -my $extra_partition = 1; -my $min_extra_partition = 10 * 1024 * 1024; # see below - -=item B<--no-extra-partition> - -By default, virt-resize creates an extra partition if there is any -extra, unused space after all resizing has happened. Use this option -to prevent the extra partition from being created. If you do this -then the extra space will be inaccessible until you run fdisk, parted, -or some other partitioning tool in the guest. - -Note that if the surplus space is smaller than 10 MB, no extra -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> - -Enable debugging messages. - -=cut - -my $dryrun; - -=item B<-n> | B<--dryrun> - -Print a summary of what would be done, but don't do anything. - -=cut - -my $quiet; - -=item B<-q> | B<--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 - -GetOptions ("help|?" => \$help, - "version" => \$version, - "resize=s" => \@resize, - "resize-force=s" => \@resize_force, - "expand=s" => \$expand, - "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|dry-run" => \$dryrun, - "q|quiet" => \$quiet, - "format=s" => \$format, - "output-format=s" => \$output_format, - ) or pod2usage (2); -pod2usage (1) if $help; -if ($version) { - my $g = Sys::Guestfs->new (); - my %h = $g->version (); - print "$h{major}.$h{minor}.$h{release}$h{extra}\n"; - exit -} - -die "virt-resize [--options] indisk outdisk\n" unless @ARGV == 2; - -# Check in and out images exist. -my $infile = $ARGV[0]; -my $outfile = $ARGV[1]; -die __x("virt-resize: {file}: does not exist or is not readable\n", file => $infile) - unless -r $infile; -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; - -# 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 < $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 < $max_bootloader; - -# Copy the boot loader across. -do_copy_boot_loader () if $copy_boot_loader; - -sub do_copy_boot_loader -{ - print "copying boot loader ...\n" if $debug; - - # 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 - - 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 $to_be_expanded = 0; - -# Get the partitions on the source disk. -my @partitions; -my %partitions; -check_source_disk (); - -sub check_source_disk -{ - local $_; - - # Partitions and PVs. - my @p = $g->part_list ("/dev/sda"); - foreach (@p) { - my $name = "/dev/sda" . $_->{part_num}; - push @partitions, $name; - - 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; - } -} - -# Examine each partition. -my @pvs_full = $g->pvs_full (); -examine_partition ($_) foreach @partitions; - -sub examine_partition -{ - local $_; - my $part = shift; - - # What is it? - my $type = "unknown"; - eval { - $type = $g->vfs_type ($part); - }; - $partitions{$part}->{type} = $type; - - # Can we get the actual size of this object (ie. to find out if it - # is smaller than the container for shrinking)? - my $fssize; - if ($type eq "LVM2_member") { # LVM PV - foreach (@pvs_full) { - $fssize = $_->{pv_size} - if canonicalize ($_->{pv_name}) eq $part; - } - } else { # Something mountable? - eval { - $g->mount_ro ($part, "/"); - - my %stat = $g->statvfs ("/"); - $fssize = $stat{bsize} * $stat{blocks}; - }; - - eval { - $g->umount_all (); - }; - } - - # This might be undef if we didn't successfully find the size. In - # 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) { - print "partitions found: ", join (", ", @partitions), "\n"; - foreach my $part (@partitions) { - print "$part:\n"; - foreach (sort keys %{$partitions{$part}}) { - print("\t", $_, " = ", - defined ($partitions{$part}->{$_}) - ? $partitions{$part}->{$_} : "undef", - "\n"); - } - } -} - -# 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; - my $option = shift; - - $_ = "/dev/$_" unless $_ =~ m{^/dev}; - $_ = canonicalize ($_); - - unless (exists $partitions{$_}) { - die __x("{p}: partition not found in the source disk image, when using the '{opt}' command line option\n", - p => $_, - opt => $option) - } - - if ($partitions{$_}->{ignore}) { - die __x("{p}: partition ignored, you cannot use it in another command line argument\n", - p => $_) - } - if ($partitions{$_}->{delete}) { - die __x("{p}: partition deleted, you cannot use it in another command line argument\n", - p => $_) - } - - return $_; -} - -# Handle --ignore. -do_ignore ($_) foreach @ignore; - -sub do_ignore -{ - local $_ = shift; - $_ = find_partition ($_, "--ignore"); - $partitions{$_}->{ignore} = 1; -} - -# Handle --delete. -do_delete ($_) foreach @delete; - -sub do_delete -{ - local $_ = shift; - $_ = find_partition ($_, "--delete"); - $partitions{$_}->{delete} = 1; -} - -# Handle --resize and --resize-force. -do_resize ($_, 0, "--resize") foreach @resize; -do_resize ($_, 1, "--resize-force") foreach @resize_force; - -sub do_resize -{ - local $_ = shift; - my $force = shift; - my $option = shift; - - # Argument is "part=size" ... - my ($part, $sizefield) = split /=/, $_, 2; - $part = find_partition ($part, $option); - - if (exists $partitions{$part}->{newsize}) { - die __x("{p}: this partition has already been marked for resizing\n", - p => $part); - } - - # Parse the size field. - my $oldsize = $partitions{$part}->{part_size}; - my $newsize; - if (!defined ($sizefield) || $sizefield eq "") { - die __x("{p}: missing size field in {o} option\n", - p => $part, o => $option); - } elsif ($sizefield =~ /^([.\d]+)([bKMGTPE])$/) { - $newsize = sizebytes ($1, $2); - } elsif ($sizefield =~ /^\+([.\d]+)([bKMGTPE])$/) { - my $incr = sizebytes ($1, $2); - $newsize = $oldsize + $incr; - } elsif ($sizefield =~ /^-([.\d]+)([bKMGTPE])$/) { - my $decr = sizebytes ($1, $2); - $newsize = $oldsize - $decr; - } elsif ($sizefield =~ /^([.\d]+)%$/) { - $newsize = $oldsize * $1 / 100; - } elsif ($sizefield =~ /^\+([.\d]+)%$/) { - $newsize = $oldsize + $oldsize * $1 / 100; - } elsif ($sizefield =~ /^-([.\d]+)%$/) { - $newsize = $oldsize - $oldsize * $1 / 100; - } else { - die __x("{p}: {f}: cannot parse size field\n", - p => $part, f => $sizefield) - } - - $newsize > 0 or - die __x("{p}: new size is zero or negative\n", p => $part); - - mark_partition_for_resize ($part, $oldsize, $newsize, $force, $option); -} - -sub mark_partition_for_resize -{ - local $_; - my $part = shift; - my $oldsize = shift; - my $newsize = shift; - my $force = shift; - my $option = shift; - - # Do nothing if the size is the same. - return if $oldsize == $newsize; - - my $bigger = $newsize > $oldsize; - - # Check there is space to shrink this. - unless ($bigger || $force) { - if (! $partitions{$part}->{fssize} || - $partitions{$part}->{fssize} > $newsize) { - die __x("{p}: cannot make this partition smaller because it contains a\nfilesystem, physical volume or other content that is larger than the new size.\nYou have to resize the content first, see virt-resize(1).\n", - p => $part); - } - } - - $partitions{$part}->{newsize} = $newsize; - - if ($partitions{$part}->{can_expand_content} && $bigger) { - $partitions{$part}->{will_expand_content} = 1; - $to_be_expanded++; - } -} - -# Handle --expand and --shrink. -my $surplus; -if (defined $expand && defined $shrink) { - die __"virt-resize: you cannot use options --expand and --shrink together\n" -} -if (defined $expand || defined $shrink) { - calculate_surplus (); - - if ($debug) { - print "surplus before --expand or --shrink: $surplus (", - human_size ($surplus), ")\n"; - } - - do_expand () if $expand; - do_shrink () if $shrink; -} - -# (Re-)calculate surplus after doing expand or shrink. -calculate_surplus (); - -# Add up the total space required on the target so far, compared -# to the size of the target. We end up with a surplus or deficit. -sub calculate_surplus -{ - local $_; - - # We need some overhead for partitioning. Worst case would be for - # EFI partitioning + massive per-partition alignment. - 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) { - if ($partitions{$_}->{newsize}) { - $required += $partitions{$_}->{newsize} - } else { - $required += $partitions{$_}->{part_size} - } - } - - # Compare that to the actual target disk. - $surplus = $outsize - ($required + $overhead); -} - -sub do_expand -{ - local $_; - - unless ($surplus > 0) { - die __x("virt-resize: error: cannot use --expand when there is no surplus space to\nexpand into. You need to make the target disk larger by at least {h}.\n", - h => human_size (-$surplus)); - } - - my $part = find_partition ($expand, "--expand"); - my $oldsize = $partitions{$part}->{part_size}; - mark_partition_for_resize ($part, $oldsize, $oldsize + $surplus, - 0, "--expand"); -} - -sub do_shrink -{ - local $_; - - unless ($surplus < 0) { - die __"virt-resize: error: cannot use --shrink because there is no deficit\n(see 'deficit' in the virt-resize(1) man page)\n" - } - - my $part = find_partition ($shrink, "--shrink"); - my $oldsize = $partitions{$part}->{part_size}; - mark_partition_for_resize ($part, $oldsize, $oldsize + $surplus, - 0, "--shrink"); -} - -# Print summary. -print_summary () unless $quiet; - -sub print_summary -{ - local $_; - print __"Summary of changes:\n"; - - foreach my $part (@partitions) { - if ($partitions{$part}->{ignore}) { - print __x("{p}: partition will be ignored\n", p => $part); - } elsif ($partitions{$part}->{delete}) { - 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}\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\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}); - } - } - - if ($surplus > 0) { - print __x("There is a surplus of {spl} bytes ({h}).\n", - spl => $surplus, - h => human_size ($surplus)); - if ($extra_partition) { - if ($surplus >= $min_extra_partition) { - print __"An extra partition will be created for the surplus.\n"; - } else { - print __"The surplus space is not large enough for an extra partition to be created\nand so it will just be ignored.\n"; - } - } else { - print __"The surplus space will be ignored. Run a partitioning program in the guest\nto partition this extra space if you want.\n"; - } - } elsif ($surplus < 0) { - die __x("virt-resize: error: there is a deficit of {def} bytes ({h}).\nYou need to make the target disk larger by at least this amount,\nor adjust your resizing requests.\n", - def => -$surplus, - h => human_size (-$surplus)); - } -} - -exit 0 if $dryrun; - -# Repartition the target disk. -my $nextpart = 1; -repartition (); - -sub repartition -{ - local $_; - - # 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; - - # Align to 64. - $start = ($start + 63) & ~63; - - print "starting to partition from $start\n" if $debug; - - # Create the new partitions. - foreach my $part (@partitions) { - unless ($partitions{$part}->{delete}) { - # Size in sectors. - my $size; - if ($partitions{$part}->{newsize}) { - $size = ($partitions{$part}->{newsize} + $sectsize - 1) - / $sectsize; - } else { - $size = ($partitions{$part}->{part_size} + $sectsize - 1) - / $sectsize; - } - - # Create it. - 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; - } - } - - # Create surplus partition. - if ($extra_partition && $surplus >= $min_extra_partition) { - add_partition ($start, $outsize / $sectsize - 64 - $start); - } -} - -# Add a partition. -sub add_partition -{ - local $_; - my $start = shift; - my $size = shift; - - 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); - $part_num = $nextpart++; - } else { - if ($nextpart == 4) { - $g->part_add ("/dev/sdb", "extended", $start, -1); - $part_num = $nextpart++; - $start += 64; - } - $target = "/dev/sdb$nextpart"; - $end = $start + $size - 1; - $g->part_add ("/dev/sdb", "logical", $start, $end); - $part_num = $nextpart++; - } - - return ($target, $end, $part_num); -} - -# Copy over the data. -copy_data (); - -sub copy_data -{ - foreach my $part (@partitions) - { - unless ($partitions{$part}->{ignore}) { - my $target = $partitions{$part}->{target}; - if ($target) { - my $oldsize = $partitions{$part}->{part_size}; - my $newsize; - if ($partitions{$part}->{newsize}) { - $newsize = $partitions{$part}->{newsize}; - } else { - $newsize = $partitions{$part}->{part_size}; - } - - if (!$quiet && !$debug) { - print __x("Copying {p} ...\n", p => $part); - } - - $g->copy_size ($part, $target, - $newsize < $oldsize ? $newsize : $oldsize); - } - } - } -} - -# 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 -{ - local $_ = shift; - my $unit = shift; - - $_ *= 1024 if $unit =~ /[KMGTPE]/; - $_ *= 1024 if $unit =~ /[MGTPE]/; - $_ *= 1024 if $unit =~ /[GTPE]/; - $_ *= 1024 if $unit =~ /[TPE]/; - $_ *= 1024 if $unit =~ /[PE]/; - $_ *= 1024 if $unit =~ /[E]/; - - return floor($_); -} - -# Convert a number of bytes to a human-readable number. -sub human_size -{ - local $_ = shift; - - my $sgn = ""; - if ($_ < 0) { - $sgn = "-"; - $_ = -$_; - } - - $_ /= 1024; - - if ($_ < 1024) { - sprintf "%s%dK", $sgn, $_; - } elsif ($_ < 1024 * 1024) { - sprintf "%s%.1fM", $sgn, ($_ / 1024); - } else { - sprintf "%s%.1fG", $sgn, ($_ / 1024 / 1024); - } -} - -# The reverse of device name translation, see -# BLOCK DEVICE NAMING in guestfs(3). -sub canonicalize -{ - local $_ = shift; - - if (m{^/dev/[hv]d([a-z]\d*)$}) { - return "/dev/sd$1"; - } - $_; -} - -# 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, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L. - -=head1 AUTHOR - -Richard W.M. Jones L - -=head1 COPYRIGHT - -Copyright (C) 2010 Red Hat Inc. - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.