OXYGENHUB
# ab3204b9974b oxygenflow / crates / flow / src / home.rs
5.1 KiB · Rust · last changed Re-read a pinned file from the disk before calling its hash wrong 1 month ago source blame history raw
e0918b88 Andy McCall 1
//! `$FLOW_HOME` (default `~/.local/share/flow`): the pinned parts of each
2
//! isolation tier, rootfs images by hash, scratch space and the runner's
3
//! configuration, all described by `flow.toml`.
4
//!
5
//! ```toml
6
//! [microvm]
7
//! firecracker = { path = "bin/firecracker", blake3 = "…" }
8
//! kernel = { path = "kernel/vmlinux", blake3 = "…" }
9
//! init = { path = "bin/flow-init", blake3 = "…" }
10
//!
11
//! [runner]                       # only for `flow runner`
12
//! hub = "https://hub.example:7343"
13
//! token = "…"                    # a runner token from the hub's [flow] runner_tokens
14
//! ca = "/path/to/hub-ca.pem"     # when the hub's certificate is self-signed
15
//! name = "earth-1"
16
//! ```
17
18
use anyhow::{bail, Context, Result};
19
use serde::Deserialize;
20
use std::collections::HashMap;
21
use std::path::{Path, PathBuf};
22
use std::sync::Mutex;
23
24
#[derive(Clone, Debug, Deserialize)]
25
pub struct Pinned {
26
    pub path: PathBuf,
27
    pub blake3: String,
28
}
29
30
#[derive(Clone, Debug, Deserialize)]
31
pub struct MicrovmConfig {
32
    pub firecracker: Pinned,
33
    pub kernel: Pinned,
34
    pub init: Pinned,
35
}
36
37
#[derive(Clone, Debug, Deserialize)]
38
pub struct RunnerConfig {
39
    pub hub: String,
40
    pub token: String,
41
    pub ca: Option<PathBuf>,
42
    pub name: Option<String>,
43
}
44
45
#[derive(Clone, Debug, Default, Deserialize)]
46
pub struct Config {
47
    pub microvm: Option<MicrovmConfig>,
48
    pub runner: Option<RunnerConfig>,
49
}
50
51
pub struct Home {
52
    pub root: PathBuf,
53
    pub config: Config,
54
    /// Files already verified, by path, with the (len, mtime) they had then.
55
    verified: Mutex<HashMap<PathBuf, (u64, std::time::SystemTime)>>,
56
}
57
58
impl Home {
59
    pub fn locate(explicit: Option<&Path>) -> Result<Home> {
60
        let root = match explicit {
61
            Some(p) => p.to_path_buf(),
62
            None => match std::env::var_os("FLOW_HOME") {
63
                Some(p) => PathBuf::from(p),
64
                None => PathBuf::from(std::env::var_os("HOME").context("HOME is not set")?).join(".local/share/flow"),
65
            },
66
        };
67
        let file = root.join("flow.toml");
68
        let config = if file.is_file() {
69
            let text = std::fs::read_to_string(&file).with_context(|| format!("read {}", file.display()))?;
70
            toml::from_str(&text).with_context(|| format!("parse {}", file.display()))?
71
        } else {
72
            Config::default()
73
        };
74
        for d in ["cas", "work", "repos"] {
75
            std::fs::create_dir_all(root.join(d)).with_context(|| format!("create {}", root.join(d).display()))?;
76
        }
77
        Ok(Home { root, config, verified: Mutex::new(HashMap::new()) })
78
    }
79
80
    pub fn resolve(&self, p: &Path) -> PathBuf {
81
        if p.is_absolute() {
82
            p.to_path_buf()
83
        } else {
84
            self.root.join(p)
85
        }
86
    }
87
88
    /// Where a rootfs image with this hash lives.
89
    pub fn rootfs(&self, hash: &str) -> PathBuf {
90
        self.root.join("cas").join(format!("{hash}.ext4"))
91
    }
92
93
    /// A pinned file, checked against its BLAKE3 before use (again only
94
    /// when its size or modification time changed since).
95
    pub fn verified(&self, path: &Path, blake3_hex: &str) -> Result<PathBuf> {
96
        let meta = std::fs::metadata(path).with_context(|| format!("{} is missing", path.display()))?;
97
        let stamp = (meta.len(), meta.modified()?);
98
        if self.verified.lock().unwrap().get(path) == Some(&stamp) {
99
            return Ok(path.to_path_buf());
100
        }
ab3204b9 Andy McCall ↶ 101
        let mut got = hash_file(path)?;
e0918b88 Andy McCall 102
        if got != blake3_hex {
ab3204b9 Andy McCall ↶ 103
            // The read may have come from bad cached pages rather than the
104
            // disk (oxygenflow#22: a RAM fault on Earth failed every job for
105
            // hours while the file was intact): drop the file's pages and
106
            // read it again from the disk before giving up.
107
            drop_cached(path);
108
            let again = hash_file(path)?;
109
            if again == blake3_hex {
110
                eprintln!(
111
                    "warning: {} hashed {got} from the page cache but {again} from the disk: the cached copy was bad (check this machine's memory)",
112
                    path.display()
113
                );
114
            }
115
            got = again;
116
        }
117
        if got != blake3_hex {
e0918b88 Andy McCall 118
            bail!("{} has BLAKE3 {got}, but {blake3_hex} is pinned", path.display());
119
        }
120
        self.verified.lock().unwrap().insert(path.to_path_buf(), stamp);
121
        Ok(path.to_path_buf())
ab3204b9 Andy McCall ↶ 122
    }
123
}
124
125
/// Ask the kernel to forget a file's cached pages, so the next read comes
126
/// from the disk. Best effort: dirty pages and other platforms stay.
127
fn drop_cached(path: &Path) {
128
    #[cfg(target_os = "linux")]
129
    if let Ok(f) = std::fs::File::open(path) {
130
        use std::os::fd::AsRawFd;
131
        // SAFETY: a valid descriptor; the advice only affects caching.
132
        unsafe {
133
            libc::posix_fadvise(f.as_raw_fd(), 0, 0, libc::POSIX_FADV_DONTNEED);
134
        }
e0918b88 Andy McCall 135
    }
ab3204b9 Andy McCall ↶ 136
    #[cfg(not(target_os = "linux"))]
137
    let _ = path;
e0918b88 Andy McCall 138
}
139
140
pub fn hash_file(path: &Path) -> Result<String> {
141
    let mut h = blake3::Hasher::new();
142
    h.update_reader(std::fs::File::open(path).with_context(|| format!("open {}", path.display()))?)?;
143
    Ok(h.finalize().to_hex().to_string())
144
}