use crate::PartitionInfoTrait;
use crate::error::{PartitionError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiskGeometry {
pub block_size: u32,
pub total_blocks: u64,
pub physical_block_size: Option<u32>,
}
impl DiskGeometry {
pub const STANDARD_BLOCK_SIZE: u32 = 512;
pub const ADVANCED_FORMAT_BLOCK_SIZE: u32 = 4096;
pub const DEFAULT_ALIGNMENT_BYTES: u64 = 1024 * 1024;
pub const fn standard(total_blocks: u64) -> Self {
Self {
block_size: Self::STANDARD_BLOCK_SIZE,
total_blocks,
physical_block_size: None,
}
}
pub const fn advanced_format(total_blocks: u64) -> Self {
Self {
block_size: Self::ADVANCED_FORMAT_BLOCK_SIZE,
total_blocks,
physical_block_size: None,
}
}
pub const fn emulated_512(total_blocks: u64) -> Self {
Self {
block_size: Self::STANDARD_BLOCK_SIZE,
total_blocks,
physical_block_size: Some(Self::ADVANCED_FORMAT_BLOCK_SIZE),
}
}
pub const fn new(block_size: u32, total_blocks: u64, physical_block_size: Option<u32>) -> Self {
Self {
block_size,
total_blocks,
physical_block_size,
}
}
pub const fn total_bytes(&self) -> u64 {
self.total_blocks * self.block_size as u64
}
pub const fn alignment_boundary(&self) -> u32 {
match self.physical_block_size {
Some(size) => size,
None => self.block_size,
}
}
pub const fn default_alignment(&self) -> u64 {
Self::DEFAULT_ALIGNMENT_BYTES / self.block_size as u64
}
pub const fn align_up(&self, lba: u64, alignment_sectors: u64) -> u64 {
if alignment_sectors == 0 {
return lba;
}
let mask = alignment_sectors - 1;
(lba + mask) & !mask
}
pub const fn align_down(&self, lba: u64, alignment_sectors: u64) -> u64 {
if alignment_sectors == 0 {
return lba;
}
let mask = alignment_sectors - 1;
lba & !mask
}
pub const fn is_aligned(&self, lba: u64, alignment_sectors: u64) -> bool {
if alignment_sectors == 0 {
return true;
}
lba.is_multiple_of(alignment_sectors)
}
pub const fn gpt_first_usable_lba(&self, num_entries: u32, entry_size: u32) -> u64 {
let entry_bytes = num_entries as u64 * entry_size as u64;
let entry_sectors = entry_bytes.div_ceil(self.block_size as u64);
2 + entry_sectors
}
pub const fn gpt_first_usable_lba_aligned(&self, num_entries: u32, entry_size: u32) -> u64 {
let first = self.gpt_first_usable_lba(num_entries, entry_size);
self.align_up(first, self.default_alignment())
}
pub const fn gpt_last_usable_lba(&self, num_entries: u32, entry_size: u32) -> u64 {
let entry_bytes = num_entries as u64 * entry_size as u64;
let entry_sectors = entry_bytes.div_ceil(self.block_size as u64);
self.total_blocks - 1 - entry_sectors - 1
}
pub const fn gpt_last_usable_lba_aligned(&self, num_entries: u32, entry_size: u32) -> u64 {
let last = self.gpt_last_usable_lba(num_entries, entry_size);
self.align_down(last + 1, self.default_alignment()) - 1
}
pub const fn gpt_usable_sectors(&self, num_entries: u32, entry_size: u32) -> u64 {
let first = self.gpt_first_usable_lba(num_entries, entry_size);
let last = self.gpt_last_usable_lba(num_entries, entry_size);
if last > first { last - first + 1 } else { 0 }
}
}
pub fn validate_partition_alignment<P: PartitionInfoTrait>(
partition: &P,
geometry: &DiskGeometry,
alignment: u64,
) -> Result<()> {
if !geometry.is_aligned(partition.start_lba(), alignment) {
return Err(PartitionError::MisalignedPartition {
lba: partition.start_lba(),
required_alignment: alignment,
});
}
Ok(())
}
pub fn validate_all_partitions_aligned<P: PartitionInfoTrait>(
partitions: &[P],
geometry: &DiskGeometry,
alignment: u64,
) -> Result<()> {
for partition in partitions {
validate_partition_alignment(partition, geometry, alignment)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standard_geometry() {
let geom = DiskGeometry::standard(2_097_152); assert_eq!(geom.block_size, 512);
assert_eq!(geom.total_bytes(), 1024 * 1024 * 1024);
assert_eq!(geom.default_alignment(), 2048); }
#[test]
fn test_advanced_format_geometry() {
let geom = DiskGeometry::advanced_format(262_144); assert_eq!(geom.block_size, 4096);
assert_eq!(geom.total_bytes(), 1024 * 1024 * 1024);
assert_eq!(geom.default_alignment(), 256); }
#[test]
fn test_emulated_512_geometry() {
let geom = DiskGeometry::emulated_512(2_097_152);
assert_eq!(geom.block_size, 512);
assert_eq!(geom.physical_block_size, Some(4096));
assert_eq!(geom.alignment_boundary(), 4096);
}
#[test]
fn test_align_up() {
let geom = DiskGeometry::standard(1000000);
assert_eq!(geom.align_up(0, 2048), 0);
assert_eq!(geom.align_up(1, 2048), 2048);
assert_eq!(geom.align_up(2047, 2048), 2048);
assert_eq!(geom.align_up(2048, 2048), 2048);
assert_eq!(geom.align_up(2049, 2048), 4096);
}
#[test]
fn test_align_down() {
let geom = DiskGeometry::standard(1000000);
assert_eq!(geom.align_down(0, 2048), 0);
assert_eq!(geom.align_down(1, 2048), 0);
assert_eq!(geom.align_down(2047, 2048), 0);
assert_eq!(geom.align_down(2048, 2048), 2048);
assert_eq!(geom.align_down(4095, 2048), 2048);
assert_eq!(geom.align_down(4096, 2048), 4096);
}
#[test]
fn test_is_aligned() {
let geom = DiskGeometry::standard(1000000);
assert!(geom.is_aligned(0, 2048));
assert!(!geom.is_aligned(1, 2048));
assert!(geom.is_aligned(2048, 2048));
assert!(geom.is_aligned(4096, 2048));
assert!(!geom.is_aligned(4097, 2048));
}
#[test]
fn test_gpt_usable_lba() {
let geom = DiskGeometry::standard(204800);
let first = geom.gpt_first_usable_lba(128, 128);
let last = geom.gpt_last_usable_lba(128, 128);
assert_eq!(first, 34);
assert_eq!(last, 204766);
let first_aligned = geom.gpt_first_usable_lba_aligned(128, 128);
assert_eq!(first_aligned, 2048);
}
#[test]
fn test_gpt_usable_sectors() {
let geom = DiskGeometry::standard(204800);
let usable = geom.gpt_usable_sectors(128, 128);
assert_eq!(usable, 204733);
}
}