io_transform! {
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(all(feature = "alloc", feature = "read"))]
use super::super::Read;
#[cfg(all(feature = "alloc", any(feature = "read", feature = "write")))]
use super::super::{Seek, SeekFrom};
#[cfg(all(feature = "alloc", feature = "write"))]
use super::super::Write;
#[cfg(all(feature = "alloc", any(feature = "read", feature = "write")))]
use crate::error::{Error, Result};
#[cfg(all(feature = "alloc", feature = "read"))]
use crate::gpt::{GptHeader, GptPartitionEntry};
#[cfg(all(feature = "alloc", feature = "read"))]
use crate::mbr::MasterBootRecord;
#[cfg(all(feature = "alloc", feature = "read"))]
use crate::scheme::{PartitionSchemeType, detect_scheme_from_mbr};
#[cfg(all(feature = "alloc", feature = "read"))]
use super::gpt_io::GptHeaderReadExt;
#[cfg(all(feature = "alloc", feature = "write"))]
use super::gpt_io::GptHeaderWriteExt;
#[cfg(all(feature = "alloc", feature = "read"))]
use super::mbr_io::MasterBootRecordReadExt;
#[cfg(all(feature = "alloc", feature = "write"))]
use super::mbr_io::MasterBootRecordWriteExt;
#[cfg(feature = "alloc")]
use crate::scheme::GptDisk;
#[cfg(feature = "alloc")]
use crate::scheme::PartitionTable;
#[cfg(all(feature = "alloc", feature = "read"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "read"))))]
pub trait GptDiskReadExt: Sized {
async fn read_from<R: Read + Seek>(
reader: &mut R,
block_size: u32,
) -> Result<Self>;
}
#[cfg(all(feature = "alloc", feature = "read"))]
impl GptDiskReadExt for GptDisk {
async fn read_from<R: Read + Seek>(
reader: &mut R,
block_size: u32,
) -> Result<Self> {
if block_size < GptHeader::STANDARD_HEADER_SIZE {
return Err(Error::InvalidBlockSize {
size: block_size,
minimum: GptHeader::STANDARD_HEADER_SIZE,
});
}
let primary_header = GptHeader::read_from_lba(reader, 1, block_size).await?;
#[cfg(feature = "crc")]
if !primary_header.verify_crc32() {
return Err(Error::GptHeaderCrcMismatch {
expected: primary_header.header_crc32.to_ne(),
actual: primary_header.calculate_crc32(),
});
}
let entry_size = primary_header.size_of_partition_entry.to_ne();
if entry_size != core::mem::size_of::<GptPartitionEntry>() as u32 {
return Err(Error::InvalidPartitionEntrySize { size: entry_size });
}
let num_entries = primary_header.num_partition_entries.to_ne() as usize;
let image_len = reader
.seek(SeekFrom::End(0))
.await
.map_err(Error::from)?;
let entry_array = primary_header
.partition_entry_lba
.to_ne()
.checked_mul(u64::from(block_size))
.and_then(|start| {
start
.checked_add(num_entries as u64 * u64::from(entry_size))
.map(|end| (start, end))
});
let available = image_len / u64::from(block_size);
let Some((entries_start, entries_end)) = entry_array else {
return Err(Error::DiskTooSmall {
required: u64::MAX,
available,
});
};
if entries_end > image_len {
return Err(Error::DiskTooSmall {
required: entries_end.div_ceil(u64::from(block_size)),
available,
});
}
let mut entries = alloc::vec![GptPartitionEntry::default(); num_entries];
reader
.seek(SeekFrom::Start(entries_start))
.await
.map_err(Error::from)?;
for entry in entries.iter_mut() {
let mut buf = [0u8; 128];
reader
.read_exact(&mut buf)
.await
.map_err(Error::from)?;
*entry = bytemuck::cast(buf);
}
#[cfg(feature = "crc")]
{
let entries_crc = crate::gpt::calculate_partition_array_crc32(&entries);
if primary_header.partition_entry_array_crc32.to_ne() != entries_crc {
return Err(Error::GptEntriesCrcMismatch {
expected: primary_header.partition_entry_array_crc32.to_ne(),
actual: entries_crc,
});
}
}
let backup_lba = primary_header.alternate_lba.to_ne();
let backup_header = match GptHeader::read_from_lba(reader, backup_lba, block_size).await {
Ok(header) => header,
Err(Error::Io(source)) => {
return Err(Error::BackupHeaderIo {
lba: backup_lba,
source,
});
}
Err(Error::InvalidGptSignature { found }) => {
return Err(Error::InvalidBackupGptSignature { found });
}
Err(error) => return Err(error),
};
#[cfg(feature = "crc")]
if !backup_header.verify_crc32() {
return Err(Error::BackupGptHeaderCrcMismatch {
expected: backup_header.header_crc32.to_ne(),
actual: backup_header.calculate_crc32(),
});
}
let entry_array_bytes = u64::from(primary_header.num_partition_entries.to_ne())
.checked_mul(u64::from(primary_header.size_of_partition_entry.to_ne()))
.ok_or(Error::BackupHeaderMismatch)?;
let entry_array_blocks = entry_array_bytes.div_ceil(u64::from(block_size));
let expected_backup_entries_lba = backup_lba
.checked_sub(entry_array_blocks)
.ok_or(Error::BackupHeaderMismatch)?;
if backup_header.my_lba != primary_header.alternate_lba
|| backup_header.alternate_lba != primary_header.my_lba
|| backup_header.revision != primary_header.revision
|| backup_header.header_size != primary_header.header_size
|| backup_header.first_usable_lba != primary_header.first_usable_lba
|| backup_header.last_usable_lba != primary_header.last_usable_lba
|| backup_header.disk_guid != primary_header.disk_guid
|| backup_header.num_partition_entries != primary_header.num_partition_entries
|| backup_header.size_of_partition_entry != primary_header.size_of_partition_entry
|| backup_header.partition_entry_array_crc32
!= primary_header.partition_entry_array_crc32
|| backup_header.partition_entry_lba.to_ne() != expected_backup_entries_lba
{
return Err(Error::BackupHeaderMismatch);
}
Ok(Self {
primary_header,
backup_header,
entries,
block_size,
})
}
}
#[cfg(all(feature = "alloc", feature = "write"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "write"))))]
pub trait GptDiskWriteExt {
async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()>;
async fn write_to_with_mbr<W: Write + Seek>(
&self,
writer: &mut W,
mbr: &MasterBootRecord,
) -> Result<()>;
}
#[cfg(all(feature = "alloc", feature = "write"))]
impl GptDiskWriteExt for GptDisk {
async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
writer
.seek(SeekFrom::Start(0))
.await
.map_err(Error::from)?;
let protective_mbr = self.create_protective_mbr();
protective_mbr.write_to(writer).await?;
self.primary_header
.write_to_lba(writer, 1, self.block_size)
.await?;
let Some(primary_entries_offset) = self
.primary_header
.partition_entry_lba
.to_ne()
.checked_mul(u64::from(self.block_size))
else {
return Err(Error::lba_offset_overflow());
};
writer
.seek(SeekFrom::Start(primary_entries_offset))
.await
.map_err(Error::from)?;
for entry in &self.entries {
writer
.write_all(bytemuck::bytes_of(entry))
.await
.map_err(Error::from)?;
}
let Some(backup_entries_offset) = self
.backup_header
.partition_entry_lba
.to_ne()
.checked_mul(u64::from(self.block_size))
else {
return Err(Error::lba_offset_overflow());
};
writer
.seek(SeekFrom::Start(backup_entries_offset))
.await
.map_err(Error::from)?;
for entry in &self.entries {
writer
.write_all(bytemuck::bytes_of(entry))
.await
.map_err(Error::from)?;
}
self.backup_header
.write_to_lba(writer, self.backup_header.my_lba.to_ne(), self.block_size)
.await?;
Ok(())
}
async fn write_to_with_mbr<W: Write + Seek>(
&self,
writer: &mut W,
mbr: &MasterBootRecord,
) -> Result<()> {
writer
.seek(SeekFrom::Start(0))
.await
.map_err(Error::from)?;
mbr.write_to(writer).await?;
self.primary_header
.write_to_lba(writer, 1, self.block_size)
.await?;
let Some(primary_entries_offset) = self
.primary_header
.partition_entry_lba
.to_ne()
.checked_mul(u64::from(self.block_size))
else {
return Err(Error::lba_offset_overflow());
};
writer
.seek(SeekFrom::Start(primary_entries_offset))
.await
.map_err(Error::from)?;
for entry in &self.entries {
writer
.write_all(bytemuck::bytes_of(entry))
.await
.map_err(Error::from)?;
}
let Some(backup_entries_offset) = self
.backup_header
.partition_entry_lba
.to_ne()
.checked_mul(u64::from(self.block_size))
else {
return Err(Error::lba_offset_overflow());
};
writer
.seek(SeekFrom::Start(backup_entries_offset))
.await
.map_err(Error::from)?;
for entry in &self.entries {
writer
.write_all(bytemuck::bytes_of(entry))
.await
.map_err(Error::from)?;
}
self.backup_header
.write_to_lba(writer, self.backup_header.my_lba.to_ne(), self.block_size)
.await?;
Ok(())
}
}
#[cfg(all(feature = "alloc", feature = "read"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "read"))))]
pub trait PartitionTableReadExt: Sized {
async fn read_from<R: Read + Seek>(
reader: &mut R,
block_size: u32,
) -> Result<Self>;
}
#[cfg(all(feature = "alloc", feature = "read"))]
impl PartitionTableReadExt for PartitionTable {
async fn read_from<R: Read + Seek>(
reader: &mut R,
block_size: u32,
) -> Result<Self> {
reader
.seek(SeekFrom::Start(0))
.await
.map_err(Error::from)?;
let mbr = MasterBootRecord::read_from(reader).await?;
let scheme_type = detect_scheme_from_mbr(&mbr);
match scheme_type {
PartitionSchemeType::Mbr => Ok(Self::Mbr(mbr)),
PartitionSchemeType::Gpt => {
let gpt = GptDisk::read_from(reader, block_size).await?;
Ok(Self::Gpt {
protective_mbr: mbr,
gpt,
})
}
PartitionSchemeType::Hybrid => {
let gpt = GptDisk::read_from(reader, block_size).await?;
Ok(Self::Hybrid {
hybrid_mbr: mbr,
gpt,
})
}
}
}
}
#[cfg(all(feature = "alloc", feature = "write"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "write"))))]
pub trait PartitionTableWriteExt {
async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()>;
}
#[cfg(all(feature = "alloc", feature = "write"))]
impl PartitionTableWriteExt for PartitionTable {
async fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
match self {
Self::Mbr(mbr) => {
writer
.seek(SeekFrom::Start(0))
.await
.map_err(Error::from)?;
mbr.write_to(writer).await
}
Self::Gpt { gpt, .. } => gpt.write_to(writer).await,
Self::Hybrid { hybrid_mbr, gpt } => gpt.write_to_with_mbr(writer, hybrid_mbr).await,
}
}
}
}