···11+## v1.0.0 (2024-02-16)
22+* Initial release of `gpt.
+14
LICENSE
···11+Copyright (c) 2014, Citrix Systems Inc
22+33+Permission to use, copy, modify, and/or distribute this software for any
44+purpose with or without fee is hereby granted, provided that the above
55+copyright notice and this permission notice appear in all copies.
66+77+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414+
+113
README.md
···11+# ocaml-gpt
22+33+## Introduction
44+55+This library provides functionality for working with the GUID Partition Table (GPT). It allows you to create, manipulate, and marshal/unmarshal GPT headers and partitions.
66+77+## Dependencies
88+- `OCaml` (version 4.02 or later)
99+- `dune` (build system)
1010+- `uuidm` (library for UUID manipulation)
1111+- `checkseum` (library for checksum calculations)
1212+- `ocaml-cstruct` (library for working with C-like structures)
1313+1414+## Installation
1515+1) Install OCaml and dune by following the instructions for your platform. [Up and Running with Ocaml](https://ocaml.org/docs/up-and-running)
1616+2) Install the required dependencies using OPAM (OCaml package manager)
1717+```sh
1818+opam install .
1919+```
2020+3) Build the project using dune
2121+```sh
2222+dune build
2323+```
2424+2525+4) Run Test
2626+```sh
2727+dune build @runtest
2828+```
2929+3030+## Usage
3131+3232+### Module: Partition
3333+This module provides functions for working with GPT partitions.
3434+```ocaml
3535+Partition.make
3636+val make :
3737+ ?name:string ->
3838+ type_guid:Uuidm.t ->
3939+ attributes:int64 ->
4040+ starting_lba:int64 ->
4141+ ending_lba:int64 ->
4242+ (Partition.t, string) result
4343+```
4444+This function creates a new GPT partition with the specified parameters. It returns a `Partition.t` value wrapped in the Result type, indicating success or failure. `name` should be a `utf-16-le` encoded string of length 72 bytes.
4545+4646+#### Partition.unmarshal
4747+```ocaml
4848+val unmarshal : Cstruct.t -> (Partition.t, string) result
4949+```
5050+This function takes a `Cstruct.t` buffer and unmarshals the data into a `Partition.t` value. It returns the unmarshalled partition wrapped in the Result type, indicating success or failure.
5151+5252+#### Partition.marshal
5353+```ocaml
5454+val marshal : Cstruct.t -> Partition.t -> unit
5555+```
5656+This function marshals a `Partition.t` value into a `Cstruct.t` buffer.
5757+5858+5959+### Module: Gpt
6060+This module provides functions for working with GPT headers.
6161+6262+#### Gpt.make
6363+```ocaml
6464+val make : ?disk_guid:Uuidm.t -> disk_size:int64
6565+ -> sector_size:int -> Partition.t list -> (t, string) result
6666+```
6767+This function creates a new GPT header with the specified list of partitions. It returns a `Gpt.t` value wrapped in the Result type, indicating success or failure.
6868+6969+#### Gpt.unmarshal
7070+```ocaml
7171+val unmarshal : Cstruct.t -> sector_size:int -> (t, string) result
7272+```
7373+This function takes a `Cstruct.t` buffer and unmarshals the data into a `Gpt.t` value. It returns the unmarshalled GPT header wrapped in the Result type, indicating success or failure.
7474+7575+#### Gpt.marshal
7676+```ocaml
7777+val marshal : Cstruct.t -> t -> unit
7878+```
7979+This function marshals a `Gpt.t` value into a `Cstruct.t` buffer.
8080+8181+## Example Usage
8282+Here's an example of how you can use this library to create and manipulate GPT headers and partitions:
8383+8484+```ocaml
8585+let create_gpt_header () =
8686+ let partition1 = Match Partition.make ~name:"Partition 1" ~type_guid:"12345678-1234-1234-1234-123456789abc" ~attributes:0L 1L 100L
8787+ with
8888+ | Ok p -> p
8989+ | Error error -> Printf.eprintf "Error %s" error
9090+ in
9191+ match make [partition1] ~disk_size:1024L ~sector_size:512 with
9292+ | Ok gpt -> gpt
9393+ | Error error -> Printf.eprintf "Error %s" error
9494+9595+```
9696+9797+## Tools
9898+9999+### Inspect the GPT Header:
100100+- `gpt_inspect.exe`: This script prints the GPT header and it's corresponding partitions. It enables you inspect your GPT disks.
101101+102102+#### Usage
103103+```ocaml
104104+ dune exec -- bin/gpt_inspect.exe disk.img
105105+```
106106+107107+- `disk.img` corresponds to the disk or disk file image you wish to inspect
108108+109109+## License
110110+This libray is licensed with the Ocaml standard ISC license. See here [License](LICENSE)
111111+112112+## Contributions
113113+Please report bugs using the issue tracker at [https://github.com/PizieDust/ocaml-gpt/issues](https://github.com/PizieDust/ocaml-gpt/issues)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Fuzz tests for GPT parsing.
77+88+ Key properties tested:
99+ 1. Header parser crash safety - no crashes on arbitrary input
1010+ 2. Partition entry parser crash safety
1111+ 3. Roundtrip - marshal(make(partitions)) is parseable
1212+ 4. CRC32 validation
1313+1414+ Security considerations:
1515+ - GPT has CRC32 checksums that must be validated
1616+ - Partition count field can claim huge values
1717+ - LBA values can overflow when multiplied by sector size *)
1818+1919+open Crowbar
2020+2121+(** Truncate input to reasonable size. *)
2222+let truncate ?(max_len = 4096) buf =
2323+ let len = min max_len (String.length buf) in
2424+ String.sub buf 0 len
2525+2626+(** GPT header unmarshal crash safety.
2727+ Parser must not crash on any input. *)
2828+let test_header_unmarshal_crash_safety buf =
2929+ let buf = truncate ~max_len:512 buf in
3030+ (* Pad to at least 92 bytes (GPT header size) *)
3131+ let buf =
3232+ if String.length buf < 512 then
3333+ buf ^ String.make (512 - String.length buf) '\x00'
3434+ else buf
3535+ in
3636+ let _ = Gpt.of_string buf ~sector_size:512 in
3737+ ()
3838+3939+(** GPT make and marshal roundtrip. *)
4040+let test_roundtrip () =
4141+ let linux_fs =
4242+ match Uuidm.of_string "0FC63DAF-8483-4772-8E79-3D69D8477DE4" with
4343+ | Some u -> u
4444+ | None -> fail "invalid UUID"
4545+ in
4646+ let p1 =
4747+ match Gpt.Partition.make ~type_guid:linux_fs ~attributes:0L 2048L 1050623L with
4848+ | Ok p -> p
4949+ | Error _ -> fail "failed to create partition"
5050+ in
5151+ let gpt =
5252+ match Gpt.make ~disk_sectors:2097152L ~sector_size:512 [ p1 ] with
5353+ | Ok g -> g
5454+ | Error _ -> fail "failed to create GPT"
5555+ in
5656+ (* Create a reader from header + partition table *)
5757+ let header = Gpt.marshal_header_to_bytes ~sector_size:512 ~primary:true gpt in
5858+ let table = Gpt.marshal_partition_table_to_string ~sector_size:512 gpt in
5959+ let data = header ^ table in
6060+ let reader = Bytesrw.Bytes.Reader.of_string data in
6161+ match Gpt.read reader ~sector_size:512 with
6262+ | Error _ -> fail "roundtrip: read failed"
6363+ | Ok _ -> ()
6464+6565+(** Partition make with arbitrary LBA values. *)
6666+let test_partition_make start_lba end_lba attrs =
6767+ let linux_fs =
6868+ match Uuidm.of_string "0FC63DAF-8483-4772-8E79-3D69D8477DE4" with
6969+ | Some u -> u
7070+ | None -> fail "invalid UUID"
7171+ in
7272+ let start_lba = Int64.of_int (abs start_lba) in
7373+ let end_lba = Int64.of_int (abs end_lba) in
7474+ let attrs = Int64.of_int attrs in
7575+ let _ = Gpt.Partition.make ~type_guid:linux_fs ~attributes:attrs start_lba end_lba in
7676+ ()
7777+7878+(** GPT make with arbitrary disk parameters. *)
7979+let test_gpt_make disk_sectors sector_mult =
8080+ let linux_fs =
8181+ match Uuidm.of_string "0FC63DAF-8483-4772-8E79-3D69D8477DE4" with
8282+ | Some u -> u
8383+ | None -> fail "invalid UUID"
8484+ in
8585+ let disk_sectors = Int64.of_int (max 100 (abs disk_sectors)) in
8686+ let sector_size = 512 * (1 + abs (sector_mult mod 8)) in
8787+ let p1 = Gpt.Partition.make ~type_guid:linux_fs ~attributes:0L 34L 100L in
8888+ match p1 with
8989+ | Error _ -> ()
9090+ | Ok p1 ->
9191+ let _ = Gpt.make ~disk_sectors ~sector_size [ p1 ] in
9292+ ()
9393+9494+let () =
9595+ add_test ~name:"gpt: header unmarshal crash safety" [ bytes ]
9696+ test_header_unmarshal_crash_safety;
9797+ add_test ~name:"gpt: roundtrip" [] test_roundtrip;
9898+ add_test ~name:"gpt: partition make" [ int; int; int ] test_partition_make;
9999+ add_test ~name:"gpt: make with params" [ int; int ] test_gpt_make
···11+(*
22+ * Copyright (C) 2013 Citrix Inc
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ *)
1616+1717+module Result_syntax = struct
1818+ let ( let* ) = Result.bind
1919+end
2020+2121+open Result_syntax
2222+2323+let guid_len = 16
2424+2525+(* The size of a header not counting the reserved space *)
2626+let sizeof = 92
2727+2828+(* Binary reading helpers - little-endian *)
2929+let get_u8 s off = Char.code (Bytes.get s off)
3030+let get_u16_le s off = get_u8 s off lor (get_u8 s (off + 1) lsl 8)
3131+3232+let get_u32_le s off =
3333+ Int32.logor
3434+ (Int32.of_int (get_u16_le s off))
3535+ (Int32.shift_left (Int32.of_int (get_u16_le s (off + 2))) 16)
3636+3737+let get_u64_le s off =
3838+ Int64.logor
3939+ (Int64.of_int32 (get_u32_le s off))
4040+ (Int64.shift_left (Int64.of_int32 (get_u32_le s (off + 4))) 32)
4141+4242+(* Binary writing helpers - little-endian *)
4343+let set_u8 s off v = Bytes.set s off (Char.chr (v land 0xff))
4444+4545+let set_u16_le s off v =
4646+ set_u8 s off (v land 0xff);
4747+ set_u8 s (off + 1) ((v lsr 8) land 0xff)
4848+4949+let set_u32_le s off v =
5050+ set_u16_le s off (Int32.to_int (Int32.logand v 0xffffl));
5151+ set_u16_le s (off + 2)
5252+ (Int32.to_int (Int32.logand (Int32.shift_right_logical v 16) 0xffffl))
5353+5454+let set_u64_le s off v =
5555+ set_u32_le s off (Int64.to_int32 v);
5656+ set_u32_le s (off + 4) (Int64.to_int32 (Int64.shift_right_logical v 32))
5757+5858+module Reader = Bytesrw.Bytes.Reader
5959+module Writer = Bytesrw.Bytes.Writer
6060+module Slice = Bytesrw.Bytes.Slice
6161+6262+(* Read exactly n bytes from reader *)
6363+let read_exactly reader n =
6464+ if n = 0 then Ok Bytes.empty
6565+ else
6666+ let buf = Bytes.create n in
6767+ let rec loop pos remaining =
6868+ if remaining = 0 then Ok buf
6969+ else
7070+ match Reader.read reader with
7171+ | slice when Slice.is_eod slice -> Error "unexpected end of data"
7272+ | slice ->
7373+ let str = Slice.to_string slice in
7474+ let available = String.length str in
7575+ let to_copy = min available remaining in
7676+ Bytes.blit_string str 0 buf pos to_copy;
7777+ if to_copy < available then begin
7878+ let leftover = String.sub str to_copy (available - to_copy) in
7979+ Reader.push_back reader (Slice.of_string leftover)
8080+ end;
8181+ loop (pos + to_copy) (remaining - to_copy)
8282+ in
8383+ loop 0 n
8484+8585+module Partition = struct
8686+ type t = {
8787+ type_guid : Uuidm.t;
8888+ partition_guid : Uuidm.t;
8989+ starting_lba : int64;
9090+ ending_lba : int64;
9191+ attributes : int64;
9292+ name : string;
9393+ }
9494+9595+ let make ?(name = String.make 72 '\000') ~type_guid ~attributes starting_lba
9696+ ending_lba =
9797+ let partition_guid = Uuidm.v4_gen (Random.State.make_self_init ()) () in
9898+ if String.length name <> 72 then
9999+ Printf.ksprintf invalid_arg "Name length %d should be exactly 72\n"
100100+ (String.length name);
101101+ Ok { type_guid; partition_guid; starting_lba; ending_lba; attributes; name }
102102+103103+ let is_zero_partition p =
104104+ let zero_uuid =
105105+ Option.get (Uuidm.of_string "00000000-0000-0000-0000-000000000000")
106106+ in
107107+ Uuidm.equal p.type_guid zero_uuid
108108+ && Uuidm.equal p.partition_guid zero_uuid
109109+ && p.starting_lba = 0L && p.ending_lba = 0L && p.attributes = 0L
110110+ && String.for_all (Char.equal '\000') p.name
111111+112112+ let sizeof = 128
113113+ let type_guid_offset = 0
114114+ let partition_guid_offset = 16
115115+ let starting_lba_offset = 32
116116+ let ending_lba_offset = 40
117117+ let attributes_offset = 48
118118+ let name_offset = 56
119119+120120+ let unmarshal buf off =
121121+ if Bytes.length buf < off + sizeof then
122122+ Printf.ksprintf invalid_arg "Partition entry too small: %d < %d"
123123+ (Bytes.length buf - off)
124124+ sizeof;
125125+ let type_guid_bytes = Bytes.sub_string buf (off + type_guid_offset) guid_len in
126126+ let type_guid = Option.get (Uuidm.of_mixed_endian_bytes type_guid_bytes) in
127127+ let partition_guid_bytes =
128128+ Bytes.sub_string buf (off + partition_guid_offset) guid_len
129129+ in
130130+ let partition_guid =
131131+ Option.get (Uuidm.of_mixed_endian_bytes partition_guid_bytes)
132132+ in
133133+ let starting_lba = get_u64_le buf (off + starting_lba_offset) in
134134+ let ending_lba = get_u64_le buf (off + ending_lba_offset) in
135135+ let attributes = get_u64_le buf (off + attributes_offset) in
136136+ let name = Bytes.sub_string buf (off + name_offset) 72 in
137137+ { type_guid; partition_guid; starting_lba; ending_lba; attributes; name }
138138+139139+ let marshal (buf : bytes) off t =
140140+ Bytes.blit_string (Uuidm.to_mixed_endian_bytes t.type_guid) 0 buf
141141+ (off + type_guid_offset) guid_len;
142142+ Bytes.blit_string (Uuidm.to_mixed_endian_bytes t.partition_guid) 0 buf
143143+ (off + partition_guid_offset) guid_len;
144144+ set_u64_le buf (off + starting_lba_offset) t.starting_lba;
145145+ set_u64_le buf (off + ending_lba_offset) t.ending_lba;
146146+ set_u64_le buf (off + attributes_offset) t.attributes;
147147+ let name_len = min 72 (String.length t.name) in
148148+ Bytes.blit_string t.name 0 buf (off + name_offset) name_len;
149149+ if name_len < 72 then Bytes.fill buf (off + name_offset + name_len) (72 - name_len) '\000'
150150+151151+ let to_string t =
152152+ let buf = Bytes.create sizeof in
153153+ marshal buf 0 t;
154154+ Bytes.to_string buf
155155+end
156156+157157+type t = {
158158+ revision : int32;
159159+ header_size : int32;
160160+ header_crc32 : int32;
161161+ reserved : int32;
162162+ current_lba : int64;
163163+ backup_lba : int64;
164164+ first_usable_lba : int64;
165165+ last_usable_lba : int64;
166166+ disk_guid : Uuidm.t;
167167+ partition_entry_lba : int64;
168168+ num_partition_entries : int32;
169169+ partition_size : int32;
170170+ partitions_crc32 : int32;
171171+ partitions : Partition.t list;
172172+}
173173+174174+let signature = "EFI PART"
175175+let signature_offset = 0
176176+let signature_len = 8
177177+let revision_offset = 8
178178+let header_size_offset = 12
179179+let header_crc32_offset = 16
180180+let reserved_offset = 20
181181+let current_lba_offset = 24
182182+let backup_lba_offset = 32
183183+let first_usable_lba_offset = 40
184184+let last_usable_lba_offset = 48
185185+let disk_guid_offset = 56
186186+let partition_entry_lba_offset = 72
187187+let num_partition_entries_offset = 80
188188+let partition_size_offset = 84
189189+let partitions_crc32_offset = 88
190190+191191+let marshal_header_bytes buf header =
192192+ Bytes.blit_string signature 0 buf 0 (String.length signature);
193193+ set_u32_le buf revision_offset header.revision;
194194+ set_u32_le buf header_size_offset header.header_size;
195195+ set_u32_le buf header_crc32_offset 0l; (* zero for CRC calculation *)
196196+ set_u32_le buf reserved_offset header.reserved;
197197+ set_u64_le buf current_lba_offset header.current_lba;
198198+ set_u64_le buf backup_lba_offset header.backup_lba;
199199+ set_u64_le buf first_usable_lba_offset header.first_usable_lba;
200200+ set_u64_le buf last_usable_lba_offset header.last_usable_lba;
201201+ Bytes.blit_string (Uuidm.to_mixed_endian_bytes header.disk_guid) 0 buf
202202+ disk_guid_offset guid_len;
203203+ set_u64_le buf partition_entry_lba_offset header.partition_entry_lba;
204204+ set_u32_le buf num_partition_entries_offset header.num_partition_entries;
205205+ set_u32_le buf partition_size_offset header.partition_size;
206206+ set_u32_le buf partitions_crc32_offset header.partitions_crc32
207207+208208+let calculate_header_crc32 header =
209209+ let buf = Bytes.create sizeof in
210210+ marshal_header_bytes buf header;
211211+ Checkseum.Crc32.digest_string (Bytes.to_string buf) 0 sizeof
212212+ Checkseum.Crc32.default
213213+214214+let calculate_partition_crc32 num_partitions partitions =
215215+ let num_partitions = Int32.to_int num_partitions in
216216+ let crc =
217217+ List.fold_left
218218+ (fun crc32 partition ->
219219+ let s = Partition.to_string partition in
220220+ Checkseum.Crc32.digest_string s 0 Partition.sizeof crc32)
221221+ Checkseum.Crc32.default partitions
222222+ in
223223+ let zero_partition = String.make Partition.sizeof '\000' in
224224+ let rec loop crc n =
225225+ if n = 0 then crc
226226+ else
227227+ let crc =
228228+ Checkseum.Crc32.digest_string zero_partition 0 Partition.sizeof crc
229229+ in
230230+ loop crc (pred n)
231231+ in
232232+ loop crc (num_partitions - List.length partitions)
233233+234234+let table_sectors_required num_partition_entries sector_size =
235235+ ((num_partition_entries * Partition.sizeof) + sector_size - 1) / sector_size
236236+237237+let make ?disk_guid ~disk_sectors ~sector_size partitions =
238238+ let num_partition_entries = 128 in
239239+ let num_actual_partition_entries = List.length partitions in
240240+ let* () =
241241+ if num_actual_partition_entries > num_partition_entries then
242242+ Error
243243+ (Printf.sprintf "Number of partitions %d exceeds required number %d\n%!"
244244+ num_actual_partition_entries num_partition_entries)
245245+ else Ok ()
246246+ in
247247+ let partitions =
248248+ List.sort
249249+ (fun p1 p2 ->
250250+ Int64.unsigned_compare p1.Partition.starting_lba p2.Partition.starting_lba)
251251+ partitions
252252+ in
253253+ let* _last_partition_lba =
254254+ List.fold_left
255255+ (fun r p ->
256256+ let* offset = r in
257257+ if Int64.unsigned_compare offset p.Partition.starting_lba < 0 then
258258+ Ok p.Partition.ending_lba
259259+ else Error "Partitions overlap")
260260+ (Ok 1L) partitions
261261+ in
262262+ let current_lba = 1L in
263263+ let backup_lba = Int64.sub disk_sectors 1L in
264264+ let last_usable_lba = Int64.sub backup_lba 1L in
265265+ let partition_entry_lba = 2L in
266266+ let first_usable_lba =
267267+ let partition_table_sectors =
268268+ table_sectors_required num_partition_entries sector_size
269269+ in
270270+ Int64.(add partition_entry_lba (of_int partition_table_sectors))
271271+ in
272272+ let disk_guid =
273273+ Option.value disk_guid
274274+ ~default:(Uuidm.v4_gen (Random.State.make_self_init ()) ())
275275+ in
276276+ let partition_size = Int32.of_int Partition.sizeof in
277277+ let header_size = Int32.of_int sizeof in
278278+ let revision = 0x010000l in
279279+ let reserved = 0l in
280280+ let num_partition_entries = Int32.of_int num_partition_entries in
281281+ let partitions_crc32 =
282282+ Optint.to_int32 (calculate_partition_crc32 num_partition_entries partitions)
283283+ in
284284+ let header =
285285+ {
286286+ revision;
287287+ header_size;
288288+ header_crc32 = 0l;
289289+ reserved;
290290+ current_lba;
291291+ backup_lba;
292292+ first_usable_lba;
293293+ last_usable_lba;
294294+ disk_guid;
295295+ partition_entry_lba;
296296+ num_partition_entries;
297297+ partitions;
298298+ partition_size;
299299+ partitions_crc32;
300300+ }
301301+ in
302302+ let header_crc32 = Optint.to_int32 (calculate_header_crc32 header) in
303303+ Ok { header with header_crc32 }
304304+305305+let unmarshal_bytes buf ~sector_size =
306306+ if Bytes.length buf < sizeof then
307307+ Printf.ksprintf invalid_arg "GPT too small: %d < %d" (Bytes.length buf)
308308+ sizeof;
309309+ let sig_str = Bytes.sub_string buf signature_offset signature_len in
310310+ let* () =
311311+ match sig_str with
312312+ | "EFI PART" -> Ok ()
313313+ | x ->
314314+ Error
315315+ (Printf.sprintf "Signature not found; expected 'EFI PART', got '%s'" x)
316316+ in
317317+ let revision = get_u32_le buf revision_offset in
318318+ let* () =
319319+ if revision = 0x010000l then Ok ()
320320+ else
321321+ Error
322322+ (Printf.sprintf "Unknown revision; expected 0x10000, got 0x%08lx"
323323+ revision)
324324+ in
325325+ let header_size = get_u32_le buf header_size_offset in
326326+ let header_crc32 = get_u32_le buf header_crc32_offset in
327327+ let* () =
328328+ let header_size_int = Int32.to_int header_size in
329329+ let buf_str = Bytes.to_string buf in
330330+ let crc32 =
331331+ Checkseum.Crc32.digest_string buf_str 0 header_crc32_offset
332332+ Checkseum.Crc32.default
333333+ in
334334+ let crc32 = Checkseum.Crc32.digest_string "\000\000\000\000" 0 4 crc32 in
335335+ let crc32 =
336336+ Checkseum.Crc32.digest_string buf_str (header_crc32_offset + 4)
337337+ (header_size_int - header_crc32_offset - 4)
338338+ crc32
339339+ in
340340+ let header_crc32' = Checkseum.Crc32.to_int32 crc32 in
341341+ if header_crc32' = header_crc32 then Ok () else Error "Bad GPT header checksum"
342342+ in
343343+ let reserved = get_u32_le buf reserved_offset in
344344+ let current_lba = get_u64_le buf current_lba_offset in
345345+ let backup_lba = get_u64_le buf backup_lba_offset in
346346+ let first_usable_lba = get_u64_le buf first_usable_lba_offset in
347347+ let last_usable_lba = get_u64_le buf last_usable_lba_offset in
348348+ let disk_guid_bytes = Bytes.sub_string buf disk_guid_offset guid_len in
349349+ let* disk_guid =
350350+ match Uuidm.of_mixed_endian_bytes disk_guid_bytes with
351351+ | Some guid -> Ok guid
352352+ | None ->
353353+ Error
354354+ (Printf.sprintf "Failed to parse disk_guid; got '%s'" disk_guid_bytes)
355355+ in
356356+ let partition_entry_lba = get_u64_le buf partition_entry_lba_offset in
357357+ let num_partition_entries = get_u32_le buf num_partition_entries_offset in
358358+ let partitions_crc32 = get_u32_le buf partitions_crc32_offset in
359359+ let partition_size = get_u32_le buf partition_size_offset in
360360+ let* () =
361361+ if partition_size <> Int32.of_int Partition.sizeof then
362362+ Error (Printf.sprintf "Unexpected partition size: %lu" partition_size)
363363+ else Ok ()
364364+ in
365365+ let partition_entry_sectors =
366366+ (Int32.to_int num_partition_entries * Partition.sizeof + sector_size - 1)
367367+ / sector_size
368368+ in
369369+ Ok
370370+ ( `Read_partition_table (partition_entry_lba, partition_entry_sectors),
371371+ fun table_buf ->
372372+ let table_size = Int32.to_int num_partition_entries * Partition.sizeof in
373373+ if Bytes.length table_buf < table_size then
374374+ Printf.ksprintf invalid_arg "partition table buffer too small";
375375+ let table_str = Bytes.sub_string table_buf 0 table_size in
376376+ let partitions_crc32' =
377377+ Checkseum.Crc32.digest_string table_str 0 table_size
378378+ Checkseum.Crc32.default
379379+ |> Checkseum.Crc32.to_int32
380380+ in
381381+ let* () =
382382+ if Int32.equal partitions_crc32' partitions_crc32 then Ok ()
383383+ else Error "Bad partition table checksum"
384384+ in
385385+ let rev_partitions =
386386+ List.fold_left
387387+ (fun acc i ->
388388+ let entry = Partition.unmarshal table_buf (i * Partition.sizeof) in
389389+ if Partition.is_zero_partition entry then acc else entry :: acc)
390390+ []
391391+ (List.init (Int32.to_int num_partition_entries) Fun.id)
392392+ in
393393+ let partitions = List.rev rev_partitions in
394394+ Ok
395395+ {
396396+ revision;
397397+ header_size;
398398+ header_crc32;
399399+ reserved;
400400+ current_lba;
401401+ backup_lba;
402402+ first_usable_lba;
403403+ last_usable_lba;
404404+ disk_guid;
405405+ partition_entry_lba;
406406+ num_partition_entries;
407407+ partitions;
408408+ partition_size;
409409+ partitions_crc32;
410410+ } )
411411+412412+let of_string s ~sector_size = unmarshal_bytes (Bytes.of_string s) ~sector_size
413413+414414+let marshal_header_to_bytes ~sector_size ~primary t =
415415+ let buf = Bytes.create sector_size in
416416+ Bytes.fill buf 0 sector_size '\000';
417417+ let t =
418418+ if primary then t
419419+ else
420420+ let t = { t with current_lba = t.backup_lba; backup_lba = t.current_lba } in
421421+ { t with header_crc32 = Optint.to_int32 (calculate_header_crc32 t) }
422422+ in
423423+ marshal_header_bytes buf t;
424424+ set_u32_le buf header_crc32_offset t.header_crc32;
425425+ Bytes.to_string buf
426426+427427+let marshal_partition_table_to_string ~sector_size t =
428428+ let table_size = Int32.to_int t.num_partition_entries * Partition.sizeof in
429429+ let padded_size =
430430+ ((table_size + sector_size - 1) / sector_size) * sector_size
431431+ in
432432+ let buf = Bytes.create padded_size in
433433+ Bytes.fill buf 0 padded_size '\000';
434434+ List.iteri
435435+ (fun i p -> Partition.marshal buf (i * Int32.to_int t.partition_size) p)
436436+ t.partitions;
437437+ Bytes.to_string buf
438438+439439+let read reader ~sector_size =
440440+ let* header_buf = read_exactly reader sector_size in
441441+ let* (`Read_partition_table (partition_lba, num_sectors), k) =
442442+ unmarshal_bytes header_buf ~sector_size
443443+ in
444444+ ignore partition_lba;
445445+ let table_size = num_sectors * sector_size in
446446+ let* table_buf = read_exactly reader table_size in
447447+ k table_buf
448448+449449+let write_header writer ~sector_size ~primary t =
450450+ let s = marshal_header_to_bytes ~sector_size ~primary t in
451451+ Writer.write writer (Slice.of_string s)
452452+453453+let write_partition_table writer ~sector_size t =
454454+ let s = marshal_partition_table_to_string ~sector_size t in
455455+ Writer.write writer (Slice.of_string s)
456456+457457+let protective_mbr ~sector_size t =
458458+ if sector_size < 512 || sector_size land 511 <> 0 then
459459+ invalid_arg "Gpt.protective_mbr";
460460+ let factor = sector_size / 512 in
461461+ let partition =
462462+ let size =
463463+ Int64.(
464464+ to_int32 (min 0xFFFFFFFFL (mul (of_int factor) (succ t.last_usable_lba))))
465465+ in
466466+ Mbr.Partition.make ~active:true ~partition_type:0xEE 1l size |> Result.get_ok
467467+ in
468468+ Mbr.make [ partition ] |> Result.get_ok
+119
lib/gpt.mli
···11+(*
22+ * Copyright (C) 2013 Citrix Inc
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ *)
1616+1717+(** GPT (GUID Partition Table) format. *)
1818+1919+module Partition : sig
2020+ type t = private {
2121+ type_guid : Uuidm.t;
2222+ partition_guid : Uuidm.t;
2323+ starting_lba : int64;
2424+ ending_lba : int64;
2525+ attributes : int64;
2626+ name : string;
2727+ }
2828+ (** A GPT partition entry. [name] should be a zero-padded UTF-16LE encoded
2929+ string of 72 bytes. *)
3030+3131+ val make :
3232+ ?name:string ->
3333+ type_guid:Uuidm.t ->
3434+ attributes:int64 ->
3535+ int64 ->
3636+ int64 ->
3737+ (t, string) result
3838+ (** [make ?name ~type_guid ~attributes starting_lba ending_lba] constructs a
3939+ partition entry.
4040+ @raise Invalid_argument if [name] is not exactly 72 bytes *)
4141+4242+ val is_zero_partition : t -> bool
4343+ (** [is_zero_partition p] is [true] if [p] is the all-zero partition entry
4444+ (unused slot). *)
4545+4646+ val sizeof : int
4747+ (** Size of a partition entry in bytes (128). *)
4848+end
4949+5050+type t = private {
5151+ revision : int32;
5252+ header_size : int32;
5353+ header_crc32 : int32;
5454+ reserved : int32;
5555+ current_lba : int64;
5656+ backup_lba : int64;
5757+ first_usable_lba : int64;
5858+ last_usable_lba : int64;
5959+ disk_guid : Uuidm.t;
6060+ partition_entry_lba : int64;
6161+ num_partition_entries : int32;
6262+ partition_size : int32;
6363+ partitions_crc32 : int32;
6464+ partitions : Partition.t list;
6565+}
6666+(** A GPT header with partition table. *)
6767+6868+val make :
6969+ ?disk_guid:Uuidm.t ->
7070+ disk_sectors:int64 ->
7171+ sector_size:int ->
7272+ Partition.t list ->
7373+ (t, string) result
7474+(** [make ?disk_guid ~disk_sectors ~sector_size partitions] constructs a GPT.
7575+ The header is written at LBA 1, partition table from LBA 2 with 128 entries.
7676+ Returns [Error _] if partitions exceed 128 or overlap. *)
7777+7878+val sizeof : int
7979+(** Size of a GPT header in bytes (92). *)
8080+8181+(** {1 Serialization} *)
8282+8383+val marshal_header_to_bytes : sector_size:int -> primary:bool -> t -> string
8484+(** [marshal_header_to_bytes ~sector_size ~primary t] serializes the GPT header.
8585+ If [primary] is false, swaps current/backup LBA and recalculates CRC. *)
8686+8787+val marshal_partition_table_to_string : sector_size:int -> t -> string
8888+(** [marshal_partition_table_to_string ~sector_size t] serializes the partition
8989+ table. *)
9090+9191+(** {1 Streaming API} *)
9292+9393+val of_string :
9494+ string ->
9595+ sector_size:int ->
9696+ ( [ `Read_partition_table of int64 * int ] * (bytes -> (t, string) result),
9797+ string )
9898+ result
9999+(** [of_string s ~sector_size] parses a GPT header. Returns a pair
100100+ [(`Read_partition_table (lba, num_sectors), k)] where the caller should read
101101+ the partition table and pass it to continuation [k]. *)
102102+103103+val read : Bytesrw.Bytes.Reader.t -> sector_size:int -> (t, string) result
104104+(** [read reader ~sector_size] reads a GPT header and partition table from a
105105+ bytesrw reader. Assumes header is at current position followed by partition
106106+ table. *)
107107+108108+val write_header :
109109+ Bytesrw.Bytes.Writer.t -> sector_size:int -> primary:bool -> t -> unit
110110+(** [write_header writer ~sector_size ~primary t] writes the GPT header. If
111111+ [primary] is false, swaps current/backup LBA and recalculates CRC. *)
112112+113113+val write_partition_table :
114114+ Bytesrw.Bytes.Writer.t -> sector_size:int -> t -> unit
115115+(** [write_partition_table writer ~sector_size t] writes the partition table. *)
116116+117117+val protective_mbr : sector_size:int -> t -> Mbr.t
118118+(** [protective_mbr ~sector_size t] creates a protective MBR for GPT.
119119+ @raise Invalid_argument if [sector_size] is not a positive multiple of 512. *)