X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=tools%2Fvirt-resize;h=8a473ca3fcf39047e1d67b94f60ae134d8935042;hp=5ced4ddc9692b6b499bdb59b7f0a0fec89f2445b;hb=2729421d176e1194537b499911a5eeb5e32a8b09;hpb=c53e64a156526adcb9937f63756f17f585f202d3 diff --git a/tools/virt-resize b/tools/virt-resize index 5ced4dd..8a473ca 100755 --- a/tools/virt-resize +++ b/tools/virt-resize @@ -33,6 +33,8 @@ $Data::Dumper::Sortkeys = 1; die __"virt-resize: sorry this program does not work on a 32 bit host\n" if ~1 == 4294967294; +$| = 1; + =encoding utf8 =head1 NAME @@ -61,7 +63,27 @@ L and L, we recommend you go and read those manual pages first. -=head1 BASIC USAGE +=head1 EXAMPLES + +Copy C to C, extending one of the guest's partitions +to fill the extra 5GB of space. + + truncate -r olddisk newdisk; truncate -s +5G newdisk + virt-list-partitions -lht olddisk + # Note "/dev/sda2" is a partition inside the "olddisk" file. + virt-resize --expand /dev/sda2 olddisk newdisk + +As above, but make the /boot partition 200MB bigger, while giving the +remaining space to /dev/sda2: + + virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 olddisk newdisk + +As above, but the output format will be uncompressed qcow2: + + qemu-img create -f qcow2 newdisk.qcow2 15G + virt-resize --expand /dev/sda2 olddisk newdisk.qcow2 + +=head1 DETAILED USAGE =head2 EXPANDING A VIRTUAL MACHINE DISK @@ -213,6 +235,25 @@ contents of a partition. Deleting a partition removes it completely, but note that it also renumbers any partitions after the one which is deleted, which can leave some guests unbootable. +=head2 QCOW2 AND NON-SPARSE RAW FORMATS + +If the input disk is in qcow2 format, then you may prefer that the +output is in qcow2 format as well. Alternately, virt-resize can +convert the format on the fly. The output format is simply determined +by the format of the empty output container that you provide. Thus to +create qcow2 output, use: + + qemu-img create [-c] -f qcow2 outdisk [size] + +instead of the truncate command (use C<-c> for a compressed disk). + +Similarly, to get non-sparse raw output use: + + fallocate -l size outdisk + +(on older systems that don't have the L command use +C
) + =head1 OPTIONS =over 4 @@ -383,6 +424,34 @@ You can give this option multiple times. =cut +my @lv_expand; + +=item B<--LV-expand logvol> + +This takes the logical volume and, as a final step, expands it to fill +all the space available in its volume group. A typical usage, +assuming a Linux guest with a single PV C and a root device +called C would be: + + virt-resize indisk outdisk \ + --expand /dev/sda2 --LV-expand /dev/vg_guest/lv_root + +This would first expand the partition (and PV), and then expand the +root device to fill the extra space in the PV. + +The contents of the LV are also resized if virt-resize knows how to do +that. You can stop virt-resize from trying to expand the content by +using the option C<--no-expand-content>. + +Use L to list the filesystems in +the guest. + +You can give this option multiple times, I it doesn't +make sense to do this unless the logical volumes you specify +are all in different volume groups. + +=cut + my $copy_boot_loader = 1; =item B<--no-copy-boot-loader> @@ -447,6 +516,36 @@ my $quiet; Don't print the summary. +=cut + +my $format; + +=item B<--format> raw + +Specify the format of the input disk image. If this flag is not +given then it is auto-detected from the image itself. + +If working with untrusted raw-format guest disk images, you should +ensure the format is always specified. + +Note that this option I affect the output format. +See L. + +=cut + +my $output_format; + +=item B<--output-format> raw + +Specify the format of the output disk image. If this flag is not +given then it is auto-detected from the image itself. + +If working with untrusted raw-format guest disk images, you should +ensure the format is always specified. + +Note that you still need to create the output disk with the right +format. See L. + =back =cut @@ -459,12 +558,15 @@ GetOptions ("help|?" => \$help, "shrink=s" => \$shrink, "ignore=s" => \@ignore, "delete=s" => \@delete, + "lv-expand=s" => \@lv_expand, "copy-boot-loader!" => \$copy_boot_loader, "extra-partition!" => \$extra_partition, "expand-content!" => \$expand_content, "d|debug" => \$debug, "n|dryrun|dry-run" => \$dryrun, "q|quiet" => \$quiet, + "format=s" => \$format, + "output-format=s" => \$output_format, ) or pod2usage (2); pod2usage (1) if $help; if ($version) { @@ -484,17 +586,71 @@ die __x("virt-resize: {file}: does not exist or is not readable\n", file => $inf 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); +# Add them to the handle and launch the appliance. +my $g; +launch_guestfs (); + +sub launch_guestfs +{ + $g = Sys::Guestfs->new (); + $g->set_trace (1) if $debug; + my @args = ($infile); + push @args, readonly => 1; + push @args, format => $format if defined $format; + $g->add_drive_opts (@args); + @args = ($outfile); + push @args, format => $output_format if defined $output_format; + $g->add_drive_opts (@args); + $g->set_progress_callback (\&progress_callback) unless $quiet; + $g->launch (); +} + +my $sectsize = $g->blockdev_getss ("/dev/sdb"); + +# Get the size in bytes of each disk. +# +# Originally we computed this by looking at the same of the host file, +# but of course this failed for qcow2 images (RHBZ#633096). The right +# way to do it is with $g->blockdev_getsize64. +my $insize = $g->blockdev_getsize64 ("/dev/sda"); +my $outsize = $g->blockdev_getsize64 ("/dev/sdb"); if ($debug) { print "$infile size $insize bytes\n"; print "$outfile size $outsize bytes\n"; } +# Create a partition table. +# +# We *must* do this before copying the bootloader across, and copying +# the bootloader must be careful not to disturb this partition table +# (RHBZ#633766). There are two reasons for this: +# +# (1) The 'parted' library is stupid and broken. In many ways. In +# this particular instance the stupid and broken bit is that it +# overwrites the whole boot sector when initializating a partition +# table. (Upstream don't consider this obvious problem to be a bug). +# +# (2) GPT has a backup partition table located at the end of the disk. +# It's non-movable, because the primary GPT contains fixed references +# to both the size of the disk and the backup partition table at the +# end. This would be a problem for any resize that didn't either +# carefully move the backup GPT (and rewrite those references) or +# recreate the whole partition table from scratch. + +my $parttype; +create_partition_table (); + +sub create_partition_table +{ + local $_; + + $parttype = $g->part_get_parttype ("/dev/sda"); + print "partition table type: $parttype\n" if $debug; + + $g->part_init ("/dev/sdb", $parttype); +} + # In reality the number of sectors containing boot loader data will be # less than this (although Windows 7 defaults to putting the first # partition on sector 2048, and has quite a large boot loader). @@ -504,14 +660,14 @@ if ($debug) { # offset of the first partition. # # It doesn't matter if we copy too much. -my $boot_sectors = 4096; +my $max_bootloader = 4096 * 512; die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", file => $infile, sz => $insize) - if $insize < $boot_sectors * 512; + if $insize < $max_bootloader; die __x("virt-resize: {file}: file is too small to be a disk image ({sz} bytes)\n", file => $outfile, sz => $outsize) - if $outsize < $boot_sectors * 512; + if $outsize < $max_bootloader; # Copy the boot loader across. do_copy_boot_loader () if $copy_boot_loader; @@ -519,30 +675,30 @@ do_copy_boot_loader () if $copy_boot_loader; sub do_copy_boot_loader { print "copying boot loader ...\n" if $debug; - open IFILE, $infile or die "$infile: $!"; - my $s; - my $r = sysread (IFILE, $s, $boot_sectors * 512) or die "$infile: $!"; - die "$infile: short read" if $r < $boot_sectors * 512; - open OFILE, "+<$outfile" or die "$outfile: $!"; - sysseek OFILE, 0, SEEK_SET or die "$outfile: seek: $!"; - $r = syswrite (OFILE, $s, $boot_sectors * 512) or die "$outfile: $!"; - die "$outfile: short write" if $r < $boot_sectors * 512; -} -# Add them to the handle and launch the appliance. -my $g; -launch_guestfs (); + # Don't disturb the partition table that we just wrote. + # https://secure.wikimedia.org/wikipedia/en/wiki/Master_Boot_Record + # https://secure.wikimedia.org/wikipedia/en/wiki/GUID_Partition_Table -sub launch_guestfs -{ - $g = Sys::Guestfs->new (); - $g->set_trace (1) if $debug; - $g->add_drive_ro ($infile); - $g->add_drive ($outfile); - $g->launch (); + my $bootsect = $g->pread_device ("/dev/sda", 446, 0); + die __"virt-resize: short read" if length ($bootsect) < 446; + + $g->pwrite_device ("/dev/sdb", $bootsect, 0); + + my $start = 512; + if ($parttype eq "gpt") { + # XXX With 4K sectors does GPT just fit more entries in a + # sector, or does it always use 34 sectors? + $start = 17408; + } + + my $loader = $g->pread_device ("/dev/sda", $max_bootloader, $start); + die __"virt-resize: short read" if length ($loader) < $max_bootloader; + + $g->pwrite_device ("/dev/sdb", $loader, $start); } -my $sectsize = $g->blockdev_getss ("/dev/sdb"); +my $to_be_expanded = 0; # Get the partitions on the source disk. my @partitions; @@ -615,7 +771,7 @@ sub examine_partition if ($type eq "LVM2_member") { $partitions{$part}->{can_expand_content} = 1; $partitions{$part}->{expand_content_method} = "pvresize"; - } elsif ($type =~ /^ext[234]/) { + } elsif ($type =~ /^ext[234]$/) { $partitions{$part}->{can_expand_content} = 1; $partitions{$part}->{expand_content_method} = "resize2fs"; } elsif ($type eq "ntfs" && feature_available ($g, "ntfsprogs")) { @@ -638,6 +794,50 @@ if ($debug) { } } +# Examine the LVs (for --lv-expand option). +my @lvs = $g->lvs (); +my %lvs; +examine_lv ($_) foreach @lvs; +mark_lvs_to_expand (); + +sub examine_lv +{ + local $_ = shift; + + $lvs{$_}->{name} = $_; + + my $type = "unknown"; + eval { + $type = $g->vfs_type ($_); + }; + $lvs{$_}->{type} = $type; + + if ($expand_content) { + if ($type =~ /^ext[234]$/) { + $lvs{$_}->{can_expand_content} = 1; + $lvs{$_}->{expand_content_method} = "resize2fs"; + } elsif ($type eq "ntfs" && feature_available ($g, "ntfsprogs")) { + $lvs{$_}->{can_expand_content} = 1; + $lvs{$_}->{expand_content_method} = "ntfsresize"; + } + } +} + +sub mark_lvs_to_expand { + local $_; + + foreach (@lv_expand) { + die __x("virt-resize: no logical volume called {n}\n", + n => $_) + unless exists $lvs{$_}; + + if ($lvs{$_}->{can_expand_content}) { + $lvs{$_}->{will_expand_content} = 1; + $to_be_expanded++; + } + } +} + sub find_partition { local $_ = shift; @@ -685,8 +885,6 @@ sub do_delete } # Handle --resize and --resize-force. -my $to_be_expanded = 0; - do_resize ($_, 0, "--resize") foreach @resize; do_resize ($_, 1, "--resize-force") foreach @resize_force; @@ -797,9 +995,9 @@ sub calculate_surplus # EFI partitioning + massive per-partition alignment. my $overhead = $sectsize * ( 2 * 64 + # GPT start and end - (64 * (@partitions + 1)) + # Maximum alignment - ($boot_sectors - 64) # Boot loader - ); + (64 * (@partitions + 1)) # Maximum alignment + ) + + ($max_bootloader - 64 * 512); # boot loader my $required = 0; foreach (@partitions) { @@ -871,6 +1069,19 @@ sub print_summary } } + foreach my $lv (@lv_expand) { + print __x("{n}: LV will be expanded to maximum size\n", + n => $lv); + } + + foreach my $lv (@lvs) { + if ($lvs{$lv}->{will_expand_content}) { + print __x("{n}: content will be expanded using the '{meth}' method\n", + n => $lv, + meth => $lvs{$lv}->{expand_content_method}); + } + } + if ($surplus > 0) { print __x("There is a surplus of {spl} bytes ({h}).\n", spl => $surplus, @@ -895,36 +1106,12 @@ exit 0 if $dryrun; # Repartition the target disk. my $nextpart = 1; -my $parttype; repartition (); sub repartition { local $_; - if ($copy_boot_loader) { - $parttype = $g->part_get_parttype ("/dev/sdb"); - } else { - $parttype = "efi"; - } - print "partition table type: $parttype\n" if $debug; - - # Delete any existing partitions on the destination disk, - # but leave the bootloader that we copied over intact. - if ($copy_boot_loader) { - # Delete in reverse as an easy way to deal with extended - # partitions. - foreach (sort { $b cmp $a } $g->list_partitions ()) { - if (m{^/dev/.db(\d+)$}) { - $g->part_del ("/dev/sdb", $1); - } - } - } else { - # Didn't copy over the initial boot loader, so we need - # to make a new partition table here. - $g->part_init ("/dev/sdb", $parttype); - } - # Work out where to start the first partition. die __"virt-resize: source disk does not have a first partition\n" unless exists ($partitions{"/dev/sda1"}); @@ -1021,16 +1208,11 @@ sub copy_data } if (!$quiet && !$debug) { - local $| = 1; - print __x("Copying {p} ...", p => $part); + print __x("Copying {p} ...\n", p => $part); } $g->copy_size ($part, $target, $newsize < $oldsize ? $newsize : $oldsize); - - if (!$quiet && !$debug) { - print " ", __"done", "\n"; - } } } } @@ -1046,6 +1228,8 @@ sub copy_data if ($to_be_expanded > 0) { restart_appliance (); expand_partitions (); + expand_lvs (); + expand_lvs_content (); } sub restart_appliance @@ -1057,7 +1241,9 @@ sub restart_appliance $g = Sys::Guestfs->new (); $g->set_trace (1) if $debug; - $g->add_drive ($outfile); + my @args = ($outfile); + push @args, format => $output_format if defined $output_format; + $g->add_drive_opts (@args); $g->launch (); # Target partitions have changed from /dev/sdb to /dev/sda, @@ -1085,7 +1271,7 @@ sub expand_partitions # Expand if requested. if ($partitions{$part}->{will_expand_content}) { if (!$quiet && !$debug) { - print __x("Expanding {p} using the '{meth}' method", + print __x("Expanding {p} using the '{meth}' method\n", p => $part, meth => $partitions{$part}->{expand_content_method}); } @@ -1126,6 +1312,38 @@ sub expand_target_partition } } +sub expand_lvs +{ + local $_; + + foreach (@lv_expand) { + $g->lvresize_free ($_, 100); + } +} + +sub expand_lvs_content +{ + local $_; + + foreach (@lvs) { + if ($lvs{$_}->{will_expand_content}) { + my $method = $lvs{$_}->{expand_content_method}; + if (!$quiet && !$debug) { + print __x("Expanding {p} using the '{meth}' method\n", + p => $_, meth => $method); + } + if ($method eq "resize2fs") { + $g->e2fsck_f ($_); + $g->resize2fs ($_); + } elsif ($method eq "ntfsresize") { + $g->ntfsresize ($_); + } else { + die "internal error: unknown method: $method"; + } + } + } +} + # Sync disk and exit. $g->umount_all (); $g->sync (); @@ -1170,19 +1388,6 @@ sub human_size } } -# Return the size in bytes of a HOST block device. -sub host_blockdevsize -{ - local $_; - my $dev = shift; - - open BD, "PATH=/usr/sbin:/sbin:\$PATH blockdev --getsize64 $dev |" - or die "blockdev: $!"; - $_ = ; - chomp $_; - $_; -} - # The reverse of device name translation, see # BLOCK DEVICE NAMING in guestfs(3). sub canonicalize @@ -1195,6 +1400,25 @@ sub canonicalize $_; } +# Not as sophisticated as the guestfish progress bar, because +# I intend to use an external library for this at some point (XXX). +sub progress_callback +{ + my $proc_nr = shift; + my $serial = shift; + my $position = shift; + my $total = shift; + + my $ratio = $position / $total; + if ($ratio < 0) { $ratio = 0 } + elsif ($ratio > 1) { $ratio = 1 } + + my $dots = int ($ratio * 76); + + print "[", "#"x$dots, "-"x(76-$dots), "]\r"; + print "\n" if $ratio == 1; +} + =head1 NOTES =head2 "Partition 1 does not end on cylinder boundary." @@ -1219,6 +1443,23 @@ Windows may initiate a lengthy "chkdsk" on first boot after a resize, if NTFS partitions have been expanded. This is just a safety check and (unless it find errors) is nothing to worry about. +=head2 GUEST BOOT STUCK AT "GRUB" + +If a Linux guest does not boot after resizing, and the boot is stuck +after printing C on the console, try reinstalling grub. This +sometimes happens on older (RHEL 5-era) guests, for reasons we don't +fully understand, although we think is to do with partition alignment. + + guestfish -i -a newdisk + > cat /boot/grub/device.map + # check the contents of this file are sensible or + # edit the file if necessary + > grub-install / /dev/vda + > exit + +For more flexible guest reconfiguration, including if you need to +specify other parameters to grub-install, use L. + =head1 ALTERNATIVE TOOLS There are several proprietary tools for resizing partitions. We @@ -1235,6 +1476,13 @@ hand-calculating sector offsets, which is something that virt-resize was designed to avoid. If you want to see the guestfish-equivalent commands that virt-resize runs, use the C<--debug> flag. +=head1 SHELL QUOTING + +Libvirt guest names can contain arbitrary characters, some of which +have meaning to the shell such as C<#> and space. You may need to +quote or escape these characters on the command line. See the shell +manual page L for details. + =head1 SEE ALSO L, @@ -1249,6 +1497,11 @@ L, L, L, L, +L, +L, +L, +L, +L, L, L.