Rewrote MBR parsing so it supports extended partitions (again).
[virt-df.git] / lib / diskimage_mbr.ml
index bd02f65..6839380 100644 (file)
@@ -24,14 +24,27 @@ open Printf
 open Unix
 open ExtList
 
-open Diskimage_utils
+open Diskimage_impl
 
-let sector_size = 512
-let sector_size64 = 512L
+open Int63.Operators
+
+let id = "mbr"
+
+let sector_size = ~^512
 
 (* Maximum number of extended partitions possible. *)
 let max_extended_partitions = 100
 
+(* The private data attached to a partitions structure. *)
+type mbr_priv = {
+  mbr_part_start : int63;              (* start of partition in SECTORS *)
+  mbr_part_size : int63;               (* size of partition in SECTORS *)
+}
+let null_priv = { mbr_part_start = ~^0; mbr_part_size = ~^0 }
+
+let attach_private_data, get_private_data =
+  private_data_functions (fun {parts_cb = {parts_cb_uq = u}} -> u)
+
 (* Device representing a single partition.  It just acts as an offset
  * into the underlying device.
  *
@@ -40,14 +53,15 @@ let max_extended_partitions = 100
  * (2) 'partno' is the partition number, starting at 1
  *     (cf. /dev/hda1 is the first partition).
  * (3) 'dev' is the underlying block device.
+ * (4) natural blocksize to use is sector size.
  *)
 class partition_device partno start size dev =
   let devname = dev#name in
   let name = sprintf "%s%d" devname partno in
-  let start = start *^ sector_size64 in
-  let size = size *^ sector_size64 in
+  let start = start *^ sector_size in
+  let size = size *^ sector_size in
 object (self)
-  inherit offset_device name start size dev
+  inherit offset_device name start size sector_size dev
 end
 
 (** Probe the
@@ -56,10 +70,10 @@ end
 
     @raise Not_found if it is not an MBR.
  *)
-let rec probe_mbr dev =
+let rec probe dev =
   (* Read the first sector. *)
   let bits =
-    try dev#read_bitstring 0L sector_size
+    try dev#read_bitstring ~^0 sector_size
     with exn -> raise Not_found in
 
   (* Does this match a likely-looking MBR? *)
@@ -72,22 +86,40 @@ let rec probe_mbr dev =
       0x55 : 8; 0xAA : 8 } ->          (* MBR signature *)
 
       (* Parse the partition table entries. *)
-      let primaries =
-       List.mapi (parse_mbr_entry dev) [part0;part1;part2;part3] in
+      let primaries = List.map parse_mbr_entry [part0;part1;part2;part3] in
 
