Skip to main content

hadris_part/
scheme_io.rs

1io_transform! {
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(all(feature = "alloc", feature = "read"))]
7use super::super::Read;
8#[cfg(all(feature = "alloc", any(feature = "read", feature = "write")))]
9use super::super::{Seek, SeekFrom};
10#[cfg(all(feature = "alloc", feature = "write"))]
11use super::super::Write;
12#[cfg(all(feature = "alloc", any(feature = "read", feature = "write")))]
13use crate::error::{Error, Result};
14#[cfg(all(feature = "alloc", feature = "read"))]
15use crate::gpt::{GptHeader, GptPartitionEntry};
16#[cfg(all(feature = "alloc", feature = "read"))]
17use crate::mbr::MasterBootRecord;
18#[cfg(all(feature = "alloc", feature = "read"))]
19use crate::scheme::{PartitionSchemeType, detect_scheme_from_mbr};
20
21#[cfg(all(feature = "alloc", feature = "read"))]
22use super::gpt_io::GptHeaderReadExt;
23#[cfg(all(feature = "alloc", feature = "write"))]
24use super::gpt_io::GptHeaderWriteExt;
25#[cfg(all(feature = "alloc", feature = "read"))]
26use super::mbr_io::MasterBootRecordReadExt;
27#[cfg(all(feature = "alloc", feature = "write"))]
28use super::mbr_io::MasterBootRecordWriteExt;
29
30#[cfg(feature = "alloc")]
31use crate::scheme::GptDisk;
32
33#[cfg(feature = "alloc")]
34use crate::scheme::PartitionTable;
35
36// I/O operations for GptDisk
37
38/// Extension trait for reading [`GptDisk`] from I/O sources.
39#[cfg(all(feature = "alloc", feature = "read"))]
40#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "read"))))]
41pub trait GptDiskReadExt: Sized {
42    /// Reads a GPT disk structure from a reader.
43    ///
44    /// Reads the primary GPT header at LBA 1 and the partition entry array.
45    /// The reader should be positioned at the beginning of the disk (LBA 0).
46    ///
47    /// # Arguments
48    ///
49    /// * `reader` - The reader to read from
50    /// * `block_size` - The logical block size in bytes (typically 512)
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if reading fails or if the GPT structure is invalid.
55    async fn read_from<R: Read + Seek>(
56        reader: &mut R,
57        block_size: u32,
58    ) -> Result<Self>;
59}
60
61#[cfg(all(feature = "alloc", feature = "read"))]
62impl GptDiskReadExt for GptDisk {
63    async fn read_from<R: Read + Seek>(
64        reader: &mut R,
65        block_size: u32,
66    ) -> Result<Self> {
67        if block_size < GptHeader::STANDARD_HEADER_SIZE {
68            return Err(Error::InvalidBlockSize {
69                size: block_size,
70                minimum: GptHeader::STANDARD_HEADER_SIZE,
71            });
72        }
73
74        // Read primary GPT header at LBA 1
75        let primary_header = GptHeader::read_from_lba(reader, 1, block_size).await?;
76
77        // Validate header CRC if feature enabled
78        #[cfg(feature = "crc")]
79        if !primary_header.verify_crc32() {
80            return Err(Error::GptHeaderCrcMismatch {
81                expected: primary_header.header_crc32.to_ne(),
82                actual: primary_header.calculate_crc32(),
83            });
84        }
85
86        // Validate partition entry size
87        let entry_size = primary_header.size_of_partition_entry.to_ne();
88        if entry_size != core::mem::size_of::<GptPartitionEntry>() as u32 {
89            return Err(Error::InvalidPartitionEntrySize { size: entry_size });
90        }
91
92        // Read partition entries
93        let num_entries = primary_header.num_partition_entries.to_ne() as usize;
94
95        // `num_entries` and `partition_entry_lba` are untrusted on-disk
96        // values; bound the entry array against the image size before
97        // allocating, otherwise a bogus count forces a huge allocation.
98        let image_len = reader
99            .seek(SeekFrom::End(0))
100            .await
101            .map_err(Error::from)?;
102        let entry_array = primary_header
103            .partition_entry_lba
104            .to_ne()
105            .checked_mul(u64::from(block_size))
106            .and_then(|start| {
107                start
108                    .checked_add(num_entries as u64 * u64::from(entry_size))
109                    .map(|end| (start, end))
110            });
111        let available = image_len / u64::from(block_size);
112        let Some((entries_start, entries_end)) = entry_array else {
113            return Err(Error::DiskTooSmall {
114                required: u64::MAX,
115                available,
116            });
117        };
118        if entries_end > image_len {
119            return Err(Error::DiskTooSmall {
120                required: entries_end.div_ceil(u64::from(block_size)),
121                available,
122            });
123        }
124
125        let mut entries = alloc::vec![GptPartitionEntry::default(); num_entries];
126
127        reader
128            .seek(SeekFrom::Start(entries_start))
129            .await
130            .map_err(Error::from)?;
131
132        for entry in entries.iter_mut() {
133            let mut buf = [0u8; 128];
134            reader
135                .read_exact(&mut buf)
136                .await
137                .map_err(Error::from)?;
138            *entry = bytemuck::cast(buf);
139        }
140
141        // Verify partition array CRC if feature enabled
142        #[cfg(feature = "crc")]
143        {
144            let entries_crc = crate::gpt::calculate_partition_array_crc32(&entries);
145            if primary_header.partition_entry_array_crc32.to_ne() != entries_crc {
146                return Err(Error::GptEntriesCrcMismatch {
147                    expected: primary_header.partition_entry_array_crc32.to_ne(),
148                    actual: entries_crc,
149                });
150            }
151        }
152
153        let backup_lba = primary_header.alternate_lba.to_ne();
154        let backup_header = match GptHeader::read_from_lba(reader, backup_lba, block_size).await {
155            Ok(header) => header,
156            Err(Error::Io(source)) => {
157                return Err(Error::BackupHeaderIo {
158                    lba: backup_lba,
159                    source,
160                });
161            }
162            Err(Error::InvalidGptSignature { found }) => {
163                return Err(Error::InvalidBackupGptSignature { found });
164            }
165            Err(error) => return Err(error),
166        };
167
168        #[cfg(feature = "crc")]
169        if !backup_header.verify_crc32() {
170            return Err(Error::BackupGptHeaderCrcMismatch {
171                expected: backup_header.header_crc32.to_ne(),
172                actual: backup_header.calculate_crc32(),
173            });
174        }
175
176        let entry_array_bytes = u64::from(primary_header.num_partition_entries.to_ne())
177            .checked_mul(u64::from(primary_header.size_of_partition_entry.to_ne()))
178            .ok_or(Error::BackupHeaderMismatch)?;
179        let entry_array_blocks = entry_array_bytes.div_ceil(u64::from(block_size));
180        let expected_backup_entries_lba = backup_lba
181            .checked_sub(entry_array_blocks)
182            .ok_or(Error::BackupHeaderMismatch)?;
183
184        if backup_header.my_lba != primary_header.alternate_lba
185            || backup_header.alternate_lba != primary_header.my_lba
186            || backup_header.revision != primary_header.revision
187            || backup_header.header_size != primary_header.header_size
188            || backup_header.first_usable_lba != primary_header.first_usable_lba
189            || backup_header.last_usable_lba != primary_header.last_usable_lba
190            || backup_header.disk_guid != primary_header.disk_guid
191            || backup_header.num_partition_entries != primary_header.num_partition_entries
192            || backup_header.size_of_partition_entry != primary_header.size_of_partition_entry
193            || backup_header.partition_entry_array_crc32
194                != primary_header.partition_entry_array_crc32
195            || backup_header.partition_entry_lba.to_ne() != expected_backup_entries_lba
196        {
197            return Err(Error::BackupHeaderMismatch);
198        }
199
200        Ok(Self {
201            primary_header,
202            backup_header,
203            entries,
204            block_size,
205        })
206    }
207}
208
209/// Extension trait for writing [`GptDisk`] to I/O sinks.
210#[cfg(all(feature = "alloc", feature = "write"))]
211#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "write"))))]
212pub trait GptDiskWriteExt {
213    /// Writes the complete GPT structure to a writer.
214    ///
215    /// Writes:
216    /// 1. Protective MBR at LBA 0
217    /// 2. Primary GPT header at LBA 1
218    /// 3. Primary partition entry array starting at LBA 2
219    /// 4. Backup partition entry array before backup header
220    /// 5. Backup GPT header at the last LBA
221    ///
222    /// # Arguments
223    ///
224    /// * `writer` - The writer to write to
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if writing fails.
229    async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()>;
230
231    /// Writes the complete GPT structure with a custom MBR.
232    ///
233    /// This is useful for hybrid MBR configurations.
234    ///
235    /// # Arguments
236    ///
237    /// * `writer` - The writer to write to
238    /// * `mbr` - The MBR to write (protective or hybrid)
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if writing fails.
243    async fn write_to_with_mbr<W: Write + Seek>(
244        &self,
245        writer: &mut W,
246        mbr: &MasterBootRecord,
247    ) -> Result<()>;
248}
249
250#[cfg(all(feature = "alloc", feature = "write"))]
251impl GptDiskWriteExt for GptDisk {
252    async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
253        // Write protective MBR at LBA 0
254        writer
255            .seek(SeekFrom::Start(0))
256            .await
257            .map_err(Error::from)?;
258        let protective_mbr = self.create_protective_mbr();
259        protective_mbr.write_to(writer).await?;
260
261        // Write primary header at LBA 1
262        self.primary_header
263            .write_to_lba(writer, 1, self.block_size)
264            .await?;
265
266        // Write primary partition entries starting at partition_entry_lba
267        let Some(primary_entries_offset) = self
268            .primary_header
269            .partition_entry_lba
270            .to_ne()
271            .checked_mul(u64::from(self.block_size))
272        else {
273            return Err(Error::lba_offset_overflow());
274        };
275        writer
276            .seek(SeekFrom::Start(primary_entries_offset))
277            .await
278            .map_err(Error::from)?;
279
280        for entry in &self.entries {
281            writer
282                .write_all(bytemuck::bytes_of(entry))
283                .await
284                .map_err(Error::from)?;
285        }
286
287        // Write backup partition entries
288        let Some(backup_entries_offset) = self
289            .backup_header
290            .partition_entry_lba
291            .to_ne()
292            .checked_mul(u64::from(self.block_size))
293        else {
294            return Err(Error::lba_offset_overflow());
295        };
296        writer
297            .seek(SeekFrom::Start(backup_entries_offset))
298            .await
299            .map_err(Error::from)?;
300
301        for entry in &self.entries {
302            writer
303                .write_all(bytemuck::bytes_of(entry))
304                .await
305                .map_err(Error::from)?;
306        }
307
308        // Write backup header at last LBA
309        self.backup_header
310            .write_to_lba(writer, self.backup_header.my_lba.to_ne(), self.block_size)
311            .await?;
312
313        Ok(())
314    }
315
316    async fn write_to_with_mbr<W: Write + Seek>(
317        &self,
318        writer: &mut W,
319        mbr: &MasterBootRecord,
320    ) -> Result<()> {
321        // Write MBR at LBA 0
322        writer
323            .seek(SeekFrom::Start(0))
324            .await
325            .map_err(Error::from)?;
326        mbr.write_to(writer).await?;
327
328        // Write primary header at LBA 1
329        self.primary_header
330            .write_to_lba(writer, 1, self.block_size)
331            .await?;
332
333        // Write primary partition entries
334        let Some(primary_entries_offset) = self
335            .primary_header
336            .partition_entry_lba
337            .to_ne()
338            .checked_mul(u64::from(self.block_size))
339        else {
340            return Err(Error::lba_offset_overflow());
341        };
342        writer
343            .seek(SeekFrom::Start(primary_entries_offset))
344            .await
345            .map_err(Error::from)?;
346
347        for entry in &self.entries {
348            writer
349                .write_all(bytemuck::bytes_of(entry))
350                .await
351                .map_err(Error::from)?;
352        }
353
354        // Write backup partition entries
355        let Some(backup_entries_offset) = self
356            .backup_header
357            .partition_entry_lba
358            .to_ne()
359            .checked_mul(u64::from(self.block_size))
360        else {
361            return Err(Error::lba_offset_overflow());
362        };
363        writer
364            .seek(SeekFrom::Start(backup_entries_offset))
365            .await
366            .map_err(Error::from)?;
367
368        for entry in &self.entries {
369            writer
370                .write_all(bytemuck::bytes_of(entry))
371                .await
372                .map_err(Error::from)?;
373        }
374
375        // Write backup header at last LBA
376        self.backup_header
377            .write_to_lba(writer, self.backup_header.my_lba.to_ne(), self.block_size)
378            .await?;
379
380        Ok(())
381    }
382}
383
384// I/O operations for PartitionTable
385
386/// Extension trait for reading [`PartitionTable`] from I/O sources.
387#[cfg(all(feature = "alloc", feature = "read"))]
388#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "read"))))]
389pub trait PartitionTableReadExt: Sized {
390    /// Detects and reads a partition scheme from a disk image.
391    ///
392    /// This method:
393    /// 1. Reads the MBR at LBA 0
394    /// 2. Detects if it's a protective MBR (GPT) or hybrid MBR
395    /// 3. If protective/hybrid, reads the GPT structure
396    /// 4. Returns the appropriate partition scheme
397    ///
398    /// # Arguments
399    ///
400    /// * `reader` - The reader to read from (should be positioned at LBA 0)
401    /// * `block_size` - The logical block size in bytes (typically 512)
402    ///
403    /// # Errors
404    ///
405    /// Returns an error if reading fails or if the partition structure is invalid.
406    async fn read_from<R: Read + Seek>(
407        reader: &mut R,
408        block_size: u32,
409    ) -> Result<Self>;
410}
411
412#[cfg(all(feature = "alloc", feature = "read"))]
413impl PartitionTableReadExt for PartitionTable {
414    async fn read_from<R: Read + Seek>(
415        reader: &mut R,
416        block_size: u32,
417    ) -> Result<Self> {
418        // Seek to beginning and read MBR
419        reader
420            .seek(SeekFrom::Start(0))
421            .await
422            .map_err(Error::from)?;
423
424        let mbr = MasterBootRecord::read_from(reader).await?;
425        let scheme_type = detect_scheme_from_mbr(&mbr);
426
427        match scheme_type {
428            PartitionSchemeType::Mbr => Ok(Self::Mbr(mbr)),
429            PartitionSchemeType::Gpt => {
430                let gpt = GptDisk::read_from(reader, block_size).await?;
431                Ok(Self::Gpt {
432                    protective_mbr: mbr,
433                    gpt,
434                })
435            }
436            PartitionSchemeType::Hybrid => {
437                let gpt = GptDisk::read_from(reader, block_size).await?;
438                Ok(Self::Hybrid {
439                    hybrid_mbr: mbr,
440                    gpt,
441                })
442            }
443        }
444    }
445}
446
447/// Extension trait for writing [`PartitionTable`] to I/O sinks.
448#[cfg(all(feature = "alloc", feature = "write"))]
449#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "write"))))]
450pub trait PartitionTableWriteExt {
451    /// Writes the partition scheme to a writer.
452    ///
453    /// # Arguments
454    ///
455    /// * `writer` - The writer to write to
456    ///
457    /// # Errors
458    ///
459    /// Returns an error if writing fails.
460    async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()>;
461}
462
463#[cfg(all(feature = "alloc", feature = "write"))]
464impl PartitionTableWriteExt for PartitionTable {
465    async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
466        match self {
467            Self::Mbr(mbr) => {
468                writer
469                    .seek(SeekFrom::Start(0))
470                    .await
471                    .map_err(Error::from)?;
472                mbr.write_to(writer).await
473            }
474            Self::Gpt { gpt, .. } => gpt.write_to(writer).await,
475            Self::Hybrid { hybrid_mbr, gpt } => gpt.write_to_with_mbr(writer, hybrid_mbr).await,
476        }
477    }
478}
479
480} // io_transform!