virt-resize: Handle extended and logical partitions (RHBZ#642821).
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 25 Oct 2011 18:03:38 +0000 (19:03 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Wed, 26 Oct 2011 09:07:26 +0000 (10:07 +0100)
resize/resize.ml
resize/virt-resize.pod

index 275e7e0..9976ff4 100644 (file)
@@ -271,7 +271,13 @@ let parttype, parttype_string =
   | _ ->
     error "%s: unknown partition table type\nvirt-resize only supports MBR (DOS) and GPT partition tables." infile
 
-(* Build a data structure describing the source disk's partition layout. *)
+(* Build a data structure describing the source disk's partition layout.
+ *
+ * NOTE: For MBR, only primary/extended partitions are tracked here.
+ * Logical partitions are contained within an extended partition, and
+ * we don't track them (they are just copied within the extended
+ * partition).  For the same reason we cannot resize logical partitions.
+ *)
 type partition = {
   p_name : string;               (* Device name, like /dev/sda1. *)
   p_part : G.partition;          (* SOURCE partition data from libguestfs. *)
@@ -289,6 +295,7 @@ and partition_content =
   | ContentUnknown               (* undetermined *)
   | ContentPV of int64           (* physical volume (size of PV) *)
   | ContentFS of string * int64  (* mountable filesystem (FS type, FS size) *)
+  | ContentExtendedPartition     (* MBR extended partition *)
 and partition_operation =
   | OpCopy                       (* copy it as-is, no resizing *)
   | OpIgnore                     (* ignore it (create on target, but don't
@@ -309,10 +316,12 @@ 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
+  | ContentExtendedPartition -> "extended partition"
 and string_of_partition_content_no_size = function
   | ContentUnknown -> "unknown data"
   | ContentPV _ -> sprintf "LVM PV"
   | ContentFS (fs, _) -> sprintf "filesystem %s" fs
+  | ContentExtendedPartition -> "extended partition"
 
 let get_partition_content =
   let pvs_full = Array.to_list (g#pvs_full ()) in
@@ -341,12 +350,26 @@ let get_partition_content =
     with
       G.Error _ -> ContentUnknown
 
+let is_extended_partition = function
+  | Some (0x05|0x0f) -> true
+  | _ -> false
+
 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";
 
+  (* Filter out logical partitions.  See note above. *)
+  let parts =
+    match parttype with
+    | GPT -> parts
+    | MBR ->
+      List.filter (function
+      | { G.part_num = part_num } when part_num >= 5_l -> false
+      | _ -> true
+      ) parts in
+
   let partitions =
     List.map (
       fun ({ G.part_num = part_num } as part) ->
@@ -356,7 +379,9 @@ let partitions : partition list =
         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
+        let typ =
+          if is_extended_partition mbr_id then ContentExtendedPartition
+          else get_partition_content name in
 
         { p_name = name; p_part = part;
           p_bootable = bootable; p_mbr_id = mbr_id; p_type = typ;
@@ -408,7 +433,8 @@ type logvol = {
   lv_type : logvol_content;
   mutable lv_operation : logvol_operation
 }
-and logvol_content = partition_content (* except ContentPV cannot occur *)
+                     (* ContentPV, ContentExtendedPartition cannot occur here *)
+and logvol_content = partition_content
 and logvol_operation =
   | LVOpNone                     (* nothing *)
   | LVOpExpand                   (* expand it *)
@@ -423,7 +449,10 @@ let lvs =
   let lvs = List.map (
     fun name ->
       let typ = get_partition_content name in
-      assert (match typ with ContentPV _ -> false | _ -> true);
+      assert (
+        match typ with ContentPV _ | ContentExtendedPartition -> false
+        | _ -> true
+      );
 
       { lv_name = name; lv_type = typ; lv_operation = LVOpNone }
   ) lvs in
@@ -456,6 +485,7 @@ let can_expand_content =
     | ContentFS (("ntfs"), _) when !ntfs_available -> true
     | ContentFS (("btrfs"), _) when !btrfs_available -> true
     | ContentFS (_, _) -> false
+    | ContentExtendedPartition -> false
   else
     fun _ -> false
 
@@ -468,6 +498,7 @@ let expand_content_method =
     | ContentFS (("ntfs"), _) when !ntfs_available -> NTFSResize
     | ContentFS (("btrfs"), _) when !btrfs_available -> BtrfsFilesystemResize
     | ContentFS (_, _) -> assert false
+    | ContentExtendedPartition -> assert false
   else
     fun _ -> assert false
 
@@ -559,6 +590,9 @@ let mark_partition_for_resize ~option ?(force = false) p 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 _ -> ()
+      | ContentExtendedPartition ->
+          error "%s: This extended partition contains logical partitions 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 logical partitions within this partition.  (This error came from '%s' option on the command line.)"
+            name option
     );
 
     p.p_operation <- OpResize newsize
@@ -926,17 +960,7 @@ let partitions =
 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
-      );
+      g#part_add "/dev/sdb" "primary" p.p_target_start p.p_target_end
   ) partitions
 
 (* Copy over the data. *)
@@ -962,11 +986,39 @@ let () =
         if not quiet then
           printf "Copying %s ...\n%!" source;
 
-        g#copy_size source target copysize;
-
+        (match p.p_type with
+         | ContentUnknown | ContentPV _ | ContentFS _ ->
+           g#copy_device_to_device ~size:copysize source target
+
+         | ContentExtendedPartition ->
+           (* You can't just copy an extended partition by name, eg.
+            * source = "/dev/sda2", because the device name only covers
+            * the first 1K of the partition.  Instead, copy the
+            * source bytes from the parent disk (/dev/sda).
+            *)
+           let srcoffset = p.p_part.G.part_start in
+           g#copy_device_to_device ~srcoffset ~size:copysize "/dev/sda" target
+        )
       | _ -> ()
   ) partitions
 
+(* Set bootable and MBR IDs.  Do this *after* copying over the data,
+ * so that we can magically change the primary partition to an extended
+ * partition if necessary.
+ *)
+let () =
+  List.iter (
+    fun p ->
+      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
+
 (* Fix the bootloader if we aligned the first partition. *)
 let () =
   if align_first_partition_and_fix_bootloader then (
index 4ce3a4e..65bb257 100644 (file)
@@ -238,6 +238,27 @@ Similarly, to get non-sparse raw output use:
 (on older systems that don't have the L<fallocate(1)> command use
 C<dd if=/dev/zero of=outdisk bs=1M count=..>)
 
+=head2 LOGICAL PARTITIONS
+
+Logical partitions (a.k.a. C</dev/sda5+> on disks using DOS partition
+tables) cannot be resized.
+
+To understand what is going on, firstly one of the four partitions
+C</dev/sda1-4> will have MBR partition type C<05> or C<0f>.  This is
+called the B<extended partition>.  Use L<virt-filesystems(1)> to see
+the MBR partition type.
+
+Logical partitions live inside the extended partition.
+
+The extended partition can be expanded, but not shrunk (unless you
+force it, which is not advisable).  When the extended partition is
+copied across, all the logical partitions contained inside are copied
+over implicitly.  Virt-resize does not look inside the extended
+partition, so it copies the logical partitions blindly.
+
+You cannot specify a logical partition (C</dev/sda5+>) at all on the
+command line.  Doing so will give an error.
+
 =head1 OPTIONS
 
 =over 4