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