summaryrefslogtreecommitdiffstats
path: root/resize/resize.ml
diff options
context:
space:
mode:
Diffstat (limited to 'resize/resize.ml')
-rw-r--r--resize/resize.ml972
1 files changed, 972 insertions, 0 deletions
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