upstream: https://github.com/mirage/ocaml-mbr
1(*
2 * Copyright (C) 2013 Citrix Inc
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 *)
16
17open Result.Syntax
18
19(* Error helpers *)
20let err_lba_max x = Error (Fmt.str "sector count exceeds LBA max: %Ld" x)
21
22let err_bad_signature s1 s2 =
23 Error (Fmt.str "Invalid signature: %02x %02x <> 0x55 0xaa" s1 s2)
24
25let err_too_small n = Error (Fmt.str "MBR too small: %d < 512" n)
26
27module Reader = Bytesrw.Bytes.Reader
28module Writer = Bytesrw.Bytes.Writer
29module Slice = Bytesrw.Bytes.Slice
30
31(* Read exactly n bytes from reader *)
32let read_exactly reader n =
33 if n = 0 then Ok Bytes.empty
34 else
35 let buf = Bytes.create n in
36 let rec loop pos remaining =
37 if remaining = 0 then Ok buf
38 else
39 match Reader.read reader with
40 | slice when Slice.is_eod slice -> Error "unexpected end of data"
41 | slice ->
42 let str = Slice.to_string slice in
43 let available = String.length str in
44 let to_copy = min available remaining in
45 Bytes.blit_string str 0 buf pos to_copy;
46 if to_copy < available then begin
47 let leftover = String.sub str to_copy (available - to_copy) in
48 Reader.push_back reader (Slice.of_string leftover)
49 end;
50 loop (pos + to_copy) (remaining - to_copy)
51 in
52 loop 0 n
53
54module Geometry = struct
55 type t = { cylinders : int; heads : int; sectors : int }
56
57 let kib = 1024L
58 let mib = Int64.mul kib 1024L
59
60 let of_lba_size x =
61 let sectors = 63 in
62 let* heads =
63 if x < Int64.(mul 504L mib) then Ok 16
64 else if x < Int64.(mul 1008L mib) then Ok 64
65 else if x < Int64.(mul 4032L mib) then Ok 128
66 else if x < Int64.(add (mul 8032L mib) (mul 512L kib)) then Ok 255
67 else err_lba_max x
68 in
69 let cylinders =
70 Int64.(to_int (div (div x (of_int sectors)) (of_int heads)))
71 in
72 Ok { cylinders; heads; sectors }
73
74 let to_chs g x =
75 let open Int64 in
76 let cylinders = to_int (div x (mul (of_int g.sectors) (of_int g.heads))) in
77 let heads = to_int (rem (div x (of_int g.sectors)) (of_int g.heads)) in
78 let sectors = to_int (succ (rem x (of_int g.sectors))) in
79 { cylinders; heads; sectors }
80end
81
82module Partition = struct
83 type t = {
84 active : bool;
85 first_absolute_sector_chs : Geometry.t;
86 ty : int;
87 last_absolute_sector_chs : Geometry.t;
88 first_absolute_sector_lba : int32;
89 sectors : int32;
90 }
91
92 let sector_start t =
93 Int64.(logand (of_int32 t.first_absolute_sector_lba) 0xFFFF_FFFFL)
94
95 let size_sectors t = Int64.(logand (of_int32 t.sectors) 0xFFFF_FFFFL)
96
97 let make ?(active = false) ~partition_type:(ty : int)
98 first_absolute_sector_lba sectors =
99 let* () =
100 if ty > 0 && ty < 256 then Ok ()
101 else Error "Mbr.Partition.make: ty must be between 1 and 255"
102 in
103 let first_absolute_sector_chs =
104 { Geometry.cylinders = 0; heads = 0; sectors = 0 }
105 in
106 let last_absolute_sector_chs = first_absolute_sector_chs in
107 Ok
108 {
109 active;
110 first_absolute_sector_chs;
111 ty;
112 last_absolute_sector_chs;
113 first_absolute_sector_lba;
114 sectors;
115 }
116
117 let make' ?active ~partition_type:(ty : int) sector_start size_sectors =
118 if
119 Int64.(
120 logand sector_start 0xFFFF_FFFFL = sector_start
121 && logand size_sectors 0xFFFF_FFFFL = size_sectors)
122 then
123 let sector_start = Int64.to_int32 sector_start in
124 let size_sectors = Int64.to_int32 size_sectors in
125 make ?active ~partition_type:ty sector_start size_sectors
126 else Error "partition parameters do not fit in int32"
127
128 let decode_chs s =
129 let heads = Char.code s.[0] in
130 let y = Char.code s.[1] in
131 let z = Char.code s.[2] in
132 { Geometry.cylinders = (y lsl 2) lor z; heads; sectors = y land 0x3F }
133
134 let f_status = Wire.Field.v "status" Wire.uint8
135
136 let f_first_chs =
137 Wire.Field.v "first_chs" (Wire.byte_array ~size:(Wire.int 3))
138
139 let f_ty = Wire.Field.v "type" Wire.uint8
140 let f_last_chs = Wire.Field.v "last_chs" (Wire.byte_array ~size:(Wire.int 3))
141 let f_lba_start = Wire.Field.v "lba_start" Wire.uint32
142 let f_sectors = Wire.Field.v "sectors" Wire.uint32
143
144 let codec =
145 Wire.Codec.v "MbrPartition"
146 (fun status first_chs ty last_chs lba sectors ->
147 {
148 active = status = 0x80;
149 first_absolute_sector_chs = decode_chs first_chs;
150 ty;
151 last_absolute_sector_chs = decode_chs last_chs;
152 first_absolute_sector_lba =
153 Int32.of_int (Wire.Private.UInt32.to_int lba);
154 sectors = Int32.of_int (Wire.Private.UInt32.to_int sectors);
155 })
156 Wire.Codec.
157 [
158 (f_status $ fun t -> if t.active then 0x80 else 0);
159 (f_first_chs $ fun _ -> "\000\000\000");
160 (f_ty $ fun t -> t.ty);
161 (f_last_chs $ fun _ -> "\000\000\000");
162 ( f_lba_start $ fun t ->
163 Wire.Private.UInt32.of_int
164 (Int32.to_int t.first_absolute_sector_lba) );
165 ( f_sectors $ fun t ->
166 Wire.Private.UInt32.of_int (Int32.to_int t.sectors) );
167 ]
168end
169
170type t = {
171 bootstrap_code : string;
172 original_physical_drive : int;
173 seconds : int;
174 minutes : int;
175 hours : int;
176 disk_signature : int32;
177 partitions : Partition.t list;
178}
179
180let pp ppf t =
181 Fmt.pf ppf "@[<v>MBR(sig=%ld, %d partitions)@]" t.disk_signature
182 (List.length t.partitions)
183
184let partitions t = t.partitions
185
186(* Security helper: check for int32 addition overflow *)
187let int32_add_overflow a b =
188 let sum = Int32.add a b in
189 (* Overflow if: a > 0 && b > 0 && sum < 0, or a < 0 && b < 0 && sum > 0 *)
190 (* For unsigned interpretation, check if sum < a when both are positive *)
191 Int32.unsigned_compare sum a < 0
192
193let validate_partitions partitions =
194 let* () =
195 if List.length partitions <= 4 then Ok () else Error "Too many partitions"
196 in
197 let num_active =
198 List.fold_left
199 (fun acc p -> if p.Partition.active then succ acc else acc)
200 0 partitions
201 in
202 let* () =
203 if num_active <= 1 then Ok ()
204 else Error "More than one active/boot partitions is not advisable"
205 in
206 let* () =
207 List.fold_left
208 (fun r p ->
209 let* () = r in
210 if
211 int32_add_overflow p.Partition.first_absolute_sector_lba
212 p.Partition.sectors
213 then
214 Error
215 (Fmt.str "Partition start %lu + size %lu overflows int32"
216 p.Partition.first_absolute_sector_lba p.Partition.sectors)
217 else Ok ())
218 (Ok ()) partitions
219 in
220 let sorted =
221 List.sort
222 (fun p1 p2 ->
223 Int32.unsigned_compare p1.Partition.first_absolute_sector_lba
224 p2.Partition.first_absolute_sector_lba)
225 partitions
226 in
227 let* (_ : int32) =
228 List.fold_left
229 (fun r p ->
230 let* offset = r in
231 if
232 Int32.unsigned_compare offset p.Partition.first_absolute_sector_lba
233 <= 0
234 then
235 Ok
236 (Int32.add p.Partition.first_absolute_sector_lba p.Partition.sectors)
237 else Error "Partitions overlap")
238 (Ok 1l) sorted
239 in
240 Ok sorted
241
242let v ?(disk_signature = 0l) partitions =
243 let* partitions = validate_partitions partitions in
244 Ok
245 {
246 bootstrap_code = String.init (218 + 216) (Fun.const '\000');
247 original_physical_drive = 0;
248 seconds = 0;
249 minutes = 0;
250 hours = 0;
251 disk_signature;
252 partitions;
253 }
254
255(* MBR layout: Wire codec for the full 512-byte sector.
256 The codec decodes/encodes a raw record with all 4 partition slots;
257 conversion to/from the public [t] type filters empty entries. *)
258
259let sizeof = 512
260let default_partition_start = 2048l
261let bootstrap_code1_len = 218
262let bootstrap_code2_len = 216
263
264(* Wire fields for the MBR header *)
265let f_bootstrap1 =
266 Wire.Field.v "bootstrap1"
267 (Wire.byte_array ~size:(Wire.int bootstrap_code1_len))
268
269let funused1 = Wire.Field.v "unused1" (Wire.byte_array ~size:(Wire.int 2))
270let f_drive = Wire.Field.v "drive" Wire.uint8
271let f_seconds = Wire.Field.v "seconds" Wire.uint8
272let f_minutes = Wire.Field.v "minutes" Wire.uint8
273let f_hours = Wire.Field.v "hours" Wire.uint8
274
275let f_bootstrap2 =
276 Wire.Field.v "bootstrap2"
277 (Wire.byte_array ~size:(Wire.int bootstrap_code2_len))
278
279let f_disk_sig = Wire.Field.v "disk_signature" Wire.uint32
280let funused2 = Wire.Field.v "unused2" (Wire.byte_array ~size:(Wire.int 2))
281let f_part1 = Wire.Field.v "partition1" (Wire.codec Partition.codec)
282let f_part2 = Wire.Field.v "partition2" (Wire.codec Partition.codec)
283let f_part3 = Wire.Field.v "partition3" (Wire.codec Partition.codec)
284let f_part4 = Wire.Field.v "partition4" (Wire.codec Partition.codec)
285let f_sig1 = Wire.Field.v "signature1" Wire.uint8
286let f_sig2 = Wire.Field.v "signature2" Wire.uint8
287
288(* A partition entry that encodes as 16 zero bytes *)
289let empty_partition =
290 {
291 Partition.active = false;
292 first_absolute_sector_chs =
293 { Geometry.cylinders = 0; heads = 0; sectors = 0 };
294 ty = 0;
295 last_absolute_sector_chs =
296 { Geometry.cylinders = 0; heads = 0; sectors = 0 };
297 first_absolute_sector_lba = 0l;
298 sectors = 0l;
299 }
300
301(* Raw decoded MBR: all 4 partition slots present, signature bytes included *)
302type raw = {
303 bootstrap1 : string;
304 unused1 : string;
305 drive : int;
306 seconds : int;
307 minutes : int;
308 hours : int;
309 bootstrap2 : string;
310 disk_signature_raw : int;
311 unused2 : string;
312 part1 : Partition.t;
313 part2 : Partition.t;
314 part3 : Partition.t;
315 part4 : Partition.t;
316 sig1 : int;
317 sig2 : int;
318}
319
320let raw_codec =
321 Wire.Codec.v "Mbr"
322 (fun bootstrap1 unused1 drive seconds minutes hours bootstrap2
323 disk_signature_raw unused2 part1 part2 part3 part4 sig1 sig2 ->
324 {
325 bootstrap1;
326 unused1;
327 drive;
328 seconds;
329 minutes;
330 hours;
331 bootstrap2;
332 disk_signature_raw;
333 unused2;
334 part1;
335 part2;
336 part3;
337 part4;
338 sig1;
339 sig2;
340 })
341 Wire.Codec.
342 [
343 (f_bootstrap1 $ fun r -> r.bootstrap1);
344 (funused1 $ fun r -> r.unused1);
345 (f_drive $ fun r -> r.drive);
346 (f_seconds $ fun r -> r.seconds);
347 (f_minutes $ fun r -> r.minutes);
348 (f_hours $ fun r -> r.hours);
349 (f_bootstrap2 $ fun r -> r.bootstrap2);
350 (f_disk_sig $ fun r -> r.disk_signature_raw);
351 (funused2 $ fun r -> r.unused2);
352 (f_part1 $ fun r -> r.part1);
353 (f_part2 $ fun r -> r.part2);
354 (f_part3 $ fun r -> r.part3);
355 (f_part4 $ fun r -> r.part4);
356 (f_sig1 $ fun r -> r.sig1);
357 (f_sig2 $ fun r -> r.sig2);
358 ]
359
360(* Filter a decoded partition: ty=0 means empty slot *)
361let filter_partition (p : Partition.t) =
362 if p.ty <> 0 then Ok (Some p)
363 else if (not p.active) && p.first_absolute_sector_lba = 0l && p.sectors = 0l
364 then Ok None
365 else Error "Non-zero empty partition type"
366
367(* Internal unmarshal from bytes buffer *)
368let unmarshal_bytes (buf : bytes) : (t, string) result =
369 let* () =
370 if Bytes.length buf < sizeof then err_too_small (Bytes.length buf)
371 else Ok ()
372 in
373 let* raw =
374 Wire.Codec.decode raw_codec buf 0
375 |> Result.map_error (Fmt.str "%a" Wire.pp_parse_error)
376 in
377 let* () =
378 if raw.sig1 = 0x55 && raw.sig2 = 0xaa then Ok ()
379 else err_bad_signature raw.sig1 raw.sig2
380 in
381 let* p1 = filter_partition raw.part1 in
382 let* p2 = filter_partition raw.part2 in
383 let* p3 = filter_partition raw.part3 in
384 let* p4 = filter_partition raw.part4 in
385 let partitions = List.filter_map Fun.id [ p1; p2; p3; p4 ] in
386 Ok
387 {
388 bootstrap_code = raw.bootstrap1 ^ raw.bootstrap2;
389 original_physical_drive = raw.drive;
390 seconds = raw.seconds;
391 minutes = raw.minutes;
392 hours = raw.hours;
393 disk_signature =
394 Int32.of_int (Wire.Private.UInt32.to_int raw.disk_signature_raw);
395 partitions;
396 }
397
398(* Internal marshal to bytes buffer *)
399let marshal_bytes (buf : bytes) t =
400 let parts = t.partitions in
401 let nth i =
402 try List.nth parts i
403 with Failure _ | Invalid_argument _ -> empty_partition
404 in
405 let raw =
406 {
407 bootstrap1 = String.sub t.bootstrap_code 0 bootstrap_code1_len;
408 unused1 = "\000\000";
409 drive = t.original_physical_drive;
410 seconds = t.seconds;
411 minutes = t.minutes;
412 hours = t.hours;
413 bootstrap2 =
414 String.sub t.bootstrap_code bootstrap_code1_len bootstrap_code2_len;
415 disk_signature_raw =
416 Wire.Private.UInt32.of_int (Int32.to_int t.disk_signature);
417 unused2 = "\000\000";
418 part1 = nth 0;
419 part2 = nth 1;
420 part3 = nth 2;
421 part4 = nth 3;
422 sig1 = 0x55;
423 sig2 = 0xaa;
424 }
425 in
426 Wire.Codec.encode raw_codec raw buf 0
427
428(* Streaming API *)
429
430let of_string s = unmarshal_bytes (Bytes.of_string s)
431
432let to_string t =
433 let buf = Bytes.make sizeof '\x00' in
434 marshal_bytes buf t;
435 Bytes.to_string buf
436
437let read reader =
438 match read_exactly reader sizeof with
439 | Error e -> Error e
440 | Ok buf -> unmarshal_bytes buf
441
442let write writer t =
443 let s = to_string t in
444 Writer.write writer (Slice.of_string s)