out-of-tree build: daemon
[libguestfs.git] / resize / resize.ml
index c532327..ef279cd 100644 (file)
@@ -28,8 +28,10 @@ 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,
+type align_first_t = [ `Never | `Always | `Auto ]
+
+let infile, outfile, align_first, alignment, copy_boot_loader, debug, deletes,
+  dryrun, expand, expand_content, extra_partition, format, ignores,
   lv_expands, machine_readable, ntfsresize_force, output_format,
   quiet, resizes, resizes_force, shrink =
   let display_version () =
@@ -42,6 +44,8 @@ let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
 
   let add xs s = xs := s :: !xs in
 
+  let align_first = ref "auto" in
+  let alignment = ref 128 in
   let copy_boot_loader = ref true in
   let debug = ref false in
   let deletes = ref [] in
@@ -71,6 +75,8 @@ let infile, outfile, copy_boot_loader, debug, deletes, dryrun,
   in
 
   let argspec = Arg.align [
+    "--align-first", Arg.Set_string align_first, "never|always|auto Align first partition (default: auto)";
+    "--alignment", Arg.Set_int alignment,   "sectors Set partition alignment (default: 128 sectors)";
     "--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,             " -\"-";
@@ -118,6 +124,7 @@ read the man page virt-resize(1).
   );
 
   (* Dereference the rest of the args. *)
+  let alignment = !alignment in
   let copy_boot_loader = !copy_boot_loader in
   let deletes = List.rev !deletes in
   let dryrun = !dryrun in
@@ -135,6 +142,18 @@ read the man page virt-resize(1).
   let resizes_force = List.rev !resizes_force in
   let shrink = match !shrink with "" -> None | str -> Some str in
 
+  if alignment < 1 then
+    error "alignment cannot be < 1";
+  let alignment = Int64.of_int alignment in
+
+  let align_first =
+    match !align_first with
+    | "never" -> `Never
+    | "always" -> `Always
+    | "auto" -> `Auto
+    | _ ->
+      error "unknown --align-first option: use never|always|auto" 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
@@ -145,6 +164,8 @@ read the man page virt-resize(1).
     printf "ntfsresize-force\n";
     printf "32bitok\n";
     printf "128-sector-alignment\n";
+    printf "alignment\n";
+    printf "align-first\n";
     let g = new G.guestfs () in
     g#add_drive_opts "/dev/null";
     g#launch ();
@@ -162,8 +183,8 @@ read the man page virt-resize(1).
     | _ ->
         error "usage is: %s [--options] indisk outdisk" prog in
 
-  infile, outfile, copy_boot_loader, debug, deletes, dryrun,
-  expand, expand_content, extra_partition, format, ignores,
+  infile, outfile, align_first, alignment, copy_boot_loader, debug, deletes,
+  dryrun, expand, expand_content, extra_partition, format, ignores,
   lv_expands, machine_readable, ntfsresize_force, output_format,
   quiet, resizes, resizes_force, shrink
 
@@ -240,12 +261,16 @@ let () =
 (* Build a data structure describing the source disk's partition layout. *)
 type partition = {
   p_name : string;               (* Device name, like /dev/sda1. *)
-  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 *)
@@ -322,7 +347,8 @@ let partitions : partition list =
 
         { 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 (
@@ -565,7 +591,7 @@ let calculate_surplus () =
   let nr_partitions = List.length partitions in
   let overhead = (Int64.of_int sectsize) *^ (
     2L *^ 64L +^                                 (* GPT start and end *)
-    (128L *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *)
+    (alignment *^ (Int64.of_int (nr_partitions + 1))) (* Maximum alignment *)
   ) +^
   (Int64.of_int (max_bootloader - 64 * 512)) in  (* Bootloader *)
 
@@ -794,117 +820,177 @@ let () =
     ignore (g#pwrite_device "/dev/sdb" loader start)
   )
 
+(* Are we going to align the first partition and fix the bootloader? *)
+let align_first_partition_and_fix_bootloader =
+  (* Bootloaders that we know how to fix. *)
+  let can_fix_boot_loader =
+    match partitions with
+    | { p_type = ContentFS ("ntfs", _); p_bootable = true;
+        p_operation = OpCopy | OpIgnore | OpResize _ } :: _ -> true
+    | _ -> false
+  in
+
+  match align_first, can_fix_boot_loader with
+  | `Never, _
+  | `Auto, false -> false
+  | `Always, _
+  | `Auto, true -> true
+
 (* 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.
-   *)
+
+(* 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 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
+  (* Return 'i' rounded up to the next multiple of 'a'. *)
+  let roundup64 i a = let a = a -^ 1L in (i +^ a) &^ (~^ a) 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_part.G.part_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 +^ 128L
-        );
-        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 128 sectors. *)
-    start := ((end_ +^ 1L) +^ 127L) &^ (~^ 127L);
+      (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 = roundup64 end_ alignment 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 = roundup64 next alignment in
+
+         { p with p_target_start = start; p_target_end = next -^ 1L;
+           p_target_partnum = partnum } :: loop (partnum+1) next ps
+      )
 
-    target_partnum
+    | [] ->
+      (* 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;
+  (* Choose the alignment of the first partition based on the
+   * '--align-first' option.  Old virt-resize used to always align this
+   * to 64 sectors, but this causes boot failures unless we are able to
+   * adjust the bootloader accordingly.
+   *)
+  let start =
+    if align_first_partition_and_fix_bootloader then
+      alignment
+    else
+      (* Preserve the existing start, but convert to sectors. *)
+      (List.hd partitions).p_part.G.part_start /^ sectsize in
 
-  (* Create the surplus partition. *)
-  if extra_partition && surplus >= min_extra_partition then (
-    let size = outsize /^ sectsize -^ 64L -^ !start in
-    ignore (add_partition size)
-  )
+  loop 1 start partitions
+
+(* 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_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
+      | _ -> ()
+  ) partitions
 
-    | _ :: ps ->
-        copy_data ps
-  in
+(* Fix the bootloader if we aligned the first partition. *)
+let () =
+  if align_first_partition_and_fix_bootloader then (
+    (* See can_fix_boot_loader above. *)
+    match partitions with
+    | { p_type = ContentFS ("ntfs", _); p_bootable = true;
+        p_target_partnum = partnum; p_target_start = start } :: _ ->
+      (* If the first partition is NTFS and bootable, set the "Number of
+       * Hidden Sectors" field in the NTFS Boot Record so that the
+       * filesystem is still bootable.
+       *)
+
+      (* Should always be /dev/sdb1? *)
+      let target = sprintf "/dev/sdb%d" partnum in
+
+      (* Sanity check: it contains the NTFS magic. *)
+      let magic = g#pread_device target 8 3L in
+      if magic <> "NTFS    " then
+        eprintf "warning: first partition is NTFS but does not contain NTFS boot loader magic\n%!"
+      else (
+        if not quiet then
+          printf "Fixing first NTFS partition boot record ...\n%!";
 
-  copy_data partitions
+        if debug then (
+          let old_hidden = int_of_le32 (g#pread_device target 4 0x1c_L) in
+          eprintf "old hidden sectors value: 0x%Lx\n%!" old_hidden
+        );
+
+        let new_hidden = le32_of_int start in
+        ignore (g#pwrite_device target new_hidden 0x1c_L)
+      )
+
+    | _ -> ()
+  )
 
 (* After copying the data over we must shut down and restart the
  * appliance in order to expand the content.  The reason for this may