use crate::error::{Error, Result};
use crate::gpt::{type_guids, SECTOR_SIZE, SIGNATURE};
use crate::probe::{Partition, PartitionKind};
use fs_core::BlockDevice;
const NUM_ENTRIES: u32 = 128;
const ENTRY_SIZE: u32 = 128;
const ENTRY_ARRAY_BYTES: u64 = (NUM_ENTRIES as u64) * (ENTRY_SIZE as u64); const ENTRY_ARRAY_SECTORS: u64 = ENTRY_ARRAY_BYTES / SECTOR_SIZE; const FIRST_USABLE_LBA: u64 = 2 + ENTRY_ARRAY_SECTORS; const HEADER_SIZE: u32 = 92;
pub fn write_gpt(
dev: &dyn BlockDevice,
partitions: &[Partition],
disk_guid: [u8; 16],
) -> Result<()> {
if !dev.is_writable() {
return Err(Error::Block(fs_core::Error::ReadOnly));
}
let total_bytes = dev.size_bytes();
if total_bytes < (FIRST_USABLE_LBA + ENTRY_ARRAY_SECTORS + 1) * SECTOR_SIZE {
return Err(Error::DeviceTooSmall);
}
let total_sectors = total_bytes / SECTOR_SIZE;
let last_lba = total_sectors - 1;
let last_usable_lba = last_lba - ENTRY_ARRAY_SECTORS - 1;
if partitions.len() > NUM_ENTRIES as usize {
return Err(Error::Invalid("too many partitions for canonical 128-slot table"));
}
let mut sorted: Vec<(usize, &Partition)> = partitions.iter().enumerate().collect();
sorted.sort_by_key(|(_, p)| p.start);
let mut prev_end_lba: Option<u64> = None;
for (_, p) in &sorted {
validate_partition(p, FIRST_USABLE_LBA, last_usable_lba)?;
let start_lba = p.start / SECTOR_SIZE;
let end_lba = (p.start + p.length) / SECTOR_SIZE - 1;
if let Some(prev) = prev_end_lba {
if start_lba <= prev {
return Err(Error::Invalid("partitions overlap"));
}
}
prev_end_lba = Some(end_lba);
}
let mut array = vec![0u8; ENTRY_ARRAY_BYTES as usize];
for (idx, p) in partitions.iter().enumerate() {
let off = idx * ENTRY_SIZE as usize;
let type_guid = match p.kind {
PartitionKind::Gpt { type_guid } => type_guid,
_ => return Err(Error::Invalid("non-GPT partition kind in GPT write")),
};
let uuid = p
.uuid
.ok_or(Error::Invalid("GPT partition missing UUID"))?;
let start_lba = p.start / SECTOR_SIZE;
let end_lba = (p.start + p.length) / SECTOR_SIZE - 1;
array[off..off + 16].copy_from_slice(&type_guid);
array[off + 16..off + 32].copy_from_slice(&uuid);
array[off + 32..off + 40].copy_from_slice(&start_lba.to_le_bytes());
array[off + 40..off + 48].copy_from_slice(&end_lba.to_le_bytes());
if let Some(label) = &p.label {
let name_off = off + 56;
for (j, c) in label.encode_utf16().enumerate() {
if j * 2 + 2 > 72 {
break;
}
array[name_off + j * 2..name_off + j * 2 + 2].copy_from_slice(&c.to_le_bytes());
}
}
}
let entry_array_crc = crc32fast::hash(&array);
let mut mbr = [0u8; 512];
mbr[446] = 0x00; mbr[447] = 0x00;
mbr[448] = 0x02;
mbr[449] = 0x00;
mbr[450] = 0xEE; mbr[451] = 0xFF;
mbr[452] = 0xFF;
mbr[453] = 0xFF;
mbr[454..458].copy_from_slice(&1u32.to_le_bytes());
let prot_sectors = (total_sectors - 1).min(0xFFFF_FFFF) as u32;
mbr[458..462].copy_from_slice(&prot_sectors.to_le_bytes());
mbr[510] = 0x55;
mbr[511] = 0xAA;
dev.write_at(0, &mbr)?;
let primary = build_header(
1,
last_lba,
2,
FIRST_USABLE_LBA,
last_usable_lba,
disk_guid,
entry_array_crc,
);
dev.write_at(SECTOR_SIZE, &primary)?;
dev.write_at(2 * SECTOR_SIZE, &array)?;
let backup_entries_lba = last_lba - ENTRY_ARRAY_SECTORS;
dev.write_at(backup_entries_lba * SECTOR_SIZE, &array)?;
let backup = build_header(
last_lba,
1,
backup_entries_lba,
FIRST_USABLE_LBA,
last_usable_lba,
disk_guid,
entry_array_crc,
);
dev.write_at(last_lba * SECTOR_SIZE, &backup)?;
Ok(())
}
fn validate_partition(p: &Partition, first_usable: u64, last_usable: u64) -> Result<()> {
if !matches!(p.kind, PartitionKind::Gpt { .. }) {
return Err(Error::Invalid("non-GPT partition kind in GPT write"));
}
if p.length == 0 {
return Err(Error::Invalid("partition has zero length"));
}
if !p.start.is_multiple_of(SECTOR_SIZE) || !p.length.is_multiple_of(SECTOR_SIZE) {
return Err(Error::Invalid("partition not sector-aligned"));
}
let start_lba = p.start / SECTOR_SIZE;
let end_lba = (p.start + p.length) / SECTOR_SIZE - 1;
if start_lba < first_usable {
return Err(Error::Invalid("partition starts before first usable LBA"));
}
if end_lba > last_usable {
return Err(Error::Invalid("partition ends past last usable LBA"));
}
if let PartitionKind::Gpt { type_guid } = p.kind {
if type_guid == type_guids::UNUSED {
return Err(Error::Invalid("partition type GUID is the unused sentinel"));
}
}
Ok(())
}
fn build_header(
my_lba: u64,
alternate_lba: u64,
entry_lba: u64,
first_usable: u64,
last_usable: u64,
disk_guid: [u8; 16],
entry_array_crc: u32,
) -> [u8; 512] {
let mut h = [0u8; 512];
h[0..8].copy_from_slice(SIGNATURE);
h[8..12].copy_from_slice(&0x0001_0000u32.to_le_bytes()); h[12..16].copy_from_slice(&HEADER_SIZE.to_le_bytes());
h[24..32].copy_from_slice(&my_lba.to_le_bytes());
h[32..40].copy_from_slice(&alternate_lba.to_le_bytes());
h[40..48].copy_from_slice(&first_usable.to_le_bytes());
h[48..56].copy_from_slice(&last_usable.to_le_bytes());
h[56..72].copy_from_slice(&disk_guid);
h[72..80].copy_from_slice(&entry_lba.to_le_bytes());
h[80..84].copy_from_slice(&NUM_ENTRIES.to_le_bytes());
h[84..88].copy_from_slice(&ENTRY_SIZE.to_le_bytes());
h[88..92].copy_from_slice(&entry_array_crc.to_le_bytes());
let header_crc = crc32fast::hash(&h[..HEADER_SIZE as usize]);
h[16..20].copy_from_slice(&header_crc.to_le_bytes());
h
}