-(*
-      (* Read extended partition data. *)
+      (* Extended partitions are primary partitions with part_type 0x05,
+       * containing extended partition data.
+       *)
       let extendeds = List.map (
        function
-       | { part_type = 0x05 } as part ->
-           probe_extended_partition
-             max_extended_partitions fd part part.part_lba_start
+       | ({ part_type = 0x05 }, { mbr_part_start = start }) ->
+           probe_extended_partition max_extended_partitions dev start
        | part -> []
       ) primaries in
       let extendeds = List.concat extendeds in
-      primaries @ extendeds
-*)
-      { parts_name = "MBR"; parts = primaries }
+
+      let parts : (partition * mbr_priv) list = primaries @ extendeds in
+
+      (* Create partition devices for all partitions that aren't null. *)
+      let parts = List.mapi (
+       fun partno (part, priv) ->
+         let partno = partno+1 in
+         match part with
+         | { part_status = (Bootable|Nonbootable) } as part ->
+             let start = priv.mbr_part_start and size = priv.mbr_part_size in
+             let dev = new partition_device partno start size dev in
+             { part with part_dev = dev }, priv
+         | _ -> (part, priv)
+      ) parts in
+
+      (* Separate out the private data and attach it. *)
+      let privs = List.map snd parts in
+      let parts = List.map fst parts in
+
+      let r = { parts_cb = callbacks (); parts_dev = dev; parts = parts } in
+      attach_private_data r privs;
+      r
 
   | { _ } ->
       raise Not_found                  (* not an MBR *)
@@ -95,77 +127,88 @@ let rec probe_mbr dev =
 (* Parse a single partition table entry.  See the table here:
  * http://en.wikipedia.org/wiki/Master_boot_record
  *)
-and parse_mbr_entry dev i bits =
+and parse_mbr_entry bits =
   bitmatch bits with
   | { 0l : 32; 0l : 32; 0l : 32; 0l : 32 } ->
       { part_status = NullEntry; part_type = 0;
-       part_dev = null_device; part_content = `Unknown }
+       part_dev = null_device; part_content = `Unknown }, null_priv
 
   | { ((0|0x80) as bootable) : 8; first_chs : 24;
       part_type : 8; last_chs : 24;
       first_lba : 32 : unsigned, littleendian;
       part_size : 32 : unsigned, littleendian } ->
+
       let bootable = if bootable = 0 then Nonbootable else Bootable in
-      make_mbr_entry bootable dev (i+1) part_type first_lba part_size
+      let first_lba = Int63.of_int32 first_lba in
+      let part_size = Int63.of_int32 part_size in
+
+      if !debug then
+       eprintf "parse_mbr_entry: first_lba = %s part_size = %s\n%!"
+         (Int63.to_string first_lba) (Int63.to_string part_size);
+
+      let part = {
+       part_status = bootable; part_type = part_type;
+       part_dev = null_device;         (* This gets overwritten by probe. *)
+       part_content = `Unknown;
+      } in
+
+      (* Extra private data which we'll use to calculate free offsets. *)
+      let priv = {
+       mbr_part_start = first_lba;
+       mbr_part_size = part_size;
+      } in
+
+      part, priv
 
   | { _ } ->
       { part_status = Malformed; part_type = 0;
-       part_dev = null_device; part_content = `Unknown }
-
-and make_mbr_entry part_status dev partno part_type first_lba part_size =
-  let first_lba = uint64_of_int32 first_lba in
-  let part_size = uint64_of_int32 part_size in
-  if !debug then
-    eprintf "make_mbr_entry: first_lba = %Lx part_size = %Lx\n%!"
-      first_lba part_size;
-  { part_status = part_status;
-    part_type = part_type;
-    part_dev = new partition_device partno first_lba part_size dev;
-    part_content = `Unknown }
-
-(*
-This code worked previously, but now needs some love ...
-XXX
+       part_dev = null_device; part_content = `Unknown }, null_priv
 
 (* Probe an extended partition. *)
-and probe_extended_partition max fd epart sect =
+and probe_extended_partition max dev start =
   if max > 0 then (
-    (* Offset of the first EBR. *)
-    let ebr_offs = sect *^ sector_size in
-    (* EBR Signature? *)
-    LargeFile.lseek fd (ebr_offs +^ 510L) SEEK_SET;
-    let str = String.create 2 in
-    if read fd str 0 2 <> 2 || str.[0] != '\x55' || str.[1] != '\xAA' then
-      [] (* Not EBR *)
-    else (
-      (* Read the extended partition table entries (just 2 of them). *)
-      LargeFile.lseek fd (ebr_offs +^ 446L) SEEK_SET;
-      let str = String.create 32 in
-      if read fd str 0 32 <> 32 then
-       failwith (s_ "error reading extended partition")
-      else (
-       (* Extract partitions from the data. *)
-       let part1, part2 =
-         match List.map (get_partition str) [ 0; 16 ] with
-         | [p1;p2] -> p1,p2
-         | _ -> failwith (s_ "probe_extended_partition: internal error") in
-       (* First partition entry has offset to the start of this partition. *)
-       let part1 = { part1 with
-                       part_lba_start = sect +^ part1.part_lba_start } in
-       (* Second partition entry is zeroes if end of list, otherwise points
-        * to the next partition.
-        *)
-       if part2.part_status = NullEntry then
-         [part1]
-       else
-         part1 :: probe_extended_partition
-                    (max-1) fd epart (sect +^ part2.part_lba_start)
+    try
+      (* Get the partition table (like a boot sector). *)
+      let bits = dev#read_bitstring (start *^ sector_size) sector_size in
+
+      (bitmatch bits with
+       | { _ : 3568 : bitstring;      (* padding to byte offset 446 *)
+          part : 128 : bitstring;    (* this partition *)
+          next : 128 : bitstring;    (* pointer to next extended partition *)
+          _ : 128 : bitstring;       (* ignored - should be zero *)
+          _ : 128 : bitstring;
+          0x55 : 8; 0xAA : 8 } ->    (* MBR signature *)
+
+          let (part, ppriv) = parse_mbr_entry part in
+
+          (* The first partition's LBA is actually offset relative
+           * to the current sector.
+           *)
+          let ppriv =
+            { ppriv with mbr_part_start = ppriv.mbr_part_start + start } in
+
+          let (next, npriv) = parse_mbr_entry next in
+
+          if next.part_status = NullEntry then
+            [ part, ppriv ]            (* End of list. *)
+          else (
+            let start_of_next = start + npriv.mbr_part_start in
+            (part, ppriv) ::
+              probe_extended_partition (max-1) dev start_of_next
+          )
+
+       | { _ } ->
+          invalid_arg "mbr: invalid extended partition table"
       )
-    )
+    with exn ->
+      prerr_endline (Printexc.to_string exn);
+      []
+  ) else (
+    prerr_endline "mbr: too many extended partitions";
+    []
   )
-  else []
-*)
 
+(*
 (* Ugh, fake a UInt32 -> UInt64 conversion without sign extension, until
  * we get working UInt32/UInt64 modules in extlib.
  *)
@@ -173,3 +216,45 @@ and uint64_of_int32 u32 =
   let i64 = Int64.of_int32 u32 in
   if u32 >= 0l then i64
   else Int64.add i64 0x1_0000_0000_L
+*)
+
+and offset_is_free parts offset =
+  let privs = get_private_data parts in
+
+  (* The first partition is somehow privileged in that we assume
+   * everything before this is not free.  Usually this is the first
+   * 63 sectors containing the MBR itself and sectors which should
+   * be blank but in reality contain all sorts of stupid hacks like
+   * alternate partitioning schemes.
+   *)
+  match privs with
+  | [] -> false
+  | { mbr_part_start = start; mbr_part_size = size } :: rest ->
+      let start = start *^ sector_size in
+      let size = size *^ sector_size in
+      if offset < start +^ size then
+       false
+      else (
+       let rec loop = function
+         | [] -> true                  (* not in a partition, must be free *)
+         | { mbr_part_start = start; mbr_part_size = size } :: rest ->
+             let start = start *^ sector_size in
+             let size = size *^ sector_size in
+             if start <= offset && offset < start +^ size then
+               false
+             else
+               loop rest
+       in
+       loop rest
+      )
+
+and callbacks =
+  let i = ref 0 in
+  fun () -> {
+    parts_cb_uq = (incr i; !i);
+    parts_cb_name = id;
+    parts_cb_offset_is_free = offset_is_free;
+  }
+
+(* Register the plugin. *)
+let () = register_plugin ~partitioner:probe id