8e1c5c05c304cc7d546a3e6a76b0c792f026c7d0
[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 private_t = mbr_part list          (* list of partitions that we found *)
40 and mbr_part = {
41   mbr_part_start : int63;               (* start of partition in bytes *)
42   mbr_part_size : int63;                (* size of partition in bytes *)
43 }
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 =
90         List.mapi (parse_mbr_entry dev) [part0;part1;part2;part3] in
91
92 (*
93       (* Read extended partition data. *)
94       let extendeds = List.map (
95         function
96         | { part_type = 0x05 } as part ->
97             probe_extended_partition
98               max_extended_partitions fd part part.part_lba_start
99         | part -> []
100       ) primaries in
101       let extendeds = List.concat extendeds in
102 *)
103
104       let parts = primaries (* @ extendeds *) in
105       let privs = List.concat (List.map snd parts) in
106       let parts = List.map fst parts in
107
108       let r = { parts_cb = callbacks (); parts_dev = dev; parts = parts } in
109       attach_private_data r privs;
110       r
111
112   | { _ } ->
113       raise Not_found                   (* not an MBR *)
114
115 (* Parse a single partition table entry.  See the table here:
116  * http://en.wikipedia.org/wiki/Master_boot_record
117  *)
118 and parse_mbr_entry dev i bits =
119   bitmatch bits with
120   | { 0l : 32; 0l : 32; 0l : 32; 0l : 32 } ->
121       { part_status = NullEntry; part_type = 0;
122         part_dev = null_device; part_content = `Unknown }, []
123
124   | { ((0|0x80) as bootable) : 8; first_chs : 24;
125       part_type : 8; last_chs : 24;
126       first_lba : 32 : unsigned, littleendian;
127       part_size : 32 : unsigned, littleendian } ->
128
129       let bootable = if bootable = 0 then Nonbootable else Bootable in
130       let first_lba = Int63.of_int32 first_lba in
131       let part_size = Int63.of_int32 part_size in
132       let partno = i+1 in
133
134       if !debug then
135         eprintf "parse_mbr_entry: first_lba = %s part_size = %s\n%!"
136           (Int63.to_string first_lba) (Int63.to_string part_size);
137
138       let part = {
139         part_status = bootable;
140         part_type = part_type;
141         part_dev = new partition_device partno first_lba part_size dev;
142         part_content = `Unknown;
143       } in
144
145       (* Extra private data which we'll use to calculate free offsets. *)
146       let priv = {
147         mbr_part_start = first_lba *^ sector_size;
148         mbr_part_size = part_size *^ sector_size;
149       } in
150
151       part, [priv]
152
153   | { _ } ->
154       { part_status = Malformed; part_type = 0;
155         part_dev = null_device; part_content = `Unknown }, []
156
157 (*
158 This code worked previously, but now needs some love ...
159 XXX
160
161 (* Probe an extended partition. *)
162 and probe_extended_partition max fd epart sect =
163   if max > 0 then (
164     (* Offset of the first EBR. *)
165     let ebr_offs = sect *^ sector_size in
166     (* EBR Signature? *)
167     LargeFile.lseek fd (ebr_offs +^ 510L) SEEK_SET;
168     let str = String.create 2 in
169     if read fd str 0 2 <> 2 || str.[0] != '\x55' || str.[1] != '\xAA' then
170       [] (* Not EBR *)
171     else (
172       (* Read the extended partition table entries (just 2 of them). *)
173       LargeFile.lseek fd (ebr_offs +^ 446L) SEEK_SET;
174       let str = String.create 32 in
175       if read fd str 0 32 <> 32 then
176         failwith (s_ "error reading extended partition")
177       else (
178         (* Extract partitions from the data. *)
179         let part1, part2 =
180           match List.map (get_partition str) [ 0; 16 ] with
181           | [p1;p2] -> p1,p2
182           | _ -> failwith (s_ "probe_extended_partition: internal error") in
183         (* First partition entry has offset to the start of this partition. *)
184         let part1 = { part1 with
185                         part_lba_start = sect +^ part1.part_lba_start } in
186         (* Second partition entry is zeroes if end of list, otherwise points
187          * to the next partition.
188          *)
189         if part2.part_status = NullEntry then
190           [part1]
191         else
192           part1 :: probe_extended_partition
193                      (max-1) fd epart (sect +^ part2.part_lba_start)
194       )
195     )
196   )
197   else []
198 *)
199
200 (*
201 (* Ugh, fake a UInt32 -> UInt64 conversion without sign extension, until
202  * we get working UInt32/UInt64 modules in extlib.
203  *)
204 and uint64_of_int32 u32 =
205   let i64 = Int64.of_int32 u32 in
206   if u32 >= 0l then i64
207   else Int64.add i64 0x1_0000_0000_L
208 *)
209
210 and offset_is_free parts offset =
211   let privs = get_private_data parts in
212
213   (* The first partition is somehow privileged in that we assume
214    * everything before this is not free.  Usually this is the first
215    * 63 sectors containing the MBR itself and sectors which should
216    * be blank but in reality contain all sorts of stupid hacks like
217    * alternate partitioning schemes.
218    *)
219   match privs with
220   | [] -> false
221   | { mbr_part_start = start; mbr_part_size = size } :: rest ->
222       if offset < start +^ size then
223         false
224       else (
225         let rec loop = function
226           | [] -> true                  (* not in a partition, must be free *)
227           | { mbr_part_start = start; mbr_part_size = size } :: rest ->
228               if start <= offset && offset < start +^ size then
229                 false
230               else
231                 loop rest
232         in
233         loop rest
234       )
235
236 and callbacks =
237   let i = ref 0 in
238   fun () -> {
239     parts_cb_uq = (incr i; !i);
240     parts_cb_name = id;
241     parts_cb_offset_is_free = offset_is_free;
242   }
243
244 (* Register the plugin. *)
245 let () = register_plugin ~partitioner:probe id