Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/src/admin_view/disks.rs

//! **The Disks pane, the disk speed test and the twin's credential read** (`estate`):
//! edda `disk-snapshot` and nordisk-twin's certificate reading, for the admin view.

use super::*;

/// Whether this process started the disk speed test (and over which root).
static SPEED_ROOT: OnceLock<PathBuf> = OnceLock::new();

/// An installed credential onto the pane: its doors, principal, issuer and end.
pub(crate) fn installed(
    c: &mut pb::AdminTwinCredential,
    cred: &crate::twin_tls::Credential,
    now_unix: i64,
) {
    c.installed = true;
    c.doors = cred.doors();
    c.principal = nordisk_twin::pki::common_name_of_cert(&cred.cert_pem)
        .ok()
        .flatten()
        .unwrap_or_default();
    c.issuer = nordisk_twin::pki::common_name_of_cert(&cred.ca_pem)
        .ok()
        .flatten()
        .unwrap_or_default();
    match nordisk_twin::pki::validity(&cred.cert_pem) {
        Ok((_, not_after)) => {
            c.not_after_unix = not_after;
            c.days_left = (not_after - now_unix).div_euclid(86_400);
        }
        Err(e) => c.error = format!("the installed certificate's validity cannot be read: {e}"),
    }
}

/// The floor the Disks pane computes with — advisory: holger refuses no write at it.
pub const FLOOR: disk_snapshot::Floor = disk_snapshot::Floor::DEFAULT;
/// [`FLOOR`] in words, on every answer, so nobody reads it as enforced.
pub const FLOOR_RULE: &str = "advisory: 5 % of the filesystem or 2 GiB, whichever is larger (edda disk-snapshot Floor::DEFAULT, gunnar's --disk-floor rule) — holger refuses no write at it today";

// ── Disks ───────────────────────────────────────────────────────────────────

/// **The paths holger cares about**, labelled: `system` = `/`, `data` = the data
/// root, `storage:<repo>` = every place a repository keeps its bytes (a path
/// named twice under one label is listed once).
pub fn volumes(
    data_root: Option<&Path>,
    places: &crate::metering::StoragePlaces,
) -> Vec<disk_snapshot::Volume> {
    let mut v = vec![disk_snapshot::Volume::new("system", "/")];
    if let Some(root) = data_root {
        v.push(disk_snapshot::Volume::new("data", root));
    }
    for (repo, ps) in places {
        let mut seen: Vec<&PathBuf> = Vec::new();
        for p in ps {
            if seen.contains(&&p.path) {
                continue;
            }
            seen.push(&p.path);
            v.push(disk_snapshot::Volume::new(
                format!("storage:{repo}"),
                &p.path,
            ));
        }
    }
    v
}

/// What the Disks pane says of a repository's storage folder that does not exist yet.
pub const MADE_AT_FIRST_UPLOAD: &str =
    "created at the first upload: holger makes this folder when it first stores something here";

/// The Disks pane over this machine (blocking: statvfs + sysfs).
pub fn disks(volumes: &[disk_snapshot::Volume]) -> pb::AdminDisks {
    disks_from(disk_snapshot::take(volumes, FLOOR))
}

/// A snapshot onto the wire, asking the filesystem whether each unplaced path exists.
pub fn disks_from(s: disk_snapshot::Snapshot) -> pb::AdminDisks {
    disks_from_with(s, &|p: &Path| p.exists())
}

/// Pure: a snapshot onto the wire; `exists` says whether a path is there.
pub fn disks_from_with(
    s: disk_snapshot::Snapshot,
    exists: &dyn Fn(&Path) -> bool,
) -> pb::AdminDisks {
    pb::AdminDisks {
        mounts: s
            .disks
            .into_iter()
            .map(|d| {
                let (f, error) = match d.figures {
                    Ok(f) => (f, String::new()),
                    Err(why) => (ZERO, why),
                };
                pb::AdminDisk {
                    source: d.mount.source,
                    mount_point: d.mount.mount_point.display().to_string(),
                    fstype: d.mount.fstype,
                    read_only: d.mount.read_only,
                    labels: d.labels,
                    paths: d.paths.iter().map(|p| p.display().to_string()).collect(),
                    total_bytes: f.total_bytes,
                    free_bytes: f.free_bytes,
                    available_bytes: f.available_bytes,
                    used_bytes: f.used_bytes,
                    reserved_bytes: f.reserved_bytes,
                    inodes_total: f.inodes_total,
                    inodes_free: f.inodes_free,
                    block_size: if error.is_empty() { f.block_size } else { 0 },
                    floor_bytes: d.floor_bytes,
                    servable_bytes: d.servable_bytes,
                    used_permille: f.used_permille(),
                    error,
                }
            })
            .collect(),
        block_devices: s
            .block_devices
            .into_iter()
            .map(|b| pb::AdminBlockDevice {
                name: b.name,
                size_bytes: b.size_bytes,
                rotational: b.rotational,
                removable: b.removable,
                read_only: b.read_only,
                partitions: b
                    .partitions
                    .into_iter()
                    .map(|(name, size_bytes)| pb::AdminPartition { name, size_bytes })
                    .collect(),
                holders: b.holders,
                members: b.members,
                raid_level: b.raid_level,
            })
            .collect(),
        unresolved: s
            .unresolved
            .into_iter()
            .map(|u| {
                // A repository's storage folder that is not there yet is made at the first
                // upload: no fault, and said so. Anything else unplaced keeps its reason.
                let later = u.label.starts_with("storage:") && !exists(&u.path);
                pb::AdminUnresolved {
                    label: u.label,
                    path: u.path.display().to_string(),
                    why: if later {
                        MADE_AT_FIRST_UPLOAD.to_string()
                    } else {
                        u.why
                    },
                    made_at_first_upload: later,
                }
            })
            .collect(),
        problems: s.problems,
        floor_rule: FLOOR_RULE.to_string(),
    }
}

