zinit 0.3.7

Process supervisor with dependency management
Documentation
//! Btrfs subvolume creation

use super::cmd;
use super::error::StorageError;
use std::fs;
use std::path::Path;

/// Names of MOS subvolumes
pub const SUBVOLUMES: &[&str] = &["system", "etc", "modules", "vm-meta"];

/// Temporary mount point for subvolume creation
const TEMP_MOUNT: &str = "/tmp/mos-storage-init";

/// Create the required btrfs subvolumes on a data partition
///
/// Creates: system, etc, modules, vm-meta
pub fn create_subvolumes(data_device: &Path) -> Result<(), StorageError> {
    let dev_path = data_device.to_string_lossy();
    log::info!("creating btrfs subvolumes on {}", dev_path);

    // Create temporary mount point
    fs::create_dir_all(TEMP_MOUNT).map_err(|e| StorageError::SubvolumeFailed {
        path: TEMP_MOUNT.to_string(),
        error: format!("failed to create temp mount: {}", e),
    })?;

    // Mount the data partition
    cmd::run("mount", &[&dev_path, TEMP_MOUNT]).map_err(|e| StorageError::MountFailed {
        from: data_device.to_path_buf(),
        target: TEMP_MOUNT.into(),
        error: e.to_string(),
    })?;

    // Create subvolumes (cleanup on error)
    let result = create_subvolumes_inner();

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

    result
}

/// Inner function to create subvolumes (allows cleanup on error)
fn create_subvolumes_inner() -> Result<(), StorageError> {
    for name in SUBVOLUMES {
        let subvol_path = format!("{}/{}", TEMP_MOUNT, name);
        log::info!("creating subvolume: {}", name);

        cmd::run("btrfs", &["subvolume", "create", &subvol_path]).map_err(|e| {
            StorageError::SubvolumeFailed {
                path: name.to_string(),
                error: e.to_string(),
            }
        })?;
    }

    Ok(())
}

/// List existing subvolumes on a mounted btrfs filesystem
pub fn list_subvolumes(mount_point: &Path) -> Result<Vec<String>, StorageError> {
    let mount_path = mount_point.to_string_lossy();

    let output = cmd::run_output("btrfs", &["subvolume", "list", &mount_path]).map_err(|e| {
        StorageError::SubvolumeFailed {
            path: mount_path.to_string(),
            error: format!("failed to list subvolumes: {}", e),
        }
    })?;

    let subvols: Vec<String> = output
        .lines()
        .filter_map(|line| {
            // Format: "ID 256 gen 7 top level 5 path system"
            line.split_whitespace().last().map(String::from)
        })
        .collect();

    Ok(subvols)
}

/// Check if all required subvolumes exist
pub fn all_subvolumes_exist(mount_point: &Path) -> Result<bool, StorageError> {
    let existing = list_subvolumes(mount_point)?;

    for required in SUBVOLUMES {
        if !existing.iter().any(|s| s == *required) {
            return Ok(false);
        }
    }

    Ok(true)
}