#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Liveness {
Alive,
Dead,
Unknown,
}
#[cfg(target_os = "linux")]
pub fn current_boot_id() -> Option<String> {
std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
#[cfg(target_os = "macos")]
pub fn current_boot_id() -> Option<String> {
std::process::Command::new("sysctl")
.arg("-n")
.arg("kern.boottime")
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|value| {
let trimmed = value.trim();
let cutoff = trimmed
.find('}')
.map(|idx| idx + 1)
.unwrap_or(trimmed.len());
trimmed[..cutoff].to_string()
})
.filter(|value| !value.is_empty())
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn current_boot_id() -> Option<String> {
None
}
#[cfg(unix)]
pub fn process_alive(pid: u32) -> bool {
let pid = pid as libc::pid_t;
if pid <= 0 {
return false;
}
let result = unsafe { libc::kill(pid, 0) };
if result == 0 {
return true;
}
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
errno != libc::ESRCH
}
#[cfg(not(unix))]
pub fn process_alive(_pid: u32) -> bool {
true
}
pub fn is_owner_alive(pid: Option<u32>, recorded_boot_id: Option<&str>) -> Liveness {
let Some(pid) = pid else {
return Liveness::Unknown;
};
if !process_alive(pid) {
return Liveness::Dead;
}
match (recorded_boot_id, current_boot_id()) {
(Some(recorded), Some(current)) if recorded != current => Liveness::Dead,
_ => Liveness::Alive,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_alive_returns_true_for_self() {
assert!(process_alive(std::process::id()));
}
#[test]
fn process_alive_returns_false_for_pid_zero() {
assert!(!process_alive(0));
}
#[test]
fn process_alive_returns_false_for_unlikely_pid() {
assert!(!process_alive(0x7fff_ffff));
}
#[test]
fn is_owner_alive_unknown_without_pid() {
assert_eq!(is_owner_alive(None, Some("boot")), Liveness::Unknown);
}
#[test]
fn is_owner_alive_dead_when_boot_id_mismatches() {
let pid = std::process::id();
let liveness = is_owner_alive(Some(pid), Some("definitely-not-the-current-boot-id"));
if current_boot_id().is_some() {
assert_eq!(liveness, Liveness::Dead);
} else {
assert_eq!(liveness, Liveness::Alive);
}
}
#[test]
fn is_owner_alive_alive_when_self_pid_and_matching_or_missing_boot_id() {
let pid = std::process::id();
let boot = current_boot_id();
assert_eq!(is_owner_alive(Some(pid), boot.as_deref()), Liveness::Alive);
}
#[test]
fn is_owner_alive_dead_when_pid_is_dead() {
let liveness = is_owner_alive(Some(0x7fff_ffff), current_boot_id().as_deref());
assert_eq!(liveness, Liveness::Dead);
}
}