resize: Add progress bar to virt-resize.
[libguestfs.git] / tools / virt-resize
index ec1b534..28f51af 100755 (executable)
@@ -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,22 @@ L<virt-list-filesystems(1)> and
 L<virt-df(1)>,
 we recommend you go and read those manual pages first.
 
-=head1 BASIC USAGE
+=head1 EXAMPLES
+
+Copy C<olddisk> to C<newdisk>, extending one of the guest's partitions
+to fill the extra 5GB of space.
+
+ truncate -r olddisk newdisk; truncate -s +5G newdisk
+ virt-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
+
+=head1 DETAILED USAGE
 
 =head2 EXPANDING A VIRTUAL MACHINE DISK
 
@@ -383,6 +400,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</dev/sda2> and a root device
+called C</dev/vg_guest/lv_root> would be:
+
+ virt-resize indisk outdisk \
+   --expand /dev/sda2 --LV-expand /dev/vg_guest/lv_root
+
+This would first expand the partition (and PV), and then expand the
+root device to fill the extra space in the PV.
+
+The contents of the LV are also resized if virt-resize knows how to do
+that.  You can stop virt-resize from trying to expand the content by
+using the option C<--no-expand-content>.
+
+Use L<virt-list-filesystems(1)> to list the filesystems in
+the guest.
+
+You can give this option multiple times, I<but> it doesn't
+make sense to do this unless the logical volumes you specify
+are all in different volume groups.
+
+=cut
+
 my $copy_boot_loader = 1;
 
 =item B<--no-copy-boot-loader>
@@ -459,6 +504,7 @@ 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,
@@ -539,11 +585,14 @@ sub launch_guestfs
     $g->set_trace (1) if $debug;
     $g->add_drive_ro ($infile);
     $g->add_drive ($outfile);
+    $g->set_progress_callback (\&progress_callback) unless $quiet;
     $g->launch ();
 }
 
 my $sectsize = $g->blockdev_getss ("/dev/sdb");
 
+my $to_be_expanded = 0;
+
 # Get the partitions on the source disk.
 my @partitions;
 my %partitions;
@@ -615,7 +664,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 +687,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 +778,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;
 
@@ -871,6 +962,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,
@@ -1021,16 +1125,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 +1145,8 @@ sub copy_data
 if ($to_be_expanded > 0) {
     restart_appliance ();
     expand_partitions ();
+    expand_lvs ();
+    expand_lvs_content ();
 }
 
 sub restart_appliance
@@ -1085,7 +1186,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 +1227,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 ();
@@ -1195,6 +1328,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 +1371,22 @@ 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.
 
+=head1 ALTERNATIVE TOOLS
+
+There are several proprietary tools for resizing partitions.  We
+won't mention any here.
+
+L<parted(8)> and its graphical shell gparted can do some types of
+resizing operations on disk images.  They can resize and move
+partitions, but I don't think they can do anything with the contents,
+and they certainly don't understand LVM.
+
+L<guestfish(1)> can do everything that virt-resize can do and a lot
+more, but at a much lower level.  You will probably end up
+hand-calculating sector offsets, which is something that virt-resize
+was designed to avoid.  If you want to see the guestfish-equivalent
+commands that virt-resize runs, use the C<--debug> flag.
+
 =head1 SEE ALSO
 
 L<virt-list-partitions(1)>,
@@ -1232,6 +1400,7 @@ L<lvresize(8)>,
 L<resize2fs(8)>,
 L<ntfsresize(8)>,
 L<virsh(1)>,
+L<parted(8)>,
 L<Sys::Guestfs(3)>,
 L<http://libguestfs.org/>.