resize: Refactor the code for creating target partitions.
[libguestfs.git] / resize / resize.ml
index dc5540f..295d121 100644 (file)
@@ -30,7 +30,7 @@ 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,
+  lv_expands, machine_readable, ntfsresize_force, output_format,
   quiet, resizes, resizes_force, shrink =
   let display_version () =
     let g = new G.guestfs () in
@@ -57,6 +57,8 @@ let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
   let format = ref "" in
   let ignores = ref [] in
   let lv_expands = ref [] in
+  let machine_readable = ref false in
+  let ntfsresize_force = ref false in
   let output_format = ref "" in
   let quiet = ref false in
   let resizes = ref [] in
@@ -72,25 +74,27 @@ let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
     "--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";
+    "--delete",  Arg.String (add deletes),  "part Delete partition";
+    "--expand",  Arg.String set_expand,     "part 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";
+    "--ignore",  Arg.String (add ignores),  "part 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 -\"-";
+    "--machine-readable", Arg.Set machine_readable, " Make output machine readable";
     "-n",        Arg.Set dryrun,            " Don't perform changes";
     "--dryrun",  Arg.Set dryrun,            " -\"-";
     "--dry-run", Arg.Set dryrun,            " -\"-";
+    "--ntfsresize-force", Arg.Set ntfsresize_force, " Force ntfsresize";
     "--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";
+    "--shrink",  Arg.String set_shrink,     "part Shrink partition";
     "-V",        Arg.Unit display_version,  " Display version and exit";
     "--version", Arg.Unit display_version,  " -\"-";
   ] in
@@ -123,12 +127,34 @@ read the man page virt-resize(1).
   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 machine_readable = !machine_readable in
+  let ntfsresize_force = !ntfsresize_force 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
 
+  (* No arguments and machine-readable mode?  Print out some facts
+   * about what this binary supports.  We only need to print out new
+   * things added since this option, or things which depend on features
+   * of the appliance.
+   *)
+  if !disks = [] && machine_readable then (
+    printf "virt-resize\n";
+    printf "ntfsresize-force\n";
+    printf "32bitok\n";
+    printf "128-sector-alignment\n";
+    let g = new G.guestfs () in
+    g#add_drive_opts "/dev/null";
+    g#launch ();
+    if feature_available g [| "ntfsprogs"; "ntfs3g" |] then
+      printf "ntfs\n";
+    if feature_available g [| "btrfs" |] then
+      printf "btrfs\n";
+    exit 0
+  );
+
   (* Verify we got exactly 2 disks. *)
   let infile, outfile =
     match List.rev !disks with
@@ -138,11 +164,12 @@ read the man page virt-resize(1).
 
   infile, outfile, copy_boot_loader, debug, deletes, dryrun,
   expand, expand_content, extra_partition, format, ignores,
-  lv_expands, output_format,
+  lv_expands, machine_readable, ntfsresize_force, output_format,
   quiet, resizes, resizes_force, shrink
 
-(* Default to true, since NTFS support is usually available. *)
+(* Default to true, since NTFS and btrfs support are usually available. *)
 let ntfs_available = ref true
+let btrfs_available = ref true
 
 (* Add in and out disks to the handle and launch. *)
 let connect_both_disks () =
@@ -150,7 +177,7 @@ let connect_both_disks () =
   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;
