Rewrote MBR parsing so it supports extended partitions (again).
[virt-df.git] / lib / diskimage_mbr.ml
1 (* 'df' command for virtual domains.
2
3    (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
4    http://libvirt.org/
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20    Support for Master Boot Record partition scheme.
21 *)
22
23 open Printf
24 open Unix
25 open ExtList
26
27 open Diskimage_impl
28
29 open Int63.Operators
30
31 let id = "mbr"
32
33 let sector_size = ~^512
34
35 (* Maximum number of extended partitions possible. *)
36 let max_extended_partitions = 100
37
38 (* The private data attached to a partitions structure. *)
39 type mbr_priv = {
40   mbr_part_start : int63;               (* start of partition in SECTORS *)
41   mbr_part_size : int63;                (* size of partition in SECTORS *)
42 }
43 let null_priv = { mbr_part_start = ~^0; mbr_part_size = ~^0 }
44
45 let attach_private_data, get_private_data =
46   private_data_functions (fun {parts_cb = {parts_cb_uq = u}} -> u)
47
48 (* Device representing a single partition.  It just acts as an offset
49  * into the underlying device.
50  *
51  * Notes:
52  * (1) 'start'/'size' are measured in sectors.
53  * (2) 'partno' is the partition number, starting at 1
54  *     (cf. /dev/hda1 is the first partition).
55  * (3) 'dev' is the underlying block device.
56  * (4) natural blocksize to use is sector size.
57  *)
58 class partition_device partno start size dev =
59   let devname = dev#name in
60   let name = sprintf "%s%d" devname partno in
61   let start = start *^ sector_size in
62   let size = size *^ sector_size in
63 object (self)
64   inherit offset_device name start size sector_size dev
65 end
66
67 (** Probe the
68     {{:http://en.wikipedia.org/wiki/Master_boot_record}master boot record}
69     (if it is one) and read the partitions.
70
71     @raise Not_found if it is not an MBR.
72  *)
73 let rec probe dev =
74   (* Read the first sector. *)
75   let bits =
76     try dev#read_bitstring ~^0 sector_size
77     with exn -> raise Not_found in
78
79   (* Does this match a likely-looking MBR? *)
80   bitmatch bits with
81   | { _ : 3568 : bitstring;             (* padding to byte offset 446 *)
82       part0 : 128 : bitstring;          (* partitions *)
83       part1 : 128 : bitstring;
84       part2 : 128 : bitstring;
85       part3 : 128 : bitstring;
86       0x55 : 8; 0xAA : 8 } ->           (* MBR signature *)
87
88       (* Parse the partition table entries. *)
89       let primaries = List.map parse_mbr_entry [part0;part1;part2;part3] in
90
91       (* Extended partitions are primary partitions with part_type 0x05,
92        * containing extended partition data.
93        *)
94       let extendeds = List.map (
95         function
96         | ({ part_type = 0x05 }, { mbr_part_start = start }) ->
97             probe_extended_partition max_extended_partitions dev start
98         | part -> []
99       ) primaries in
100       let extendeds = List.concat extendeds in
101
102       let parts : (partition * mbr_priv) list = primaries @ extendeds in
103
104       (* Create partition devices for all partitions that aren't null. *)
105       let parts = List.mapi (
106         fun partno (part, priv) ->
107           let partno = partno+1 in
108           match part with
109           | { part_status = (Bootable|Nonbootable) } as part ->
110               let start = priv.mbr_part_start and size = priv.mbr_part_size in
111               let dev = new partition_device partno start size dev in
112               { part with part_dev = dev }, priv
113           | _ -> (part, priv)
114       ) parts in
115
116       (* Separate out the private data and attach it. *)
117       let privs = List.map snd parts in
118       let parts = List.map fst parts in
119
120       let r = { parts_cb = callbacks (); parts_dev = dev; parts = parts } in
121       attach_private_data r privs;
122       r
123
124   | { _ } ->
125       raise Not_found                   (* not an MBR *)
126
127 (* Parse a single partition table entry.  See the table here:
128  * http://en.wikipedia.org/wiki/Master_boot_record
129  *)
130 and parse_mbr_entry bits =
131   bitmatch bits with
132   | { 0l : 32; 0l : 32; 0l : 32; 0l : 32 } ->
133       { part_status = NullEntry; part_type = 0;
134         part_dev = null_device; part_content = `Unknown }, null_priv
135
136   | { ((0|0x80) as bootable) : 8; first_chs : 24;
137       part_type : 8; last_chs : 24;
138       first_lba : 32 : unsigned, littleendian;
139       part_size : 32 : unsigned, littleendian } ->
140
141       let bootable = if bootable = 0 then Nonbootable else Bootable in
142       let first_lba = Int63.of_int32 first_lba in
143       let part_size = Int63.of_int32 part_size in
144
145       if !debug then
146         eprintf "parse_mbr_entry: first_lba = %s part_size = %s\n%!"
147           (Int63.to_string first_lba) (Int63.to_string part_size);
148
149       let part = {
150         part_status = bootable; part_type = part_type;
151         part_dev = null_device;         (* This gets overwritten by probe. *)
152         part_content = `Unknown;
153       } in
154
155       (* Extra private data which we'll use to calculate free offsets. *)
156       let priv = {
157         mbr_part_start = first_lba;
158         mbr_part_size = part_size;
159       } in
160
161       part, priv
162
163   | { _ } ->
164       { part_status = Malformed; part_type = 0;
165         part_dev = null_device; part_content = `Unknown }, null_priv
166
167 (* Probe an extended partition. *)
168 and probe_extended_partition max dev start =
169   if max > 0 then (
170     try
171       (* Get the partition table (like a boot sector). *)
172       let bits = dev#read_bitstring (start *^ sector_size) sector_size in
173
174       (bitmatch bits with
175        | { _ : 3568 : bitstring;      (* padding to byte offset 446 *)
176            part : 128 : bitstring;    (* this partition *)
177            next : 128 : bitstring;    (* pointer to next extended partition *)
178            _ : 128 : bitstring;       (* ignored - should be zero *)
179            _ : 128 : bitstring;
180            0x55 : 8; 0xAA : 8 } ->    (* MBR signature *)
181
182            let (part, ppriv) = parse_mbr_entry part in
183
184            (* The first partition's LBA is actually offset relative
185             * to the current sector.
186             *)
187            let ppriv =
188              { ppriv with mbr_part_start = ppriv.mbr_part_start + start } in
189
190            let (next, npriv) = parse_mbr_entry next in
191
192            if next.part_status = NullEntry then
193              [ part, ppriv ]            (* End of list. *)
194            else (
195              let start_of_next = start + npriv.mbr_part_start in
196              (part, ppriv) ::
197                probe_extended_partition (max-1) dev start_of_next
198            )
199
200        | { _ } ->
201            invalid_arg "mbr: invalid extended partition table"
202       )
203     with exn ->
204       prerr_endline (Printexc.to_string exn);
205       []
206   ) else (
207     prerr_endline "mbr: too many extended partitions";
208     []
209   )
210
211 (*
212 (* Ugh, fake a UInt32 -> UInt64 conversion without sign extension, until
213  * we get working UInt32/UInt64 modules in extlib.
214  *)
215 and uint64_of_int32 u32 =
216   let i64 = Int64.of_int32 u32 in
217   if u32 >= 0l then i64
218   else Int64.add i64 0x1_0000_0000_L
219 *)
220
221 and offset_is_free parts offset =
222   let privs = get_private_data parts in
223
224   (* The first partition is somehow privileged in that we assume
225    * everything before this is not free.  Usually this is the first
226    * 63 sectors containing the MBR itself and sectors which should
227    * be blank but in reality contain all sorts of stupid hacks like
228    * alternate partitioning schemes.
229    *)
230   match privs with
231   | [] -> false
232   | { mbr_part_start = start; mbr_part_size = size } :: rest ->
233       let start = start *^ sector_size in
234       let size = size *^ sector_size in
235       if offset < start +^ size then
236         false
237       else (
238         let rec loop = function
239           | [] -> true                  (* not in a partition, must be free *)
240           | { mbr_part_start = start; mbr_part_size = size } :: rest ->
241               let start = start *^ sector_size in
242               let size = size *^ sector_size in
243               if start <= offset && offset < start +^ size then
244                 false
245               else
246                 loop rest
247         in
248         loop rest
249       )
250
251 and callbacks =
252   let i = ref 0 in
253   fun () -> {
254     parts_cb_uq = (incr i; !i);
255     parts_cb_name = id;
256     parts_cb_offset_is_free = offset_is_free;
257   }
258
259 (* Register the plugin. *)
260 let () = register_plugin ~partitioner:probe id