summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am7
-rw-r--r--README2
-rw-r--r--configure.ac13
-rw-r--r--po/POTFILES.in1
-rw-r--r--resize/.depend6
-rw-r--r--resize/Makefile.am101
-rw-r--r--resize/progress.ml49
-rw-r--r--resize/resize.ml972
-rwxr-xr-xresize/run-resize-locally53
-rwxr-xr-xresize/test-virt-resize.sh (renamed from tools/test-virt-resize.sh)23
-rw-r--r--resize/utils.ml154
-rw-r--r--resize/virt-resize.pod556
-rw-r--r--tools/Makefile.am4
-rwxr-xr-xtools/virt-resize1530
15 files changed, 1930 insertions, 1543 deletions
diff --git a/.gitignore b/.gitignore
index 9a844fa6..67fd7a90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -281,6 +281,8 @@ regressions/test.out
rescue/stamp-virt-rescue.pod
rescue/virt-rescue
rescue/virt-rescue.1
+resize/virt-resize
+resize/virt-resize.1
ruby/bindtests.rb
ruby/doc/site/api
ruby/examples/guestfs-ruby.3
diff --git a/Makefile.am b/Makefile.am
index 96c4d354..3d629e9c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -64,6 +64,13 @@ endif
# Unconditional because nothing is built yet.
SUBDIRS += csharp
+# virt-resize 2.0 is written in OCaml.
+if HAVE_OCAML
+if HAVE_OCAML_PCRE
+SUBDIRS += resize
+endif
+endif
+
# Perl tools and guestmount.
if HAVE_TOOLS
SUBDIRS += tools
diff --git a/README b/README
index fbff0259..659feb93 100644
--- a/README
+++ b/README
@@ -59,6 +59,8 @@ Requirements
- (Optional) OCaml if you want to rebuild the generated files, and
also to build the OCaml bindings
+- (Optional) OCaml PCRE bindings (ocaml-pcre).
+
- (Optional) Perl if you want to build the perl bindings
- (Optional) Python if you want to build the python bindings
diff --git a/configure.ac b/configure.ac
index 268119a0..05db6e0c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -476,9 +476,14 @@ AS_IF([test "x$enable_ocaml" != "xno"],
OCAMLFIND=
AC_PROG_OCAML
AC_PROG_FINDLIB
+
+ AS_IF([test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno"],
+ [AC_CHECK_OCAML_PKG([pcre])])
])
AM_CONDITIONAL([HAVE_OCAML],
[test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno"])
+AM_CONDITIONAL([HAVE_OCAML_PCRE],
+ [test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno" && test "x$OCAML_PKG_pcre" != "xno"])
dnl Check for Perl (optional, for Perl bindings).
PERL=no
@@ -843,7 +848,8 @@ AC_CONFIG_FILES([Makefile
df/Makefile
rescue/Makefile
debian/changelog
- ocaml/META perl/Makefile.PL])
+ ocaml/META perl/Makefile.PL
+ resize/Makefile])
AC_OUTPUT
dnl Produce summary.
@@ -871,8 +877,11 @@ echo -n "Haskell bindings .................... "
if test "x$HAVE_HASKELL_TRUE" = "x"; then echo "yes"; else echo "no"; fi
echo -n "PHP bindings ........................ "
if test "x$HAVE_PHP_TRUE" = "x"; then echo "yes"; else echo "no"; fi
-echo -n "virt-* tools ........................ "
+echo "guestfish and C virt tools .......... yes"
+echo -n "Perl virt tools ..................... "
if test "x$HAVE_TOOLS_TRUE" = "x"; then echo "yes"; else echo "no"; fi
+echo -n "virt-resize ......................... "
+if test "x$HAVE_OCAML" = "x" && test "x$HAVE_OCAML_PCRE" = "x"; then echo "yes"; else echo "no"; fi
echo "FUSE filesystem ..................... $enable_fuse"
echo
echo "If any optional component is configured 'no' when you expected 'yes'"
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5f5919ad..e0b2b295 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -147,6 +147,5 @@ tools/virt-edit.pl
tools/virt-list-filesystems.pl
tools/virt-list-partitions.pl
tools/virt-make-fs.pl
-tools/virt-resize.pl
tools/virt-tar.pl
tools/virt-win-reg.pl
diff --git a/resize/.depend b/resize/.depend
new file mode 100644
index 00000000..96f38ccd
--- /dev/null
+++ b/resize/.depend
@@ -0,0 +1,6 @@
+progress.cmo: utils.cmo
+progress.cmx: utils.cmx
+resize.cmo: utils.cmo progress.cmo
+resize.cmx: utils.cmx progress.cmx
+utils.cmo:
+utils.cmx:
diff --git a/resize/Makefile.am b/resize/Makefile.am
new file mode 100644
index 00000000..8ef16fff
--- /dev/null
+++ b/resize/Makefile.am
@@ -0,0 +1,101 @@
+# libguestfs virt-resize 2.0 tools
+# Copyright (C) 2011 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.
+
+include $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+ $(SOURCES) \
+ run-resize-locally \
+ virt-resize.pod \
+ test-virt-resize.sh
+
+CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-resize test.img
+
+if HAVE_OCAML
+if HAVE_OCAML_PCRE
+
+# Alphabetical order.
+SOURCES = \
+ progress.ml \
+ resize.ml \
+ utils.ml
+
+# Note this list must be in dependency order.
+OBJECTS = \
+ utils.cmx \
+ progress.cmx \
+ resize.cmx
+
+bin_SCRIPTS = virt-resize
+
+OCAMLPACKAGES = -package guestfs,pcre
+OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
+OCAMLOPTFLAGS = $(OCAMLCFLAGS)
+
+virt-resize: $(OBJECTS)
+ $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -linkpkg $^ -o $@
+
+.mli.cmi:
+ $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmo:
+ $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmx:
+ $(OCAMLFIND) ocamlopt $(OCAMLCFLAGS) -c $< -o $@
+
+# Manual pages and HTML files for the website.
+
+man_MANS = virt-resize.1
+
+noinst_DATA = $(top_builddir)/html/virt-resize.1.html
+
+virt-%.1: virt-%.pod
+ $(top_srcdir)/podwrapper.sh \
+ --man $@ \
+ $<
+
+$(top_builddir)/html/virt-%.1.html: virt-%
+ $(top_srcdir)/podwrapper.sh \
+ --html $@ \
+ $<
+
+# Tests.
+
+random_val := $(shell awk 'BEGIN{srand(); print 1+int(255*rand())}' < /dev/null)
+
+TESTS_ENVIRONMENT = \
+ MALLOC_PERTURB_=$(random_val) \
+ LD_LIBRARY_PATH=$(top_builddir)/src/.libs \
+ LIBGUESTFS_PATH=$(top_builddir)/appliance
+
+TESTS = test-virt-resize.sh
+
+# Dependencies.
+depend: .depend
+
+.depend: $(wildcard *.mli) $(wildcard *.ml)
+ rm -f $@ $@-t
+ $(OCAMLFIND) ocamldep $(OCAMLPACKAGES) $^ | \
+ $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
+ sort > $@-t
+ mv $@-t $@
+
+include .depend
+
+.PHONY: depend docs
+
+endif
+endif
diff --git a/resize/progress.ml b/resize/progress.ml
new file mode 100644
index 00000000..a4eff0fe
--- /dev/null
+++ b/resize/progress.ml
@@ -0,0 +1,49 @@
+(* virt-resize
+ * Copyright (C) 2010-2011 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Utils
+
+module G = Guestfs
+
+let set_up_progress_bar (g : Guestfs.guestfs) =
+ let progress_callback g event evh buf array =
+ if event = G.EVENT_PROGRESS && Array.length array >= 4 then (
+ (*let proc_nr = array.(0)
+ and serial = array.(1)*)
+ let position = array.(2)
+ and total = array.(3) in
+
+ let ratio =
+ if total <> 0L then Int64.to_float position /. Int64.to_float total
+ else 0. in
+ let ratio =
+ if ratio < 0. then 0. else if ratio > 1. then 1. else ratio in
+
+ let dots = int_of_float (ratio *. 72.) in
+
+ print_string "[";
+ for i = 0 to dots-1 do print_char '#' done;
+ for i = dots to 71 do print_char '-' done;
+ print_string "]\r";
+ if ratio = 1. then print_string "\n";
+ flush stdout
+ )
+ in
+ ignore (g#set_event_callback progress_callback [G.EVENT_PROGRESS])
diff --git a/resize/resize.ml b/resize/resize.ml
new file mode 100644
index 00000000..9a4c86bb
--- /dev/null
+++ b/resize/resize.ml
@@ -0,0 +1,972 @@
+(* virt-resize
+ * Copyright (C) 2010-2011 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+module G = Guestfs
+
+open Utils
+
+(* Minimum surplus before we create an extra partition. *)
+let min_extra_partition = 10L *^ 1024L *^ 1024L
+
+(* Command line argument parsing. *)
+let prog = Filename.basename Sys.executable_name
+
+let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
+ expand, expand_content, extra_partition, format, ignores,
+ lv_expands, output_format,
+ quiet, resizes, resizes_force, shrink =
+ let display_version () =
+ let g = new G.guestfs () in
+ let version = g#version () in
+ printf "virt-resize %Ld.%Ld.%Ld%s"
+ version.G.major version.G.minor version.G.release version.G.extra;
+ exit 0
+ in
+
+ let add xs s = xs := s :: !xs in
+
+ let copy_boot_loader = ref true in
+ let debug = ref false in
+ let deletes = ref [] in
+ let dryrun = ref false in
+ let expand = ref "" in
+ let set_expand s =
+ if s = "" then error "%s: empty --expand option" prog
+ else if !expand <> "" then error "--expand option given twice"
+ else expand := s
+ in
+ let expand_content = ref true in
+ let extra_partition = ref true in
+ let format = ref "" in
+ let ignores = ref [] in
+ let lv_expands = ref [] in
+ let output_format = ref "" in
+ let quiet = ref false in
+ let resizes = ref [] in
+ let resizes_force = ref [] in
+ let shrink = ref "" in
+ let set_shrink s =
+ if s = "" then error "empty --shrink option"
+ else if !shrink <> "" then error "--shrink option given twice"
+ else shrink := s
+ in
+
+ let argspec = Arg.align [
+ "--no-copy-boot-loader", Arg.Clear copy_boot_loader, " Don't copy boot loader";
+ "-d", Arg.Set debug, " Enable debugging messages";
+ "--debug", Arg.Set debug, " -\"-";
+ "--delete", Arg.String (add deletes), "dev Delete partition";
+ "--expand", Arg.String set_expand, "dev Expand partition";
+ "--no-expand-content", Arg.Clear expand_content, " Don't expand content";
+ "--no-extra-partition", Arg.Clear extra_partition, " Don't create extra partition";
+ "--format", Arg.Set_string format, "format Format of input disk";
+ "--ignore", Arg.String (add ignores), "dev Ignore partition";
+ "--lv-expand", Arg.String (add lv_expands), "lv Expand logical volume";
+ "--LV-expand", Arg.String (add lv_expands), "lv -\"-";
+ "--lvexpand", Arg.String (add lv_expands), "lv -\"-";
+ "--LVexpand", Arg.String (add lv_expands), "lv -\"-";
+ "-n", Arg.Set dryrun, " Don't perform changes";
+ "--dryrun", Arg.Set dryrun, " -\"-";
+ "--dry-run", Arg.Set dryrun, " -\"-";
+ "--output-format", Arg.Set_string format, "format Format of output disk";
+ "-q", Arg.Set quiet, " Don't print the summary";
+ "--quiet", Arg.Set quiet, " -\"-";
+ "--resize", Arg.String (add resizes), "part=size Resize partition";
+ "--resize-force", Arg.String (add resizes_force), "part=size Forcefully resize partition";
+ "--shrink", Arg.String set_shrink, "dev Shrink partition";
+ "-V", Arg.Unit display_version, " Display version and exit";
+ "--version", Arg.Unit display_version, " -\"-";
+ ] in
+ let disks = ref [] in
+ let anon_fun s = disks := s :: !disks in
+ let usage_msg =
+ sprintf "\
+%s: resize a virtual machine disk
+
+A short summary of the options is given below. For detailed help please
+read the man page virt-resize(1).
+"
+ prog in
+ Arg.parse argspec anon_fun usage_msg;
+
+ let debug = !debug in
+ if debug then (
+ eprintf "command line:";
+ List.iter (eprintf " %s") (Array.to_list Sys.argv);
+ prerr_newline ()
+ );
+
+ (* Dereference the rest of the args. *)
+ let copy_boot_loader = !copy_boot_loader in
+ let deletes = List.rev !deletes in
+ let dryrun = !dryrun in
+ let expand = match !expand with "" -> None | str -> Some str in
+ let expand_content = !expand_content in
+ let extra_partition = !extra_partition in
+ let format = match !format with "" -> None | str -> Some str in
+ let ignores = List.rev !ignores in
+ let lv_expands = List.rev !lv_expands in
+ let output_format = match !output_format with "" -> None | str -> Some str in
+ let quiet = !quiet in
+ let resizes = List.rev !resizes in
+ let resizes_force = List.rev !resizes_force in
+ let shrink = match !shrink with "" -> None | str -> Some str in
+
+ (* Verify we got exactly 2 disks. *)
+ let infile, outfile =
+ match List.rev !disks with
+ | [infile; outfile] -> infile, outfile
+ | _ ->
+ error "usage is: %s [--options] indisk outdisk" prog in
+
+ infile, outfile, copy_boot_loader, debug, deletes, dryrun,
+ expand, expand_content, extra_partition, format, ignores,
+ lv_expands, output_format,
+ quiet, resizes, resizes_force, shrink
+
+(* Default to true, since NTFS support is usually available. *)
+let ntfs_available = ref true
+
+(* Add in and out disks to the handle and launch. *)
+let connect_both_disks () =
+ let g = new G.guestfs () in
+ if debug then g#set_trace true;
+ g#add_drive_opts ?format ~readonly:true infile;
+ g#add_drive_opts ?format:output_format ~readonly:false outfile;
+ if not quiet then Progress.set_up_progress_bar g;
+ g#launch ();
+
+ (* Set the filter to /dev/sda, in case there are any rogue
+ * PVs lying around on the target disk.
+ *)
+ g#lvm_set_filter [|"/dev/sda"|];
+
+ (* Update features available in the daemon. *)
+ ntfs_available := feature_available g [|"ntfsprogs"; "ntfs3g"|];
+
+ g
+
+let g =
+ if not quiet then
+ printf "Examining %s ...\n%!" infile;
+
+ let g = connect_both_disks () in
+
+ g
+
+(* 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.
+ *)
+let sectsize, insize, outsize =
+ let sectsize = g#blockdev_getss "/dev/sdb" in
+ let insize = g#blockdev_getsize64 "/dev/sda" in
+ let outsize = g#blockdev_getsize64 "/dev/sdb" in
+ if debug then (
+ eprintf "%s size %Ld bytes\n" infile insize;
+ eprintf "%s size %Ld bytes\n" outfile outsize
+ );
+ sectsize, insize, outsize
+
+let max_bootloader =
+ (* 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).
+ *
+ * However make this large enough to be sure that we have copied over
+ * the boot loader. We could also do this by looking for the sector
+ * offset of the first partition.
+ *
+ * It doesn't matter if we copy too much.
+ *)
+ 4096 * 512
+
+(* Check the disks are at least as big as the bootloader. *)
+let () =
+ if insize < Int64.of_int max_bootloader then
+ error "%s: file is too small to be a disk image (%Ld bytes)"
+ infile insize;
+ if outsize < Int64.of_int max_bootloader then
+ error "%s: file is too small to be a disk image (%Ld bytes)"
+ outfile outsize
+
+(* Build a data structure describing the source disk's partition layout. *)
+type partition = {
+ p_name : string; (* Device name, like /dev/sda1. *)
+ p_size : int64; (* Current size of this partition. *)
+ p_part : G.partition; (* Partition data from libguestfs. *)
+ p_bootable : bool; (* Is it bootable? *)
+ p_mbr_id : int option; (* MBR ID, if it has one. *)
+ p_type : partition_content; (* Content type and content size. *)
+ mutable p_operation : partition_operation; (* What we're going to do. *)
+ mutable p_target_partnum : int; (* Partition number on target. *)
+}
+and partition_content =
+ | ContentUnknown (* undetermined *)
+ | ContentPV of int64 (* physical volume (size of PV) *)
+ | ContentFS of string * int64 (* mountable filesystem (FS type, FS size) *)
+and partition_operation =
+ | OpCopy (* copy it as-is, no resizing *)
+ | OpIgnore (* ignore it (create on target, but don't
+ copy any content) *)
+ | OpDelete (* delete it *)
+ | OpResize of int64 (* resize it to the new size *)
+
+let rec debug_partition p =
+ eprintf "%s:\n" p.p_name;
+ eprintf "\tpartition data: %ld %Ld-%Ld (%Ld bytes)\n"
+ p.p_part.G.part_num p.p_part.G.part_start p.p_part.G.part_end
+ p.p_part.G.part_size;
+ eprintf "\tbootable: %b\n" p.p_bootable;
+ eprintf "\tpartition ID: %s\n"
+ (match p.p_mbr_id with None -> "(none)" | Some i -> sprintf "0x%x" i);
+ eprintf "\tcontent: %s\n" (string_of_partition_content p.p_type)
+and string_of_partition_content = function
+ | ContentUnknown -> "unknown data"
+ | ContentPV sz -> sprintf "LVM PV (%Ld bytes)" sz
+ | ContentFS (fs, sz) -> sprintf "filesystem %s (%Ld bytes)" fs sz
+and string_of_partition_content_no_size = function
+ | ContentUnknown -> "unknown data"
+ | ContentPV _ -> sprintf "LVM PV"
+ | ContentFS (fs, _) -> sprintf "filesystem %s" fs
+
+let get_partition_content =
+ let pvs_full = Array.to_list (g#pvs_full ()) in
+ fun dev ->
+ try
+ let fs = g#vfs_type dev in
+ if fs = "unknown" then
+ ContentUnknown
+ else if fs = "LVM2_member" then (
+ let rec loop = function
+ | [] ->
+ error "%s: physical volume not returned by pvs_full"
+ dev
+ | pv :: _ when canonicalize pv.G.pv_name = dev ->
+ ContentPV pv.G.pv_size
+ | _ :: pvs -> loop pvs
+ in
+ loop pvs_full
+ )
+ else (
+ g#mount_ro dev "/";
+ let stat = g#statvfs "/" in
+ let size = stat.G.bsize *^ stat.G.blocks in
+ ContentFS (fs, size)
+ )
+ with
+ G.Error _ -> ContentUnknown
+
+let partitions : partition list =
+ let parts = Array.to_list (g#part_list "/dev/sda") in
+
+ if List.length parts = 0 then
+ error "the source disk has no partitions";
+
+ let partitions =
+ List.map (
+ fun ({ G.part_num = part_num } as part) ->
+ let part_num = Int32.to_int part_num in
+ let name = sprintf "/dev/sda%d" part_num in
+ let bootable = g#part_get_bootable "/dev/sda" part_num in
+ let mbr_id =
+ try Some (g#part_get_mbr_id "/dev/sda" part_num)
+ with G.Error _ -> None in
+ let typ = get_partition_content name in
+
+ { p_name = name; p_size = part.G.part_size; p_part = part;
+ p_bootable = bootable; p_mbr_id = mbr_id; p_type = typ;
+ p_operation = OpCopy; p_target_partnum = 0 }
+ ) parts in
+
+ if debug then (
+ eprintf "%d partitions found\n" (List.length partitions);
+ List.iter debug_partition partitions
+ );
+
+ (* Check content isn't larger than partitions. If it is then
+ * something has gone wrong and we shouldn't continue. Old
+ * virt-resize didn't do these checks.
+ *)
+ List.iter (
+ function
+ | { p_name = name; p_size = size; p_type = ContentPV pv_size }
+ when size < pv_size ->
+ error "%s: partition size %Ld < physical volume size %Ld"
+ name size pv_size
+ | { p_name = name; p_size = size; p_type = ContentFS (_, fs_size) }
+ when size < fs_size ->
+ error "%s: partition size %Ld < filesystem size %Ld"
+ name size fs_size
+ | _ -> ()
+ ) partitions;
+
+ (* Check partitions don't overlap. *)
+ let rec loop end_of_prev = function
+ | [] -> ()
+ | { p_name = name; p_part = { G.part_start = part_start } } :: _
+ when end_of_prev > part_start ->
+ error "%s: this partition overlaps the previous one" name
+ | { p_part = { G.part_end = part_end } } :: parts -> loop part_end parts
+ in
+ loop 0L partitions;
+
+ partitions
+
+(* Build a data structure describing LVs on the source disk.
+ * This is only used if the user gave the --lv-expand option.
+ *)
+type logvol = {
+ lv_name : string;
+ lv_type : logvol_content;
+ mutable lv_operation : logvol_operation
+}
+and logvol_content = partition_content (* except ContentPV cannot occur *)
+and logvol_operation =
+ | LVOpNone (* nothing *)
+ | LVOpExpand (* expand it *)
+
+let debug_logvol lv =
+ eprintf "%s:\n" lv.lv_name;
+ eprintf "\tcontent: %s\n" (string_of_partition_content lv.lv_type)
+
+let lvs =
+ let lvs = Array.to_list (g#lvs ()) in
+
+ let lvs = List.map (
+ fun name ->
+ let typ = get_partition_content name in
+ assert (match typ with ContentPV _ -> false | _ -> true);
+
+ { lv_name = name; lv_type = typ; lv_operation = LVOpNone }
+ ) lvs in
+
+ if debug then (
+ eprintf "%d logical volumes found\n" (List.length lvs);
+ List.iter debug_logvol lvs
+ );
+
+ lvs
+
+(* These functions tell us if we know how to expand the content of
+ * a particular partition or LV, and what method to use.
+ *)
+type expand_content_method = PVResize | Resize2fs | NTFSResize
+
+let string_of_expand_content_method = function
+ | PVResize -> "pvresize"
+ | Resize2fs -> "resize2fs"
+ | NTFSResize -> "ntfsresize"
+
+let can_expand_content =
+ if expand_content then
+ function
+ | ContentUnknown -> false
+ | ContentPV _ -> true
+ | ContentFS (("ext2"|"ext3"|"ext4"), _) -> true
+ | ContentFS (("ntfs"), _) when !ntfs_available -> true
+ | ContentFS (_, _) -> false
+ else
+ fun _ -> false
+
+let expand_content_method =
+ if expand_content then
+ function
+ | ContentUnknown -> assert false
+ | ContentPV _ -> PVResize
+ | ContentFS (("ext2"|"ext3"|"ext4"), _) -> Resize2fs
+ | ContentFS (("ntfs"), _) when !ntfs_available -> NTFSResize
+ | ContentFS (_, _) -> assert false
+ else
+ fun _ -> assert false
+
+(* Helper function to locate a partition given what the user might
+ * type on the command line. It also gives errors for partitions
+ * that the user has asked to be ignored or deleted.
+ *)
+let find_partition =
+ let hash = Hashtbl.create 13 in
+ List.iter (fun ({ p_name = name } as p) -> Hashtbl.add hash name p)
+ partitions;
+ fun ~option name ->
+ let name =
+ if String.length name < 5 || String.sub name 0 5 <> "/dev/" then
+ "/dev/" ^ name
+ else
+ name in
+ let name = canonicalize name in
+
+ let partition =
+ try Hashtbl.find hash name
+ with Not_found ->
+ error "%s: partition not found in the source disk image (this error came from '%s' option on the command line). Try running this command: virt-filesystems --partitions --long -a %s"
+ name option infile in
+
+ if partition.p_operation = OpIgnore then
+ error "%s: partition already ignored, you cannot use it in '%s' option"
+ name option;
+
+ if partition.p_operation = OpDelete then
+ error "%s: partition already deleted, you cannot use it in '%s' option"
+ name option;
+
+ partition
+
+(* Handle --ignore option. *)
+let () =
+ List.iter (
+ fun dev ->
+ let p = find_partition ~option:"--ignore" dev in
+ p.p_operation <- OpIgnore
+ ) ignores
+
+(* Handle --delete option. *)
+let () =
+ List.iter (
+ fun dev ->
+ let p = find_partition ~option:"--delete" dev in
+ p.p_operation <- OpDelete
+ ) deletes
+
+(* Helper function to mark a partition for resizing. It prevents the
+ * user from trying to mark the same partition twice. If the force
+ * flag is given, then we will allow the user to shrink the partition
+ * even if we think that would destroy the content.
+ *)
+let mark_partition_for_resize ~option ?(force = false) p newsize =
+ let name = p.p_name in
+ let oldsize = p.p_size in
+
+ (match p.p_operation with
+ | OpResize _ ->
+ error "%s: this partition has already been marked for resizing"
+ name
+ | OpIgnore | OpDelete ->
+ (* This error should have been caught already by find_partition ... *)
+ error "%s: this partition has already been ignored or deleted"
+ name
+ | OpCopy -> ()
+ );
+
+ (* Only do something if the size will change. *)
+ if oldsize <> newsize then (
+ let bigger = newsize > oldsize in
+
+ if not bigger && not force then (
+ (* Check if this contains filesystem content, and how big that is
+ * and whether we will destroy any content by shrinking this.
+ *)
+ match p.p_type with
+ | ContentUnknown ->
+ error "%s: This partition has unknown content which might be damaged by shrinking it. If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
+ name option
+ | ContentPV size when size > newsize ->
+ error "%s: This partition has contains an LVM physical volume which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes). If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
+ name size newsize option
+ | ContentPV _ -> ()
+ | ContentFS (fstype, size) when size > newsize ->
+ error "%s: This partition has contains a %s filesystem which will be damaged by shrinking it below %Ld bytes (user asked to shrink it to %Ld bytes). If you want to shrink this partition, you need to use the '--resize-force' option, but that could destroy any data on this partition. (This error came from '%s' option on the command line.)"
+ name fstype size newsize option
+ | ContentFS _ -> ()
+ );
+
+ p.p_operation <- OpResize newsize
+ )
+
+(* Handle --resize and --resize-force options. *)
+let () =
+ let do_resize ~option ?(force = false) arg =
+ (* Argument is "dev=size". *)
+ let dev, sizefield =
+ try
+ let i = String.index arg '=' in
+ let n = String.length arg - (i+1) in
+ if n == 0 then raise Not_found;
+ String.sub arg 0 i, String.sub arg (i+1) n
+ with Not_found ->
+ error "%s: missing size field in '%s' option" arg option in
+
+ let p = find_partition ~option dev in
+
+ (* Parse the size field. *)
+ let oldsize = p.p_size in
+ let newsize = parse_size oldsize sizefield in
+
+ if newsize <= 0L then
+ error "%s: new partition size is zero or negative" dev;
+
+ mark_partition_for_resize ~option ~force p newsize
+ in
+
+ List.iter (do_resize ~option:"--resize") resizes;
+ List.iter (do_resize ~option:"--resize-force" ~force:true) resizes_force
+
+(* Helper function calculates the surplus space, given the total
+ * required so far for the current partition layout, compared to
+ * the size of the target disk. If the return value >= 0 then it's
+ * a surplus, if it is < 0 then it's a deficit.
+ *)
+let calculate_surplus () =
+ (* We need some overhead for partitioning. Worst case would be for
+ * EFI partitioning + massive per-partition alignment.
+ *)
+ let nr_partitions = List.length partitions in
+ let overhead = (Int64.of_int sectsize) *^ (
+ 2L *^ 64L +^ (* GPT start and end *)
+ (64L *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *)
+ ) +^
+ (Int64.of_int (max_bootloader - 64 * 512)) in (* Bootloader *)
+
+ let required = List.fold_left (
+ fun total p ->
+ let newsize =
+ match p.p_operation with
+ | OpCopy | OpIgnore -> p.p_size
+ | OpDelete -> 0L
+ | OpResize newsize -> newsize in
+ total +^ newsize
+ ) 0L partitions in
+
+ outsize -^ (required +^ overhead)
+
+(* Handle --expand and --shrink options. *)
+let () =
+ if expand <> None && shrink <> None then
+ error "you cannot use options --expand and --shrink together";
+
+ if expand <> None || shrink <> None then (
+ let surplus = calculate_surplus () in
+
+ if debug then
+ eprintf "surplus before --expand or --shrink: %Ld\n" surplus;
+
+ (match expand with
+ | None -> ()
+ | Some dev ->
+ if surplus < 0L then
+ error "You cannot use --expand when there is no surplus space to expand into. You need to make the target disk larger by at least %s."
+ (human_size (Int64.neg surplus));
+
+ let option = "--expand" in
+ let p = find_partition ~option dev in
+ let oldsize = p.p_size in
+ mark_partition_for_resize ~option p (oldsize +^ surplus)
+ );
+ (match shrink with
+ | None -> ()
+ | Some dev ->
+ if surplus > 0L then
+ error "You cannot use --shrink when there is no deficit (see 'deficit' in the virt-resize(1) man page).";
+
+ let option = "--shrink" in
+ let p = find_partition ~option dev in
+ let oldsize = p.p_size in
+ mark_partition_for_resize ~option p (oldsize +^ surplus)
+ )
+ )
+
+(* Calculate the final surplus.
+ * At this point, this number must be >= 0.
+ *)
+let surplus =
+ let surplus = calculate_surplus () in
+
+ if surplus < 0L then (
+ let deficit = Int64.neg surplus in
+ error "There is a deficit of %Ld bytes (%s). You need to make the target disk larger by at least this amount or adjust your resizing requests."
+ deficit (human_size deficit)
+ );
+
+ surplus
+
+(* Mark the --lv-expand LVs. *)
+let () =
+ let hash = Hashtbl.create 13 in
+ List.iter (fun ({ lv_name = name } as lv) -> Hashtbl.add hash name lv) lvs;
+
+ List.iter (
+ fun name ->
+ let lv =
+ try Hashtbl.find hash name
+ with Not_found ->
+ error "%s: logical volume not found in the source disk image (this error came from '--lv-expand' option on the command line). Try running this command: virt-filesystems --logical-volumes --long -a %s"
+ name infile in
+ lv.lv_operation <- LVOpExpand
+ ) lv_expands
+
+(* Print a summary of what we will do. *)
+let () =
+ flush stderr;
+
+ if not quiet then (
+ printf "**********\n\n";
+ printf "Summary of changes:\n\n";
+
+ List.iter (
+ fun ({ p_name = name; p_size = oldsize } as p) ->
+ let text =
+ match p.p_operation with
+ | OpCopy ->
+ sprintf "%s: This partition will be left alone." name
+ | OpIgnore ->
+ sprintf "%s: This partition will be created, but the contents will be ignored (ie. not copied to the target)." name
+ | OpDelete ->
+ sprintf "%s: This partition will be deleted." name
+ | OpResize newsize ->
+ sprintf "%s: This partition will be resized from %s to %s."
+ name (human_size oldsize) (human_size newsize) ^
+ if can_expand_content p.p_type then (
+ sprintf " The %s on %s will be expanded using the '%s' method."
+ (string_of_partition_content_no_size p.p_type)
+ name
+ (string_of_expand_content_method
+ (expand_content_method p.p_type))
+ ) else "" in
+
+ wrap ~hanging:4 (text ^ "\n\n")
+ ) partitions;
+
+ List.iter (
+ fun ({ lv_name = name } as lv) ->
+ match lv.lv_operation with
+ | LVOpNone -> ()
+ | LVOpExpand ->
+ let text =
+ sprintf "%s: This logical volume will be expanded to maximum size."
+ name ^
+ if can_expand_content lv.lv_type then (
+ sprintf " The %s on %s will be expanded using the '%s' method."
+ (string_of_partition_content_no_size lv.lv_type)
+ name
+ (string_of_expand_content_method
+ (expand_content_method lv.lv_type))
+ ) else "" in
+
+ wrap ~hanging:4 (text ^ "\n\n")
+ ) lvs;
+
+ if surplus > 0L then (
+ let text =
+ sprintf "There is a surplus of %s." (human_size surplus) ^
+ if extra_partition then (
+ if surplus >= min_extra_partition then
+ sprintf " An extra partition will be created for the surplus."
+ else
+ sprintf " The surplus space is not large enough for an extra partition to be created and so it will just be ignored."
+ ) else
+ sprintf " The surplus space will be ignored. Run a partitioning program in the guest to partition this extra space if you want." in
+
+ wrap (text ^ "\n\n")
+ );
+
+ printf "**********\n";
+ flush stdout
+ );
+
+ if dryrun then exit 0
+
+(* 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.
+ *)
+let g, parttype =
+ let parttype = g#part_get_parttype "/dev/sda" in
+ if debug then eprintf "partition table type: %s\n%!" parttype;
+
+ (* Try hard to initialize the partition table. This might involve
+ * relaunching another handle.
+ *)
+ if not quiet then
+ printf "Setting up initial partition table on %s ...\n%!" outfile;
+
+ let last_error = ref "" in
+ let rec initialize_partition_table g attempts =
+ let ok =
+ try g#part_init "/dev/sdb" parttype; true
+ with G.Error error -> last_error := error; false in
+ if ok then g, true
+ else if attempts > 0 then (
+ g#zero "/dev/sdb";
+ g#sync ();
+ g#close ();
+
+ let g = connect_both_disks () in
+ initialize_partition_table g (attempts-1)
+ )
+ else g, false
+ in
+
+ let g, ok = initialize_partition_table g 5 in
+ if not ok then
+ error "Failed to initialize the partition table on the target disk. You need to wipe or recreate the target disk and then run virt-resize again.\n\nThe underlying error was: %s" !last_error;
+
+ g, parttype
+
+(* Copy the bootloader across.
+ * 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
+ *)
+let () =
+ if copy_boot_loader then (
+ let bootsect = g#pread_device "/dev/sda" 446 0L in
+ if String.length bootsect < 446 then
+ error "pread-device: short read";
+ ignore (g#pwrite_device "/dev/sdb" bootsect 0L);
+
+ let start =
+ if parttype <> "gpt" then 512L
+ else
+ (* XXX With 4K sectors does GPT just fit more entries in a
+ * sector, or does it always use 34 sectors?
+ *)
+ 17408L in
+
+ let loader = g#pread_device "/dev/sda" max_bootloader start in
+ if String.length loader < max_bootloader then
+ error "pread-device: short read";
+ ignore (g#pwrite_device "/dev/sdb" loader start)
+ )
+
+(* Repartition the target disk. *)
+let () =
+ (* The first partition must start at the same position as the old
+ * first partition. Old virt-resize used to align this to 64
+ * sectors, but I suspect this is the cause of boot failures, so
+ * let's not do this.
+ *)
+ let sectsize = Int64.of_int sectsize in
+ let start = ref ((List.hd partitions).p_part.G.part_start /^ sectsize) in
+
+ (* This counts the partition numbers on the target disk. *)
+ let nextpart = ref 1 in
+
+ let rec repartition = function
+ | [] -> ()
+ | p :: ps ->
+ let target_partnum =
+ match p.p_operation with
+ | OpDelete -> None (* do nothing *)
+ | OpIgnore | OpCopy -> (* new partition, same size *)
+ (* Size in sectors. *)
+ let size = (p.p_size +^ sectsize -^ 1L) /^ sectsize in
+ Some (add_partition size)
+ | OpResize newsize -> (* new partition, resized *)
+ (* Size in sectors. *)
+ let size = (newsize +^ sectsize -^ 1L) /^ sectsize in
+ Some (add_partition size) in
+
+ (match target_partnum with
+ | None -> (* OpDelete *)
+ ()
+ | Some target_partnum -> (* not OpDelete *)
+ p.p_target_partnum <- target_partnum;
+
+ (* Set bootable and MBR IDs *)
+ if p.p_bootable then
+ g#part_set_bootable "/dev/sdb" target_partnum true;
+
+ (match p.p_mbr_id with
+ | None -> ()
+ | Some mbr_id ->
+ g#part_set_mbr_id "/dev/sdb" target_partnum mbr_id
+ );
+ );
+
+ repartition ps
+
+ (* Add a partition, returns the partition number on the target. *)
+ and add_partition size (* in SECTORS *) =
+ let target_partnum, end_ =
+ if !nextpart <= 3 || parttype <> "msdos" then (
+ let target_partnum = !nextpart in
+ let end_ = !start +^ size -^ 1L in
+ g#part_add "/dev/sdb" "primary" !start end_;
+ incr nextpart;
+ target_partnum, end_
+ ) else (
+ if !nextpart = 4 then (
+ g#part_add "/dev/sdb" "extended" !start (-1L);
+ incr nextpart;
+ start := !start +^ 64L
+ );
+ let target_partnum = !nextpart in
+ let end_ = !start +^ size -^ 1L in
+ g#part_add "/dev/sdb" "logical" !start end_;
+ incr nextpart;
+ target_partnum, end_
+ ) in
+
+ (* Start of next partition + alignment to 64 sectors. *)
+ start := ((end_ +^ 1L) +^ 63L) &^ (~^ 63L);
+
+ target_partnum
+ in
+
+ repartition partitions;
+
+ (* Create the surplus partition. *)
+ if extra_partition && surplus >= min_extra_partition then (
+ let size = outsize /^ sectsize -^ 64L -^ !start in
+ ignore (add_partition size)
+ )
+
+(* Copy over the data. *)
+let () =
+ let rec copy_data = function
+ | [] -> ()
+
+ | ({ p_name = source; p_target_partnum = target_partnum;
+ p_operation = (OpCopy | OpResize _) } as p) :: ps
+ when target_partnum > 0 ->
+ let oldsize = p.p_size in
+ let newsize =
+ match p.p_operation with OpResize s -> s | _ -> oldsize in
+
+ let copysize = if newsize < oldsize then newsize else oldsize in
+
+ let target = sprintf "/dev/sdb%d" target_partnum in
+
+ if not quiet then
+ printf "Copying %s ...\n%!" source;
+
+ g#copy_size source target copysize;
+
+ copy_data ps
+
+ | _ :: ps ->
+ copy_data ps
+ in
+
+ copy_data partitions
+
+(* After copying the data over we must shut down and restart the
+ * appliance in order to expand the content. The reason for this may
+ * not be obvious, but it's because otherwise we'll have duplicate VGs
+ * (the old VG(s) and the new VG(s)) which breaks LVM.
+ *
+ * The restart is only required if we're going to expand something.
+ *)
+let to_be_expanded =
+ List.exists (
+ function
+ | ({ p_operation = OpResize _ } as p) -> can_expand_content p.p_type
+ | _ -> false
+ ) partitions
+ || List.exists (
+ function
+ | ({ lv_operation = LVOpExpand } as lv) -> can_expand_content lv.lv_type
+ | _ -> false
+ ) lvs
+
+let g =
+ if to_be_expanded then (
+ g#umount_all ();
+ g#sync ();
+ g#close ();
+
+ let g = new G.guestfs () in
+ if debug then g#set_trace true;
+ g#add_drive_opts ?format:output_format ~readonly:false outfile;
+ if not quiet then Progress.set_up_progress_bar g;
+ g#launch ();
+
+ g (* Return new handle. *)
+ )
+ else g (* Return existing handle. *)
+
+let () =
+ if to_be_expanded then (
+ (* Helper function to expand partition or LV content. *)
+ let do_expand_content target = function
+ | PVResize -> g#pvresize target
+ | Resize2fs ->
+ g#e2fsck_f target;
+ g#resize2fs target
+ | NTFSResize -> g#ntfsresize target
+ in
+
+ (* Expand partition content as required. *)
+ List.iter (
+ function
+ | ({ p_operation = OpResize _ } as p) when can_expand_content p.p_type ->
+ let source = p.p_name in
+ let target = sprintf "/dev/sda%d" p.p_target_partnum in
+ let meth = expand_content_method p.p_type in
+
+ if not quiet then
+ printf "Expanding %s%s using the '%s' method ...\n%!"
+ source
+ (if source <> target then sprintf " (now %s)" target else "")
+ (string_of_expand_content_method meth);
+
+ do_expand_content target meth
+ | _ -> ()
+ ) partitions;
+
+ (* Expand logical volume content as required. *)
+ List.iter (
+ function
+ | ({ lv_operation = LVOpExpand } as lv) when can_expand_content lv.lv_type ->
+ let name = lv.lv_name in
+ let meth = expand_content_method lv.lv_type in
+
+ if not quiet then
+ printf "Expanding %s using the '%s' method ...\n%!"
+ name
+ (string_of_expand_content_method meth);
+
+ (* First expand the LV itself to maximum size. *)
+ g#lvresize_free name 100;
+
+ (* Then expand the content in the LV. *)
+ do_expand_content name meth
+ | _ -> ()
+ ) lvs
+ )
+
+(* Finished. Unmount disks and exit. *)
+let () =
+ g#umount_all ();
+ g#sync ();
+ g#close ();
+
+ if not quiet then (
+ print_newline ();
+ wrap "Resize operation completed with no errors. Before deleting the old disk, carefully check that the resized disk boots and works correctly.\n";
+ );
+
+ exit 0
diff --git a/resize/run-resize-locally b/resize/run-resize-locally
new file mode 100755
index 00000000..0d840dd8
--- /dev/null
+++ b/resize/run-resize-locally
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+# Copyright (C) 2009-2011 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.
+
+# This script sets up the environment so you can run virt-* tools in
+# place without needing to do 'make install' first. You can also run
+# the tools by creating a symlink to this script and putting it in
+# your path.
+#
+# Use it like this:
+# ./run-resize-locally [usual virt-resize args ...]
+
+use strict;
+use warnings;
+
+use File::Basename qw(dirname);
+use File::Spec;
+use Cwd qw(abs_path);
+
+my $path = $0;
+my $tool = "resize";
+
+# Follow symlinks until we get to the real file
+while(-l $path) {
+ my $link = readlink($path) or die "readlink: $path: $!";
+ if(File::Spec->file_name_is_absolute($link)) {
+ $path = $link;
+ } else {
+ $path = File::Spec->catfile(dirname($path), $link);
+ }
+}
+
+# Get the absolute path of the parent directory
+$path = abs_path(dirname($path).'/..');
+
+$ENV{LD_LIBRARY_PATH} = $path.'/src/.libs';
+$ENV{LIBGUESTFS_PATH} = $path.'/appliance';
+
+#print (join " ", ("$path/$tool/virt-$tool", @ARGV), "\n");
+exec("$path/$tool/virt-$tool", @ARGV);
diff --git a/tools/test-virt-resize.sh b/resize/test-virt-resize.sh
index 2c343cb4..2d5cce90 100755
--- a/tools/test-virt-resize.sh
+++ b/resize/test-virt-resize.sh
@@ -1,11 +1,20 @@
#!/bin/bash -
-
-# virt-resize does not work on 32 bit because of limitations in Perl
-# so short-circuit this test on a 32 bit host.
-perl -e 'exit 1 if ~1 == 4294967294' || {
- echo "$0: Skipping this test on 32 bit."
- exit 0
-}
+# libguestfs virt-resize 2.0 test script
+# Copyright (C) 2010-2011 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.
export LANG=C
set -e
diff --git a/resize/utils.ml b/resize/utils.ml
new file mode 100644
index 00000000..38519757
--- /dev/null
+++ b/resize/utils.ml
@@ -0,0 +1,154 @@
+(* virt-resize
+ * Copyright (C) 2010-2011 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+module G = Guestfs
+
+let ( +^ ) = Int64.add
+let ( -^ ) = Int64.sub
+let ( *^ ) = Int64.mul
+let ( /^ ) = Int64.div
+let ( &^ ) = Int64.logand
+let ( ~^ ) = Int64.lognot
+
+let output_spaces chan n = for i = 0 to n-1 do output_char chan ' ' done
+
+let wrap ?(chan = stdout) ?(hanging = 0) str =
+ let rec _wrap col str =
+ let n = String.length str in
+ let i = try String.index str ' ' with Not_found -> n in
+ let col =
+ if col+i >= 72 then (
+ output_char chan '\n';
+ output_spaces chan hanging;
+ i+hanging+1
+ ) else col+i+1 in
+ output_string chan (String.sub str 0 i);
+ if i < n then (
+ output_char chan ' ';
+ _wrap col (String.sub str (i+1) (n-(i+1)))
+ )
+ in
+ _wrap 0 str
+
+let error fs =
+ let display str =
+ wrap ~chan:stderr ("virt-resize: error: " ^ str);
+ prerr_newline ();
+ prerr_newline ();
+ wrap ~chan:stderr
+ "If reporting bugs, run virt-resize with the '-d' option and include the complete output.";
+ prerr_newline ();
+ exit 1
+ in
+ ksprintf display fs
+
+(* The reverse of device name translation, see
+ * BLOCK DEVICE NAMING in guestfs(3).
+ *)
+let canonicalize dev =
+ if String.length dev >= 8 &&
+ dev.[0] = '/' && dev.[1] = 'd' && dev.[2] = 'e' && dev.[3] = 'v' &&
+ dev.[4] = '/' && (dev.[5] = 'h' || dev.[5] = 'v') && dev.[6] = 'd' then (
+ let dev = String.copy dev in
+ dev.[5] <- 's';
+ dev
+ )
+ else
+ dev
+
+let feature_available (g : Guestfs.guestfs) names =
+ try g#available names; true
+ with G.Error _ -> false
+
+(* Parse the size field from --resize and --resize-force options. *)
+let parse_size =
+ let const_re = Pcre.regexp "^([.\\d]+)([bKMG])$"
+ and plus_const_re = Pcre.regexp "^\\+([.\\d]+)([bKMG])$"
+ and minus_const_re = Pcre.regexp "^-([.\\d]+)([bKMG])$"
+ and percent_re = Pcre.regexp "^([.\\d]+)%$"
+ and plus_percent_re = Pcre.regexp "^\\+([.\\d]+)%$"
+ and minus_percent_re = Pcre.regexp "^-([.\\d]+)%$"
+ in
+ fun oldsize field ->
+ let subs = ref None in
+ let matches rex =
+ try subs := Some (Pcre.exec ~rex field); true
+ with Not_found -> false
+ in
+ let sub i =
+ match !subs with None -> assert false
+ | Some subs -> Pcre.get_substring subs i
+ in
+ let size_scaled f = function
+ | "b" -> Int64.of_float f
+ | "K" -> Int64.of_float (f *. 1024.)
+ | "M" -> Int64.of_float (f *. 1024. *. 1024.)
+ | "G" -> Int64.of_float (f *. 1024. *. 1024. *. 1024.)
+ | _ -> assert false
+ in
+
+ if matches const_re then (
+ size_scaled (float_of_string (sub 1)) (sub 2)
+ )
+ else if matches plus_const_re then (
+ let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
+ oldsize +^ incr
+ )
+ else if matches minus_const_re then (
+ let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
+ oldsize -^ incr
+ )
+ else if matches percent_re then (
+ let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+ oldsize *^ percent /^ 1000L
+ )
+ else if matches plus_percent_re then (
+ let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+ oldsize +^ oldsize *^ percent /^ 1000L
+ )
+ else if matches minus_percent_re then (
+ let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+ oldsize -^ oldsize *^ percent /^ 1000L
+ )
+ else
+ error "virt-resize: %s: cannot parse size field" field
+
+let human_size i =
+ let sign, i = if i < 0L then "-", Int64.neg i else "", i in
+
+ if i < 1024L then
+ sprintf "%s%Ld" sign i
+ else (
+ let f = Int64.to_float i /. 1024. in
+ let i = i /^ 1024L in
+ if i < 1024L then
+ sprintf "%s%.1fK" sign f
+ else (
+ let f = Int64.to_float i /. 1024. in
+ let i = i /^ 1024L in
+ if i < 1024L then
+ sprintf "%s%.1fM" sign f
+ else (
+ let f = Int64.to_float i /. 1024. in
+ (*let i = i /^ 1024L in*)
+ sprintf "%s%.1fG" sign f
+ )
+ )
+ )
diff --git a/resize/virt-resize.pod b/resize/virt-resize.pod
new file mode 100644
index 00000000..1a32f25e
--- /dev/null
+++ b/resize/virt-resize.pod
@@ -0,0 +1,556 @@
+=encoding utf8
+
+=head1 NAME
+
+virt-resize - Resize a virtual machine disk
+
+=head1 SYNOPSIS
+
+ virt-resize [--resize /dev/sdaN=[+/-]<size>[%]]
+ [--expand /dev/sdaN] [--shrink /dev/sdaN]
+ [--ignore /dev/sdaN] [--delete /dev/sdaN] [...] 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
+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.
+
+If you are not familiar with the associated tools:
+L<virt-filesystems(1)> and L<virt-df(1)>, we recommend you go and read
+those manual pages first.
+
+=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-filesystems --long -h --all -a 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
+
+=over 4
+
+=item 1. Shut down the virtual machine
+
+=item 2. Locate input disk image
+
+Locate the input disk image (ie. the file or device on the host
+containing the guest's disk). 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 3. Look at current sizing
+
+Use L<virt-filesystems(1)> to display the current partitions and
+sizes:
+
+ # virt-filesystems --long --parts --blkdevs -h -a /dev/vg/lv_guest
+ Name Type Size Parent
+ /dev/sda1 partition 101M /dev/sda
+ /dev/sda2 partition 7.9G /dev/sda
+ /dev/sda device 8.0G -
+
+(This example is a virtual machine with an 8 GB disk which we would
+like to expand up to 10 GB).
+
+=item 4. Create output disk
+
+Virt-resize cannot do in-place disk modifications. You have to have
+space to store the resized output disk.
+
+To store the resized disk image in a file, create a file of a suitable
+size:
+
+ # rm -f outdisk
+ # truncate -s 10G outdisk
+
+Or 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
+
+=item 5. Resize
+
+virt-resize takes two mandatory parameters, the input disk (eg. device
+or file) and the output disk. The output disk is the one created in
+the previous step.
+
+ # 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. 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.
+
+More realistically you'd want to expand existing partitions in the
+disk image by passing extra options (for the full list see the
+L</OPTIONS> section below).
+
+L</--expand> is the most useful option. It expands the named
+partition within the disk to fill any extra space:
+
+ # virt-resize --expand /dev/sda2 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).
+
+L</--resize> is the other commonly used option. The following would
+increase the size of /dev/sda1 by 200M, and expand /dev/sda2
+to fill the rest of the available space:
+
+ # virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 \
+ indisk outdisk
+
+If the expanded partition in the image contains a filesystem or LVM
+PV, then if virt-resize knows how, it will resize the contents, the
+equivalent of calling a command such as L<pvresize(8)>,
+L<resize2fs(8)> or L<ntfsresize(8)>. However virt-resize does not
+know how to resize some filesystems, so you would have to online
+resize them after booting the guest.
+
+Other options are covered below.
+
+=item 6. 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. See also the L</NOTES> section below
+for additional information.
+
+=item 7. Resize LVs etc inside the guest
+
+(This can also be done offline using L<guestfish(1)>)
+
+Once the guest has booted you should see the new space available, at
+least for filesystems that virt-resize knows how to resize, and for
+PVs. The user may need to resize LVs inside PVs, and also resize
+filesystem types that virt-resize does not know how to expand.
+
+=back
+
+=head2 SHRINKING A VIRTUAL MACHINE DISK
+
+Shrinking is somewhat more complex than expanding, and only an
+overview is given here.
+
+Firstly virt-resize will not attempt to shrink any partition content
+(PVs, filesystems). The user has to shrink content before passing the
+disk image to virt-resize, and virt-resize will check that the content
+has been shrunk properly.
+
+(Shrinking can also be done offline using L<guestfish(1)>)
+
+After shrinking PVs and filesystems, shut down the guest, and proceed
+with steps 3 and 4 above to allocate a new disk image.
+
+Then run virt-resize with any of the C<--shrink> and/or C<--resize>
+options.
+
+=head2 IGNORING OR DELETING PARTITIONS
+
+virt-resize also gives a convenient way to ignore or delete partitions
+when copying from the input disk to the output disk. Ignoring a
+partition speeds up the copy where you don't care about the existing
+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<fallocate(1)> command use
+C<dd if=/dev/zero of=outdisk bs=1M count=..>)
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help.
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=item B<--resize part=size>
+
+Resize the named partition (expanding or shrinking it) so that it has
+the given size.
+
+C<size> can be expressed as an absolute number followed by
+b/K/M/G to mean bytes, Kilobytes, Megabytes, or Gigabytes;
+or as a percentage of the current size;
+or as a relative number or percentage.
+For example:
+
+ --resize /dev/sda2=10G
+
+ --resize /dev/sda4=90%
+
+ --resize /dev/sda2=+1G
+
+ --resize /dev/sda2=-200M
+
+ --resize /dev/sda1=+128K
+
+ --resize /dev/sda1=+10%
+
+ --resize /dev/sda1=-10%
+
+You can increase the size of any partition. Virt-resize will expand
+the direct content of the partition if it knows how (see C<--expand>
+below).
+
+You can only I<decrease> the size of partitions that contain
+filesystems or PVs which have already been shrunk. Virt-resize will
+check this has been done before proceeding, or else will print an
+error (see also C<--resize-force>).
+
+You can give this option multiple times.
+
+=item B<--resize-force part=size>
+
+This is the same as C<--resize> except that it will let you decrease
+the size of any partition. Generally this means you will lose any
+data which was at the end of the partition 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.
+
+=item B<--expand part>
+
+Expand the named partition so it uses up all extra space (space left
+over after any other resize changes that you request have been done).
+
+If virt-resize knows how, it will expand the direct content of the
+partition. For example, if the partition is an LVM PV, it will expand
+the PV to fit (like calling L<pvresize(8)>). Virt-resize leaves any
+other content it doesn't know about alone.
+
+Currently virt-resize can resize:
+
+=over 4
+
+=item *
+
+ext2, ext3 and ext4 filesystems when they are contained
+directly inside a partition.
+
+=item *
+
+NTFS filesystems contained directly in a partition, if libguestfs was
+compiled with support for NTFS.
+
+The filesystem must have been shut down consistently last time it was
+used. Additionally, L<ntfsresize(8)> marks the resized filesystem as
+requiring a consistency check, so at the first boot after resizing
+Windows will check the disk.
+
+=item *
+
+LVM PVs (physical volumes). virt-resize does not usually resize
+anything inside the PV, but see the C<--LV-expand> option. The user
+could also resize LVs as desired after boot.
+
+=back
+
+Note that you cannot use C<--expand> and C<--shrink> together.
+
+=item B<--shrink part>
+
+Shrink the named partition until the overall disk image fits in the
+destination. The named partition B<must> contain a filesystem or PV
+which has already been shrunk using another tool (eg. L<guestfish(1)>
+or other online tools). Virt-resize will check this and give an error
+if it has not been done.
+
+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 a partition to shrink and that partition had more than a
+gigabyte of free space.
+
+Note that you cannot use C<--expand> and C<--shrink> together.
+
+=item B<--ignore part>
+
+Ignore the named partition. Effectively this means the partition is
+allocated on the destination disk, but the content is not copied
+across from the source disk. The content of the partition will be
+blank (all zero bytes).
+
+You can give this option multiple times.
+
+=item B<--delete part>
+
+Delete the named partition. 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 when 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>, which can easily make your guest
+unbootable.
+
+You can give this option multiple times.
+
+=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-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.
+
+=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.
+
+=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.
+
+Note that if the surplus space is smaller than 10 MB, no extra
+partition will be created.
+
+=item B<--no-expand-content>
+
+By default, virt-resize will try to expand the direct contents
+of partitions, if it knows how (see C<--expand> option above).
+
+If you give the C<--no-expand-content> option then virt-resize
+will not attempt this.
+
+=item B<-d>
+
+=item B<--debug>
+
+Enable debugging messages.
+
+=item B<-n>
+
+=item B<--dryrun>
+
+Print a summary of what would be done, but don't do anything.
+
+=item B<-q>
+
+=item B<--quiet>
+
+Don't print the summary.
+
+=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<does not> affect the output format.
+See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
+
+=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 this option I<does not create> the output format. This
+option just tells libguestfs what it is so it doesn't try to guess it.
+You still need to create the output disk with the right format. See
+L</QCOW2 AND NON-SPARSE RAW FORMATS>.
+
+=back
+
+=head1 NOTES
+
+=head2 "Partition 1 does not end on cylinder boundary."
+
+Virt-resize aligns partitions to multiples of 64 sectors. Usually
+this means the partitions will not be aligned to the ancient CHS
+geometry. However CHS geometry is meaningless for disks manufactured
+since the early 1990s, and doubly so for virtual hard drives.
+Alignment of partitions to cylinders is not required by any modern
+operating system.
+
+=head2 RESIZING WINDOWS VIRTUAL MACHINES
+
+In Windows Vista and later versions, Microsoft switched to using a
+separate boot partition. In these VMs, typically C</dev/sda1> is the
+boot partition and C</dev/sda2> is the main (C:) drive. We have not
+had any luck resizing the boot partition. Doing so seems to break the
+guest completely. However expanding the second partition (ie. C:
+drive) should work.
+
+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<GRUB> 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
+ ><fs> cat /boot/grub/device.map
+ # check the contents of this file are sensible or
+ # edit the file if necessary
+ ><fs> grub-install / /dev/vda
+ ><fs> exit
+
+For more flexible guest reconfiguration, including if you need to
+specify other parameters to grub-install, use L<virt-rescue(1)>.
+
+=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 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<sh(1)> for details.
+
+=head1 SEE ALSO
+
+L<virt-filesystems(1)>,
+L<virt-df(1)>,
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<lvm(8)>,
+L<pvresize(8)>,
+L<lvresize(8)>,
+L<resize2fs(8)>,
+L<ntfsresize(8)>,
+L<virsh(1)>,
+L<parted(8)>,
+L<truncate(1)>,
+L<fallocate(1)>,
+L<grub(8)>,
+L<grub-install(8)>,
+L<virt-rescue(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010-2011 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.
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 18030496..806f2444 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,5 +1,5 @@
# libguestfs virt-* tools
-# Copyright (C) 2009-2010 Red Hat Inc.
+# Copyright (C) 2009-2011 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
@@ -22,7 +22,6 @@ tools = \
list-filesystems \
list-partitions \
make-fs \
- resize \
tar \
win-reg
@@ -68,7 +67,6 @@ TESTS_ENVIRONMENT = \
TESTS = test-virt-list-filesystems.sh \
test-virt-make-fs.sh \
- test-virt-resize.sh \
test-virt-tar.sh
endif
diff --git a/tools/virt-resize b/tools/virt-resize
deleted file mode 100755
index 5d0673a3..00000000
--- a/tools/virt-resize
+++ /dev/null
@@ -1,1530 +0,0 @@
-#!/usr/bin/perl -w
-# virt-resize
-# Copyright (C) 2010-2011 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 Sys::Guestfs::Lib qw(feature_available);
-use Fcntl qw(S_ISREG SEEK_SET);
-use POSIX qw(floor);
-use Pod::Usage;
-use Getopt::Long;
-use Data::Dumper;
-use Locale::TextDomain 'libguestfs';
-
-$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
-
-virt-resize - Resize a virtual machine disk
-
-=head1 SYNOPSIS
-
- virt-resize [--resize /dev/sdaN=[+/-]<size>[%]]
- [--expand /dev/sdaN] [--shrink /dev/sdaN]
- [--ignore /dev/sdaN] [--delete /dev/sdaN] [...] 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
-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.
-
-If you are not familiar with the associated tools:
-L<virt-filesystems(1)> and L<virt-df(1)>, we recommend you go and read
-those manual pages first.
-
-=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-filesystems --long -h --all -a 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
-
-=over 4
-
-=item 1. Shut down the virtual machine
-
-=item 2. Locate input disk image
-
-Locate the input disk image (ie. the file or device on the host
-containing the guest's disk). 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 3. Look at current sizing
-
-Use L<virt-filesystems(1)> to display the current partitions and
-sizes:
-
- # virt-filesystems --long --parts --blkdevs -h -a /dev/vg/lv_guest
- Name Type Size Parent
- /dev/sda1 partition 101M /dev/sda
- /dev/sda2 partition 7.9G /dev/sda
- /dev/sda device 8.0G -
-
-(This example is a virtual machine with an 8 GB disk which we would
-like to expand up to 10 GB).
-
-=item 4. Create output disk
-
-Virt-resize cannot do in-place disk modifications. You have to have
-space to store the resized output disk.
-
-To store the resized disk image in a file, create a file of a suitable
-size:
-
- # rm -f outdisk
- # truncate -s 10G outdisk
-
-Or 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
-
-=item 5. Resize
-
-virt-resize takes two mandatory parameters, the input disk (eg. device
-or file) and the output disk. The output disk is the one created in
-the previous step.
-
- # 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. 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.
-
-More realistically you'd want to expand existing partitions in the
-disk image by passing extra options (for the full list see the
-L</OPTIONS> section below).
-
-L</--expand> is the most useful option. It expands the named
-partition within the disk to fill any extra space:
-
- # virt-resize --expand /dev/sda2 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).
-
-L</--resize> is the other commonly used option. The following would
-increase the size of /dev/sda1 by 200M, and expand /dev/sda2
-to fill the rest of the available space:
-
- # virt-resize --resize /dev/sda1=+200M --expand /dev/sda2 \
- indisk outdisk
-
-If the expanded partition in the image contains a filesystem or LVM
-PV, then if virt-resize knows how, it will resize the contents, the
-equivalent of calling a command such as L<pvresize(8)>,
-L<resize2fs(8)> or L<ntfsresize(8)>. However virt-resize does not
-know how to resize some filesystems, so you would have to online
-resize them after booting the guest.
-
-Other options are covered below.
-
-=item 6. 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. See also the L</NOTES> section below
-for additional information.
-
-=item 7. Resize LVs etc inside the guest
-
-(This can also be done offline using L<guestfish(1)>)
-
-Once the guest has booted you should see the new space available, at
-least for filesystems that virt-resize knows how to resize, and for
-PVs. The user may need to resize LVs inside PVs, and also resize
-filesystem types that virt-resize does not know how to expand.
-
-=back
-
-=head2 SHRINKING A VIRTUAL MACHINE DISK
-
-Shrinking is somewhat more complex than expanding, and only an
-overview is given here.
-
-Firstly virt-resize will not attempt to shrink any partition content
-(PVs, filesystems). The user has to shrink content before passing the
-disk image to virt-resize, and virt-resize will check that the content
-has been shrunk properly.
-
-(Shrinking can also be done offline using L<guestfish(1)>)
-
-After shrinking PVs and filesystems, shut down the guest, and proceed
-with steps 3 and 4 above to allocate a new disk image.
-
-Then run virt-resize with any of the C<--shrink> and/or C<--resize>
-options.
-
-=head2 IGNORING OR DELETING PARTITIONS
-
-virt-resize also gives a convenient way to ignore or delete partitions
-when copying from the input disk to the output disk. Ignoring a
-partition speeds up the copy where you don't care about the existing
-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<fallocate(1)> command use
-C<dd if=/dev/zero of=outdisk bs=1M count=..>)
-
-=head1 OPTIONS
-
-=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 part=size>
-
-Resize the named partition (expanding or shrinking it) so that it has
-the given size.
-
-C<size> can be expressed as an absolute number followed by
-b/K/M/G/T/P/E to mean bytes, 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/sda4=90%
-
- --resize /dev/sda2=+1G
-
- --resize /dev/sda2=-200M
-
- --resize /dev/sda1=+128K
-
- --resize /dev/sda1=+10%
-
- --resize /dev/sda1=-10%
-
-You can increase the size of any partition. Virt-resize will expand
-the direct content of the partition if it knows how (see C<--expand>
-below).
-
-You can only I<decrease> the size of partitions that contain
-filesystems or PVs which have already been shrunk. Virt-resize will
-check this has been done before proceeding, or else will print an
-error (see also C<--resize-force>).
-
-You can give this option multiple times.
-
-=cut
-
-my @resize_force;
-
-=item B<--resize-force part=size>
-
-This is the same as C<--resize> except that it will let you decrease
-the size of any partition. Generally this means you will lose any
-data which was at the end of the partition 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 part>
-
-Expand the named partition so it uses up all extra space (space left
-over after any other resize changes that you request have been done).
-
-If virt-resize knows how, it will expand the direct content of the
-partition. For example, if the partition is an LVM PV, it will expand
-the PV to fit (like calling L<pvresize(8)>). Virt-resize leaves any
-other content it doesn't know about alone.
-
-Currently virt-resize can resize:
-
-=over 4
-
-=item *
-
-ext2, ext3 and ext4 filesystems when they are contained
-directly inside a partition.
-
-=item *
-
-NTFS filesystems contained directly in a partition, if libguestfs was
-compiled with support for NTFS.
-
-The filesystem must have been shut down consistently last time it was
-used. Additionally, L<ntfsresize(8)> marks the resized filesystem as
-requiring a consistency check, so at the first boot after resizing
-Windows will check the disk.
-
-=item *
-
-LVM PVs (physical volumes). virt-resize does not usually resize
-anything inside the PV, but see the C<--LV-expand> option. The user
-could also resize LVs as desired after boot.
-
-=back
-
-Note that you cannot use C<--expand> and C<--shrink> together.
-
-=cut
-
-my $shrink;
-
-=item B<--shrink part>
-
-Shrink the named partition until the overall disk image fits in the
-destination. The named partition B<must> contain a filesystem or PV
-which has already been shrunk using another tool (eg. L<guestfish(1)>
-or other online tools). Virt-resize will check this and give an error
-if it has not been done.
-
-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 a partition to shrink and that partition had more than a
-gigabyte of free space.
-
-Note that you cannot use C<--expand> and C<--shrink> together.
-
-=cut
-
-my @ignore;
-
-=item B<--ignore part>
-
-Ignore the named partition. Effectively this means the partition is
-allocated on the destination disk, but the content is not copied
-across from the source disk. The content of the partition will be
-blank (all zero bytes).
-
-You can give this option multiple times.
-
-=cut
-
-my @delete;
-
-=item B<--delete part>
-
-Delete the named partition. 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 when 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>, which can easily make your guest
-unbootable.
-
-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-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>
-
-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;
-my $min_extra_partition = 10 * 1024 * 1024; # see below
-
-=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.
-
-Note that if the surplus space is smaller than 10 MB, no extra
-partition will be created.
-
-=cut
-
-my $expand_content = 1;
-
-=item B<--no-expand-content>
-
-By default, virt-resize will try to expand the direct contents
-of partitions, if it knows how (see C<--expand> option above).
-
-If you give the C<--no-expand-content> option then virt-resize
-will not attempt this.
-
-=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.
-
-=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<does not> affect the output format.
-See L</QCOW2 AND NON-SPARSE RAW FORMATS>.
-
-=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</QCOW2 AND NON-SPARSE RAW FORMATS>.
-
-=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,
- "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) {
- 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;
-
-# 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_event_callback (\&progress_callback, $Sys::Guestfs::EVENT_PROGRESS)
- 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).
-#
-# However make this large enough to be sure that we have copied over
-# the boot loader. We could also do this by looking for the sector
-# offset of the first partition.
-#
-# It doesn't matter if we copy too much.
-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 < $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 < $max_bootloader;
-
-# 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;
-
- # 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
-
- 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 $to_be_expanded = 0;
-
-# Get the partitions on the source disk.
-my @partitions;
-my %partitions;
-check_source_disk ();
-
-sub check_source_disk
-{
- local $_;
-
- # Partitions and PVs.
- my @p = $g->part_list ("/dev/sda");
- foreach (@p) {
- my $name = "/dev/sda" . $_->{part_num};
- push @partitions, $name;
-
- my %h = %$_;
- $h{name} = $name;
- $h{bootable} = $g->part_get_bootable ("/dev/sda", $h{part_num});
- eval { $h{mbr_id} = $g->part_get_mbr_id ("/dev/sda", $h{part_num}); };
- $partitions{$name} = \%h;
- }
-}
-
-# Examine each partition.
-my @pvs_full = $g->pvs_full ();
-examine_partition ($_) foreach @partitions;
-
-sub examine_partition
-{
- local $_;
- my $part = shift;
-
- # What is it?
- my $type = "unknown";
- eval {
- $type = $g->vfs_type ($part);
- };
- $partitions{$part}->{type} = $type;
-
- # Can we get the actual size of this object (ie. to find out if it
- # is smaller than the container for shrinking)?
- my $fssize;
- if ($type eq "LVM2_member") { # LVM PV
- foreach (@pvs_full) {
- $fssize = $_->{pv_size}
- if canonicalize ($_->{pv_name}) eq $part;
- }
- } else { # Something mountable?
- eval {
- $g->mount_ro ($part, "/");
-
- my %stat = $g->statvfs ("/");
- $fssize = $stat{bsize} * $stat{blocks};
- };
-
- eval {
- $g->umount_all ();
- };
- }
-
- # This might be undef if we didn't successfully find the size. In
- # that case user won't be allowed to shrink this partition except
- # by forcing it.
- $partitions{$part}->{fssize} = $fssize;
-
- # Is it partition content that we know how to expand?
- $partitions{$part}->{can_expand_content} = 0;
- if ($expand_content) {
- if ($type eq "LVM2_member") {
- $partitions{$part}->{can_expand_content} = 1;
- $partitions{$part}->{expand_content_method} = "pvresize";
- } elsif ($type =~ /^ext[234]$/) {
- $partitions{$part}->{can_expand_content} = 1;
- $partitions{$part}->{expand_content_method} = "resize2fs";
- } elsif ($type eq "ntfs" && feature_available ($g, "ntfsprogs")) {
- $partitions{$part}->{can_expand_content} = 1;
- $partitions{$part}->{expand_content_method} = "ntfsresize";
- }
- }
-}
-
-if ($debug) {
- print "partitions found: ", join (", ", @partitions), "\n";
- foreach my $part (@partitions) {
- print "$part:\n";
- foreach (sort keys %{$partitions{$part}}) {
- print("\t", $_, " = ",
- defined ($partitions{$part}->{$_})
- ? $partitions{$part}->{$_} : "undef",
- "\n");
- }
- }
-}
-
-# 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;
- my $option = shift;
-
- $_ = "/dev/$_" unless $_ =~ m{^/dev};
- $_ = canonicalize ($_);
-
- unless (exists $partitions{$_}) {
- die __x("{p}: partition not found in the source disk image, when using the '{opt}' command line option\n",
- p => $_,
- opt => $option)
- }
-
- if ($partitions{$_}->{ignore}) {
- die __x("{p}: partition ignored, you cannot use it in another command line argument\n",
- p => $_)
- }
- if ($partitions{$_}->{delete}) {
- die __x("{p}: partition deleted, you cannot use it in another command line argument\n",
- p => $_)
- }
-
- return $_;
-}
-
-# Handle --ignore.
-do_ignore ($_) foreach @ignore;
-
-sub do_ignore
-{
- local $_ = shift;
- $_ = find_partition ($_, "--ignore");
- $partitions{$_}->{ignore} = 1;
-}
-
-# Handle --delete.
-do_delete ($_) foreach @delete;
-
-sub do_delete
-{
- local $_ = shift;
- $_ = find_partition ($_, "--delete");
- $partitions{$_}->{delete} = 1;
-}
-
-# Handle --resize and --resize-force.
-do_resize ($_, 0, "--resize") foreach @resize;
-do_resize ($_, 1, "--resize-force") foreach @resize_force;
-
-sub do_resize
-{
- local $_ = shift;
- my $force = shift;
- my $option = shift;
-
- # Argument is "part=size" ...
- my ($part, $sizefield) = split /=/, $_, 2;
- $part = find_partition ($part, $option);
-
- if (exists $partitions{$part}->{newsize}) {
- die __x("{p}: this partition has already been marked for resizing\n",
- p => $part);
- }
-
- # Parse the size field.
- my $oldsize = $partitions{$part}->{part_size};
- my $newsize;
- if (!defined ($sizefield) || $sizefield eq "") {
- die __x("{p}: missing size field in {o} option\n",
- p => $part, o => $option);
- } elsif ($sizefield =~ /^([.\d]+)([bKMGTPE])$/) {
- $newsize = sizebytes ($1, $2);
- } elsif ($sizefield =~ /^\+([.\d]+)([bKMGTPE])$/) {
- my $incr = sizebytes ($1, $2);
- $newsize = $oldsize + $incr;
- } elsif ($sizefield =~ /^-([.\d]+)([bKMGTPE])$/) {
- my $decr = sizebytes ($1, $2);
- $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("{p}: {f}: cannot parse size field\n",
- p => $part, f => $sizefield)
- }
-
- $newsize > 0 or
- die __x("{p}: new size is zero or negative\n", p => $part);
-
- mark_partition_for_resize ($part, $oldsize, $newsize, $force, $option);
-}
-
-sub mark_partition_for_resize
-{
- local $_;
- my $part = shift;
- my $oldsize = shift;
- my $newsize = shift;
- my $force = shift;
- my $option = shift;
-
- # Do nothing if the size is the same.
- return if $oldsize == $newsize;
-
- my $bigger = $newsize > $oldsize;
-
- # Check there is space to shrink this.
- unless ($bigger || $force) {
- if (! $partitions{$part}->{fssize} ||
- $partitions{$part}->{fssize} > $newsize) {
- die __x("{p}: cannot make this partition smaller because it contains a\nfilesystem, physical volume or other content that is larger than the new size.\nYou have to resize the content first, see virt-resize(1).\n",
- p => $part);
- }
- }
-
- $partitions{$part}->{newsize} = $newsize;
-
- if ($partitions{$part}->{can_expand_content} && $bigger) {
- $partitions{$part}->{will_expand_content} = 1;
- $to_be_expanded++;
- }
-}
-
-# Handle --expand and --shrink.
-my $surplus;
-if (defined $expand && defined $shrink) {
- die __"virt-resize: you cannot use options --expand and --shrink together\n"
-}
-if (defined $expand || defined $shrink) {
- calculate_surplus ();
-
- if ($debug) {
- print "surplus before --expand or --shrink: $surplus (",
- human_size ($surplus), ")\n";
- }
-
- do_expand () if $expand;
- do_shrink () if $shrink;
-}
-
-# (Re-)calculate surplus after doing expand or shrink.
-calculate_surplus ();
-
-# Add up the total space required on the target so far, compared
-# to the size of the target. We end up with a surplus or deficit.
-sub calculate_surplus
-{
- local $_;
-
- # We need some overhead for partitioning. Worst case would be for
- # EFI partitioning + massive per-partition alignment.
- my $overhead = $sectsize * (
- 2 * 64 + # GPT start and end
- (64 * (@partitions + 1)) # Maximum alignment
- ) +
- ($max_bootloader - 64 * 512); # boot loader
-
- my $required = 0;
- foreach (@partitions) {
- if ($partitions{$_}->{newsize}) {
- $required += $partitions{$_}->{newsize}
- } else {
- $required += $partitions{$_}->{part_size}
- }
- }
-
- # Compare that to the actual target disk.
- $surplus = $outsize - ($required + $overhead);
-}
-
-sub do_expand
-{
- local $_;
-
- unless ($surplus > 0) {
- die __x("virt-resize: error: cannot use --expand when there is no surplus space to\nexpand into. You need to make the target disk larger by at least {h}.\n",
- h => human_size (-$surplus));
- }
-
- my $part = find_partition ($expand, "--expand");
- my $oldsize = $partitions{$part}->{part_size};
- mark_partition_for_resize ($part, $oldsize, $oldsize + $surplus,
- 0, "--expand");
-}
-
-sub do_shrink
-{
- local $_;
-
- unless ($surplus < 0) {
- die __"virt-resize: error: cannot use --shrink because there is no deficit\n(see 'deficit' in the virt-resize(1) man page)\n"
- }
-
- my $part = find_partition ($shrink, "--shrink");
- my $oldsize = $partitions{$part}->{part_size};
- mark_partition_for_resize ($part, $oldsize, $oldsize + $surplus,
- 0, "--shrink");
-}
-
-# Print summary.
-print_summary () unless $quiet;
-
-sub print_summary
-{
- local $_;
- print __"Summary of changes:\n";
-
- foreach my $part (@partitions) {
- if ($partitions{$part}->{ignore}) {
- print __x("{p}: partition will be ignored\n", p => $part);
- } elsif ($partitions{$part}->{delete}) {
- print __x("{p}: partition will be deleted\n", p => $part);
- } elsif ($partitions{$part}->{newsize}) {
- print __x("{p}: partition will be resized from {oldsize} to {newsize}\n",
- p => $part,
- oldsize => human_size ($partitions{$part}->{part_size}),
- newsize => human_size ($partitions{$part}->{newsize}));
- if ($partitions{$part}->{will_expand_content}) {
- print __x("{p}: content will be expanded using the '{meth}' method\n",
- p => $part,
- meth => $partitions{$part}->{expand_content_method});
- }
- } else {
- print __x("{p}: partition will be left alone\n", p => $part);
- }
- }
-
- 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,
- h => human_size ($surplus));
- if ($extra_partition) {
- if ($surplus >= $min_extra_partition) {
- print __"An extra partition will be created for the surplus.\n";
- } else {
- print __"The surplus space is not large enough for an extra partition to be created\nand so it will just be ignored.\n";
- }
- } else {
- print __"The surplus space will be ignored. Run a partitioning program in the guest\nto partition this extra space if you want.\n";
- }
- } elsif ($surplus < 0) {
- die __x("virt-resize: error: there is a deficit of {def} bytes ({h}).\nYou need to make the target disk larger by at least this amount,\nor adjust your resizing requests.\n",
- def => -$surplus,
- h => human_size (-$surplus));
- }
-}
-
-exit 0 if $dryrun;
-
-# Repartition the target disk.
-my $nextpart = 1;
-repartition ();
-
-sub repartition
-{
- local $_;
-
- # 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"});
- my $start = $partitions{"/dev/sda1"}->{part_start} / $sectsize;
-
- # Align to 64.
- $start = ($start + 63) & ~63;
-
- print "starting to partition from $start\n" if $debug;
-
- # Create the new partitions.
- foreach my $part (@partitions) {
- unless ($partitions{$part}->{delete}) {
- # Size in sectors.
- my $size;
- if ($partitions{$part}->{newsize}) {
- $size = ($partitions{$part}->{newsize} + $sectsize - 1)
- / $sectsize;
- } else {
- $size = ($partitions{$part}->{part_size} + $sectsize - 1)
- / $sectsize;
- }
-
- # Create it.
- my ($target, $end, $part_num) = add_partition ($start, $size);
- $partitions{$part}->{target} = $target;
-
- if ($partitions{$part}->{bootable}) {
- $g->part_set_bootable ("/dev/sdb", $part_num, 1);
- }
-
- if ($partitions{$part}->{mbr_id}) {
- $g->part_set_mbr_id ("/dev/sdb", $part_num,
- $partitions{$part}->{mbr_id});
- }
-
- # Start of next partition + alignment.
- $start = $end + 1;
- $start = ($start + 63) & ~63;
- }
- }
-
- # Create surplus partition.
- if ($extra_partition && $surplus >= $min_extra_partition) {
- add_partition ($start, $outsize / $sectsize - 64 - $start);
- }
-}
-
-# Add a partition.
-sub add_partition
-{
- local $_;
- my $start = shift;
- my $size = shift;
-
- my ($target, $end, $part_num);
-
- if ($nextpart <= 3 || $parttype ne "msdos") {
- $target = "/dev/sdb$nextpart";
- $end = $start + $size - 1;
- $g->part_add ("/dev/sdb", "primary", $start, $end);
- $part_num = $nextpart++;
- } else {
- if ($nextpart == 4) {
- $g->part_add ("/dev/sdb", "extended", $start, -1);
- $part_num = $nextpart++;
- $start += 64;
- }
- $target = "/dev/sdb$nextpart";
- $end = $start + $size - 1;
- $g->part_add ("/dev/sdb", "logical", $start, $end);
- $part_num = $nextpart++;
- }
-
- return ($target, $end, $part_num);
-}
-
-# Copy over the data.
-copy_data ();
-
-sub copy_data
-{
- foreach my $part (@partitions)
- {
- unless ($partitions{$part}->{ignore}) {
- my $target = $partitions{$part}->{target};
- if ($target) {
- my $oldsize = $partitions{$part}->{part_size};
- my $newsize;
- if ($partitions{$part}->{newsize}) {
- $newsize = $partitions{$part}->{newsize};
- } else {
- $newsize = $partitions{$part}->{part_size};
- }
-
- if (!$quiet && !$debug) {
- print __x("Copying {p} ...\n", p => $part);
- }
-
- $g->copy_size ($part, $target,
- $newsize < $oldsize ? $newsize : $oldsize);
- }
- }
- }
-}
-
-# After copying the data over we must shut down and restart the
-# appliance in order to expand the content. The reason for this may
-# not be obvious, but it's because otherwise we'll have duplicate VGs
-# (the old VG(s) and the new VG(s)) which breaks LVM.
-#
-# The restart is only required if we're going to expand something.
-
-if ($to_be_expanded > 0) {
- restart_appliance ();
- expand_partitions ();
- expand_lvs ();
- expand_lvs_content ();
-}
-
-sub restart_appliance
-{
- # Sync disk and exit.
- $g->umount_all ();
- $g->sync ();
- undef $g;
-
- $g = Sys::Guestfs->new ();
- $g->set_trace (1) if $debug;
- 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,
- # so change them.
- foreach my $part (@partitions)
- {
- my $target = $partitions{$part}->{target};
- if ($target) {
- if ($target =~ m{/dev/(.)db(.*)}) {
- $partitions{$part}->{target} = "/dev/$1da$2";
- } else {
- die "internal error: unexpected partition target: $target";
- }
- }
- }
-}
-
-sub expand_partitions
-{
- foreach my $part (@partitions)
- {
- unless ($partitions{$part}->{ignore}) {
- my $target = $partitions{$part}->{target};
- if ($target) {
- # Expand if requested.
- if ($partitions{$part}->{will_expand_content}) {
- if (!$quiet && !$debug) {
- print __x("Expanding {p} using the '{meth}' method\n",
- p => $part,
- meth => $partitions{$part}->{expand_content_method});
- }
- expand_target_partition ($part)
- }
- }
- }
- }
-}
-
-sub expand_target_partition
-{
- local $_;
- my $part = shift;
-
- # Assertions.
- die unless $part;
- die unless $partitions{$part}->{can_expand_content};
- die unless $partitions{$part}->{will_expand_content};
- die unless $partitions{$part}->{expand_content_method};
- die unless $partitions{$part}->{target};
- die unless $expand_content;
-
- my $target = $partitions{$part}->{target};
- my $method = $partitions{$part}->{expand_content_method};
- if ($method eq "pvresize") {
- $g->pvresize ($target);
- }
- elsif ($method eq "resize2fs") {
- $g->e2fsck_f ($target);
- $g->resize2fs ($target);
- }
- elsif ($method eq "ntfsresize") {
- $g->ntfsresize ($target);
- }
- else {
- die "internal error: unknown method: $method";
- }
-}
-
-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 ();
-undef $g;
-
-exit 0;
-
-sub sizebytes
-{
- local $_ = shift;
- my $unit = shift;
-
- $_ *= 1024 if $unit =~ /[KMGTPE]/;
- $_ *= 1024 if $unit =~ /[MGTPE]/;
- $_ *= 1024 if $unit =~ /[GTPE]/;
- $_ *= 1024 if $unit =~ /[TPE]/;
- $_ *= 1024 if $unit =~ /[PE]/;
- $_ *= 1024 if $unit =~ /[E]/;
-
- return floor($_);
-}
-
-# Convert a number of bytes to a human-readable number.
-sub human_size
-{
- local $_ = shift;
-
- my $sgn = "";
- if ($_ < 0) {
- $sgn = "-";
- $_ = -$_;
- }
-
- $_ /= 1024;
-
- if ($_ < 1024) {
- sprintf "%s%dK", $sgn, $_;
- } elsif ($_ < 1024 * 1024) {
- sprintf "%s%.1fM", $sgn, ($_ / 1024);
- } else {
- sprintf "%s%.1fG", $sgn, ($_ / 1024 / 1024);
- }
-}
-
-# The reverse of device name translation, see
-# BLOCK DEVICE NAMING in guestfs(3).
-sub canonicalize
-{
- local $_ = shift;
-
- if (m{^/dev/[hv]d([a-z]\d*)$}) {
- return "/dev/sd$1";
- }
- $_;
-}
-
-# 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 $event = shift;
- my $event_handle = shift;
- my $buf = shift;
- my $array = shift;
-
- my $proc_nr = $array->[0];
- my $serial = $array->[1];
- my $position = $array->[2];
- my $total = $array->[3];
-
- 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."
-
-Virt-resize aligns partitions to multiples of 64 sectors. Usually
-this means the partitions will not be aligned to the ancient CHS
-geometry. However CHS geometry is meaningless for disks manufactured
-since the early 1990s, and doubly so for virtual hard drives.
-Alignment of partitions to cylinders is not required by any modern
-operating system.
-
-=head2 RESIZING WINDOWS VIRTUAL MACHINES
-
-In Windows Vista and later versions, Microsoft switched to using a
-separate boot partition. In these VMs, typically C</dev/sda1> is the
-boot partition and C</dev/sda2> is the main (C:) drive. We have not
-had any luck resizing the boot partition. Doing so seems to break the
-guest completely. However expanding the second partition (ie. C:
-drive) should work.
-
-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<GRUB> 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
- ><fs> cat /boot/grub/device.map
- # check the contents of this file are sensible or
- # edit the file if necessary
- ><fs> grub-install / /dev/vda
- ><fs> exit
-
-For more flexible guest reconfiguration, including if you need to
-specify other parameters to grub-install, use L<virt-rescue(1)>.
-
-=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 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<sh(1)> for details.
-
-=head1 SEE ALSO
-
-L<virt-filesystems(1)>,
-L<virt-df(1)>,
-L<guestfs(3)>,
-L<guestfish(1)>,
-L<lvm(8)>,
-L<pvresize(8)>,
-L<lvresize(8)>,
-L<resize2fs(8)>,
-L<ntfsresize(8)>,
-L<virsh(1)>,
-L<parted(8)>,
-L<truncate(1)>,
-L<fallocate(1)>,
-L<grub(8)>,
-L<grub-install(8)>,
-L<virt-rescue(1)>,
-L<Sys::Guestfs(3)>,
-L<http://libguestfs.org/>.
-
-=head1 AUTHOR
-
-Richard W.M. Jones L<http://people.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.