file_system/mbr/partition/
entry.rs

1//! Partition entry structures for MBR partition tables.
2//!
3//! This module provides the [`PartitionEntry`] structure which represents
4//! individual partition entries in Master Boot Record (MBR) partition tables.
5//! Each entry contains information about a partition's location, size, type, and bootability.
6
7use core::fmt;
8
9use shared::Unit;
10
11use crate::mbr::PartitionKind;
12
13/// MBR partition table entry structure (16 bytes).
14///
15/// This structure represents a single partition entry in an MBR partition table.
16/// Each MBR contains exactly 4 partition entries, defining up to 4 primary partitions.
17/// The structure follows the traditional PC BIOS partition table format.
18///
19/// # Memory Layout
20///
21/// The structure is packed and has a fixed 16-byte layout for MBR compatibility:
22/// - Bytes 0: Boot indicator
23/// - Bytes 1-3: CHS start address (legacy)
24/// - Byte 4: Partition type ID
25/// - Bytes 5-7: CHS end address (legacy)
26/// - Bytes 8-11: LBA start address (little-endian)
27/// - Bytes 12-15: Partition size in sectors (little-endian)
28///
29/// # Examples
30///
31/// ```rust
32/// # extern crate alloc;
33/// use file_system::mbr::{PartitionEntry, PartitionKind};
34///
35/// // Create a new bootable FAT32 partition
36/// let partition = PartitionEntry::new_with_params(
37///     true,
38///     PartitionKind::Fat32Lba,
39///     2048,
40///     204800
41/// );
42///
43/// assert!(partition.bootable);
44/// assert_eq!(partition.start_block, 2048);
45/// assert_eq!(partition.block_count, 204800);
46/// ```
47#[derive(Debug, Clone, Copy)]
48pub struct PartitionEntry {
49    /// Boot indicator
50    pub bootable: bool,
51    /// Starting head
52    pub start_head: u8,
53    /// Starting sector (bits 5-0) and cylinder high bits (bits 7-6)
54    pub start_sector: u8,
55    /// Starting cylinder (low 8 bits)
56    pub start_cylinder: u8,
57    /// Partition type ID
58    pub kind: PartitionKind,
59    /// Ending head
60    pub end_head: u8,
61    /// Ending sector (bits 5-0) and cylinder high bits (bits 7-6)
62    pub end_sector: u8,
63    /// Ending cylinder (low 8 bits)
64    pub end_cylinder: u8,
65    /// Starting LBA (Logical Block Address)
66    pub start_block: u32,
67    /// Size in sectors
68    pub block_count: u32,
69}
70
71impl PartitionEntry {
72    pub const SIZE: usize = 16;
73    pub const BOOTABLE_FLAG: u8 = 0x80;
74
75    pub fn parse(data: &[u8]) -> Option<Self> {
76        if data.len() < Self::SIZE {
77            return None;
78        }
79
80        Some(Self {
81            bootable: data[0] == Self::BOOTABLE_FLAG,
82            start_head: data[1],
83            start_sector: data[2],
84            start_cylinder: data[3],
85            kind: PartitionKind::from_u8(data[4]),
86            end_head: data[5],
87            end_sector: data[6],
88            end_cylinder: data[7],
89            start_block: u32::from_le_bytes([data[8], data[9], data[10], data[11]]),
90            block_count: u32::from_le_bytes([data[12], data[13], data[14], data[15]]),
91        })
92    }
93
94    /// Create a new empty (invalid) partition entry.
95    ///
96    /// All fields are initialized to zero, making this an invalid partition entry
97    /// that will not be recognized by the MBR parser.
98    ///
99    /// # Examples
100    ///
101    /// ```rust
102    /// # extern crate alloc;
103    /// use file_system::mbr::PartitionEntry;
104    ///
105    /// let partition = PartitionEntry::new_empty();
106    /// assert!(!partition.is_valid());
107    /// assert!(!partition.bootable);
108    /// ```
109    pub fn new_empty() -> Self {
110        Self {
111            bootable: false,
112            start_head: 0,
113            start_sector: 0,
114            start_cylinder: 0,
115            kind: PartitionKind::Empty,
116            end_head: 0,
117            end_sector: 0,
118            end_cylinder: 0,
119            start_block: 0,
120            block_count: 0,
121        }
122    }
123
124    /// Create a new partition entry with specified parameters.
125    ///
126    /// This constructor creates a valid partition entry with the specified type,
127    /// location, and size. The CHS (Cylinder-Head-Sector) fields are not set
128    /// as modern systems use LBA addressing.
129    ///
130    /// # Arguments
131    ///
132    /// * `Bootable` - Whether this partition should be marked as bootable
133    /// * `Partition_type` - The type of partition (FAT32, Linux, etc.)
134    /// * `Start_lba` - Starting logical block address (sector number)
135    /// * `Size_sectors` - Size of the partition in 512-byte sectors
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// # extern crate alloc;
141    /// use file_system::mbr::{PartitionEntry, PartitionKind};
142    ///
143    /// // Create a 100MB FAT32 partition starting at sector 2048
144    /// let partition = PartitionEntry::new_with_params(
145    ///     true,
146    ///     PartitionKind::Fat32Lba,
147    ///     2048,
148    ///     204800
149    /// );
150    ///
151    /// assert!(partition.is_valid());
152    /// assert!(partition.bootable);
153    /// ```
154    pub fn new_with_params(
155        bootable: bool,
156        kind: PartitionKind,
157        start_lba: u32,
158        size_sectors: u32,
159    ) -> Self {
160        let mut entry = Self::new_empty();
161        entry.bootable = bootable;
162        entry.kind = kind;
163        entry.start_block = start_lba.to_le();
164        entry.block_count = size_sectors.to_le();
165        entry
166    }
167
168    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
169        let mut data = [0u8; Self::SIZE];
170
171        data[0] = if self.bootable {
172            Self::BOOTABLE_FLAG
173        } else {
174            0x00
175        };
176        data[1] = self.start_head;
177        data[2] = self.start_sector;
178        data[3] = self.start_cylinder;
179        data[4] = self.kind.to_u8();
180        data[5] = self.end_head;
181        data[6] = self.end_sector;
182        data[7] = self.end_cylinder;
183        data[8..12].copy_from_slice(&self.start_block.to_le_bytes());
184        data[12..16].copy_from_slice(&self.block_count.to_le_bytes());
185
186        data
187    }
188
189    /// Check if this partition entry is valid (non-zero)
190    pub fn is_valid(&self) -> bool {
191        self.kind != PartitionKind::Empty && self.block_count > 0
192    }
193
194    /// Check if this partition overlaps with another partition
195    pub fn overlaps_with(&self, other: &Self) -> bool {
196        if !self.is_valid() || !other.is_valid() {
197            return false;
198        }
199
200        let self_start = self.start_block;
201        let self_end = self.start_block + self.block_count - 1;
202        let other_start = other.start_block;
203        let other_end = other.start_block + other.block_count - 1;
204
205        !(self_end < other_start || other_end < self_start)
206    }
207
208    /// Check if a given LBA is within this partition
209    pub fn contains_lba(&self, lba: u32) -> bool {
210        if !self.is_valid() {
211            return false;
212        }
213
214        let start = self.start_block;
215        let end = self.start_block + self.block_count - 1;
216        lba >= start && lba <= end
217    }
218
219    /// Clear the partition entry (make it empty)
220    pub fn clear(&mut self) {
221        *self = Self::new_empty();
222    }
223}
224
225impl Default for PartitionEntry {
226    fn default() -> Self {
227        Self::new_empty()
228    }
229}
230
231impl fmt::Display for PartitionEntry {
232    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
233        if !self.is_valid() {
234            write!(formatter, "Empty partition")
235        } else {
236            write!(
237                formatter,
238                "Partition: Type={:02X} ({}), Start_LBA={}, Size={} sectors ({}), Bootable={}",
239                self.kind.to_u8(),
240                self.kind,
241                self.start_block,
242                self.block_count,
243                Unit::new(self.block_count * 512, "B"),
244                self.bootable
245            )
246        }
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use crate::mbr::Mbr;
253
254    use super::{PartitionEntry, PartitionKind};
255    use alloc::format;
256
257    fn create_test_partition() -> PartitionEntry {
258        PartitionEntry::new_with_params(
259            true,                            // Bootable
260            PartitionKind::Fat32Lba,         // Type
261            Mbr::MINIMUM_START_BLOCK as u32, // Start LBA
262            204800,                          // Size in sectors (100MB)
263        )
264    }
265
266    #[test]
267    fn test_partition_entry_new() {
268        let entry = PartitionEntry::new_empty();
269        assert!(!entry.is_valid());
270        assert!(!entry.bootable);
271        assert_eq!(entry.start_block, 0);
272        assert_eq!(entry.block_count, 0);
273        assert_eq!(entry.kind, PartitionKind::Empty);
274    }
275
276    #[test]
277    fn test_partition_entry_new_with_params() {
278        let entry = create_test_partition();
279        assert!(entry.is_valid());
280        assert!(entry.bootable);
281        assert_eq!(entry.start_block, 2048);
282        assert_eq!(entry.block_count, 204800);
283        assert_eq!(entry.kind, PartitionKind::Fat32Lba);
284    }
285
286    #[test]
287    fn test_partition_entry_overlaps() {
288        let partition1 = PartitionEntry::new_with_params(false, PartitionKind::Fat32, 1000, 2000);
289        let partition2 = PartitionEntry::new_with_params(false, PartitionKind::Linux, 2400, 1000);
290        let partition3 =
291            PartitionEntry::new_with_params(false, PartitionKind::LinuxSwap, 1500, 1000);
292
293        // Partition1: 1000-2999, Partition2: 2400-3399, Partition3: 1500-2499
294        assert!(partition1.overlaps_with(&partition3)); // 1000-2999 overlaps 1500-2499
295        assert!(partition2.overlaps_with(&partition3)); // 2400-3399 overlaps 1500-2499 (overlap: 2400-2499)
296        assert!(partition1.overlaps_with(&partition2)); // 1000-2999 overlaps 2400-3399 (overlap: 2400-2999)
297    }
298
299    #[test]
300    fn test_partition_entry_no_overlap() {
301        let partition1 = PartitionEntry::new_with_params(false, PartitionKind::Fat32, 1000, 1000);
302        let partition2 = PartitionEntry::new_with_params(false, PartitionKind::Linux, 2000, 1000);
303
304        // Partition1: 1000-1999, Partition2: 2000-2999
305        assert!(!partition1.overlaps_with(&partition2));
306        assert!(!partition2.overlaps_with(&partition1));
307    }
308
309    #[test]
310    fn test_partition_entry_contains_lba() {
311        let entry = create_test_partition();
312
313        assert!(!entry.contains_lba(2047)); // Before start
314        assert!(entry.contains_lba(2048)); // At start
315        assert!(entry.contains_lba(100000)); // In middle
316        assert!(entry.contains_lba(206847)); // At end (2048 + 204800 - 1)
317        assert!(!entry.contains_lba(206848)); // After end
318    }
319
320    #[test]
321    fn test_partition_entry_clear() {
322        let mut entry = create_test_partition();
323        assert!(entry.is_valid());
324
325        entry.clear();
326        assert!(!entry.is_valid());
327        assert!(!entry.bootable);
328        assert_eq!(entry.start_block, 0);
329        assert_eq!(entry.block_count, 0);
330    }
331
332    #[test]
333    fn test_partition_entry_default() {
334        let entry = PartitionEntry::default();
335        assert!(!entry.is_valid());
336        assert_eq!(entry.kind, PartitionKind::Empty);
337    }
338
339    #[test]
340    fn test_partition_entry_display() {
341        let entry = create_test_partition();
342        let display_string = format!("{entry}");
343
344        assert!(display_string.contains("Type=0C"));
345        assert!(display_string.contains("FAT32 LBA"));
346        assert!(display_string.contains("Start_LBA=2048"));
347        assert!(display_string.contains("Size=204800"));
348        assert!(display_string.contains("Bootable=true"));
349
350        let empty_entry = PartitionEntry::new_empty();
351        let empty_string = format!("{empty_entry}");
352        assert!(empty_string.contains("Empty partition"));
353    }
354
355    #[test]
356    fn test_partition_entry_validity() {
357        // Valid partition must have non-zero type and size
358        let valid = PartitionEntry::new_with_params(false, PartitionKind::Linux, 100, 200);
359        assert!(valid.is_valid());
360
361        // Zero size makes it invalid
362        let zero_size = PartitionEntry::new_with_params(false, PartitionKind::Linux, 100, 0);
363        assert!(!zero_size.is_valid());
364
365        // Empty type makes it invalid
366        let empty_type = PartitionEntry::new_with_params(false, PartitionKind::Empty, 100, 200);
367        assert!(!empty_type.is_valid());
368    }
369}