Files
zosstorage/docs/API-SKELETONS.md

22 KiB

zosstorage API Skeletons (Proposed)

Purpose

  • This document proposes concrete Rust module skeletons with public API surface and doc comments.
  • Bodies intentionally use todo!() or are omitted for approval-first workflow.
  • After approval, these will be created in the src tree in Code mode.

Index

Conventions


Crate root

References

Skeleton (for later implementation in code mode)

//! Crate root for zosstorage: one-shot disk provisioning utility for initramfs.

pub mod cli;
pub mod logging;
pub mod config;
pub mod device;
pub mod partition;
pub mod fs;
pub mod mount;
pub mod report;
pub mod orchestrator;
pub mod idempotency;
pub mod util;
pub mod errors;

pub use errors::{Error, Result};

/// Crate version constants.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

Errors

References

Skeleton

use thiserror::Error as ThisError;

/// Top-level error for zosstorage.
#[derive(Debug, ThisError)]
pub enum Error {
    #[error("configuration error: {0}")]
    Config(String),
    #[error("validation error: {0}")]
    Validation(String),
    #[error("device discovery error: {0}")]
    Device(String),
    #[error("partitioning error: {0}")]
    Partition(String),
    #[error("filesystem error: {0}")]
    Filesystem(String),
    #[error("mount error: {0}")]
    Mount(String),
    #[error("report error: {0}")]
    Report(String),
    #[error("external tool '{tool}' failed with status {status}: {stderr}")]
    Tool {
        tool: String,
        status: i32,
        stderr: String,
    },
    #[error("unimplemented: {0}")]
    Unimplemented(&'static str),
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

pub type Result<T> = std::result::Result<T, Error>;

Entrypoint

References

Skeleton

use zosstorage::{cli, config, logging, orchestrator, Result};

fn main() {
    // Initialize minimal logging early (fallback).
    // Proper logging config will be applied after CLI parsing.
    // No stdout printing.
    if let Err(e) = real_main() {
        // In initramfs, emit minimal error to stderr and exit non-zero.
        eprintln!("error: {e}");
        std::process::exit(1);
    }
}

fn real_main() -> Result<()> {
    let cli = cli::from_args();
    let log_opts = logging::LogOptions::from_cli(&cli);
    logging::init_logging(&log_opts)?;

    let cfg = config::load_and_merge(&cli)?;
    config::validate(&cfg)?;

    let ctx = orchestrator::Context::new(cfg, log_opts);
    orchestrator::run(&ctx)
}

CLI

References

Skeleton

use clap::{Parser, ValueEnum};

/// zosstorage - one-shot disk initializer for initramfs.
#[derive(Debug, Parser)]
#[command(name = "zosstorage", disable_help_subcommand = true)]
pub struct Cli {
    /// Path to YAML configuration (mirrors kernel cmdline key 'zosstorage.config=')
    #[arg(long = "config")]
    pub config: Option<String>,

    /// Log level: error, warn, info, debug
    #[arg(long = "log-level", default_value = "info")]
    pub log_level: String,

    /// Also log to /run/zosstorage/zosstorage.log
    #[arg(long = "log-to-file", default_value_t = false)]
    pub log_to_file: bool,

    /// Enable writing /etc/fstab entries
    #[arg(long = "fstab", default_value_t = false)]
    pub fstab: bool,

    /// Print preview JSON to stdout (non-destructive)
    #[arg(long = "show", default_value_t = false)]
    pub show: bool,

    /// Write preview JSON to a file (non-destructive)
    #[arg(long = "report")]
    pub report: Option<String>,

    /// Perform partitioning, filesystem creation, and mounts (DESTRUCTIVE)
    #[arg(long = "apply", default_value_t = false)]
    pub apply: bool,

    /// Present but non-functional; returns unimplemented error
    #[arg(long = "force")]
    pub force: bool,
}

/// Parse CLI arguments (non-interactive; suitable for initramfs).
pub fn from_args() -> Cli {
    Cli::parse()
}

Logging

References

Skeleton

use crate::Result;

/// Logging options resolved from CLI and/or config.
#[derive(Debug, Clone)]
pub struct LogOptions {
    pub level: String,       // "error" | "warn" | "info" | "debug"
    pub to_file: bool,       // when true, logs to /run/zosstorage/zosstorage.log
}

impl LogOptions {
    pub fn from_cli(cli: &crate::cli::Cli) -> Self {
        Self { level: cli.log_level.clone(), to_file: cli.log_to_file }
    }
}

/// Initialize tracing subscriber according to options.
/// Must be idempotent when called once in process lifetime.
pub fn init_logging(opts: &LogOptions) -> Result<()> {
    todo!("set up tracing for stderr and optional file layer")
}

Configuration types

References

Skeleton

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    pub level: String,     // default "info"
    pub to_file: bool,     // default false
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceSelection {
    pub include_patterns: Vec<String>,
    pub exclude_patterns: Vec<String>,
    pub allow_removable: bool,
    pub min_size_gib: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Topology {
    BtrfsSingle,
    BcachefsSingle,
    DualIndependent,
    Bcachefs2Copy,
    SsdHddBcachefs,
    BtrfsRaid1,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BiosBootSpec {
    pub enabled: bool,
    pub size_mib: u64,
    pub gpt_name: String, // "zosboot"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EspSpec {
    pub size_mib: u64,
    pub label: String,     // "ZOSBOOT"
    pub gpt_name: String,  // "zosboot"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataSpec {
    pub gpt_name: String,  // "zosdata"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheSpec {
    pub gpt_name: String,  // "zoscache"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Partitioning {
    pub alignment_mib: u64,
    pub require_empty_disks: bool,
    pub bios_boot: BiosBootSpec,
    pub esp: EspSpec,
    pub data: DataSpec,
    pub cache: CacheSpec,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BtrfsOptions {
    pub label: String,       // "ZOSDATA"
    pub compression: String, // "zstd:3"
    pub raid_profile: String // "none" | "raid1"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BcachefsOptions {
    pub label: String,       // "ZOSDATA"
    pub cache_mode: String,  // "promote" | "writeback?"
    pub compression: String, // "zstd"
    pub checksum: String,    // "crc32c"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VfatOptions {
    pub label: String,       // "ZOSBOOT"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsOptions {
    pub btrfs: BtrfsOptions,
    pub bcachefs: BcachefsOptions,
    pub vfat: VfatOptions,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MountSchemeKind {
    PerUuid,
    Custom, // reserved
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MountScheme {
    pub base_dir: String,      // "/var/cache"
    pub scheme: MountSchemeKind,
    pub fstab_enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportOptions {
    pub path: String,          // "/run/zosstorage/state.json"
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    pub version: u32,
    pub logging: LoggingConfig,
    pub device_selection: DeviceSelection,
    pub topology: Topology,
    pub partitioning: Partitioning,
    pub filesystem: FsOptions,
    pub mount: MountScheme,
    pub report: ReportOptions,
}

Configuration I/O

References

Skeleton

use crate::{cli::Cli, Result};

/// Load defaults, merge file config, overlay CLI, and finally kernel cmdline.
pub fn load_and_merge(cli: &Cli) -> Result<crate::config::types::Config> {
    todo!("implement precedence: file < CLI < kernel cmdline key 'zosstorage.config='")
}

/// Validate semantic correctness of the configuration.
pub fn validate(cfg: &crate::config::types::Config) -> Result<()> {
    todo!("ensure device filters, sizes, topology combinations are valid")
}

Device discovery

References

Skeleton

use crate::Result;

/// Eligible block device.
#[derive(Debug, Clone)]
pub struct Disk {
    pub path: String,
    pub size_bytes: u64,
    pub rotational: bool,
    pub model: Option<String>,
    pub serial: Option<String>,
}

/// Compiled device filters from config patterns.
#[derive(Debug, Clone)]
pub struct DeviceFilter {
    pub include: Vec<regex::Regex>,
    pub exclude: Vec<regex::Regex>,
    pub min_size_gib: u64,
}

/// Abstract provider for devices to enable testing with doubles.
pub trait DeviceProvider {
    fn list_block_devices(&self) -> Result<Vec<Disk>>;
    fn probe_properties(&self, disk: &mut Disk) -> Result<()>;
}

/// Discover eligible disks according to the filter policy.
pub fn discover(filter: &DeviceFilter) -> Result<Vec<Disk>> {
    todo!("enumerate /dev, apply include/exclude, probe properties")
}

Partitioning

References

Skeleton

use crate::{config::types::Config, device::Disk, Result};

#[derive(Debug, Clone, Copy)]
pub enum PartRole {
    BiosBoot,
    Esp,
    Data,
    Cache,
}

#[derive(Debug, Clone)]
pub struct PartitionSpec {
    pub role: PartRole,
    pub size_mib: Option<u64>, // None means "remainder"
    pub gpt_name: String,      // zosboot | zosdata | zoscache
}

#[derive(Debug, Clone)]
pub struct DiskPlan {
    pub disk: Disk,
    pub parts: Vec<PartitionSpec>,
}

#[derive(Debug, Clone)]
pub struct PartitionPlan {
    pub alignment_mib: u64,
    pub disks: Vec<DiskPlan>,
    pub require_empty_disks: bool,
}

#[derive(Debug, Clone)]
pub struct PartitionResult {
    pub disk: String,
    pub part_number: u32,
    pub role: PartRole,
    pub gpt_name: String,
    pub uuid: String,
    pub start_mib: u64,
    pub size_mib: u64,
    pub device_path: String, // e.g., /dev/nvme0n1p2
}

/// Compute GPT-only plan per topology and constraints.
pub fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result<PartitionPlan> {
    todo!("layout bios boot, ESP, data (and cache for SSD/HDD); align to 1 MiB")
}

/// Apply plan using sgdisk and verify via blkid; require empty disks if configured.
pub fn apply_partitions(plan: &PartitionPlan) -> Result<Vec<PartitionResult>> {
    todo!("shell out to sgdisk, trigger udev settle, collect partition GUIDs")
}

Filesystems

References

Skeleton

use crate::{partition::PartitionResult, config::types::Config, Result};

#[derive(Debug, Clone, Copy)]
pub enum FsKind {
    Vfat,
    Btrfs,
    Bcachefs,
}

#[derive(Debug, Clone)]
pub struct FsSpec {
    pub kind: FsKind,
    pub devices: Vec<String>, // 1 device for vfat/btrfs; 2 for bcachefs (cache + backing)
    pub label: String,        // "ZOSBOOT" (vfat) or "ZOSDATA" (data)
}

#[derive(Debug, Clone)]
pub struct FsPlan {
    pub specs: Vec<FsSpec>,
}

#[derive(Debug, Clone)]
pub struct FsResult {
    pub kind: FsKind,
    pub devices: Vec<String>,
    pub uuid: String,
    pub label: String,
}

/// Decide which partitions get which filesystem based on topology.
pub fn plan_filesystems(
    parts: &[PartitionResult],
    cfg: &Config,
) -> Result<FsPlan> {
    todo!("map ESP to vfat, data to btrfs or bcachefs according to topology")
}

//// Create the filesystems and return identity info (UUIDs, labels).
pub fn make_filesystems(plan: &FsPlan, cfg: &Config) -> Result<Vec<FsResult>> {
    todo!("invoke mkfs tools with configured options via util::run_cmd")
}

Mounting

References

Skeleton

use crate::{fs::FsResult, config::types::Config, Result};

#[derive(Debug, Clone)]
pub struct MountPlan {
    pub entries: Vec<(String /* source */, String /* target */, String /* fstype */, String /* options */)>,
}

#[derive(Debug, Clone)]
pub struct MountResult {
    pub source: String,
    pub target: String,
    pub fstype: String,
    pub options: String,
}

//// Build mount plan:
//// - Root-mount all data filesystems under `/var/mounts/{UUID}` (runtime only)
//// - Ensure/create subvolumes on the primary data filesystem: system, etc, modules, vm-meta
//// - Plan final mounts to `/var/cache/{system,etc,modules,vm-meta}` using
////   `subvol=` for btrfs and `X-mount.subdir=` for bcachefs.
pub fn plan_mounts(fs_results: &[FsResult], cfg: &Config) -> Result<MountPlan> {
    todo!("root mounts under /var/mounts/{UUID}; final subvol/subdir mounts to /var/cache/{system,etc,modules,vm-meta}")
}

/// Apply mounts using syscalls (nix), ensuring directories exist.
pub fn apply_mounts(plan: &MountPlan) -> Result<Vec<MountResult>> {
    todo!("perform mount syscalls and return results")
}

//// Optionally generate /etc/fstab entries for final subvolume/subdir mounts only.
//// - Write exactly four entries: system, etc, modules, vm-meta
//// - Use UUID= sources; deterministic order by target path
//// - Exclude runtime root mounts under `/var/mounts/{UUID}`
pub fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config) -> Result<()> {
    todo!("when enabled, write only the four final subvolume/subdir entries with UUID= sources")
}

Reporting

References

Skeleton

use serde::{Serialize, Deserialize};
use crate::{device::Disk, partition::PartitionResult, fs::FsResult, mount::MountResult, Result};

pub const REPORT_VERSION: &str = "v1";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateReport {
    pub version: String,
    pub timestamp: String, // RFC3339
    pub status: String,    // "success" | "already_provisioned" | "error"
    pub disks: Vec<serde_json::Value>,
    pub partitions: Vec<serde_json::Value>,
    pub filesystems: Vec<serde_json::Value>,
    pub mounts: Vec<serde_json::Value>,
    pub error: Option<String>,
}

/// Build the machine-readable state report.
pub fn build_report(
    disks: &[Disk],
    parts: &[PartitionResult],
    fs: &[FsResult],
    mounts: &[MountResult],
    status: &str,
) -> StateReport {
    todo!("assemble structured report in v1 format")
}

/// Write the state report to the configured path (default /run/zosstorage/state.json).
pub fn write_report(report: &StateReport, path: &str) -> Result<()> {
    todo!("serialize to JSON and persist atomically")
}

Orchestrator

References

Skeleton

use crate::{config::types::Config, logging::LogOptions, Result};

/// Execution context holding resolved configuration and environment flags.
#[derive(Debug, Clone)]
pub struct Context {
    pub cfg: Config,
    pub log: LogOptions,
}

impl Context {
    pub fn new(cfg: Config, log: LogOptions) -> Self { Self { cfg, log } }
}

/// High-level one-shot flow; aborts on any validation failure.
/// Returns Ok(()) on success and also on no-op when already provisioned.
pub fn run(ctx: &Context) -> Result<()> {
    todo!("idempotency check, discovery, planning, application, reporting")
}

Idempotency

References

Skeleton

use crate::{device::Disk, report::StateReport, Result};

/// Return existing state if system is already provisioned; otherwise None.
pub fn detect_existing_state() -> Result<Option<StateReport>> {
    todo!("probe GPT names (zosboot, zosdata, zoscache) and FS labels (ZOSBOOT, ZOSDATA)")
}

/// Determine if a disk is empty (no partitions and no known FS signatures).
pub fn is_empty_disk(disk: &Disk) -> Result<bool> {
    todo!("use blkid and partition table inspection to declare emptiness")
}

Utilities

References

Skeleton

use crate::Result;

/// Captured output from an external tool invocation.
#[derive(Debug, Clone)]
pub struct CmdOutput {
    pub status: i32,
    pub stdout: String,
    pub stderr: String,
}

/// Locate the absolute path to required tool if available.
pub fn which_tool(name: &str) -> Result<Option<String>> {
    todo!("use 'which' crate or manual PATH scanning")
}

/// Run a command and return Ok if the exit status is zero.
pub fn run_cmd(args: &[&str]) -> Result<()> {
    todo!("spawn process, log stderr on failure, map to Error::Tool")
}

/// Run a command and capture stdout/stderr for parsing (e.g., blkid).
pub fn run_cmd_capture(args: &[&str]) -> Result<CmdOutput> {
    todo!("spawn process and collect output")
}

/// Call udevadm settle with a timeout; warn if unavailable, then no-op.
pub fn udev_settle(timeout_ms: u64) -> Result<()> {
    todo!("invoke udevadm settle when present")
}

/// Detect UEFI environment by checking /sys/firmware/efi; used to suppress BIOS boot partition on UEFI.
pub fn is_efi_boot() -> bool {
    todo!("return Path::new(\"/sys/firmware/efi\").exists()")
}

Approval gate

  • This API skeleton is ready for implementation as source files with todo!() bodies.
  • Upon approval, we will:
    • Create the src/ files as outlined.
    • Add dependencies via cargo add.
    • Ensure all modules compile with placeholders.
    • Add initial tests scaffolding and example configs.

References summary