zinit 0.3.6

Process supervisor with dependency management
Documentation
#![allow(dead_code)]
//! MOS Storage Initialization Library
//!
//! Called by zinit during early boot to initialize or mount persistent storage.
//!
//! This library is idempotent:
//! - If MOS storage exists (by label), mounts it
//! - If no storage exists, finds empty disk and initializes
//! - If no suitable disk found, returns NoDisk (system runs diskless)
//!
//! # Safety
//!
//! This library NEVER touches disks that have existing data:
//! - Disks with partition tables
//! - Disks with filesystem signatures
//! - Disks with existing partitions
//! - Disks that are mounted or in use

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;

/// Result of storage initialization
#[derive(Debug, Clone)]
pub enum StorageState {
    /// Storage was already present and has been mounted
    Mounted {
        /// Data partition device path
        device: PathBuf,
        /// Boot partition device path (ESP for EFI systems)
        boot_device: Option<PathBuf>,
    },
    /// Storage was initialized on this boot
    Initialized {
        /// Data partition device path
        device: PathBuf,
        /// Boot partition device path (ESP for EFI systems)
        boot_device: Option<PathBuf>,
    },
    /// No suitable empty disk found, running diskless
    NoDisk,
}

/// Storage status information
#[derive(Debug, Clone)]
pub struct StorageStatus {
    /// Device path for mosdata partition (if found)
    pub mosdata_device: Option<PathBuf>,
    /// Device path for MOSBOOT partition (if found)
    pub mosboot_device: Option<PathBuf>,
    /// Whether storage is currently mounted
    pub is_mounted: bool,
    /// Subvolume information
    pub subvolumes: Vec<SubvolumeInfo>,
}

/// Information about a subvolume
#[derive(Debug, Clone)]
pub struct SubvolumeInfo {
    /// Subvolume name
    pub name: String,
    /// Mount point (if mounted)
    pub mount_point: Option<PathBuf>,
    /// Whether the subvolume is currently mounted
    pub is_mounted: bool,
}

/// Main entry point - call from zinit
///
/// This function is idempotent:
/// - If MOS storage exists (by label), mounts it
/// - If no storage exists, finds empty disk and initializes
/// - If no suitable disk found, returns NoDisk (system runs diskless)
pub fn init() -> Result<StorageState, StorageError> {
    log::info!("MOS storage: checking for existing storage");

    // Check if storage already exists
    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");

    // Find candidate disks
    let disks = detect::enumerate_disks()?;

    if disks.is_empty() {
        log::info!("MOS storage: no candidate disks found");
        return Ok(StorageState::NoDisk);
    }

    // Find first empty disk
    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 the disk
    initialize_disk(empty_disk)
}

/// Check if MOS storage already exists
pub fn storage_exists() -> bool {
    format::mosdata_exists()
}

/// Mount existing MOS storage
///
/// Assumes storage_exists() returned true
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);

    // Ensure subvolumes exist (they might be missing if init was interrupted)
    if let Some(ref device) = mosdata_device {
        ensure_subvolumes_exist(device)?;
    }

    // Mount everything
    mount::mount_all(boot_mode)?;

    log::info!("MOS storage: mount complete");

    Ok(StorageState::Mounted {
        device: mosdata_device.unwrap_or_default(),
        boot_device: mosboot_device,
    })
}

/// Ensure all required subvolumes exist on the data partition
fn ensure_subvolumes_exist(device: &std::path::Path) -> Result<(), StorageError> {
    use std::fs;

    const TEMP_MOUNT: &str = "/tmp/mos-subvol-check";

    // Create temp mount point
    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),
    })?;

    // Mount btrfs root (no subvol option)
    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(),
    })?;

    // Check and create missing subvolumes
    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(())
    })();

    // Always cleanup
    let _ = cmd::run("umount", &[TEMP_MOUNT]);
    let _ = fs::remove_dir(TEMP_MOUNT);

    result
}

/// Initialize a new disk for MOS storage
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
    );

    // Create partitions
    log::info!("MOS storage: creating partition table");
    let partitions = partition::create_partitions(device, boot_mode, data_size_gb)?;

    // Format partitions
    log::info!("MOS storage: formatting partitions");
    format::format_partitions(&partitions, boot_mode)?;

    // Create subvolumes
    log::info!("MOS storage: creating btrfs subvolumes");
    subvolume::create_subvolumes(&partitions.data.path)?;

    // Mount everything
    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,
    })
}

/// Get storage status without modifying anything
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,
    }
}

/// Unmount all MOS storage
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;
    }
}