mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
1525 lines
44 KiB
Perl
Executable File
1525 lines
44 KiB
Perl
Executable File
#!/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=[+/-]<size>[%]]
|
|
[--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<cannot> resize disk images in-place. Virt-resize
|
|
B<should not> 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<virt-filesystems(1)> and L<virt-df(1)>, we recommend you go and read
|
|
those manual pages first.
|
|
|
|
=head1 EXAMPLES
|
|
|
|
Copy C<olddisk> to C<newdisk>, extending one of the guest's partitions
|
|
to fill the extra 5GB of space.
|
|
|
|
truncate -r olddisk newdisk; truncate -s +5G newdisk
|
|
virt-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<virsh dumpxml> like this to find the disk image name:
|
|
|
|
# virsh dumpxml guestname | xpath /domain/devices/disk/source
|
|
Found 1 nodes:
|
|
-- NODE --
|
|
<source dev="/dev/vg/lv_guest" />
|
|
|
|
=item 3. Look at current sizing
|
|
|
|
Use L<virt-filesystems(1)> 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<lvcreate(1)> to create a logical volume:
|
|
|
|
# lvcreate -L 10G -n lv_name vg_name
|
|
|
|
Or use L<virsh(1)> vol-create-as to create a libvirt storage volume:
|
|
|
|
# virsh pool-list
|
|
# virsh vol-create-as poolname newvol 10G
|
|
|
|
=item 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<indisk> to disk image C<outdisk>
|
|
I<without> resizing or changing any existing partitions. If
|
|
C<outdisk> is larger, then an extra, empty partition is created at the
|
|
end of the disk covering the extra space. If C<outdisk> is smaller,
|
|
then it will give an error.
|
|
|
|
More realistically you'd want to expand existing partitions in the
|
|
disk image by passing extra options (for the full list see the
|
|
L</OPTIONS> section below).
|
|
|
|
L</--expand> is the most useful option. It expands the named
|
|
partition within the disk to fill any extra space:
|
|
|
|
# virt-resize --expand /dev/sda2 indisk outdisk
|
|
|
|
(In this case, an extra partition is I<not> created at the end of the
|
|
disk, because there will be no unused space).
|
|
|
|
L</--resize> is the other commonly used option. The following would
|
|
increase the size of /dev/sda1 by 200M, and expand /dev/sda2
|
|
to fill the rest of the available space:
|
|
|
|
# virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 \
|
|
indisk outdisk
|
|
|
|
If the expanded partition in the image contains a filesystem or LVM
|
|
PV, then if virt-resize knows how, it will resize the contents, the
|
|
equivalent of calling a command such as L<pvresize(8)>,
|
|
L<resize2fs(8)> or L<ntfsresize(8)>. However virt-resize does not
|
|
know how to resize some filesystems, so you would have to online
|
|
resize them after booting the guest.
|
|
|
|
Other options are covered below.
|
|
|
|
=item 6. Test
|
|
|
|
Thoroughly test the new disk image I<before> discarding the old one.
|
|
|
|
If you are using libvirt, edit the XML to point at the new disk:
|
|
|
|
# virsh edit guestname
|
|
|
|
Change E<lt>source ...E<gt>, see
|
|
L<http://libvirt.org/formatdomain.html#elementsDisks>
|
|
|
|
Then start up the domain with the new, resized disk:
|
|
|
|
# virsh start guestname
|
|
|
|
and check that it still works. See also the L</NOTES> section below
|
|
for additional information.
|
|
|
|
=item 7. Resize LVs etc inside the guest
|
|
|
|
(This can also be done offline using L<guestfish(1)>)
|
|
|
|
Once the guest has booted you should see the new space available, at
|
|
least for filesystems that virt-resize knows how to resize, and for
|
|
PVs. The user may need to resize LVs inside PVs, and also resize
|
|
filesystem types that virt-resize does not know how to expand.
|
|
|
|
=back
|
|
|
|
=head2 SHRINKING A VIRTUAL MACHINE DISK
|
|
|
|
Shrinking is somewhat more complex than expanding, and only an
|
|
overview is given here.
|
|
|
|
Firstly virt-resize will not attempt to shrink any partition content
|
|
(PVs, filesystems). The user has to shrink content before passing the
|
|
disk image to virt-resize, and virt-resize will check that the content
|
|
has been shrunk properly.
|
|
|
|
(Shrinking can also be done offline using L<guestfish(1)>)
|
|
|
|
After shrinking PVs and filesystems, shut down the guest, and proceed
|
|
with steps 3 and 4 above to allocate a new disk image.
|
|
|
|
Then run virt-resize with any of the C<--shrink> and/or C<--resize>
|
|
options.
|
|
|
|
=head2 IGNORING OR DELETING PARTITIONS
|
|
|
|
virt-resize also gives a convenient way to ignore or delete partitions
|
|
when copying from the input disk to the output disk. Ignoring a
|
|
partition speeds up the copy where you don't care about the existing
|
|
contents of a partition. Deleting a partition removes it completely,
|
|
but note that it also renumbers any partitions after the one which is
|
|
deleted, which can leave some guests unbootable.
|
|
|
|
=head2 QCOW2 AND NON-SPARSE RAW FORMATS
|
|
|
|
If the input disk is in qcow2 format, then you may prefer that the
|
|
output is in qcow2 format as well. Alternately, virt-resize can
|
|
convert the format on the fly. The output format is simply determined
|
|
by the format of the empty output container that you provide. Thus to
|
|
create qcow2 output, use:
|
|
|
|
qemu-img create [-c] -f qcow2 outdisk [size]
|
|
|
|
instead of the truncate command (use C<-c> for a compressed disk).
|
|
|
|
Similarly, to get non-sparse raw output use:
|
|
|
|
fallocate -l size outdisk
|
|
|
|
(on older systems that don't have the L<fallocate(1)> command use
|
|
C<dd if=/dev/zero of=outdisk bs=1M count=..>)
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 4
|
|
|
|
=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<size> 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<decrease> the size of partitions that contain
|
|
filesystems or PVs which have already been shrunk. Virt-resize will
|
|
check this has been done before proceeding, or else will print an
|
|
error (see also C<--resize-force>).
|
|
|
|
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<pvresize(8)>). Virt-resize leaves any
|
|
other content it doesn't know about alone.
|
|
|
|
Currently virt-resize can resize:
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
ext2, ext3 and ext4 filesystems when they are contained
|
|
directly inside a partition.
|
|
|
|
=item *
|
|
|
|
NTFS filesystems contained directly in a partition, if libguestfs was
|
|
compiled with support for NTFS.
|
|
|
|
The filesystem must have been shut down consistently last time it was
|
|
used. Additionally, L<ntfsresize(8)> marks the resized filesystem as
|
|
requiring a consistency check, so at the first boot after resizing
|
|
Windows will check the disk.
|
|
|
|
=item *
|
|
|
|
LVM PVs (physical volumes). virt-resize does not usually resize
|
|
anything inside the PV, but see the C<--LV-expand> option. The user
|
|
could also resize LVs as desired after boot.
|
|
|
|
=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<must> contain a filesystem or PV
|
|
which has already been shrunk using another tool (eg. L<guestfish(1)>
|
|
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<renumbered>, 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</dev/sda2> and a root device
|
|
called C</dev/vg_guest/lv_root> would be:
|
|
|
|
virt-resize indisk outdisk \
|
|
--expand /dev/sda2 --LV-expand /dev/vg_guest/lv_root
|
|
|
|
This would first expand the partition (and PV), and then expand the
|
|
root device to fill the extra space in the PV.
|
|
|
|
The contents of the LV are also resized if virt-resize knows how to do
|
|
that. You can stop virt-resize from trying to expand the content by
|
|
using the option C<--no-expand-content>.
|
|
|
|
Use L<virt-filesystems(1)> to list the filesystems in
|
|
the guest.
|
|
|
|
You can give this option multiple times, I<but> it doesn't
|
|
make sense to do this unless the logical volumes you specify
|
|
are all in different volume groups.
|
|
|
|
=cut
|
|
|
|
my $copy_boot_loader = 1;
|
|
|
|
=item B<--no-copy-boot-loader>
|
|
|
|
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<does not> affect the output format.
|
|
See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
|
|
|
|
=cut
|
|
|
|
my $output_format;
|
|
|
|
=item B<--output-format> raw
|
|
|
|
Specify the format of the output disk image. If this flag is not
|
|
given then it is auto-detected from the image itself.
|
|
|
|
If working with untrusted raw-format guest disk images, you should
|
|
ensure the format is always specified.
|
|
|
|
Note that you still need to create the output disk with the right
|
|
format. See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
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</dev/sda1> is the
|
|
boot partition and C</dev/sda2> is the main (C:) drive. We have not
|
|
had any luck resizing the boot partition. Doing so seems to break the
|
|
guest completely. However expanding the second partition (ie. C:
|
|
drive) should work.
|
|
|
|
Windows may initiate a lengthy "chkdsk" on first boot after a resize,
|
|
if NTFS partitions have been expanded. This is just a safety check
|
|
and (unless it find errors) is nothing to worry about.
|
|
|
|
=head2 GUEST BOOT STUCK AT "GRUB"
|
|
|
|
If a Linux guest does not boot after resizing, and the boot is stuck
|
|
after printing C<GRUB> on the console, try reinstalling grub. This
|
|
sometimes happens on older (RHEL 5-era) guests, for reasons we don't
|
|
fully understand, although we think is to do with partition alignment.
|
|
|
|
guestfish -i -a newdisk
|
|
><fs> cat /boot/grub/device.map
|
|
# check the contents of this file are sensible or
|
|
# edit the file if necessary
|
|
><fs> grub-install / /dev/vda
|
|
><fs> exit
|
|
|
|
For more flexible guest reconfiguration, including if you need to
|
|
specify other parameters to grub-install, use L<virt-rescue(1)>.
|
|
|
|
=head1 ALTERNATIVE TOOLS
|
|
|
|
There are several proprietary tools for resizing partitions. We
|
|
won't mention any here.
|
|
|
|
L<parted(8)> and its graphical shell gparted can do some types of
|
|
resizing operations on disk images. They can resize and move
|
|
partitions, but I don't think they can do anything with the contents,
|
|
and they certainly don't understand LVM.
|
|
|
|
L<guestfish(1)> can do everything that virt-resize can do and a lot
|
|
more, but at a much lower level. You will probably end up
|
|
hand-calculating sector offsets, which is something that virt-resize
|
|
was designed to avoid. If you want to see the guestfish-equivalent
|
|
commands that virt-resize runs, use the C<--debug> flag.
|
|
|
|
=head1 SHELL QUOTING
|
|
|
|
Libvirt guest names can contain arbitrary characters, some of which
|
|
have meaning to the shell such as C<#> and space. You may need to
|
|
quote or escape these characters on the command line. See the shell
|
|
manual page L<sh(1)> for details.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<virt-filesystems(1)>,
|
|
L<virt-df(1)>,
|
|
L<guestfs(3)>,
|
|
L<guestfish(1)>,
|
|
L<lvm(8)>,
|
|
L<pvresize(8)>,
|
|
L<lvresize(8)>,
|
|
L<resize2fs(8)>,
|
|
L<ntfsresize(8)>,
|
|
L<virsh(1)>,
|
|
L<parted(8)>,
|
|
L<truncate(1)>,
|
|
L<fallocate(1)>,
|
|
L<grub(8)>,
|
|
L<grub-install(8)>,
|
|
L<virt-rescue(1)>,
|
|
L<Sys::Guestfs(3)>,
|
|
L<http://libguestfs.org/>.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Richard W.M. Jones L<http://people.redhat.com/~rjones/>
|
|
|
|
=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.
|