feat(orchestrator,cli,config,fs): implement 3 modes, CLI-first precedence, kernel topo, defaults
- Orchestrator: - Add mutually exclusive modes: --mount-existing, --report-current, --apply - Wire mount-existing/report-current flows and JSON summaries - Reuse mount planning/application; never mount ESP - Context builders for new flags (see: src/orchestrator/run.rs:1) - CLI: - Add --mount-existing and --report-current flags - Keep -t/--topology (ValueEnum) as before (see: src/cli/args.rs:1) - FS: - Implement probe_existing_filesystems() using blkid to detect ZOSDATA/ZOSBOOT and dedupe by UUID (see: src/fs/plan.rs:1) - Config loader: - Precedence now: CLI flags > kernel cmdline (zosstorage.topo) > built-in defaults - Read kernel cmdline topology only if CLI didn’t set -t/--topology - Default topology set to DualIndependent - Do not read /etc config by default in initramfs (see: src/config/loader.rs:1) - Main: - Wire new Context builder flags (see: src/main.rs:1) Rationale: - Enables running from in-kernel initramfs with no config file - Topology can be selected via kernel cmdline (zosstorage.topo) or CLI; CLI has priority
This commit is contained in:
@@ -49,6 +49,7 @@ use crate::{
|
||||
Error,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
use std::fs;
|
||||
|
||||
/// Filesystem kinds supported by zosstorage.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -342,6 +343,95 @@ fn parse_blkid_export(s: &str) -> std::collections::HashMap<String, String> {
|
||||
map
|
||||
}
|
||||
|
||||
/// Probe existing filesystems on the system and return their identities (kind, uuid, label).
|
||||
///
|
||||
/// This inspects /proc/partitions and uses `blkid -o export` on each device to detect:
|
||||
/// - Data filesystems: Btrfs or Bcachefs with label "ZOSDATA"
|
||||
/// - ESP filesystems: Vfat with label "ZOSBOOT"
|
||||
/// Multi-device filesystems (e.g., btrfs) are de-duplicated by UUID.
|
||||
///
|
||||
/// Returns:
|
||||
/// - Vec<FsResult> with at most one entry per filesystem UUID.
|
||||
pub fn probe_existing_filesystems() -> Result<Vec<FsResult>> {
|
||||
let Some(blkid) = which_tool("blkid")? else {
|
||||
return Err(Error::Filesystem("blkid not found in PATH; cannot probe existing filesystems".into()));
|
||||
};
|
||||
|
||||
let content = fs::read_to_string("/proc/partitions")
|
||||
.map_err(|e| Error::Filesystem(format!("/proc/partitions read error: {}", e)))?;
|
||||
|
||||
let mut results_by_uuid: std::collections::HashMap<String, FsResult> = std::collections::HashMap::new();
|
||||
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with("major") {
|
||||
continue;
|
||||
}
|
||||
// Format: major minor #blocks name
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let name = parts[3];
|
||||
// Skip pseudo devices commonly not relevant (loop, ram, zram, fd)
|
||||
if name.starts_with("loop")
|
||||
|| name.starts_with("ram")
|
||||
|| name.starts_with("zram")
|
||||
|| name.starts_with("fd")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let dev_path = format!("/dev/{}", name);
|
||||
// Probe with blkid -o export; ignore non-zero statuses meaning "nothing found"
|
||||
let out = match run_cmd_capture(&[blkid.as_str(), "-o", "export", dev_path.as_str()]) {
|
||||
Ok(o) => o,
|
||||
Err(Error::Tool { status, .. }) if status != 0 => {
|
||||
// No recognizable signature; skip
|
||||
continue;
|
||||
}
|
||||
Err(_) => {
|
||||
// Unexpected failure; skip this device
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let map = parse_blkid_export(&out.stdout);
|
||||
let ty = map.get("TYPE").cloned().unwrap_or_default();
|
||||
let label = map
|
||||
.get("ID_FS_LABEL").cloned()
|
||||
.or_else(|| map.get("LABEL").cloned())
|
||||
.unwrap_or_default();
|
||||
let uuid = map
|
||||
.get("ID_FS_UUID").cloned()
|
||||
.or_else(|| map.get("UUID").cloned());
|
||||
|
||||
let (kind_opt, expected_label) = match ty.as_str() {
|
||||
"btrfs" => (Some(FsKind::Btrfs), "ZOSDATA"),
|
||||
"bcachefs" => (Some(FsKind::Bcachefs), "ZOSDATA"),
|
||||
"vfat" => (Some(FsKind::Vfat), "ZOSBOOT"),
|
||||
_ => (None, ""),
|
||||
};
|
||||
|
||||
if let (Some(kind), Some(u)) = (kind_opt, uuid) {
|
||||
// Enforce reserved label semantics
|
||||
if !expected_label.is_empty() && label != expected_label {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deduplicate multi-device filesystems by UUID; record first-seen device
|
||||
results_by_uuid.entry(u.clone()).or_insert(FsResult {
|
||||
kind,
|
||||
devices: vec![dev_path.clone()],
|
||||
uuid: u,
|
||||
label: label.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results_by_uuid.into_values().collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_parse {
|
||||
use super::parse_blkid_export;
|
||||
|
||||
Reference in New Issue
Block a user