#[cfg(target_os = "linux")]
pub(super) fn terminate(pid: u32, expected_start: &str) -> anyhow::Result<()> {
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) };
if fd < 0 {
return Err(std::io::Error::last_os_error().into());
}
let fd = unsafe { OwnedFd::from_raw_fd(fd as i32) };
anyhow::ensure!(
start(pid).as_deref() == Some(expected_start),
"Command died or its process identity changed; no signal sent"
);
let sent = unsafe {
libc::syscall(
libc::SYS_pidfd_send_signal,
fd.as_raw_fd(),
libc::SIGTERM,
std::ptr::null::<libc::siginfo_t>(),
0,
)
};
if sent < 0 {
return Err(std::io::Error::last_os_error().into());
}
Ok(())
}
#[cfg(target_os = "macos")]
pub(super) fn terminate(pid: u32, expected_start: &str) -> anyhow::Result<()> {
let native_pid = i32::try_from(pid)?;
anyhow::ensure!(native_pid > 0, "invalid Command process ID");
#[repr(C)]
struct UniqueInfo {
uuid: [u8; 16],
unique_id: u64,
parent_unique_id: u64,
id_version: i32,
parent_id_version: i32,
reserved: [u64; 2],
}
let mut info = std::mem::MaybeUninit::<UniqueInfo>::uninit();
let size = std::mem::size_of::<UniqueInfo>() as i32;
let read = unsafe { libc::proc_pidinfo(native_pid, 17, 0, info.as_mut_ptr().cast(), size) };
anyhow::ensure!(
read == size,
"could not read Command process identity; no signal sent"
);
let info = unsafe { info.assume_init() };
anyhow::ensure!(
start(pid).as_deref() == Some(expected_start),
"Command died or its process identity changed; no signal sent"
);
let symbol =
unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"proc_signal_with_audittoken".as_ptr()) };
anyhow::ensure!(
!symbol.is_null(),
"identity-checked Stop is unavailable on this macOS version; no signal sent"
);
let signal: unsafe extern "C" fn(*mut [u32; 8], i32) -> i32 =
unsafe { std::mem::transmute(symbol) };
let mut token = [0u32; 8];
token[5] = pid;
token[7] = info.id_version as u32;
let error = unsafe { signal(&mut token, libc::SIGTERM) };
if error != 0 {
return Err(std::io::Error::from_raw_os_error(error).into());
}
Ok(())
}
#[cfg(target_os = "linux")]
pub(super) fn start(pid: u32) -> Option<String> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
let (_, fields) = stat.rsplit_once(") ")?;
let fields: Vec<_> = fields.split_whitespace().collect();
if matches!(*fields.first()?, "Z" | "X") {
return None;
}
let ticks = fields.get(19)?; let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id").ok()?;
Some(format!("{}:{ticks}", boot.trim()))
}
#[cfg(target_os = "macos")]
pub(super) fn start(pid: u32) -> Option<String> {
let pid = i32::try_from(pid).ok()?;
let mut info = std::mem::MaybeUninit::<libc::proc_bsdinfo>::uninit();
let size = std::mem::size_of::<libc::proc_bsdinfo>() as i32;
let read = unsafe {
libc::proc_pidinfo(
pid,
libc::PROC_PIDTBSDINFO,
0,
info.as_mut_ptr().cast(),
size,
)
};
if read != size {
return None;
}
let info = unsafe { info.assume_init() };
if info.pbi_status == libc::SZOMB {
return None;
}
Some(format!(
"{}:{}",
info.pbi_start_tvsec, info.pbi_start_tvusec
))
}