crates/appliance-pid1/src/install.rs
//! **Whether this /data is the one the box had before its last install** (Gunnar's
//! `pid1::{INSTALL_RECORD, InstallRecord, Kept, announce_install_mode}`, moved here).
//! korp-installer (edda c09dd54, the data-survives rule) writes `/boot/korp/install`:
//! `mode=upgrade` + the kept `korp-data` span when it re-ran on a disk it had laid and kept
//! P3; `mode=upgrade` + `kept=data-set` on the boot disk of a multi-disk box; `mode=fresh`
//! otherwise. One `install.mode` line says it, so an operator knows a reinstall did NOT
//! re-open bootstrap. Silent when the record is absent (a container, a live ISO).
/// Where korp-installer wrote the install-mode record (the ESP, mounted at `/boot`).
pub const INSTALL_RECORD: &str = "/boot/korp/install";
/// What korp-installer's `korp/install` record says.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstallRecord {
/// `upgrade` / `fresh`; `-` when the line is missing.
pub mode: String,
/// What was kept, when the record names it.
pub kept: Option<Kept>,
}
/// The kept /data, in the two shapes the record can spell.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Kept {
/// `kept=korp-data` + `kept_partno=` + `kept_bytes=`: the boot disk's P3.
Partition {
/// The partition number.
partno: u32,
/// Its size in bytes (0 when the record did not say).
bytes: u64,
},
/// `kept=data-set`: the members hold it, the record names no span.
DataSet,
}
impl InstallRecord {
/// Pure over the record text (`key=value` lines). A `kept=korp-data` without a
/// parsable `kept_partno` says nothing about the span: `kept: None`, never `p-`.
pub fn parse(text: &str) -> Self {
let get = |k: &str| text.lines().find_map(|l| l.strip_prefix(k).and_then(|r| r.strip_prefix('='))).map(str::trim);
let mode = get("mode").unwrap_or("-").to_owned();
let kept = match get("kept") {
Some("data-set") => Some(Kept::DataSet),
Some("korp-data") => get("kept_partno").and_then(|p| p.parse().ok()).map(|partno| Kept::Partition { partno, bytes: get("kept_bytes").and_then(|b| b.parse().ok()).unwrap_or(0) }),
_ => None,
};
InstallRecord { mode, kept }
}
/// The `install.mode` line (Gunnar's event fields, one line): `set` is the kept data
/// set's shape as the product measured it (`4×75G`), `-` when unknown.
pub fn line(&self, set: Option<&str>) -> String {
match &self.kept {
Some(Kept::Partition { partno, bytes }) => format!("install.mode mode={} kept=korp-data kept_partno={partno} kept_gib={:.1}", self.mode, *bytes as f64 / (1u64 << 30) as f64),
Some(Kept::DataSet) => format!("install.mode mode={} kept=data-set kept_set={}", self.mode, set.unwrap_or("-")),
None => format!("install.mode mode={} kept=-", self.mode),
}
}
}
/// **Say the install record at `path`**, once, through `say`; `false` (and silence)
/// when there is none.
pub fn announce_install_mode_at(path: &std::path::Path, set: Option<&str>, say: &dyn Fn(&str)) -> bool {
let Ok(text) = std::fs::read_to_string(path) else { return false };
say(&format!("{} (install record read off the ESP)", InstallRecord::parse(&text).line(set)));
true
}
/// [`announce_install_mode_at`] on the real ESP ([`INSTALL_RECORD`]).
pub fn announce_install_mode(set: Option<&str>, say: &dyn Fn(&str)) -> bool {
announce_install_mode_at(std::path::Path::new(INSTALL_RECORD), set, say)
}
#[cfg(test)]
mod tests {
use super::*;
/// The install record in both shapes korp-installer writes (edda
/// `install::install_record`), and the shapes that must not turn into a `p-` on the
/// screen.
#[test]
fn the_install_record_parses_both_kept_shapes_and_never_invents_a_span() {
let single = "mode=upgrade\nkept=korp-data\nkept_partno=3\nkept_first_lba=33693663\nkept_last_lba=505511\nkept_bytes=241591910400\nkept_fs=xfs\n";
assert_eq!(InstallRecord::parse(single), InstallRecord { mode: "upgrade".into(), kept: Some(Kept::Partition { partno: 3, bytes: 241591910400 }) });
assert_eq!(InstallRecord::parse("mode=upgrade\nkept=data-set\n"), InstallRecord { mode: "upgrade".into(), kept: Some(Kept::DataSet) });
assert_eq!(InstallRecord::parse("mode=fresh\n"), InstallRecord { mode: "fresh".into(), kept: None });
assert_eq!(InstallRecord::parse("mode=upgrade\nkept=korp-data\n").kept, None, "a span-less korp-data record names no partition");
assert_eq!(InstallRecord::parse("").mode, "-");
}
/// The line said for each shape; the set's shape is the product's to measure (Gunnar
/// reads it off the md sysfs), and an unknown one is `-`, never a made-up number.
#[test]
fn the_install_line_names_the_mode_and_what_was_kept() {
assert_eq!(INSTALL_RECORD, "/boot/korp/install");
let p = InstallRecord::parse("mode=upgrade\nkept=korp-data\nkept_partno=3\nkept_bytes=241591910400\n");
assert_eq!(p.line(None), "install.mode mode=upgrade kept=korp-data kept_partno=3 kept_gib=225.0");
let s = InstallRecord::parse("mode=upgrade\nkept=data-set\n");
assert_eq!(s.line(Some("4\u{d7}75G")), "install.mode mode=upgrade kept=data-set kept_set=4\u{d7}75G");
assert_eq!(s.line(None), "install.mode mode=upgrade kept=data-set kept_set=-");
assert_eq!(InstallRecord::parse("mode=fresh\n").line(None), "install.mode mode=fresh kept=-");
}
/// Absent record: nothing is said.
#[test]
fn no_record_says_nothing() {
let d = tempfile::tempdir().unwrap();
let said = std::cell::RefCell::new(Vec::<String>::new());
assert!(!announce_install_mode_at(&d.path().join("install"), None, &|l| said.borrow_mut().push(l.to_string())));
assert!(said.borrow().is_empty());
std::fs::write(d.path().join("install"), "mode=fresh\n").unwrap();
assert!(announce_install_mode_at(&d.path().join("install"), None, &|l| said.borrow_mut().push(l.to_string())));
assert_eq!(said.borrow().as_slice(), ["install.mode mode=fresh kept=- (install record read off the ESP)".to_string()]);
}
}