use super::boot::BootMode;
use super::cmd;
use super::detect::BlockDevice;
use super::error::StorageError;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct PartitionInfo {
pub boot: PartitionDevice,
pub data: PartitionDevice,
}
#[derive(Debug, Clone)]
pub struct PartitionDevice {
pub number: u32,
pub path: PathBuf,
pub name: String,
}
pub fn create_partitions(
device: &BlockDevice,
boot_mode: BootMode,
data_size_gb: u32,
) -> Result<PartitionInfo, StorageError> {
let dev_path = device.path.to_string_lossy();
log::info!("creating partition table on {}", dev_path);
zap_disk(device)?;
match boot_mode {
BootMode::Efi => create_efi_partitions(device, data_size_gb),
BootMode::Bios => create_bios_partitions(device, data_size_gb),
}
}
fn zap_disk(device: &BlockDevice) -> Result<(), StorageError> {
let dev_path = device.path.to_string_lossy();
cmd::run("sgdisk", &["--zap-all", &dev_path]).map_err(|e| {
StorageError::PartitioningFailed {
device: device.path.clone(),
error: format!("zap failed: {}", e),
}
})?;
cmd::run("sgdisk", &["--clear", &dev_path]).map_err(|e| StorageError::PartitioningFailed {
device: device.path.clone(),
error: format!("clear failed: {}", e),
})?;
Ok(())
}
fn create_efi_partitions(
device: &BlockDevice,
data_size_gb: u32,
) -> Result<PartitionInfo, StorageError> {
let dev_path = device.path.to_string_lossy();
log::info!("creating ESP partition (512MB)");
sgdisk_create_partition(device, 1, "2048", "+512M")?;
sgdisk_set_type(device, 1, "EF00")?;
sgdisk_set_name(device, 1, "mosboot")?;
log::info!("creating data partition ({}GB)", data_size_gb);
sgdisk_create_partition(device, 2, "0", &format!("+{}G", data_size_gb))?;
sgdisk_set_type(device, 2, "8300")?;
sgdisk_set_name(device, 2, "mosdata")?;
settle_partitions()?;
Ok(PartitionInfo {
boot: PartitionDevice {
number: 1,
path: partition_path(&dev_path, 1),
name: "mosboot".to_string(),
},
data: PartitionDevice {
number: 2,
path: partition_path(&dev_path, 2),
name: "mosdata".to_string(),
},
})
}
fn create_bios_partitions(
device: &BlockDevice,
data_size_gb: u32,
) -> Result<PartitionInfo, StorageError> {
let dev_path = device.path.to_string_lossy();
log::info!("creating BIOS boot partition (1MB)");
sgdisk_create_partition(device, 1, "2048", "+1M")?;
sgdisk_set_type(device, 1, "EF02")?;
sgdisk_set_name(device, 1, "mosboot")?;
log::info!("creating data partition ({}GB)", data_size_gb);
sgdisk_create_partition(device, 2, "0", &format!("+{}G", data_size_gb))?;
sgdisk_set_type(device, 2, "8300")?;
sgdisk_set_name(device, 2, "mosdata")?;
settle_partitions()?;
Ok(PartitionInfo {
boot: PartitionDevice {
number: 1,
path: partition_path(&dev_path, 1),
name: "mosboot".to_string(),
},
data: PartitionDevice {
number: 2,
path: partition_path(&dev_path, 2),
name: "mosdata".to_string(),
},
})
}
fn sgdisk_create_partition(
device: &BlockDevice,
number: u32,
start: &str,
size: &str,
) -> Result<(), StorageError> {
let dev_path = device.path.to_string_lossy();
let new_arg = format!("--new={}:{}:{}", number, start, size);
cmd::run("sgdisk", &[&new_arg, &dev_path]).map_err(|e| StorageError::PartitioningFailed {
device: device.path.clone(),
error: format!("create partition {} failed: {}", number, e),
})?;
Ok(())
}
fn sgdisk_set_type(device: &BlockDevice, number: u32, type_code: &str) -> Result<(), StorageError> {
let dev_path = device.path.to_string_lossy();
let type_arg = format!("--typecode={}:{}", number, type_code);
cmd::run("sgdisk", &[&type_arg, &dev_path]).map_err(|e| StorageError::PartitioningFailed {
device: device.path.clone(),
error: format!("set type {} failed: {}", number, e),
})?;
Ok(())
}
fn sgdisk_set_name(device: &BlockDevice, number: u32, name: &str) -> Result<(), StorageError> {
let dev_path = device.path.to_string_lossy();
let name_arg = format!("--change-name={}:{}", number, name);
cmd::run("sgdisk", &[&name_arg, &dev_path]).map_err(|e| StorageError::PartitioningFailed {
device: device.path.clone(),
error: format!("set name {} failed: {}", number, e),
})?;
Ok(())
}
fn settle_partitions() -> Result<(), StorageError> {
if cmd::run_allow_fail("udevadm", &["settle", "--timeout=5"]).is_some() {
return Ok(());
}
if cmd::run_allow_fail("partprobe", &[]).is_some() {
return Ok(());
}
std::thread::sleep(std::time::Duration::from_secs(1));
Ok(())
}
fn partition_path(device_path: &str, number: u32) -> PathBuf {
if device_path.contains("nvme") || device_path.contains("mmcblk") {
PathBuf::from(format!("{}p{}", device_path, number))
} else {
PathBuf::from(format!("{}{}", device_path, number))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partition_path_nvme() {
assert_eq!(
partition_path("/dev/nvme0n1", 1),
PathBuf::from("/dev/nvme0n1p1")
);
assert_eq!(
partition_path("/dev/nvme0n1", 2),
PathBuf::from("/dev/nvme0n1p2")
);
}
#[test]
fn test_partition_path_sata() {
assert_eq!(partition_path("/dev/sda", 1), PathBuf::from("/dev/sda1"));
assert_eq!(partition_path("/dev/sdb", 2), PathBuf::from("/dev/sdb2"));
}
#[test]
fn test_partition_path_mmcblk() {
assert_eq!(
partition_path("/dev/mmcblk0", 1),
PathBuf::from("/dev/mmcblk0p1")
);
}
}