+  if not quiet then Progress.set_up_progress_bar ~machine_readable g;
   g#launch ();
 
   (* Set the filter to /dev/sda, in case there are any rogue
@@ -160,6 +187,7 @@ let connect_both_disks () =
 
   (* Update features available in the daemon. *)
   ntfs_available := feature_available g [|"ntfsprogs"; "ntfs3g"|];
+  btrfs_available := feature_available g [|"btrfs"|];
 
   g
 
@@ -212,13 +240,16 @@ let () =
 (* 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_part : G.partition;          (* SOURCE 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. *)
+
+  (* What we're going to do: *)
+  mutable p_operation : partition_operation;
+  p_target_partnum : int;        (* TARGET partition number. *)
+  p_target_start : int64;        (* TARGET partition start (sector num). *)
+  p_target_end : int64;          (* TARGET partition end (sector num). *)
 }
 and partition_content =
   | ContentUnknown               (* undetermined *)
@@ -293,9 +324,10 @@ let partitions : partition list =
           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_name = name; p_part = part;
           p_bootable = bootable; p_mbr_id = mbr_id; p_type = typ;
-          p_operation = OpCopy; p_target_partnum = 0 }
+          p_operation = OpCopy; p_target_partnum = 0;
+          p_target_start = 0L; p_target_end = 0L }
     ) parts in
 
   if debug then (
@@ -309,11 +341,13 @@ let partitions : partition list =
    *)
   List.iter (
     function
-    | { p_name = name; p_size = size; p_type = ContentPV pv_size }
+    | { p_name = name; p_part = { G.part_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) }
+    | { p_name = name; p_part = { G.part_size = size };
+        p_type = ContentFS (_, fs_size) }
         when size < fs_size ->
         error "%s: partition size %Ld < filesystem size %Ld"
           name size fs_size
@@ -370,12 +404,14 @@ let 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
+type expand_content_method =
+  | PVResize | Resize2fs | NTFSResize | BtrfsFilesystemResize
 
 let string_of_expand_content_method = function
   | PVResize -> "pvresize"
   | Resize2fs -> "resize2fs"
   | NTFSResize -> "ntfsresize"
+  | BtrfsFilesystemResize -> "btrfs-filesystem-resize"
 
 let can_expand_content =
   if expand_content then
@@ -384,6 +420,7 @@ let can_expand_content =
     | ContentPV _ -> true
     | ContentFS (("ext2"|"ext3"|"ext4"), _) -> true
     | ContentFS (("ntfs"), _) when !ntfs_available -> true
+    | ContentFS (("btrfs"), _) when !btrfs_available -> true
     | ContentFS (_, _) -> false
   else
     fun _ -> false
@@ -395,6 +432,7 @@ let expand_content_method =
     | ContentPV _ -> PVResize
     | ContentFS (("ext2"|"ext3"|"ext4"), _) -> Resize2fs
     | ContentFS (("ntfs"), _) when !ntfs_available -> NTFSResize
+    | ContentFS (("btrfs"), _) when !btrfs_available -> BtrfsFilesystemResize
     | ContentFS (_, _) -> assert false
   else
     fun _ -> assert false
@@ -454,7 +492,7 @@ let () =
  *)
 let mark_partition_for_resize ~option ?(force = false) p newsize =
   let name = p.p_name in
-  let oldsize = p.p_size in
+  let oldsize = p.p_part.G.part_size in
 
   (match p.p_operation with
    | OpResize _ ->
@@ -508,7 +546,7 @@ let () =
     let p = find_partition ~option dev in
 
     (* Parse the size field. *)
-    let oldsize = p.p_size in
+    let oldsize = p.p_part.G.part_size in
     let newsize = parse_size oldsize sizefield in
 
     if newsize <= 0L then
@@ -532,7 +570,7 @@ let calculate_surplus () =
   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 *)
+    (128L *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *)
   ) +^
   (Int64.of_int (max_bootloader - 64 * 512)) in  (* Bootloader *)
 
@@ -540,7 +578,7 @@ let calculate_surplus () =
     fun total p ->
       let newsize =
         match p.p_operation with
-        | OpCopy | OpIgnore -> p.p_size
+        | OpCopy | OpIgnore -> p.p_part.G.part_size
         | OpDelete -> 0L
         | OpResize newsize -> newsize in
       total +^ newsize
@@ -568,7 +606,7 @@ let () =
 
          let option = "--expand" in
          let p = find_partition ~option dev in
-         let oldsize = p.p_size in
+         let oldsize = p.p_part.G.part_size in
          mark_partition_for_resize ~option p (oldsize +^ surplus)
     );
     (match shrink with
@@ -579,7 +617,7 @@ let () =
 
          let option = "--shrink" in
          let p = find_partition ~option dev in
-         let oldsize = p.p_size in
+         let oldsize = p.p_part.G.part_size in
          mark_partition_for_resize ~option p (oldsize +^ surplus)
     )
   )
