[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