objects/store/
liveness.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Liveness {
18 Alive,
20 Dead,
22 Unknown,
25}
26
27#[cfg(target_os = "linux")]
35pub fn current_boot_id() -> Option<String> {
36 std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
37 .ok()
38 .map(|value| value.trim().to_string())
39 .filter(|value| !value.is_empty())
40}
41
42#[cfg(target_os = "macos")]
43pub fn current_boot_id() -> Option<String> {
44 std::process::Command::new("sysctl")
45 .arg("-n")
46 .arg("kern.boottime")
47 .output()
48 .ok()
49 .filter(|output| output.status.success())
50 .and_then(|output| String::from_utf8(output.stdout).ok())
51 .map(|value| {
52 let trimmed = value.trim();
53 let cutoff = trimmed
54 .find('}')
55 .map(|idx| idx + 1)
56 .unwrap_or(trimmed.len());
57 trimmed[..cutoff].to_string()
58 })
59 .filter(|value| !value.is_empty())
60}
61
62#[cfg(not(any(target_os = "linux", target_os = "macos")))]
63pub fn current_boot_id() -> Option<String> {
64 None
65}
66
67#[cfg(unix)]
73pub fn process_alive(pid: u32) -> bool {
74 let pid = pid as libc::pid_t;
75 if pid <= 0 {
76 return false;
77 }
78 let result = unsafe { libc::kill(pid, 0) };
79 if result == 0 {
80 return true;
81 }
82 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
83 errno != libc::ESRCH
84}
85
86#[cfg(not(unix))]
87pub fn process_alive(_pid: u32) -> bool {
88 true
92}
93
94pub fn is_owner_alive(pid: Option<u32>, recorded_boot_id: Option<&str>) -> Liveness {
98 let Some(pid) = pid else {
99 return Liveness::Unknown;
100 };
101
102 if !process_alive(pid) {
103 return Liveness::Dead;
104 }
105
106 match (recorded_boot_id, current_boot_id()) {
107 (Some(recorded), Some(current)) if recorded != current => Liveness::Dead,
108 _ => Liveness::Alive,
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn process_alive_returns_true_for_self() {
118 assert!(process_alive(std::process::id()));
119 }
120
121 #[test]
122 fn process_alive_returns_false_for_pid_zero() {
123 assert!(!process_alive(0));
124 }
125
126 #[test]
127 fn process_alive_returns_false_for_unlikely_pid() {
128 assert!(!process_alive(0x7fff_ffff));
133 }
134
135 #[test]
136 fn is_owner_alive_unknown_without_pid() {
137 assert_eq!(is_owner_alive(None, Some("boot")), Liveness::Unknown);
138 }
139
140 #[test]
141 fn is_owner_alive_dead_when_boot_id_mismatches() {
142 let pid = std::process::id();
143 let liveness = is_owner_alive(Some(pid), Some("definitely-not-the-current-boot-id"));
144 if current_boot_id().is_some() {
147 assert_eq!(liveness, Liveness::Dead);
148 } else {
149 assert_eq!(liveness, Liveness::Alive);
150 }
151 }
152
153 #[test]
154 fn is_owner_alive_alive_when_self_pid_and_matching_or_missing_boot_id() {
155 let pid = std::process::id();
156 let boot = current_boot_id();
157 assert_eq!(is_owner_alive(Some(pid), boot.as_deref()), Liveness::Alive);
158 }
159
160 #[test]
161 fn is_owner_alive_dead_when_pid_is_dead() {
162 let liveness = is_owner_alive(Some(0x7fff_ffff), current_boot_id().as_deref());
165 assert_eq!(liveness, Liveness::Dead);
166 }
167}