Skip to main content

running_process/broker/
host_identity.rs

1//! Host identity values stored in v1 CacheManifest files.
2//!
3//! Phase 2 of #228 (#231). The cleanup tool uses this identity to skip
4//! manifests restored from another machine or from a prior boot.
5//!
6//! The facts themselves come from [`crate::platform::host`]. What is decided
7//! here is what their *absence* means, which is a property of the comparison
8//! this identity exists to support, not of any host.
9
10use std::path::Path;
11
12use running_process_protocol::broker::v1::HostIdentity;
13
14/// Return the current host identity using the current directory as the
15/// filesystem-device probe.
16pub fn current() -> HostIdentity {
17    let cwd = std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir());
18    current_for_path(&cwd)
19}
20
21/// Return the current host identity, including the filesystem device id
22/// for `path` when the platform exposes it.
23pub fn current_for_path(path: &Path) -> HostIdentity {
24    HostIdentity {
25        hostname: crate::platform::host::hostname().unwrap_or_else(unknown),
26        machine_id: crate::platform::host::machine_id().unwrap_or_else(unknown),
27        boot_id: crate::platform::host::boot_id().unwrap_or_else(unavailable_boot_id),
28        // Zero is the manifest's "no device recorded" value, and comparing
29        // equal to another zero is harmless: a device id only ever narrows a
30        // match that hostname and machine id already made.
31        fs_dev_id: crate::platform::host::filesystem_device_id(path).unwrap_or(0),
32        // Empty means "this host has no namespaces to distinguish", which is
33        // the value hosts without them have always recorded.
34        namespace_id: crate::platform::host::namespace_id().unwrap_or_default(),
35    }
36}
37
38fn unknown() -> String {
39    "unknown".to_string()
40}
41
42/// Fail closed when the host cannot name the current boot.
43///
44/// A shared constant here would make every process on every such host compare
45/// as the same boot, which is exactly the mistake this field exists to catch.
46/// The token is instead stable for this process and deliberately different in
47/// the next one, so an identity probe refuses a daemon from an unknown boot
48/// rather than accepting it.
49///
50/// The `windows-boot-` prefix is historical: Windows is the only host that has
51/// ever reached this path, and manifests already on disk carry the spelling.
52fn unavailable_boot_id() -> String {
53    use std::sync::OnceLock;
54    use std::time::{SystemTime, UNIX_EPOCH};
55
56    static TOKEN: OnceLock<String> = OnceLock::new();
57    TOKEN
58        .get_or_init(|| {
59            let created = SystemTime::now()
60                .duration_since(UNIX_EPOCH)
61                .map(|duration| duration.as_nanos())
62                .unwrap_or_default();
63            format!("windows-boot-unavailable-{}-{created}", std::process::id())
64        })
65        .clone()
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn current_identity_has_required_strings() {
74        let id = current();
75        assert!(!id.hostname.is_empty());
76        assert!(!id.machine_id.is_empty());
77        assert!(!id.boot_id.is_empty());
78    }
79
80    /// A path that no host can attribute to a device still yields a usable
81    /// identity -- the device is the one field allowed to be absent.
82    #[test]
83    fn an_unattributable_path_still_yields_the_rest_of_the_identity() {
84        let id = current_for_path(Path::new(""));
85        assert!(!id.hostname.is_empty());
86        assert!(!id.machine_id.is_empty());
87        assert!(!id.boot_id.is_empty());
88    }
89
90    #[test]
91    fn unavailable_boot_id_is_stable_and_fail_closed() {
92        let first = unavailable_boot_id();
93        assert_eq!(unavailable_boot_id(), first);
94        assert!(first.starts_with(&format!("windows-boot-unavailable-{}-", std::process::id())));
95        assert_ne!(first, "unknown");
96    }
97}