const ZERO: disk_snapshot::Figures = disk_snapshot::Figures {
    total_bytes: 0,
    free_bytes: 0,
    available_bytes: 0,
    used_bytes: 0,
    reserved_bytes: 0,
    inodes_total: 0,
    inodes_free: 0,
    block_size: 0,
};

/// **Start the disk speed test** over `data_root`, once per process, on its
/// own thread (edda `disk_snapshot::speed`, 256 MiB written and removed). The
/// CLI calls it on an appliance's PID 1 or when [`SPEED_ENV`] is set; serving
/// never waits for it. Returns whether it started.
pub fn start_disk_speed(data_root: PathBuf) -> bool {
    if SPEED_ROOT.set(data_root.clone()).is_err() {
        return false;
    }
    // A32-14, stays a task: the disk speed probe (disk-snapshot), one bounded measurement.
    disk_snapshot::speed::spawn(data_root, "holger-disk-speed", crate::clock::now_unix_ms)
}

/// The disk speed pane figure: the latched run, or why there is none.
pub fn disk_speed() -> pb::AdminDiskSpeed {
    disk_speed_from(
        SPEED_ROOT.get().map(PathBuf::as_path),
        disk_snapshot::speed::last(),
    )
}

/// Pure: the latch onto the wire. No run is `measured = false` with its reason.
pub fn disk_speed_from(
    started: Option<&Path>,
    last: Option<disk_snapshot::speed::Latched>,
) -> pb::AdminDiskSpeed {
    use disk_snapshot::speed::Outcome;
    let Some(l) = last else {
        return match started {
            None => pb::AdminDiskSpeed {
                measured: false,
                why: SPEED_NOT_RUN.into(),
                ..Default::default()
            },
            Some(p) => pb::AdminDiskSpeed {
                measured: false,
                path: p.display().to_string(),
                why: SPEED_RUNNING.into(),
                ..Default::default()
            },
        };
    };
    let path = l.path.display().to_string();
    match l.outcome {
        Outcome::Done(r) => pb::AdminDiskSpeed {
            measured: true,
            path,
            seq_write_bps: r.seq_write_bps,
            seq_read_bps: r.seq_read_bps,
            read_how: r.read_how.as_str().into(),
            read_cached: r.read_was_cached(),
            sync_4k_ops_per_s: r.sync_ops_per_s,
            seconds: r.elapsed.as_secs_f64(),
            at_unix_ms: l.at_unix_ms,
            why: String::new(),
        },
        other => pb::AdminDiskSpeed {
            measured: false,
            path,
            at_unix_ms: l.at_unix_ms,
            why: format!("not measured: {}", other.row().0),
            ..Default::default()
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_volumes_are_system_data_and_every_storage_place_once() {
        let mut places = crate::metering::StoragePlaces::new();
        places.insert(
            "crates".into(),
            vec![
                crate::repository::StoragePlace {
                    kind: "archive (writable)",
                    path: "/data/crates.zip".into(),
                },
                crate::repository::StoragePlace {
                    kind: "files",
                    path: "/data/crates".into(),
                },
                crate::repository::StoragePlace {
                    kind: "archive",
                    path: "/data/crates.zip".into(),
                },
            ],
        );
        let v = volumes(Some(Path::new("/data")), &places);
        let got: Vec<_> = v
            .iter()
            .map(|v| (v.label.as_str(), v.path.display().to_string()))
            .collect();
        assert_eq!(
            got,
            [
                ("system", "/".to_string()),
                ("data", "/data".into()),
                ("storage:crates", "/data/crates.zip".into()),
                ("storage:crates", "/data/crates".into())
            ]
        );
    }
    /// ★ HOL-UI-FIX (owner 2026-11-01): a repository's storage folder that does not exist yet
    /// is made at the first upload. The pane said "sits on no mount: No such file or
    /// directory" for each, in warning yellow, on every fresh box; the truth is that nothing is
    /// wrong. Any other unplaced path stays a fault with its reason.
    #[test]
    fn a_storage_folder_not_made_yet_is_created_at_the_first_upload() {
        let snap = |label: &str, path: &str| disk_snapshot::Snapshot {
            unresolved: vec![disk_snapshot::Unresolved {
                label: label.into(),
                path: path.into(),
                why: format!("{path}: No such file or directory (os error 2)"),
            }],
            ..Default::default()
        };
        let d = disks_from_with(snap("storage:incoming", "/data/store/incoming"), &|_| false);
        let u = &d.unresolved[0];
        assert!(
            u.made_at_first_upload,
            "a storage folder not made yet is made at the first upload"
        );
        assert_eq!(u.why, MADE_AT_FIRST_UPLOAD);
        assert_eq!(
            (u.label.as_str(), u.path.as_str()),
            ("storage:incoming", "/data/store/incoming")
        );
        // The data root missing is a fault, whatever exists.
        let d = disks_from_with(snap("data", "/data"), &|_| false);
        assert!(!d.unresolved[0].made_at_first_upload);
        assert_eq!(
            d.unresolved[0].why,
            "/data: No such file or directory (os error 2)"
        );
        // A storage folder that IS there and sits on no mount is a fault too.
        let d = disks_from_with(snap("storage:incoming", "/data/store/incoming"), &|_| true);
        assert!(!d.unresolved[0].made_at_first_upload);
    }
    #[test]
    fn an_unmeasured_mount_carries_its_reason_and_no_figure() {
        let s = disk_snapshot::Snapshot {
            disks: vec![disk_snapshot::DiskEntry {
                mount: disk_snapshot::Mount {
                    source: "/dev/x".into(),
                    mount_point: "/x".into(),
                    fstype: "xfs".into(),
                    read_only: false,
                },
                labels: vec!["data".into()],
                paths: vec!["/x".into()],
                figures: Err("statvfs(/x) failed: EIO".into()),
                floor_bytes: 0,
                servable_bytes: 0,
            }],
            ..Default::default()
        };
        let d = &disks_from(s).mounts[0];
        assert_eq!(d.error, "statvfs(/x) failed: EIO");
        assert_eq!((d.total_bytes, d.available_bytes, d.block_size), (0, 0, 0));
        assert_eq!(d.labels, ["data"]);
    }
    #[test]
    fn the_disk_speed_is_the_latched_run_or_why_there_is_none() {
        use disk_snapshot::speed::{Latched, Outcome, ReadHow, Report};
        let none = disk_speed_from(None, None);
        assert!(!none.measured);
        assert_eq!(none.why, SPEED_NOT_RUN);
        assert_eq!(
            (
                none.seq_write_bps,
                none.seq_read_bps,
                none.sync_4k_ops_per_s
            ),
            (0.0, 0.0, 0.0)
        );
        let running = disk_speed_from(Some(Path::new("/data")), None);
        assert!(!running.measured);
        assert_eq!(
            (running.path.as_str(), running.why.as_str()),
            ("/data", SPEED_RUNNING)
        );
        let r = Report {
            seq_write_bps: 812e6,
            seq_read_bps: 1.9e9,
            read_how: ReadHow::Direct,
            sync_ops_per_s: 3100.0,
            elapsed: std::time::Duration::from_millis(1600),
        };
        let done = disk_speed_from(
            Some(Path::new("/data")),
            Some(Latched {
                outcome: Outcome::Done(r),
                path: "/data".into(),
                at_unix_ms: 7,
            }),
        );
        assert!(done.measured && done.why.is_empty() && !done.read_cached);
        assert_eq!(
            (
                done.seq_write_bps,
                done.sync_4k_ops_per_s,
                done.read_how.as_str(),
                done.at_unix_ms
            ),
            (812e6, 3100.0, "direct", 7)
        );
        let skipped = disk_speed_from(
            Some(Path::new("/data")),
            Some(Latched {
                outcome: Outcome::Skipped {
                    free: 800 << 20,
                    need: 1 << 30,
                },
                path: "/data".into(),
                at_unix_ms: 9,
            }),
        );
        assert!(!skipped.measured);
        assert!(
            skipped.why.starts_with("not measured: skipped"),
            "{}",
            skipped.why
        );
    }
}