use std::io;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Identity {
pub(crate) parent: u32,
pub(crate) started_sec: u64,
pub(crate) started_usec: u64,
}
pub(crate) const SUPPORTED: bool = cfg!(any(
target_os = "macos",
target_os = "linux",
target_os = "android"
));
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Origin {
Known(Identity),
Unavailable,
Opaque,
Missing,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Proof {
Same,
Changed,
}
impl Origin {
pub(crate) fn capture(pid: u32) -> Self {
if !SUPPORTED {
return Self::Unavailable;
}
match describe(pid) {
Ok(Some(identity)) => Self::Known(identity),
Ok(None) if exists(pid) => Self::Opaque,
Err(_) if exists(pid) => Self::Opaque,
_ => Self::Missing,
}
}
pub(crate) fn check(&self, pid: u32) -> io::Result<Proof> {
match self {
Self::Unavailable => Ok(Proof::Same),
Self::Opaque => Ok(match describe(pid).unwrap_or(None) {
None => Proof::Same,
Some(found) if found.parent == std::process::id() => Proof::Same,
Some(_) => Proof::Changed,
}),
Self::Missing => Err(io::Error::other(
"the child could not be identified at spawn; refusing to act on its pid",
)),
Self::Known(expected) => Ok(match describe(pid)? {
Some(found) if found == *expected => Proof::Same,
_ => Proof::Changed,
}),
}
}
}
pub(crate) fn exists(pid: u32) -> bool {
if unsafe { libc::kill(pid as libc::pid_t, 0) } == 0 {
return true;
}
io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
#[cfg(target_os = "macos")]
pub(crate) fn describe(pid: u32) -> io::Result<Option<Identity>> {
let mut info: libc::proc_bsdinfo = unsafe { std::mem::zeroed() };
let size = std::mem::size_of::<libc::proc_bsdinfo>() as libc::c_int;
unsafe { *libc::__error() = 0 };
let written = unsafe {
libc::proc_pidinfo(
pid as libc::pid_t,
libc::PROC_PIDTBSDINFO,
1,
(&mut info as *mut libc::proc_bsdinfo).cast(),
size,
)
};
if written == size {
return Ok((info.pbi_pid == pid).then_some(Identity {
parent: info.pbi_ppid,
started_sec: info.pbi_start_tvsec,
started_usec: info.pbi_start_tvusec,
}));
}
let error = io::Error::last_os_error();
match error.raw_os_error() {
Some(libc::ESRCH) | Some(0) | None if written <= 0 => Ok(None),
_ => Err(error),
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn describe(pid: u32) -> io::Result<Option<Identity>> {
let text = match std::fs::read_to_string(format!("/proc/{pid}/stat")) {
Ok(text) => text,
Err(error)
if error.kind() == io::ErrorKind::NotFound
|| error.raw_os_error() == Some(libc::ESRCH) =>
{
return if std::path::Path::new("/proc/self/stat").exists() {
Ok(None)
} else {
Err(error)
};
}
Err(error) => return Err(error),
};
let malformed = || io::Error::new(io::ErrorKind::InvalidData, "unreadable /proc stat");
let rest = text.rsplit_once(')').ok_or_else(malformed)?.1;
let mut fields = rest.split_whitespace();
let _state = fields.next().ok_or_else(malformed)?;
let parent = fields
.next()
.and_then(|f| f.parse().ok())
.ok_or_else(malformed)?;
let started = fields
.nth(17)
.and_then(|f| f.parse().ok())
.ok_or_else(malformed)?;
Ok(Some(Identity {
parent,
started_sec: started,
started_usec: 0,
}))
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "android")))]
pub(crate) fn describe(_pid: u32) -> io::Result<Option<Identity>> {
Err(io::Error::from(io::ErrorKind::Unsupported))
}