@@ -622,7 +660,7 @@ let () =
     printf "Summary of changes:\n\n";
 
     List.iter (
-      fun ({ p_name = name; p_size = oldsize } as p) ->
+      fun ({ p_name = name; p_part = { G.part_size = oldsize }} as p) ->
         let text =
           match p.p_operation with
           | OpCopy ->
@@ -762,116 +800,116 @@ let () =
   )
 
 (* 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
+(* Calculate the location of the partitions on the target disk.  This
+ * also removes from the list any partitions that will be deleted, so
+ * the final list just contains partitions that need to be created
+ * on the target.
+ *)
+let partitions =
+  let sectsize = Int64.of_int sectsize in
 
-  let rec repartition = function
-    | [] -> ()
+  let rec loop partnum start = 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
+      (match p.p_operation with
+       | OpDelete -> loop partnum start ps      (* skip p *)
+
+       | OpIgnore | OpCopy ->           (* same size *)
+         (* Size in sectors. *)
+         let size = (p.p_part.G.part_size +^ sectsize -^ 1L) /^ sectsize in
+         (* Start of next partition + alignment. *)
+         let end_ = start +^ size in
+         let next = (end_ +^ 127L) &^ (~^ 127L) in
+
+         { p with p_target_start = start; p_target_end = end_ -^ 1L;
+           p_target_partnum = partnum } :: loop (partnum+1) next ps
+
+       | OpResize newsize ->            (* resized partition *)
+         (* New size in sectors. *)
+         let size = (newsize +^ sectsize -^ 1L) /^ sectsize in
+         (* Start of next partition + alignment. *)
+         let next = start +^ size in
+         let next = (next +^ 127L) &^ (~^ 127L) in
+
+         { p with p_target_start = start; p_target_end = next -^ 1L;
+           p_target_partnum = partnum } :: loop (partnum+1) next ps
+      )
+
+    | [] ->
+      (* Create the surplus partition if there is room for it. *)
+      if extra_partition && surplus >= min_extra_partition then (
+        [ {
+          (* Since this partition has no source, this data is
+           * meaningless and not used since the operation is
+           * OpIgnore.
+           *)
+          p_name = "";
+          p_part = { G.part_num = 0l; part_start = 0L; part_end = 0L;
+                     part_size = 0L };
+          p_bootable = false; p_mbr_id = None; p_type = ContentUnknown;
+
+          (* Target information is meaningful. *)
+          p_operation = OpIgnore;
+          p_target_partnum = partnum;
+          p_target_start = start; p_target_end = ~^ 64L
+        } ]
+      )
+      else
+        []
   in
 
-  repartition partitions;
+  (* 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 start = (List.hd partitions).p_part.G.part_start /^ sectsize in
+  loop 1 start partitions
 
-  (* Create the surplus partition. *)
-  if extra_partition && surplus >= min_extra_partition then (
-    let size = outsize /^ sectsize -^ 64L -^ !start in
-    ignore (add_partition size)
-  )
+(* Now partition the target disk. *)
+let () =
+  List.iter (
+    fun p ->
+      g#part_add "/dev/sdb" "primary" p.p_target_start p.p_target_end;
+
+      (* Set bootable and MBR IDs *)
+      if p.p_bootable then
+        g#part_set_bootable "/dev/sdb" p.p_target_partnum true;
+
+      (match p.p_mbr_id with
+      | None -> ()
+      | Some mbr_id ->
+        g#part_set_mbr_id "/dev/sdb" p.p_target_partnum mbr_id
+      );
+  ) partitions
 
 (* Copy over the data. *)
 let () =
-  let rec copy_data = function
-    | [] -> ()
+  List.iter (
+    fun p ->
+      match p.p_operation with
+      | OpCopy | OpResize _ ->
+        (* XXX Old code had 'when target_partnum > 0', but it appears
+         * to have served no purpose since the field could never be 0
+         * at this point.
+         *)
 
-    | ({ 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 oldsize = p.p_part.G.part_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
+        let source = p.p_name in
+        let target = sprintf "/dev/sdb%d" p.p_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
+      | _ -> ()
+  ) 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
@@ -901,7 +939,7 @@ let g =
     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;
+    if not quiet then Progress.set_up_progress_bar ~machine_readable g;
     g#launch ();
 
     g (* Return new handle. *)
@@ -916,7 +954,15 @@ let () =
       | Resize2fs ->
           g#e2fsck_f target;
           g#resize2fs target
-      | NTFSResize -> g#ntfsresize target
+      | NTFSResize -> g#ntfsresize_opts ~force:ntfsresize_force target
+      | BtrfsFilesystemResize ->
+          (* Complicated ...  Btrfs forces us to mount the filesystem
+           * in order to resize it.
+           *)
+          assert (Array.length (g#mounts ()) = 0);
+          g#mount_options "" target "/";
+          g#btrfs_filesystem_resize "/";
+          g#umount "/"
     in
 
     (* Expand partition content as required. *)