#![allow(dead_code)]
mod boot;
mod cmd;
mod cmdline;
mod detect;
mod empty;
mod error;
mod format;
mod mount;
mod partition;
mod subvolume;
pub use boot::BootMode;
pub use error::StorageError;
pub use mount::MountPoints;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum StorageState {
Mounted {
device: PathBuf,
boot_device: Option<PathBuf>,
},
Initialized {
device: PathBuf,
boot_device: Option<PathBuf>,
},
NoDisk,
}
#[derive(Debug, Clone)]
pub struct StorageStatus {
pub mosdata_device: Option<PathBuf>,
pub mosboot_device: Option<PathBuf>,
pub is_mounted: bool,
pub subvolumes: Vec<SubvolumeInfo>,
}
#[derive(Debug, Clone)]
pub struct SubvolumeInfo {
pub name: String,
pub mount_point: Option<PathBuf>,
pub is_mounted: bool,
}
pub fn init() -> Result<StorageState, StorageError> {
log::info!("MOS storage: checking for existing storage");
if storage_exists() {
log::info!("MOS storage: found existing storage, mounting");
return mount_existing();
}
log::info!("MOS storage: no existing storage found, scanning for empty disks");
let disks = detect::enumerate_disks()?;
if disks.is_empty() {
log::info!("MOS storage: no candidate disks found");
return Ok(StorageState::NoDisk);
}
let empty_disk = match empty::find_first_empty(&disks)? {
Some(disk) => disk,
None => {
log::info!("MOS storage: no empty disks found (all have existing data)");
return Ok(StorageState::NoDisk);
}
};
log::info!(
"MOS storage: selected {} for initialization",
empty_disk.name
);
initialize_disk(empty_disk)
}
pub fn storage_exists() -> bool {
format::mosdata_exists()
}
pub fn mount_existing() -> Result<StorageState, StorageError> {
let boot_mode = boot::BootMode::detect();
log::info!(
"MOS storage: mounting existing storage (boot mode: {:?})",
boot_mode
);
let mosdata_device = format::find_by_label(format::MOSDATA_LABEL);
let mosboot_device = format::find_by_label(format::MOSBOOT_LABEL);
if let Some(ref device) = mosdata_device {
ensure_subvolumes_exist(device)?;
}
mount::mount_all(boot_mode)?;
log::info!("MOS storage: mount complete");
Ok(StorageState::Mounted {
device: mosdata_device.unwrap_or_default(),
boot_device: mosboot_device,
})
}
fn ensure_subvolumes_exist(device: &std::path::Path) -> Result<(), StorageError> {
use std::fs;
const TEMP_MOUNT: &str = "/tmp/mos-subvol-check";
fs::create_dir_all(TEMP_MOUNT).map_err(|e| StorageError::MountFailed {
from: device.to_path_buf(),
target: TEMP_MOUNT.into(),
error: format!("failed to create temp mount: {}", e),
})?;
let dev_str = device.to_string_lossy();
cmd::run("mount", &[&dev_str, TEMP_MOUNT]).map_err(|e| StorageError::MountFailed {
from: device.to_path_buf(),
target: TEMP_MOUNT.into(),
error: e.to_string(),
})?;
let result = (|| {
let existing = subvolume::list_subvolumes(std::path::Path::new(TEMP_MOUNT))?;
for name in subvolume::SUBVOLUMES {
if !existing.iter().any(|s| s == *name) {
log::info!("MOS storage: creating missing subvolume: {}", name);
let subvol_path = format!("{}/{}", TEMP_MOUNT, name);
cmd::run("btrfs", &["subvolume", "create", &subvol_path]).map_err(|e| {
StorageError::SubvolumeFailed {
path: name.to_string(),
error: e.to_string(),
}
})?;
}
}
Ok(())
})();
let _ = cmd::run("umount", &[TEMP_MOUNT]);
let _ = fs::remove_dir(TEMP_MOUNT);
result
}
fn initialize_disk(device: &detect::BlockDevice) -> Result<StorageState, StorageError> {
let boot_mode = boot::BootMode::detect();
let data_size_gb = cmdline::get_data_partition_size_gb();
log::info!(
"MOS storage: initializing {} (boot mode: {:?}, data size: {}GB)",
device.name,
boot_mode,
data_size_gb
);
log::info!("MOS storage: creating partition table");
let partitions = partition::create_partitions(device, boot_mode, data_size_gb)?;
log::info!("MOS storage: formatting partitions");
format::format_partitions(&partitions, boot_mode)?;
log::info!("MOS storage: creating btrfs subvolumes");
subvolume::create_subvolumes(&partitions.data.path)?;
log::info!("MOS storage: mounting storage");
mount::mount_all(boot_mode)?;
log::info!("MOS storage: initialization complete");
let boot_device = if boot_mode.is_efi() {
Some(partitions.boot.path)
} else {
None
};
Ok(StorageState::Initialized {
device: partitions.data.path,
boot_device,
})
}
pub fn status() -> StorageStatus {
let mosdata_device = format::find_by_label(format::MOSDATA_LABEL);
let mosboot_device = format::find_by_label(format::MOSBOOT_LABEL);
let mount_points = mount::MountPoints::new();
let is_mounted = mount::all_mounted();
let subvolumes = mount_points
.subvolumes
.iter()
.map(|sv| {
let is_mounted = mount::is_mounted(&sv.path);
SubvolumeInfo {
name: sv.name.clone(),
mount_point: if is_mounted {
Some(sv.path.clone())
} else {
None
},
is_mounted,
}
})
.collect();
StorageStatus {
mosdata_device,
mosboot_device,
is_mounted,
subvolumes,
}
}
pub fn unmount() -> Result<(), StorageError> {
mount::unmount_all()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storage_state_variants() {
let _mounted = StorageState::Mounted {
device: PathBuf::from("/dev/sda2"),
boot_device: Some(PathBuf::from("/dev/sda1")),
};
let _initialized = StorageState::Initialized {
device: PathBuf::from("/dev/sda2"),
boot_device: None,
};
let _no_disk = StorageState::NoDisk;
}
}