#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::gpt::GptPartitionEntry;
use crate::mbr::{Chs, MasterBootRecord, MbrPartition, MbrPartitionTable, MbrPartitionType};
use endian_num::Le;
#[derive(Debug, Clone, Copy)]
pub struct MirroredPartition {
pub gpt_partition_index: u32,
pub mbr_type: MbrPartitionType,
pub bootable: bool,
}
impl MirroredPartition {
pub const fn new(gpt_index: u32, mbr_type: MbrPartitionType) -> Self {
Self {
gpt_partition_index: gpt_index,
mbr_type,
bootable: false,
}
}
pub const fn with_bootable(mut self, bootable: bool) -> Self {
self.bootable = bootable;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct HybridMbrConfig {
pub protective_slot: usize,
#[cfg(feature = "alloc")]
pub mirrored: Vec<MirroredPartition>,
#[cfg(not(feature = "alloc"))]
pub mirrored: [Option<MirroredPartition>; 3],
#[cfg(not(feature = "alloc"))]
pub mirrored_count: usize,
}
impl HybridMbrConfig {
pub const fn new() -> Self {
Self {
protective_slot: 0,
#[cfg(feature = "alloc")]
mirrored: Vec::new(),
#[cfg(not(feature = "alloc"))]
mirrored: [None, None, None],
#[cfg(not(feature = "alloc"))]
mirrored_count: 0,
}
}
pub const fn with_protective_slot(mut self, slot: usize) -> Self {
self.protective_slot = slot;
self
}
#[cfg(feature = "alloc")]
pub fn add_mirrored(mut self, partition: MirroredPartition) -> Self {
self.mirrored.push(partition);
self
}
#[cfg(not(feature = "alloc"))]
pub fn add_mirrored(mut self, partition: MirroredPartition) -> Self {
if self.mirrored_count < 3 {
self.mirrored[self.mirrored_count] = Some(partition);
self.mirrored_count += 1;
}
self
}
#[cfg(feature = "alloc")]
pub fn mirrored_count(&self) -> usize {
self.mirrored.len()
}
#[cfg(not(feature = "alloc"))]
pub fn mirrored_count(&self) -> usize {
self.mirrored_count
}
pub fn validate(&self) -> Result<()> {
if self.protective_slot > 3 {
return Err(Error::InvalidHybridMbr {
reason: "protective slot must be 0-3",
});
}
let count = self.mirrored_count();
if count > 3 {
return Err(Error::TooManyPartitions {
max: 3,
requested: count,
});
}
#[cfg(feature = "alloc")]
for (i, _) in self.mirrored.iter().enumerate() {
let slot = self.calculate_slot(i);
if slot == self.protective_slot {
return Err(Error::InvalidHybridMbr {
reason: "mirrored partition conflicts with protective slot",
});
}
}
Ok(())
}
fn calculate_slot(&self, mirror_index: usize) -> usize {
let mut slot = 0;
let mut count = 0;
while count <= mirror_index && slot < 4 {
if slot != self.protective_slot {
if count == mirror_index {
return slot;
}
count += 1;
}
slot += 1;
}
slot
}
}
#[derive(Debug)]
pub struct HybridMbrBuilder {
config: HybridMbrConfig,
disk_sectors: u64,
}
impl HybridMbrBuilder {
pub fn new(disk_sectors: u64) -> Self {
Self {
config: HybridMbrConfig::new(),
disk_sectors,
}
}
pub fn protective_slot(mut self, slot: usize) -> Self {
self.config.protective_slot = slot;
self
}
pub fn mirror_partition(
self,
gpt_index: u32,
mbr_type: MbrPartitionType,
bootable: bool,
) -> Self {
let partition = MirroredPartition::new(gpt_index, mbr_type).with_bootable(bootable);
Self {
config: self.config.add_mirrored(partition),
..self
}
}
pub fn build(self, gpt_entries: &[GptPartitionEntry]) -> Result<MasterBootRecord> {
self.config.validate()?;
let mut mbr = MasterBootRecord::default();
let mut partition_table = MbrPartitionTable::new();
#[cfg(feature = "alloc")]
let mirrored_iter = self.config.mirrored.iter();
#[cfg(not(feature = "alloc"))]
let mirrored_iter = self.config.mirrored[..self.config.mirrored_count]
.iter()
.filter_map(|p| p.as_ref());
let mut mirrored_ranges: [(u64, u64); 3] = [(0, 0); 3];
let mut mirror_count = 0;
for (i, mirrored) in mirrored_iter.enumerate() {
let gpt_idx = mirrored.gpt_partition_index as usize;
if gpt_idx >= gpt_entries.len() {
return Err(Error::InvalidHybridMbr {
reason: "GPT partition index out of bounds",
});
}
let gpt_entry = &gpt_entries[gpt_idx];
if gpt_entry.is_unused() {
return Err(Error::InvalidHybridMbr {
reason: "referenced GPT partition is unused",
});
}
let first_native = gpt_entry.first_lba.to_ne();
let last_native = gpt_entry.last_lba.to_ne();
if first_native > last_native {
return Err(Error::InvalidHybridMbr {
reason: "GPT partition has an inverted LBA range",
});
}
if last_native > u32::MAX as u64 {
return Err(Error::InvalidHybridMbr {
reason: "GPT partition extends beyond MBR 32-bit limit",
});
}
let slot = self.config.calculate_slot(i);
let start_lba = first_native as u32;
let sector_count = (last_native - first_native + 1) as u32;
partition_table[slot] = MbrPartition {
boot_indicator: if mirrored.bootable { 0x80 } else { 0x00 },
start_chs: Chs::new(start_lba),
part_type: mirrored.mbr_type.to_u8(),
end_chs: Chs::new(start_lba + sector_count - 1),
start_lba: Le::<u32>::from_ne(start_lba),
sector_count: Le::<u32>::from_ne(sector_count),
};
mirrored_ranges[mirror_count] = (first_native, last_native);
mirror_count += 1;
}
let protective_end = if mirror_count > 0 {
let mut first_start = u64::MAX;
for range in mirrored_ranges.iter().take(mirror_count) {
if range.0 < first_start && range.0 > 1 {
first_start = range.0;
}
}
if first_start == u64::MAX {
self.disk_sectors.min(u32::MAX as u64) as u32
} else {
(first_start - 1).min(u32::MAX as u64) as u32
}
} else {
self.disk_sectors.min(u32::MAX as u64) as u32
};
let protective_size = if protective_end > 1 {
protective_end - 1
} else {
1
};
partition_table[self.config.protective_slot] = MbrPartition {
boot_indicator: 0x00,
start_chs: Chs::new(1),
part_type: MbrPartitionType::ProtectiveMbr.to_u8(),
end_chs: Chs::new(protective_end),
start_lba: Le::<u32>::from_ne(1),
sector_count: Le::<u32>::from_ne(protective_size),
};
mbr.partition_table = partition_table;
Ok(mbr)
}
}
pub fn is_hybrid_mbr(mbr: &MasterBootRecord) -> bool {
let mut has_protective = false;
let mut has_other = false;
let pt = mbr.get_partition_table();
for partition in &pt.partitions {
if partition.is_empty() {
continue;
}
if partition.partition_type().is_protective() {
has_protective = true;
} else {
has_other = true;
}
}
has_protective && has_other
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gpt::Guid;
#[test]
fn test_hybrid_mbr_config_validation() {
let config = HybridMbrConfig::new()
.with_protective_slot(0)
.add_mirrored(MirroredPartition::new(0, MbrPartitionType::Fat32));
assert!(config.validate().is_ok());
}
#[test]
fn test_hybrid_mbr_config_too_many_partitions() {
let mut config = HybridMbrConfig::new();
#[cfg(feature = "alloc")]
{
config.mirrored = alloc::vec![
MirroredPartition::new(0, MbrPartitionType::Fat32),
MirroredPartition::new(1, MbrPartitionType::LinuxNative),
MirroredPartition::new(2, MbrPartitionType::LinuxNative),
MirroredPartition::new(3, MbrPartitionType::LinuxNative),
];
}
let result = config.validate();
assert!(matches!(result, Err(Error::TooManyPartitions { .. })));
}
#[test]
fn test_hybrid_mbr_builder() {
let gpt_entries = [
GptPartitionEntry::new(Guid::EFI_SYSTEM, Guid::UNUSED, 2048, 206847),
GptPartitionEntry::default(),
];
let mbr = HybridMbrBuilder::new(1000000)
.protective_slot(0)
.mirror_partition(0, MbrPartitionType::EfiSystemPartition, false)
.build(&gpt_entries)
.unwrap();
assert!(mbr.has_valid_signature());
assert!(is_hybrid_mbr(&mbr));
}
#[test]
fn test_is_hybrid_mbr() {
let protective = MasterBootRecord::protective(1000000);
assert!(!is_hybrid_mbr(&protective));
let mut hybrid = protective;
hybrid.with_partition_table(|pt| {
pt[1] = MbrPartition::new(MbrPartitionType::Fat32, 2048, 100000);
});
assert!(is_hybrid_mbr(&hybrid));
}
}