Skip to main content

hadris_part/
lib.rs

1//! Partition table support for MBR, GPT, and Hybrid MBR.
2//!
3//! This crate provides types and utilities for working with disk partition tables:
4//!
5//! - **MBR (Master Boot Record)**: Legacy BIOS partition table format supporting up to 4 primary
6//!   partitions. See the [`mbr`] module.
7//!
8//! - **GPT (GUID Partition Table)**: Modern UEFI partition table format supporting up to 128
9//!   partitions with GUIDs for type identification. See the [`gpt`] module.
10//!
11//! - **Hybrid MBR**: A non-standard configuration that combines GPT with MBR entries for
12//!   dual BIOS/UEFI compatibility. See the [`hybrid`] module.
13//!
14//! # Features
15//!
16//! - `std` (default): Enables standard library support and includes `alloc`.
17//! - `read` (default): Enables reading partition tables via `*ReadExt` traits.
18//! - `alloc`: Enables heap allocation for `Vec`-based APIs (e.g., `GptDisk`, `PartitionTable`).
19//! - `write`: Enables writing partition tables (requires `alloc` + `read`).
20//! - `sync` / `async`: Synchronous or asynchronous I/O traits (via `hadris-io`).
21//! - `crc`: Enables CRC32 verification/calculation for GPT headers (via the `crc` crate).
22//! - `rand`: Enables random GUID generation (via the `rand` crate).
23//!
24//! `std` does not select an I/O mode. The default feature set enables `sync`
25//! explicitly; custom configurations should enable `sync`, `async`, or both.
26//!
27//! # Examples
28//!
29//! ## Creating a protective MBR for a GPT disk
30//!
31//! ```rust
32//! use hadris_part::mbr::MasterBootRecord;
33//!
34//! // Create a protective MBR for a 1TB disk (in 512-byte sectors)
35//! let disk_sectors = 1_953_525_168u64; // ~1TB
36//! let mbr = MasterBootRecord::protective(disk_sectors);
37//!
38//! assert!(mbr.has_valid_signature());
39//! assert!(mbr.get_partition_table().is_protective());
40//! ```
41//!
42//! ## Working with GPT partition entries
43//!
44//! ```rust
45//! use hadris_part::gpt::{Guid, GptPartitionEntry};
46//!
47//! // Create an EFI System Partition entry
48//! let esp = GptPartitionEntry::new(
49//!     Guid::EFI_SYSTEM,
50//!     Guid::UNUSED, // Would normally be a unique GUID
51//!     2048,         // Start at 1MB (2048 * 512 bytes)
52//!     206847,       // ~100MB partition
53//! );
54//!
55//! assert!(!esp.is_unused());
56//! assert_eq!(esp.size_sectors(), 204800);
57//! ```
58
59#![no_std]
60#![deny(missing_docs)]
61#![allow(async_fn_in_trait)]
62// Sync and async APIs intentionally compile the same source modules twice.
63#![allow(clippy::duplicate_mod)]
64#![cfg_attr(docsrs, feature(doc_cfg))]
65
66#[cfg(feature = "alloc")]
67extern crate alloc;
68
69#[cfg(feature = "std")]
70extern crate std;
71
72// ---------------------------------------------------------------------------
73// Shared types (compiled once, not duplicated by sync/async modules)
74// ---------------------------------------------------------------------------
75
76pub mod error;
77pub mod geometry;
78pub mod gpt;
79pub mod hybrid;
80pub mod mbr;
81pub mod scheme;
82
83// ---------------------------------------------------------------------------
84// Sync module
85// ---------------------------------------------------------------------------
86
87#[cfg(feature = "sync")]
88#[path = ""]
89pub mod sync {
90    //! Synchronous partition table API.
91    //!
92    //! All I/O operations use synchronous `Read`/`Write`/`Seek` traits.
93
94    pub use hadris_io::Result as IoResult;
95    pub use hadris_io::sync::{Parsable, Read, ReadExt, Seek, Writable, Write};
96    pub use hadris_io::{Error, ErrorKind, SeekFrom};
97
98    macro_rules! io_transform {
99        ($($item:tt)*) => { hadris_macros::strip_async!{ $($item)* } };
100    }
101
102    #[allow(unused_macros)]
103    macro_rules! sync_only {
104        ($($item:tt)*) => { $($item)* };
105    }
106
107    #[allow(unused_macros)]
108    macro_rules! async_only {
109        ($($item:tt)*) => {};
110    }
111
112    #[path = "."]
113    mod __inner {
114        /// GPT parsing and serialization extensions.
115        pub mod gpt_io;
116        /// MBR parsing and serialization extensions.
117        pub mod mbr_io;
118        #[cfg(all(feature = "alloc", feature = "read"))]
119        #[path = "partition_table_io.rs"]
120        /// Generic partition-table detection and I/O.
121        pub mod partition_table;
122        /// Partition-scheme parsing and serialization extensions.
123        pub mod scheme_io;
124    }
125    pub use __inner::*;
126}
127
128// ---------------------------------------------------------------------------
129// Async module
130// ---------------------------------------------------------------------------
131
132#[cfg(feature = "async")]
133#[path = ""]
134pub mod r#async {
135    //! Asynchronous partition table API.
136    //!
137    //! All I/O operations use async `Read`/`Write`/`Seek` traits.
138
139    pub use hadris_io::Result as IoResult;
140    pub use hadris_io::r#async::{Parsable, Read, ReadExt, Seek, Writable, Write};
141    pub use hadris_io::{Error, ErrorKind, SeekFrom};
142
143    macro_rules! io_transform {
144        ($($item:tt)*) => { $($item)* };
145    }
146
147    #[allow(unused_macros)]
148    macro_rules! sync_only {
149        ($($item:tt)*) => {};
150    }
151
152    #[allow(unused_macros)]
153    macro_rules! async_only {
154        ($($item:tt)*) => { $($item)* };
155    }
156
157    #[path = "."]
158    mod __inner {
159        /// GPT parsing and serialization extensions.
160        pub mod gpt_io;
161        /// MBR parsing and serialization extensions.
162        pub mod mbr_io;
163        #[cfg(all(feature = "alloc", feature = "read"))]
164        #[path = "partition_table_io.rs"]
165        /// Generic partition-table detection and I/O.
166        pub mod partition_table;
167        /// Partition-scheme parsing and serialization extensions.
168        pub mod scheme_io;
169    }
170    pub use __inner::*;
171}
172
173// ---------------------------------------------------------------------------
174// Default re-exports for backwards compatibility (sync)
175// ---------------------------------------------------------------------------
176
177#[cfg(feature = "sync")]
178pub use sync::*;
179
180// Re-export commonly used types at the crate root
181pub use endian_num::Le;
182pub use error::{Error, Result};
183pub use geometry::{DiskGeometry, validate_partition_alignment};
184pub use gpt::{GptHeader, GptPartitionEntry, Guid};
185pub use mbr::{Chs, MasterBootRecord, MbrPartition, MbrPartitionTable, MbrPartitionType};
186pub use scheme::{PartitionInfo, PartitionSchemeType, PartitionType};
187
188#[cfg(feature = "alloc")]
189#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
190pub use scheme::{GptDisk, PartitionTable};
191
192// Flatten I/O extension traits to the crate root for discoverability
193#[cfg(all(feature = "sync", feature = "read"))]
194#[cfg_attr(docsrs, doc(cfg(feature = "read")))]
195pub use sync::gpt_io::GptHeaderReadExt;
196#[cfg(all(feature = "sync", feature = "write"))]
197#[cfg_attr(docsrs, doc(cfg(feature = "write")))]
198pub use sync::gpt_io::GptHeaderWriteExt;
199#[cfg(all(feature = "sync", feature = "read"))]
200#[cfg_attr(docsrs, doc(cfg(feature = "read")))]
201pub use sync::mbr_io::MasterBootRecordReadExt;
202#[cfg(all(feature = "sync", feature = "write"))]
203#[cfg_attr(docsrs, doc(cfg(feature = "write")))]
204pub use sync::mbr_io::MasterBootRecordWriteExt;
205#[cfg(all(feature = "sync", feature = "alloc", feature = "read"))]
206#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "read"))))]
207pub use sync::scheme_io::{GptDiskReadExt, PartitionTableReadExt};
208#[cfg(all(feature = "sync", feature = "alloc", feature = "write"))]
209#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "write"))))]
210pub use sync::scheme_io::{GptDiskWriteExt, PartitionTableWriteExt};
211
212/// Trait for types that represent partition information.
213///
214/// This trait provides a common interface for accessing basic partition properties
215/// regardless of the underlying partition table format (MBR or GPT).
216pub trait PartitionInfoTrait {
217    /// Returns the starting LBA of the partition.
218    fn start_lba(&self) -> u64;
219
220    /// Returns the size of the partition in sectors.
221    fn size_sectors(&self) -> u64;
222
223    /// Returns the ending LBA of the partition (inclusive).
224    ///
225    /// Saturates to `u64::MAX` on corrupt inputs where
226    /// `start_lba + size_sectors` would overflow.
227    fn end_lba(&self) -> u64 {
228        let size = self.size_sectors();
229        if size == 0 {
230            self.start_lba()
231        } else {
232            self.start_lba().saturating_add(size - 1)
233        }
234    }
235
236    /// Returns the inclusive ending LBA, or `None` if it overflows.
237    fn checked_end_lba(&self) -> Option<u64> {
238        let size = self.size_sectors();
239        if size == 0 {
240            Some(self.start_lba())
241        } else {
242            self.start_lba().checked_add(size - 1)
243        }
244    }
245
246    /// Returns the partition length in bytes for an explicit logical block size.
247    fn byte_len(&self, logical_block_size: u32) -> Option<u64> {
248        self.size_sectors().checked_mul(logical_block_size as u64)
249    }
250}
251
252impl PartitionInfoTrait for MbrPartition {
253    fn start_lba(&self) -> u64 {
254        self.start_lba.to_ne() as u64
255    }
256
257    fn size_sectors(&self) -> u64 {
258        self.sector_count.to_ne() as u64
259    }
260}
261
262impl PartitionInfoTrait for GptPartitionEntry {
263    fn start_lba(&self) -> u64 {
264        self.first_lba.to_ne()
265    }
266
267    fn size_sectors(&self) -> u64 {
268        let first = self.first_lba.to_ne();
269        let last = self.last_lba.to_ne();
270        if self.is_unused() || last < first {
271            0
272        } else {
273            // Saturating: first=0/last=u64::MAX is representable on disk but
274            // its size exceeds u64.
275            (last - first).saturating_add(1)
276        }
277    }
278
279    fn end_lba(&self) -> u64 {
280        self.last_lba.to_ne()
281    }
282}
283
284impl PartitionInfoTrait for PartitionInfo {
285    fn start_lba(&self) -> u64 {
286        self.start_lba
287    }
288
289    fn size_sectors(&self) -> u64 {
290        self.size_sectors
291    }
292
293    fn end_lba(&self) -> u64 {
294        self.end_lba
295    }
296}
297
298/// Trait for partition table types that support reading.
299pub trait PartitionTableRead {
300    /// The partition entry type for this table.
301    type Partition: PartitionInfoTrait;
302
303    /// Returns the number of partitions in the table.
304    fn partition_count(&self) -> usize;
305
306    /// Returns a reference to a partition by index.
307    fn partition(&self, index: usize) -> Option<&Self::Partition>;
308}
309
310impl PartitionTableRead for MbrPartitionTable {
311    type Partition = MbrPartition;
312
313    fn partition_count(&self) -> usize {
314        self.count()
315    }
316
317    fn partition(&self, index: usize) -> Option<&Self::Partition> {
318        if index < 4 && !self.partitions[index].is_empty() {
319            Some(&self.partitions[index])
320        } else {
321            None
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn test_mbr_partition_trait() {
332        let partition = MbrPartition::new(MbrPartitionType::Fat32, 2048, 204800);
333        assert_eq!(partition.start_lba(), 2048);
334        assert_eq!(partition.size_sectors(), 204800);
335        assert_eq!(partition.end_lba(), 2048 + 204800 - 1);
336        assert_eq!(partition.byte_len(512), Some(204800 * 512));
337    }
338
339    #[test]
340    fn test_gpt_partition_trait() {
341        let partition = GptPartitionEntry::new(Guid::EFI_SYSTEM, Guid::UNUSED, 2048, 206847);
342        assert_eq!(partition.start_lba(), 2048);
343        assert_eq!(partition.size_sectors(), 204800);
344        assert_eq!(partition.end_lba(), 206847);
345    }
346
347    #[test]
348    fn test_mbr_table_read_trait() {
349        let mut table = MbrPartitionTable::new();
350        table[0] = MbrPartition::new(MbrPartitionType::Fat32, 2048, 204800);
351        table[1] = MbrPartition::new(MbrPartitionType::LinuxNative, 206848, 1000000);
352
353        assert_eq!(table.partition_count(), 2);
354        assert!(table.partition(0).is_some());
355        assert!(table.partition(1).is_some());
356        assert!(table.partition(2).is_none());
357        assert!(table.partition(3).is_none());
358    }
359
360    #[test]
361    fn test_struct_sizes() {
362        // Verify that our structures have the expected sizes
363        assert_eq!(core::mem::size_of::<MbrPartition>(), 16);
364        assert_eq!(core::mem::size_of::<MbrPartitionTable>(), 64);
365        assert_eq!(core::mem::size_of::<MasterBootRecord>(), 512);
366        // GptHeader uses native alignment so it may be larger than 92 bytes.
367        // The on-disk format is 92 bytes; serialization should handle this.
368        assert!(core::mem::size_of::<GptHeader>() >= 92);
369        assert_eq!(core::mem::size_of::<GptPartitionEntry>(), 128);
370    }
371}