# 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 - [src/lib.rs](../src/lib.rs) - [src/errors.rs](../src/errors.rs) - [src/main.rs](../src/main.rs) - [src/cli/args.rs](../src/cli/args.rs) - [src/logging/mod.rs](../src/logging/mod.rs) - [src/types.rs](../src/types.rs) - [src/config/loader.rs](../src/config/loader.rs) - [src/device/discovery.rs](../src/device/discovery.rs) - [src/partition/plan.rs](../src/partition/plan.rs) - [src/fs/plan.rs](../src/fs/plan.rs) - [src/mount/ops.rs](../src/mount/ops.rs) - [src/report/state.rs](../src/report/state.rs) - [src/orchestrator/run.rs](../src/orchestrator/run.rs) - [src/idempotency/mod.rs](../src/idempotency/mod.rs) - [src/util/mod.rs](../src/util/mod.rs) Conventions - Shared [type Result](../src/errors.rs:1) and [enum Error](../src/errors.rs:1). - No stdout prints; use tracing only. - External tools invoked via [util](../src/util/mod.rs) wrappers. --- ## Crate root References - [src/lib.rs](../src/lib.rs) - [type Result = std::result::Result](../src/errors.rs:1) Skeleton (for later implementation in code mode) ```rust //! 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 - [enum Error](../src/errors.rs:1) - [type Result](../src/errors.rs:1) Skeleton ```rust 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 = std::result::Result; ``` --- ## Entrypoint References - [fn main()](../src/main.rs:1) - [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) Skeleton ```rust 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 - [struct Cli](../src/cli/args.rs:1) - [fn from_args() -> Cli](../src/cli/args.rs:1) Skeleton ```rust 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, /// 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, /// 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 - [struct LogOptions](../src/logging/mod.rs:1) - [fn init_logging(opts: &LogOptions) -> Result<()>](../src/logging/mod.rs:1) Skeleton ```rust 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 - [struct Config](../src/types.rs:1) - [enum Topology](../src/types.rs:1) - [struct DeviceSelection](../src/types.rs:1) - [struct Partitioning](../src/types.rs:1) - [struct FsOptions](../src/types.rs:1) - [struct MountScheme](../src/types.rs:1) - [struct ReportOptions](../src/types.rs:1) Skeleton ```rust 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, pub exclude_patterns: Vec, 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 - [fn load_and_merge(cli: &Cli) -> Result](../src/config/loader.rs:1) - [fn validate(cfg: &Config) -> Result<()>](../src/config/loader.rs:1) Skeleton ```rust use crate::{cli::Cli, Result}; /// Load defaults, merge file config, overlay CLI, and finally kernel cmdline. pub fn load_and_merge(cli: &Cli) -> Result { 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 - [struct Disk](../src/device/discovery.rs:1) - [struct DeviceFilter](../src/device/discovery.rs:1) - [trait DeviceProvider](../src/device/discovery.rs:1) - [fn discover(filter: &DeviceFilter) -> Result>](../src/device/discovery.rs:1) Skeleton ```rust 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, pub serial: Option, } /// Compiled device filters from config patterns. #[derive(Debug, Clone)] pub struct DeviceFilter { pub include: Vec, pub exclude: Vec, pub min_size_gib: u64, } /// Abstract provider for devices to enable testing with doubles. pub trait DeviceProvider { fn list_block_devices(&self) -> Result>; fn probe_properties(&self, disk: &mut Disk) -> Result<()>; } /// Discover eligible disks according to the filter policy. pub fn discover(filter: &DeviceFilter) -> Result> { todo!("enumerate /dev, apply include/exclude, probe properties") } ``` --- ## Partitioning References - [enum PartRole](../src/partition/plan.rs:1) - [struct PartitionSpec](../src/partition/plan.rs:1) - [struct PartitionPlan](../src/partition/plan.rs:1) - [struct PartitionResult](../src/partition/plan.rs:1) - [fn plan_partitions(disks: &[Disk], cfg: &Config) -> Result](../src/partition/plan.rs:1) - [fn apply_partitions(plan: &PartitionPlan) -> Result>](../src/partition/plan.rs:1) Skeleton ```rust 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, // None means "remainder" pub gpt_name: String, // zosboot | zosdata | zoscache } #[derive(Debug, Clone)] pub struct DiskPlan { pub disk: Disk, pub parts: Vec, } #[derive(Debug, Clone)] pub struct PartitionPlan { pub alignment_mib: u64, pub disks: Vec, 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 { 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> { todo!("shell out to sgdisk, trigger udev settle, collect partition GUIDs") } ``` --- ## Filesystems References - [enum FsKind](../src/fs/plan.rs:1) - [struct FsSpec](../src/fs/plan.rs:1) - [struct FsPlan](../src/fs/plan.rs:1) - [struct FsResult](../src/fs/plan.rs:1) - [fn plan_filesystems(...)](../src/fs/plan.rs:1) - [fn make_filesystems(...)](../src/fs/plan.rs:1) Skeleton ```rust 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, // 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, } #[derive(Debug, Clone)] pub struct FsResult { pub kind: FsKind, pub devices: Vec, pub uuid: String, pub label: String, } /// Decide which partitions get which filesystem based on topology. pub fn plan_filesystems( parts: &[PartitionResult], cfg: &Config, ) -> Result { 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> { todo!("invoke mkfs tools with configured options via util::run_cmd") } ``` --- ## Mounting References - [struct MountPlan](../src/mount/ops.rs:1) - [struct MountResult](../src/mount/ops.rs:1) - [fn plan_mounts(...)](../src/mount/ops.rs:1) - [fn apply_mounts(...)](../src/mount/ops.rs:1) - [fn maybe_write_fstab(...)](../src/mount/ops.rs:1) Skeleton ```rust 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 { 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> { 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 - [const REPORT_VERSION: &str](../src/report/state.rs:1) - [struct StateReport](../src/report/state.rs:1) - [fn build_report(...)](../src/report/state.rs:1) - [fn write_report(...)](../src/report/state.rs:1) Skeleton ```rust 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, pub partitions: Vec, pub filesystems: Vec, pub mounts: Vec, pub error: Option, } /// 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 - [struct Context](../src/orchestrator/run.rs:1) - [fn run(ctx: &Context) -> Result<()>](../src/orchestrator/run.rs:1) Skeleton ```rust 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 - [fn detect_existing_state() -> Result>](../src/idempotency/mod.rs:1) - [fn is_empty_disk(disk: &Disk) -> Result](../src/idempotency/mod.rs:1) Skeleton ```rust use crate::{device::Disk, report::StateReport, Result}; /// Return existing state if system is already provisioned; otherwise None. pub fn detect_existing_state() -> Result> { 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 { todo!("use blkid and partition table inspection to declare emptiness") } ``` --- ## Utilities References - [struct CmdOutput](../src/util/mod.rs:1) - [fn which_tool(name: &str) -> Result>](../src/util/mod.rs:1) - [fn run_cmd(args: &[&str]) -> Result<()>](../src/util/mod.rs:1) - [fn run_cmd_capture(args: &[&str]) -> Result](../src/util/mod.rs:1) - [fn udev_settle(timeout_ms: u64) -> Result<()>](../src/util/mod.rs:1) Skeleton ```rust 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> { 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 { 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 - [fn main()](../src/main.rs:1) - [fn from_args()](../src/cli/args.rs:1) - [fn init_logging(opts: &LogOptions)](../src/logging/mod.rs:1) - [fn load_and_merge(cli: &Cli)](../src/config/loader.rs:1) - [fn validate(cfg: &Config)](../src/config/loader.rs:1) - [fn discover(filter: &DeviceFilter)](../src/device/discovery.rs:1) - [fn plan_partitions(disks: &[Disk], cfg: &Config)](../src/partition/plan.rs:1) - [fn apply_partitions(plan: &PartitionPlan)](../src/partition/plan.rs:1) - [fn plan_filesystems(parts: &[PartitionResult], cfg: &Config)](../src/fs/plan.rs:1) - [fn make_filesystems(plan: &FsPlan)](../src/fs/plan.rs:1) - [fn plan_mounts(fs_results: &[FsResult], cfg: &Config)](../src/mount/ops.rs:1) - [fn apply_mounts(plan: &MountPlan)](../src/mount/ops.rs:1) - [fn maybe_write_fstab(mounts: &[MountResult], cfg: &Config)](../src/mount/ops.rs:1) - [fn build_report(...)](../src/report/state.rs:1) - [fn write_report(report: &StateReport)](../src/report/state.rs:1) - [fn detect_existing_state()](../src/idempotency/mod.rs:1) - [fn is_empty_disk(disk: &Disk)](../src/idempotency/mod.rs:1) - [fn which_tool(name: &str)](../src/util/mod.rs:1) - [fn run_cmd(args: &[&str])](../src/util/mod.rs:1) - [fn run_cmd_capture(args: &[&str])](../src/util/mod.rs:1) - [fn udev_settle(timeout_ms: u64)](../src/util/mod.rs:1)