Files
One-KVM/libs/ventoy-img-rs/src/partition.rs
mofeng-git d143d158e4 init
2025-12-28 18:19:16 +08:00

192 lines
5.3 KiB
Rust

//! MBR partition table implementation
use crate::error::{Result, VentoyError};
use std::io::{Seek, SeekFrom, Write};
/// Sector size in bytes
pub const SECTOR_SIZE: u64 = 512;
/// Data partition starts at sector 2048 (1MB aligned)
pub const DATA_PART_START_SECTOR: u64 = 2048;
/// EFI partition size: 32MB = 65536 sectors
pub const EFI_PART_SIZE_SECTORS: u64 = 65536;
/// Minimum image size: 64MB
pub const MIN_IMAGE_SIZE: u64 = 64 * 1024 * 1024;
/// MBR partition type: NTFS/exFAT (0x07)
pub const MBR_TYPE_EXFAT: u8 = 0x07;
/// MBR partition type: EFI System (0xEF)
pub const MBR_TYPE_EFI: u8 = 0xEF;
/// Ventoy signature offset in MBR
pub const VENTOY_SIG_OFFSET: u64 = 0x190; // 400
/// Partition layout information
#[derive(Debug, Clone)]
pub struct PartitionLayout {
pub total_sectors: u64,
pub data_start_sector: u64,
pub data_size_sectors: u64,
pub efi_start_sector: u64,
pub efi_size_sectors: u64,
}
impl PartitionLayout {
/// Calculate partition layout for given image size
pub fn calculate(total_size: u64) -> Result<Self> {
if total_size < MIN_IMAGE_SIZE {
return Err(VentoyError::InvalidSize(format!(
"{}MB (minimum 64MB)",
total_size / (1024 * 1024)
)));
}
let total_sectors = total_size / SECTOR_SIZE;
// EFI partition at the end, 4KB aligned
let efi_start = ((total_sectors - EFI_PART_SIZE_SECTORS) / 8) * 8;
// Data partition fills the gap
let data_size = efi_start - DATA_PART_START_SECTOR;
Ok(Self {
total_sectors,
data_start_sector: DATA_PART_START_SECTOR,
data_size_sectors: data_size,
efi_start_sector: efi_start,
efi_size_sectors: EFI_PART_SIZE_SECTORS,
})
}
/// Get data partition offset in bytes
pub fn data_offset(&self) -> u64 {
self.data_start_sector * SECTOR_SIZE
}
/// Get data partition size in bytes
pub fn data_size(&self) -> u64 {
self.data_size_sectors * SECTOR_SIZE
}
/// Get EFI partition offset in bytes
pub fn efi_offset(&self) -> u64 {
self.efi_start_sector * SECTOR_SIZE
}
}
/// MBR partition entry (16 bytes)
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
struct MbrPartitionEntry {
boot_indicator: u8,
start_chs: [u8; 3],
partition_type: u8,
end_chs: [u8; 3],
start_lba: u32,
size_sectors: u32,
}
impl MbrPartitionEntry {
fn new(bootable: bool, partition_type: u8, start_lba: u64, size_sectors: u64) -> Self {
Self {
boot_indicator: if bootable { 0x80 } else { 0x00 },
start_chs: [0xFE, 0xFF, 0xFF], // LBA mode
partition_type,
end_chs: [0xFE, 0xFF, 0xFF], // LBA mode
start_lba: start_lba as u32,
size_sectors: size_sectors as u32,
}
}
fn to_bytes(&self) -> [u8; 16] {
let mut bytes = [0u8; 16];
bytes[0] = self.boot_indicator;
bytes[1..4].copy_from_slice(&self.start_chs);
bytes[4] = self.partition_type;
bytes[5..8].copy_from_slice(&self.end_chs);
bytes[8..12].copy_from_slice(&self.start_lba.to_le_bytes());
bytes[12..16].copy_from_slice(&self.size_sectors.to_le_bytes());
bytes
}
}
/// Write MBR partition table to image
pub fn write_mbr_partition_table<W: Write + Seek>(
writer: &mut W,
layout: &PartitionLayout,
) -> Result<()> {
// Partition 1: Data partition (exFAT, bootable)
let part1 = MbrPartitionEntry::new(
true,
MBR_TYPE_EXFAT,
layout.data_start_sector,
layout.data_size_sectors,
);
// Partition 2: EFI System partition
let part2 = MbrPartitionEntry::new(
false,
MBR_TYPE_EFI,
layout.efi_start_sector,
layout.efi_size_sectors,
);
// Write partition table entries (offset 0x1BE = 446)
writer.seek(SeekFrom::Start(446))?;
writer.write_all(&part1.to_bytes())?;
writer.write_all(&part2.to_bytes())?;
// Clear partition 3 and 4
writer.write_all(&[0u8; 32])?;
// Write MBR signature (0x55AA)
writer.seek(SeekFrom::Start(510))?;
writer.write_all(&[0x55, 0xAA])?;
Ok(())
}
/// Parse size string like "8G", "1024M" into bytes
pub fn parse_size(s: &str) -> Result<u64> {
let s = s.trim().to_uppercase();
let (num_str, multiplier) = if s.ends_with('G') {
(&s[..s.len() - 1], 1024 * 1024 * 1024u64)
} else if s.ends_with('M') {
(&s[..s.len() - 1], 1024 * 1024u64)
} else if s.ends_with('K') {
(&s[..s.len() - 1], 1024u64)
} else {
(s.as_str(), 1u64)
};
let num: u64 = num_str
.parse()
.map_err(|_| VentoyError::SizeParseError(s.clone()))?;
Ok(num * multiplier)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_size() {
assert_eq!(parse_size("8G").unwrap(), 8 * 1024 * 1024 * 1024);
assert_eq!(parse_size("1024M").unwrap(), 1024 * 1024 * 1024);
assert_eq!(parse_size("512K").unwrap(), 512 * 1024);
}
#[test]
fn test_partition_layout() {
let layout = PartitionLayout::calculate(8 * 1024 * 1024 * 1024).unwrap();
assert_eq!(layout.data_start_sector, 2048);
assert_eq!(layout.efi_size_sectors, 65536);
assert!(layout.efi_start_sector > layout.data_start_sector);
}
}