[Libguestfs] [PATCH FOR DISCUSSION ONLY] virt-resize tool for resizing virtual machines.

Richard W.M. Jones rjones at redhat.com
Fri Mar 19 18:00:37 UTC 2010


Unfinished patch to implement virt-resize.

The concept is solid.  It just needs a little more thought as to
exactly how containers [of containers] expand and shrink in response
to user requests.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
libguestfs lets you edit virtual machines.  Supports shell scripting,
bindings from many languages.  http://et.redhat.com/~rjones/libguestfs/
See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
-------------- next part --------------
>From b5063af88c757e2213df0aa9f747a22aab29bb16 Mon Sep 17 00:00:00 2001
From: Richard Jones <rjones at redhat.com>
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<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.
+
+This program is intended to be used in conjunction with
+L<virt-list-filesystems(1)> and L<virt-df(1)>.  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<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 2. Look at current sizing
+
+Use L<virt-list-filesystems(1)> and/or L<virt-df(1)> 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</dev/sda1> and
+C</dev/VolGroup00/LogVol00>.
+
+=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<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
+
+B<Note:> 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<truncate> 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<indisk> to disk image C<outdisk>
+I<without> resizing or changing any existing partitions or
+filesystems.  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.
+
+To resize, you need to pass extra options (for the full list see the
+L</OPTIONS> section below).
+
+L</--expand> 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<not> created at the end of the
+disk, because there will be no unused space).  If C</dev/sda1> in the
+image contains a filesystem, then the filesystem is resized (if
+possible because only some filesystem types support resizing).
+
+L</--resize> is the other commonly used option.  The following would
+increase the size of C</dev/sda1> by 200M, and expand C</dev/vg/lv>
+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<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.
+
+=back
+
+=head1 OPTIONS
+
+In the options below, "fs" means some filesystem name, eg.
+C</dev/sda1> or C</dev/VolGroup00/LogVol00>.  Use
+L<virt-list-filesystems(1)> and/or L<virt-df(1)> 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<size> 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<only> 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<must> 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<renumbered>.
+
+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;
+ at s = stat $infile;
+my $insize = S_ISREG ($s[2]) ? $s[7] : host_blockdevsize ($infile);
+ at 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: $!";
+    $_ = <BD>;
+    chomp $_;
+    $_;
+}
+
+=head1 SEE ALSO
+
+L<virt-list-filesystems(1)>,
+L<virt-df(1)>,
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<lvm(8)>,
+L<virsh(1)>,
+L<Sys::Guestfs(3)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://et.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.
-- 
1.6.5.2



More information about the Libguestfs mailing list