X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=tools%2Fvirt-resize;h=a70492e846acb9e60f96a11ac5064122a85b0106;hb=HEAD;hp=fbbf7f62d5d6cc281e1f2927e5999351eec64ebc;hpb=5466638279b51d46e6b24d4f7148d520cb4f3c34;p=libguestfs.git diff --git a/tools/virt-resize b/tools/virt-resize deleted file mode 100755 index fbbf7f6..0000000 --- a/tools/virt-resize +++ /dev/null @@ -1,1258 +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; - -=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, -L and -L, -we recommend you go and read those manual pages first. - -=head1 BASIC 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-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 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. - -=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 $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. - -=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, - "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, - ) 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; - -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); - -if ($debug) { - print "$infile size $insize bytes\n"; - print "$outfile size $outsize bytes\n"; -} - -# 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 $boot_sectors = 4096; - -die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", - file => $infile, sz => $insize) - if $insize < $boot_sectors * 512; -die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", - file => $outfile, sz => $outsize) - if $outsize < $boot_sectors * 512; - -# 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; - open IFILE, $infile or die "$infile: $!"; - my $s; - my $r = sysread (IFILE, $s, $boot_sectors * 512) or die "$infile: $!"; - die "$infile: short read" if $r < $boot_sectors * 512; - open OFILE, "+<$outfile" or die "$outfile: $!"; - sysseek OFILE, 0, SEEK_SET or die "$outfile: seek: $!"; - $r = syswrite (OFILE, $s, $boot_sectors * 512) or die "$outfile: $!"; - die "$outfile: short write" if $r < $boot_sectors * 512; -} - -# 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; - $g->add_drive_ro ($infile); - $g->add_drive ($outfile); - $g->launch (); -} - -my $sectsize = $g->blockdev_getss ("/dev/sdb"); - -# 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"); - } - } -} - -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. -my $to_be_expanded = 0; - -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 - ($boot_sectors - 64) # 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); - } - } - - 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; -my $parttype; -repartition (); - -sub repartition -{ - local $_; - - if ($copy_boot_loader) { - $parttype = $g->part_get_parttype ("/dev/sdb"); - } else { - $parttype = "efi"; - } - print "partition table type: $parttype\n" if $debug; - - # Delete any existing partitions on the destination disk, - # but leave the bootloader that we copied over intact. - if ($copy_boot_loader) { - # Delete in reverse as an easy way to deal with extended - # partitions. - foreach (sort { $b cmp $a } $g->list_partitions ()) { - if (m{^/dev/.db(\d+)$}) { - $g->part_del ("/dev/sdb", $1); - } - } - } else { - # Didn't copy over the initial boot loader, so we need - # to make a new partition table here. - $g->part_init ("/dev/sdb", $parttype); - } - - # 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) { - local $| = 1; - print __x("Copying {p} ...", 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 (); -} - -sub restart_appliance -{ - # Sync disk and exit. - $g->umount_all (); - $g->sync (); - undef $g; - - $g = Sys::Guestfs->new (); - $g->set_trace (1) if $debug; - $g->add_drive ($outfile); - $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", - 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"; - } -} - -# 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); - } -} - -# 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 -{ - local $_ = shift; - - if (m{^/dev/[hv]d([a-z]\d)$}) { - return "/dev/sd$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. - -=head1 SEE ALSO - -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.