>From b5063af88c757e2213df0aa9f747a22aab29bb16 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Wed, 17 Mar 2010 19:31:10 +0000 Subject: [PATCH] virt-resize tool for resizing virtual machines. --- .gitignore | 1 + Makefile.am | 2 + po/POTFILES.in | 1 + tools/Makefile.am | 2 +- tools/virt-resize | 1068 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1073 insertions(+), 1 deletions(-) create mode 100755 tools/virt-resize diff --git a/.gitignore b/.gitignore index 2d7f383..6124e00 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,7 @@ html/virt-inspector.1.html html/virt-list-filesystems.1.html html/virt-ls.1.html html/virt-rescue.1.html +html/virt-resize.1.html html/virt-tar.1.html html/virt-win-reg.1.html images/100kallnewlines diff --git a/Makefile.am b/Makefile.am index c1fc85d..684a5d9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -125,6 +125,7 @@ HTMLFILES = \ html/virt-list-filesystems.1.html \ html/virt-ls.1.html \ html/virt-rescue.1.html \ + html/virt-resize.1.html \ html/virt-tar.1.html \ html/virt-win-reg.1.html \ html/recipes.html \ @@ -161,6 +162,7 @@ all-local: -name 'virt-list-filesystems' -o \ -name 'virt-ls' -o \ -name 'virt-rescue' -o \ + -name 'virt-resize' -o \ -name 'virt-tar' -o \ -name 'virt-win-reg' | \ grep -v '^perl/blib/' | \ diff --git a/po/POTFILES.in b/po/POTFILES.in index c88abc5..fbf23a8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -104,5 +104,6 @@ tools/virt-edit tools/virt-list-filesystems tools/virt-ls tools/virt-rescue +tools/virt-resize tools/virt-tar tools/virt-win-reg diff --git a/tools/Makefile.am b/tools/Makefile.am index 6e6872c..624c2eb 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -17,7 +17,7 @@ include $(top_srcdir)/subdir-rules.mk -tools = cat df edit list-filesystems ls rescue tar win-reg +tools = cat df edit list-filesystems ls rescue resize tar win-reg EXTRA_DIST = \ run-locally \ diff --git a/tools/virt-resize b/tools/virt-resize new file mode 100755 index 0000000..82c852c --- /dev/null +++ b/tools/virt-resize @@ -0,0 +1,1068 @@ +#!/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 Fcntl qw(S_ISREG SEEK_SET); +use Pod::Usage; +use Getopt::Long; +use Data::Dumper; +use Locale::TextDomain 'libguestfs'; + +$Data::Dumper::Sortkeys = 1; + +=encoding utf8 + +=head1 NAME + +virt-resize - Resize a virtual machine disk + +=head1 SYNOPSIS + + virt-resize [--options] 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 +and filesystems 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. + +This program is intended to be used in conjunction with +L and L. You should read the +manpages for those tools if you are not already familiar with them. + +=head2 BASIC USAGE + +=over 4 + +=item 1. Locate disk image + +Locate the disk image that you want to resize. It could be in a local +file or device. If the guest is managed by libvirt, you can use +C like this to find the disk image name: + + # virsh dumpxml guestname | xpath /domain/devices/disk/source + Found 1 nodes: + -- NODE -- + + +=item 2. Look at current sizing + +Use L and/or L on the +disk image to find out what is currently inside the disk image, +and how much free space is available. For example: + + # virt-df -h /dev/vg/lv_guest + Filesystem Size Used Available Use% + /dev/vg/lv_guest:/dev/sda1 98.7M 35.4M 58.3M 41.0% + /dev/vg/lv_guest:/dev/VolGroup00/LogVol00 + 8.6G 8.0G 0.1G 98.8% + +In this example the filesystems are called C and +C. + +=item 3. Create destination disk + +Virt-resize cannot do in-place disk modifications. You have to have +space to store the resized destination disk. + +To store the resized disk image in a file, create a file of a suitable +size: + + # rm -f outdisk + # truncate -s 10G outdisk + +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 + +B The destination disk must be empty (all zero bytes) before +virt-resize is run. For files, you should delete any old file before +using the C command. For logical volumes, you should create +the LV afresh before each use of virt-resize. + +=item 4. Resize + + virt-resize indisk outdisk + +This command just copies disk image C to disk image C +I resizing or changing any existing partitions or +filesystems. 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. + +To resize, you need to pass extra options (for the full list see the +L section below). + +L is the most useful option. It expands the named +filesystem within the disk to fill any extra space: + + virt-resize --expand /dev/sda1 indisk outdisk + +(In this case, an extra partition is I created at the end of the +disk, because there will be no unused space). If C in the +image contains a filesystem, then the filesystem is resized (if +possible because only some filesystem types support resizing). + +L is the other commonly used option. The following would +increase the size of C by 200M, and expand C +to fill the rest of the available space: + + virt-resize --resize /dev/sda1=+200M --expand /dev/vg/lv \ + indisk outdisk + +Other options are covered below. + +=item 5. 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. + +=back + +=head1 OPTIONS + +In the options below, "fs" means some filesystem name, eg. +C or C. Use +L and/or L to list the names of +filesystems within an image. + +=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 fs=size> + +Resize the named filesystem (expanding or shrinking it) so that it has +the given size. + +C can be expressed as an absolute number followed by +b/s/K/M/G/T/P/E to mean bytes, sectors, 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/VolGroup00/LogVol00=90% + + --resize /dev/sda2=+1G + + --resize /dev/sda2=-200M + + --resize /dev/sda1=+128s + + --resize /dev/VolGroup00/LogVol00=+10% + + --resize /dev/VolGroup00/LogVol00=-10% + +You can increase the size of anything: filesystems, partitions, +PVs or LVs. + +If you increase the size of a filesystem, then virt-resize will try to +resize the filesystem itself to fit the extra space. This is only +possible for ext2/3/4, NTFS and LVM PV. + +You can I decrease the size of ext2/3/4 filesystems, NTFS and +LVM PVs, and then only if there is free space in the filesystem or PV +to shrink it. + +You can give this option multiple times. + +=cut + +my @resize_force; + +=item B<--resize-force fs=size> + +This is the same as C<--resize> except that it will let you decrease +the size of anything. Generally this means you will lose any data +which was at the end of the thing 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 fs> + +=item B<--expand percent:fs> + +Expand the named filesystem so it uses up all extra space (space left +over after any other filesystem changes that you request have been +done). + +You can give this option multiple times: + + --expand fs1 --expand fs2 + +which divides the extra space equally between fs1 and fs2. + +You can also prefix each filesystem with a percentage, thus: + + --expand 20:fs1 --expand 50:fs2 + +will give 20% of the extra space to fs1, 50% to fs2, and the remainder +(30%) will go into an extra partition. + +As you can see from the example, the percentages do not need to add up +to 100 (but the total must not be larger than 100). + +If possible, we resize the filesystem to fit the extra space. This +can be done for ext2/3/4 and NTFS filesystems, and LVM PVs. + +You can also expand partitions and LVM LVs. + +Note that you cannot use C<--expand> and C<--shrink> together. + +=cut + +my @shrink; + +=item B<--shrink fs> + +=item B<--shrink percent:fs> + +Shrink the named filesystem until the overall disk image fits in the +destination. The named filesystem B be ext2/3/4, NTFS or an +LVM PV, and it must have enough free space to allow it to be shrunk. + +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 at least one filesystem to shrink and that filesystem had +more than a gigabyte of free space. + +You can give this option multiple times: + + --shrink fs1 --shrink fs2 + +which divides the deficit equally between fs1 and fs2. + +You can also prefix each filesystem with a percentage in order to spread +the deficit unequally: + + --shrink 20:fs1 --shrink 80:fs2 + +When shrinking with percentages, the percentages must sum to exactly +100. + +Note that you cannot use C<--expand> and C<--shrink> together. + +=cut + +my @ignore; + +=item B<--ignore fs> + +Ignore the named filesystem. Effectively this means the filesystem +is created on the destination disk, but the content is not copied +across from the source disk. The content of the filesystem will be +blank (all zero bytes). + +You can ignore partitions, filesystems or any LVM object. This +applies recursively, so for example if you ignore an LVM VG, then none +of the LVs or filesystems inside that VG are copied over. + +You can give this option multiple times. + +=cut + +my @delete; + +=item B<--delete fs> + +Delete the named filesystem, partition or LVM object. 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 if you delete a partition, then anything contained in +the partition is also deleted. Furthermore, this causes any +partitions that come after to be I. + +Also if you delete an LVM object, then anything contained in that LVM +object is deleted too. So for example, deleting an LVM VG deletes any +LVs inside that VG. + +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; + +=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. + +=cut + +my $resize_fs = 1; + +=item B<--no-resize-fs> + +By default, virt-resize will resize ext2/3/4 and NTFS filesystems, and +LVM PVs (not merely the containers they are in). If you use this +option, then only the container is resized, not the filesystem. + +Note that if this option is specified, then virt-resize will refuse to +shrink anything unless you use C<--resize-force>. + +=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, + "resize-fs!" => \$resize_fs, + "d|debug" => \$debug, + "n|dryrun" => \$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"; +} + +die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", + file => $infile, sz => $insize) + if $insize < 64 * 512; +die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", + file => $outfile, sz => $outsize) + if $outsize < 64 * 512; + +# 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, 64 * 512) or die "$infile: $!"; + die "$infile: short read" if $r < 64 * 512; + open OFILE, "+<$outfile" or die "$outfile: $!"; + sysseek OFILE, 0, SEEK_SET or die "$outfile: seek: $!"; + $r = syswrite (OFILE, $s, 64 * 512) or die "$outfile: $!"; + die "$outfile: short write" if $r < 64 * 512; +} + +# Add them to the handle and launch the appliance. +my $g; +launch_guestfs (); + +sub launch_guestfs +{ + $g = Sys::Guestfs->new (); + $g->set_trace (1) if $debug; + # NB. The source MUST be readonly here, because we make changes + # and rely on the snapshot to discard them without modifying the + # original image. + $g->add_drive_ro ($infile); + $g->add_drive ($outfile); + $g->launch (); +} + +# Evaluate what is in the source disk. +my (%parts, %fses, %lvs, %vgs, %objects); +check_source_disk (); + +sub check_source_disk +{ + local $_; + + # Partitions and PVs. + my @all_partitions = $g->list_partitions (); + @all_partitions = grep { m{^/dev/.da} } @all_partitions; + my @pvs = $g->pvs (); + @pvs = grep { m{^/dev/.da} } @pvs; + + my @partitions = grep { ! member ($_, @pvs) } @all_partitions; + + if ($debug) { + print "all partitions: ", join (", ", @all_partitions), "\n"; + print "partitions (not PV): ", join (", ", @partitions), "\n"; + print "PVs: ", join (", ", @pvs), "\n"; + } + + # VGs and LVs. + my @vgs = $g->vgs (); + my @lvs = $g->lvs (); + + if ($debug) { + print "VGs: ", join (", ", @vgs), "\n"; + print "LVs: ", join (", ", @lvs), "\n"; + } + + # LVM object UUIDs and their relationships. + my %pvuuids; + foreach (@pvs) { + my $uuid = $g->pvuuid ($_); + $pvuuids{$uuid} = $_; + } + my %lvuuids; + foreach (@lvs) { + my $uuid = $g->lvuuid ($_); + $lvuuids{$uuid} = $_; + } + + my %vg_to_pv; + my %vg_to_lv; + my %lv_to_vg; + foreach my $vgname (@vgs) { + my @m = $g->vgpvuuids ($vgname); + @m = map { $pvuuids{$_} } @m; + $vg_to_pv{$vgname} = \@m; + print "vg_to_pv{$vgname} = [", join (", ", @m), "]\n" if $debug; + + my @n = $g->vglvuuids ($vgname); + @n = map { $lvuuids{$_} } @n; + $vg_to_lv{$vgname} = \@n; + print "vg_to_lv{$vgname} = [", join (", ", @n), "]\n" if $debug; + + foreach (@n) { + $lv_to_vg{$_} = $vgname; + print "lv_to_vg{$_} = $vgname\n" if $debug; + } + } + + # Mountable filesystems. + my @mountables = (@lvs, @partitions); + @mountables = grep { can_mount ($_) } @mountables; + + print "mountable filesystems: ", join (", ", @mountables), "\n" + if $debug; + + # Things we know how to resize. + my @resizable_fses = (@lvs, @partitions); + @resizable_fses = grep { can_resize_fs ($_) } @resizable_fses; + + print "resizable filesystems: ", join (", ", @resizable_fses), "\n" + if $debug; + + # Current size of everything. + my %origsize; + foreach (@partitions, @pvs, @lvs) { + $origsize{$_} = $g->blockdev_getsize64 ($_); + print "size of $_ = $origsize{$_} bytes\n" if $debug; + } + + # Current sector size of everything. + my %sectorsize; + foreach (@partitions, @pvs, @lvs) { + $sectorsize{$_} = $g->blockdev_getss ($_); + } + + # Assemble what we know into a structure describing the + # current state of the disk. + # + # What we care about really are: (1) Partitions (2) Filesystems. + # This structure relates (1) and (2) together: + # + # Partitions Filesystems + # ---------- ----------- + # /dev/sda1 <--- contained directly in --- ext3 + # + # /dev/sda2 <--\ /--- LV1 <--- ext3 + # +-- VolGroup00 ---+ + # /dev/sda3 <--/ \--- LV2 <--- swap + # + # The stuff in the middle (VGs and LVs) is glue that holds + # together the relationship. Resizing a filesystem has a cascade + # effect; it may mean we have to resize the LV, the VG and hence a + # PV/partition. + + foreach (@all_partitions) { + my $is_pv = member ($_, @pvs); + $parts{$_} = { + type => "part", + name => $_, + size => $origsize{$_}, + sectorsize => $sectorsize{$_}, + is_pv => $is_pv, + resizable_fs => $is_pv, # PVs are like resizable filesystems + owns => [], # filled in later + }; + } + + foreach my $vgname (@vgs) { + my @containers = @{$vg_to_pv{$vgname}}; + @containers = map { $parts{$_} } @containers; + $vgs{$vgname} = { + type => "vg", + name => $vgname, + containers => \@containers, # ie. the PVs. + owns => [], # filled in later + }; + } + + foreach (@lvs) { + my $container = $vgs{$lv_to_vg{$_}}; + $lvs{$_} = { + type => "lv", + name => $_, + size => $origsize{$_}, + sectorsize => $sectorsize{$_}, + container => $container, # ie. the VG. + owns => [], # filled in later + } + } + + foreach (@lvs, @partitions) { + my $is_on_lv = member ($_, @lvs); + my $container = $is_on_lv ? $lvs{$_} : $parts{$_}; + $fses{$_} = { + type => "fs", + name => $_, + size => $origsize{$_}, + sectorsize => $sectorsize{$_}, + resizable_fs => member ($_, @resizable_fses), + mountable => member ($_, @mountables), + container => $container, + }; + } + + # If someone says '--expand /dev/VG/LV' that could refer to the LV + # or to the filesystem contained in that LV. In fact it's most + # helpful to refer to the rightmost object (see the diagram above) + # and cascade changes leftwards. The %objects array maps names + # that the user can use to the rightmost object (ie. having the + # precedence: fs > lv > vg > part). Each 'foreach' statement can + # overwrite keys added by previous foreach statements. + foreach (keys %parts) { + $objects{$_} = $parts{$_} + } + foreach (keys %vgs) { + $objects{$_} = $vgs{$_}; + # "VG" is the same as "/dev/VG" + $objects{"/dev/$_"} = $vgs{$_} + } + #foreach (keys %lvs) { # all LVs are filesystems, so no effect + # $objects{$_} = $lvs{$_} + #} + foreach (keys %fses) { + $objects{$_} = $fses{$_} + } + + # Set up reverse pointers ("container{,s}" points left, + # "owns" points right). + foreach my $vg (values %vgs) { + foreach (@{$vg->{containers}}) { + push @{$_->{owns}}, $vg; + } + } + foreach (values %lvs) { + push @{$_->{container}->{owns}}, $_; + } + foreach (values %fses) { + push @{$_->{container}->{owns}}, $_; + } + + if ($debug) { + print "----------\n"; + print (Dumper (\%parts, \%fses, \%lvs, \%vgs, \%objects)); + print "----------\n"; + } +} + +sub object_exists_or_die +{ + local $_; + $_ = shift; + my $opt = shift; + + die __x("{o}: object not found in the source disk image, when using the '{opt}' command line option\n", + o => $_, + opt => $opt) + unless exists $objects{$_}; +} + +sub object_not_ignored_or_deleted +{ + local $_; + $_ = shift; + die __x("{o}: objected ignored (either directly with --ignore or indirectly)\n", + o => $_) + if $objects{$_}->{ignore}; + die __x("{o}: objected deleted (either directly with --delete or indirectly)\n", + o => $_) + if $objects{$_}->{delete}; +} + +# Handle --ignore. +do_ignore ($_) foreach @ignore; + +sub do_ignore +{ + local $_ = shift; + object_exists_or_die ($_, "--ignore"); + object_not_ignored_or_deleted ($_); + visit_right ($objects{$_}, sub { $_[0]->{ignore} = 1 }); +} + +# Visit the structure rightwards, ie. through the 'owns' pointers. +sub visit_right +{ + local $_; + my $obj = shift; + my $fn = shift; + my $indent = shift || 0; + + &$fn ($obj, $indent); + if (exists $obj->{owns}) { + foreach (sort { $a->{name} cmp $b->{name} } @{$obj->{owns}}) { + visit_right ($_, $fn, $indent+1); + } + } +} + +# Handle --delete. +do_delete ($_) foreach @delete; + +sub do_delete +{ + local $_ = shift; + object_exists_or_die ($_, "--delete"); + object_not_ignored_or_deleted ($_); + visit_right ($objects{$_}, sub { $_[0]->{delete} = 1 }); +} + +# Handle --resize and --resize-force (before handling --expand and --shrink). +do_resize ($_, 0) foreach @resize; +do_resize ($_, 1) foreach @resize_force; + +sub do_resize +{ + local $_ = shift; + my $force = shift; + + # The parameter is "name=size". + my ($name, $sizefield) = split /=/, $_, 2; + + object_exists_or_die ($name, $force ? "--resize-force" : "--force"); + object_not_ignored_or_deleted ($name); + + my $obj = $objects{$name}; + + if (exists $obj->{newsize}) { + die __x("{o}: this object is already marked for resizing\n", + o => $name); + } + + # Parse the size field. + my $oldsize = $obj->{size}; + my $newsize; + if ($sizefield =~ /^(\d+)([bsKMGTPE])$/) { + $newsize = sizebytes ($1, $2, $obj); + } elsif ($sizefield =~ /^\+(\d+)([bsKMGTPE])$/) { + my $incr = sizebytes ($1, $2, $obj); + $newsize = $oldsize + $incr; + } elsif ($sizefield =~ /^-(\d+)([bsKMGTPE])$/) { + my $decr = sizebytes ($1, $2, $obj); + $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("{o}: {f}: cannot parse size field\n", + o => $name, f => $sizefield) + } + + die __x("{n}: new size is zero or negative\n", n => $name) if $newsize <= 0; + + mark_object_for_resize ($obj, $name, $newsize, $force); +} + +sub mark_object_for_resize +{ + local $_; + my $obj = shift; + my $name = shift; + my $newsize = shift; + my $force = shift; + + my $oldsize = $obj->{size}; + + my $bigger = $newsize >= $oldsize; + + # Can't resize things that don't have a size (ie. VGs). + unless ($obj->{size}) { + die __x("{o}: this type of object does not have a size. Try resizing its container or something contained inside it instead.\n", + o => $name); + } + + # Check we can resize this. + if (!$bigger && !$obj->{resizable_fs} && !$force) { + die __x("{o}: requested to make this smaller, but this program does not know how to resize something of this type to make it smaller\n", + o => $name); + } + + warn "NOT DOING FREESPACE CHECK"; +# if (!$bigger && $obj->{resizable_fs} && !$force && +# $obj->{freespace} < $oldsize - $newsize) { +# die __x("{o}: not enough free space in this object to make it smaller by this amount\n", +# o => $name); +# } + + $obj->{newsize} = $newsize; + $obj->{shrink_fs_required} = !$bigger && $obj->{resizable_fs} && !$force; + + if ($bigger) { + visit_left ($obj, sub { + if ($_[0]->{size}) { + $_[0]->{newsize} = $_[0]->{size} + ($newsize - $oldsize); + $_[0]->{expand_fs_required} = $_[0]->{resizable_fs}; + } + }); + } +} + +sub sizebytes +{ + local $_ = shift; + my $unit = shift; + my $obj = shift; + + $_ *= $obj->{sectorsize} if $unit eq "s"; + $_ *= 1024 if $unit =~ /[KMGTPE]/; + $_ *= 1024 if $unit =~ /[MGTPE]/; + $_ *= 1024 if $unit =~ /[GTPE]/; + $_ *= 1024 if $unit =~ /[TPE]/; + $_ *= 1024 if $unit =~ /[PE]/; + $_ *= 1024 if $unit =~ /[E]/; + $_; +} + +# Visit the structure leftwards, ie. through the 'container{,s}' pointers. +sub visit_left +{ + local $_; + my $obj = shift; + my $fn = shift; + my $indent = shift || 0; + + &$fn ($obj, $indent); + if (exists $obj->{container}) { + visit_left ($obj->{container}, $fn, $indent+1); + } + if (exists $obj->{containers}) { + foreach (sort { $a->{name} cmp $b->{name} } @{$obj->{containers}}) { + visit_left ($_, $fn, $indent+1); + } + } +} + + + +# Handle --expand and --shrink. + + + + + + + +# Print summary. +print_summary () unless $quiet; + +sub print_summary +{ + local $_; + print __"Summary of changes:\n"; + foreach (sort { $a->{name} cmp $b->{name} } values %parts) { + visit_right ($_, sub { + local $_; + my $obj = shift; + my $indent = " " x shift; + + print "$indent", $obj->{name}, " "; + if ($obj->{type} eq "fs") { + print "(filesystem)" + } elsif ($obj->{type} eq "lv") { + print "(LVM logical volume)" + } elsif ($obj->{type} eq "vg") { + print "(LVM volume group)" + } elsif ($obj->{type} eq "part") { + if ($obj->{is_pv}) { + print "(LVM physical volume)" + } else { + print "(partition)" + } + } else { + die "internal error" + } + print ":\n"; + + $indent .= " "; + + if ($obj->{newsize}) { + my $oldsize = $obj->{size}; + my $newsize = $obj->{newsize}; + my $bigger = $newsize >= $oldsize; + + my $s; + if ($bigger) { + $s = __x("will be expanded from {oldsize} to {newsize}", + oldsize => $oldsize, newsize => $newsize); + } else { + $s = __x("will be shrunk from {oldsize} to {newsize}", + oldsize => $oldsize, newsize => $newsize); + } + print "$indent$s\n"; + + if ($obj->{expand_fs_required}) { + print "$indent", __"this filesystem will be expanded to fit the container", "\n"; + } elsif ($obj->{shrink_fs_required}) { + print "$indent", __"this filesystem will be shrunk to fit the container", "\n"; + } + } + + + }); + } +} + +exit 0 if $dryrun; + +# Delete any existing partitions on the destination disk. +my $parttype = $g->part_get_parttype ("/dev/sdb"); +print "partition table type: $parttype\n" if $debug; + +$g->part_init ("/dev/sdb", $parttype); + + + + + + + + + + +# Returns true iff first arg is an element of the list +# of subsequent args. +sub member +{ + local $_; + my $t = shift; + + foreach (@_) { + return 1 if $_ eq $t; + } + 0; +} + +# Can we mount this object? +sub can_mount +{ + local $_; + my $fs = shift; + + eval { + $g->mount_ro ($fs, "/"); + }; + my $r = !$@; + $g->umount_all (); + return $r; +} + +# Is this a filesystem (or PV) that we know how to resize? +sub can_resize_fs +{ + local $_; + my $fs = shift; + + my $r; + eval { + my $t = $g->vfs_type ($fs); + if ($t =~ /^ext[234]/ || + $t eq "ntfs" || + $t eq "LVM2_member") { + $r = 1; + } + }; + return $r; +} + +# 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 $_; + $_; +} + +=head1 SEE ALSO + +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. -- 1.6.5.2