#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use crate::error::{PartitionError, Result};
use crate::gpt::Guid;
#[cfg(feature = "alloc")]
use crate::gpt::{GptHeader, GptPartitionEntry};
use crate::hybrid::is_hybrid_mbr;
use crate::mbr::MasterBootRecord;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartitionSchemeType {
Mbr,
Gpt,
Hybrid,
}
impl core::fmt::Display for PartitionSchemeType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Mbr => write!(f, "MBR"),
Self::Gpt => write!(f, "GPT"),
Self::Hybrid => write!(f, "Hybrid MBR"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PartitionInfo {
pub index: usize,
pub start_lba: u64,
pub end_lba: u64,
pub size_sectors: u64,
pub bootable: bool,
pub partition_type: PartitionType,
}
#[derive(Debug, Clone, Copy)]
pub enum PartitionType {
Mbr(u8),
Gpt(Guid),
}
impl PartitionInfo {
pub const fn size_bytes(&self) -> u64 {
self.size_sectors * 512
}
pub const fn size_bytes_with_sector_size(&self, sector_size: u32) -> u64 {
self.size_sectors * sector_size as u64
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct GptDisk {
pub primary_header: GptHeader,
pub backup_header: GptHeader,
pub entries: Vec<GptPartitionEntry>,
pub block_size: u32,
}
#[cfg(feature = "alloc")]
impl GptDisk {
pub const DEFAULT_ENTRY_COUNT: u32 = 128;
pub fn new(disk_sectors: u64, block_size: u32) -> Self {
let entry_count = Self::DEFAULT_ENTRY_COUNT;
let entry_size = core::mem::size_of::<GptPartitionEntry>() as u32;
let entries_per_sector = block_size / entry_size;
let entry_sectors = entry_count.div_ceil(entries_per_sector);
let first_usable = 2 + entry_sectors as u64;
let last_usable = disk_sectors - 2 - entry_sectors as u64;
let disk_guid = {
#[cfg(feature = "rand")]
{
Guid::generate_v4()
}
#[cfg(not(feature = "rand"))]
{
Guid::UNUSED
}
};
#[cfg_attr(not(feature = "crc"), allow(unused_mut))]
let mut primary_header = GptHeader {
signature: GptHeader::SIGNATURE,
revision: GptHeader::REVISION_1_0,
header_size: GptHeader::STANDARD_HEADER_SIZE,
header_crc32: 0,
reserved: 0,
my_lba: 1,
alternate_lba: disk_sectors - 1,
first_usable_lba: first_usable,
last_usable_lba: last_usable,
disk_guid,
partition_entry_lba: 2,
num_partition_entries: entry_count,
size_of_partition_entry: entry_size,
partition_entry_array_crc32: 0,
};
#[cfg_attr(not(feature = "crc"), allow(unused_mut))]
let mut backup_header = GptHeader {
my_lba: disk_sectors - 1,
alternate_lba: 1,
partition_entry_lba: disk_sectors - 1 - entry_sectors as u64,
..primary_header
};
let entries = alloc::vec![GptPartitionEntry::default(); entry_count as usize];
#[cfg(feature = "crc")]
{
let entries_crc = crate::gpt::calculate_partition_array_crc32(&entries);
primary_header.partition_entry_array_crc32 = entries_crc;
backup_header.partition_entry_array_crc32 = entries_crc;
primary_header.update_crc32();
backup_header.update_crc32();
}
Self {
primary_header,
backup_header,
entries,
block_size,
}
}
pub fn partition_count(&self) -> usize {
self.entries.iter().filter(|e| !e.is_unused()).count()
}
pub fn partitions(&self) -> impl Iterator<Item = (usize, &GptPartitionEntry)> {
self.entries
.iter()
.enumerate()
.filter(|(_, e)| !e.is_unused())
}
pub fn add_partition(&mut self, entry: GptPartitionEntry) -> Result<usize> {
for (i, slot) in self.entries.iter_mut().enumerate() {
if slot.is_unused() {
*slot = entry;
self.update_crcs();
return Ok(i);
}
}
Err(PartitionError::TooManyPartitions {
max: self.entries.len(),
requested: self.entries.len() + 1,
})
}
pub fn validate(&self) -> Result<()> {
if !self.primary_header.has_valid_signature() {
return Err(PartitionError::InvalidGptSignature {
found: self.primary_header.signature,
});
}
#[cfg(feature = "crc")]
{
if !self.primary_header.verify_crc32() {
return Err(PartitionError::GptHeaderCrcMismatch {
expected: self.primary_header.header_crc32,
actual: self.primary_header.calculate_crc32(),
});
}
let entries_crc = crate::gpt::calculate_partition_array_crc32(&self.entries);
if self.primary_header.partition_entry_array_crc32 != entries_crc {
return Err(PartitionError::GptEntriesCrcMismatch {
expected: self.primary_header.partition_entry_array_crc32,
actual: entries_crc,
});
}
}
let used: Vec<_> = self.partitions().collect();
for i in 0..used.len() {
for j in (i + 1)..used.len() {
let (idx1, p1) = used[i];
let (idx2, p2) = used[j];
if p1.first_lba <= p2.last_lba && p2.first_lba <= p1.last_lba {
let overlap_start = p1.first_lba.max(p2.first_lba);
let overlap_end = p1.last_lba.min(p2.last_lba);
return Err(PartitionError::PartitionOverlap {
index1: idx1,
index2: idx2,
overlap_start,
overlap_end,
});
}
}
}
for (idx, entry) in self.partitions() {
if entry.first_lba < self.primary_header.first_usable_lba
|| entry.last_lba > self.primary_header.last_usable_lba
{
return Err(PartitionError::PartitionOutOfBounds {
index: idx,
partition_end: entry.last_lba,
disk_end: self.primary_header.last_usable_lba,
});
}
}
Ok(())
}
#[cfg(feature = "crc")]
pub fn update_crcs(&mut self) {
let entries_crc = crate::gpt::calculate_partition_array_crc32(&self.entries);
self.primary_header.partition_entry_array_crc32 = entries_crc;
self.backup_header.partition_entry_array_crc32 = entries_crc;
self.primary_header.update_crc32();
self.backup_header.update_crc32();
}
#[cfg(not(feature = "crc"))]
pub fn update_crcs(&mut self) {
}
pub fn create_protective_mbr(&self) -> MasterBootRecord {
let disk_sectors = self.backup_header.my_lba + 1;
MasterBootRecord::protective(disk_sectors)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub enum DiskPartitionScheme {
Mbr(MasterBootRecord),
Gpt {
protective_mbr: MasterBootRecord,
gpt: GptDisk,
},
Hybrid {
hybrid_mbr: MasterBootRecord,
gpt: GptDisk,
},
}
#[cfg(feature = "alloc")]
impl DiskPartitionScheme {
pub fn new_mbr() -> Self {
Self::Mbr(MasterBootRecord::default())
}
pub fn new_gpt(disk_sectors: u64, block_size: u32) -> Self {
let gpt = GptDisk::new(disk_sectors, block_size);
let protective_mbr = gpt.create_protective_mbr();
Self::Gpt {
protective_mbr,
gpt,
}
}
pub fn scheme_type(&self) -> PartitionSchemeType {
match self {
Self::Mbr(_) => PartitionSchemeType::Mbr,
Self::Gpt { .. } => PartitionSchemeType::Gpt,
Self::Hybrid { .. } => PartitionSchemeType::Hybrid,
}
}
pub fn partitions(&self) -> Vec<PartitionInfo> {
match self {
Self::Mbr(mbr) => {
let pt = mbr.get_partition_table();
pt.partitions
.iter()
.enumerate()
.filter(|(_, p)| !p.is_empty())
.map(|(i, p)| PartitionInfo {
index: i,
start_lba: p.start_lba as u64,
end_lba: p.end_lba() as u64,
size_sectors: p.sector_count as u64,
bootable: p.is_bootable(),
partition_type: PartitionType::Mbr(p.part_type),
})
.collect()
}
Self::Gpt { gpt, .. } | Self::Hybrid { gpt, .. } => gpt
.partitions()
.map(|(i, e)| PartitionInfo {
index: i,
start_lba: e.first_lba,
end_lba: e.last_lba,
size_sectors: e.size_sectors(),
bootable: e.attributes.is_legacy_bios_bootable(),
partition_type: PartitionType::Gpt(e.type_guid),
})
.collect(),
}
}
pub fn validate(&self) -> Result<()> {
match self {
Self::Mbr(mbr) => {
if !mbr.has_valid_signature() {
return Err(PartitionError::InvalidMbrSignature {
found: mbr.signature,
});
}
let pt = mbr.get_partition_table();
if !pt.is_valid() {
return Err(PartitionError::InvalidHybridMbr {
reason: "invalid MBR partition table",
});
}
Ok(())
}
Self::Gpt {
protective_mbr,
gpt,
} => {
if !protective_mbr.has_valid_signature() {
return Err(PartitionError::InvalidMbrSignature {
found: protective_mbr.signature,
});
}
let pt = protective_mbr.get_partition_table();
if !pt.is_protective() {
return Err(PartitionError::NoProtectiveMbr);
}
gpt.validate()
}
Self::Hybrid { hybrid_mbr, gpt } => {
if !hybrid_mbr.has_valid_signature() {
return Err(PartitionError::InvalidMbrSignature {
found: hybrid_mbr.signature,
});
}
if !is_hybrid_mbr(hybrid_mbr) {
return Err(PartitionError::InvalidHybridMbr {
reason: "not a valid hybrid MBR",
});
}
gpt.validate()
}
}
}
}
pub fn detect_scheme_from_mbr(mbr: &MasterBootRecord) -> PartitionSchemeType {
if !mbr.has_valid_signature() {
return PartitionSchemeType::Mbr;
}
let pt = mbr.get_partition_table();
if is_hybrid_mbr(mbr) {
PartitionSchemeType::Hybrid
} else if pt.is_protective() {
PartitionSchemeType::Gpt
} else {
PartitionSchemeType::Mbr
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mbr::MbrPartition;
#[cfg(feature = "alloc")]
#[test]
fn test_gpt_disk_creation() {
let disk = GptDisk::new(1000000, 512);
assert!(disk.primary_header.has_valid_signature());
assert_eq!(disk.entries.len(), 128);
assert_eq!(disk.partition_count(), 0);
}
#[cfg(feature = "alloc")]
#[test]
fn test_scheme_detection() {
let mbr = MasterBootRecord::default();
assert_eq!(detect_scheme_from_mbr(&mbr), PartitionSchemeType::Mbr);
let protective = MasterBootRecord::protective(1000000);
assert_eq!(
detect_scheme_from_mbr(&protective),
PartitionSchemeType::Gpt
);
let mut hybrid = protective;
hybrid.with_partition_table(|pt| {
pt[1] = MbrPartition::new(crate::mbr::MbrPartitionType::Fat32, 2048, 100000);
});
assert_eq!(detect_scheme_from_mbr(&hybrid), PartitionSchemeType::Hybrid);
}
#[cfg(feature = "alloc")]
#[test]
fn test_partition_scheme_new_gpt() {
let scheme = DiskPartitionScheme::new_gpt(1000000, 512);
assert_eq!(scheme.scheme_type(), PartitionSchemeType::Gpt);
assert!(scheme.validate().is_ok());
assert!(scheme.partitions().is_empty());
}
}