use crate::service::{Context, Platform};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
struct BsdInfo {
ppid: u32,
start: (i64, i64),
}
#[cfg(target_os = "macos")]
fn bsd_info(pid: u32) -> Option<BsdInfo> {
let pid = libc::c_int::try_from(pid).ok()?;
let mut info: libc::proc_bsdinfo = unsafe { std::mem::zeroed() };
let size = std::mem::size_of::<libc::proc_bsdinfo>() as libc::c_int;
let n = unsafe { libc::proc_pidinfo(pid, libc::PROC_PIDTBSDINFO, 0, (&raw mut info).cast(), size) };
(n == size).then(|| BsdInfo {
ppid: info.pbi_ppid,
start: (info.pbi_start_tvsec as i64, info.pbi_start_tvusec as i64 * 1000),
})
}
#[cfg(not(target_os = "macos"))]
fn bsd_info(_pid: u32) -> Option<BsdInfo> {
None
}
#[cfg(target_os = "macos")]
fn bsd_path(pid: u32) -> Option<PathBuf> {
use std::os::unix::ffi::OsStrExt;
let pid = libc::c_int::try_from(pid).ok()?;
let mut buf = vec![0u8; libc::PROC_PIDPATHINFO_MAXSIZE as usize];
let n = unsafe { libc::proc_pidpath(pid, buf.as_mut_ptr().cast(), buf.len() as u32) };
let n = usize::try_from(n).ok().filter(|&n| n > 0)?;
Some(PathBuf::from(std::ffi::OsStr::from_bytes(&buf[..n])))
}
#[cfg(not(target_os = "macos"))]
fn bsd_path(_pid: u32) -> Option<PathBuf> {
None
}
fn proc_stat(cx: &Context, pid: u32) -> Option<Vec<String>> {
let text = std::fs::read_to_string(cx.root.join(format!("proc/{pid}/stat"))).ok()?;
let (_, rest) = text.rsplit_once(')')?;
Some(rest.split_whitespace().map(str::to_string).collect())
}
pub fn parent(cx: &Context, pid: u32) -> Option<u32> {
match cx.platform {
Platform::Linux => proc_stat(cx, pid)?.get(1)?.parse().ok(),
Platform::MacOs => bsd_info(pid).map(|info| info.ppid),
}
}
pub fn runs(cx: &Context, pid: u32, exe: &Path) -> Option<bool> {
let on_disk = std::fs::metadata(exe).ok()?;
match cx.platform {
Platform::Linux => {
let link = cx.root.join(format!("proc/{pid}/exe"));
if std::fs::read_link(&link)
.ok()?
.to_string_lossy()
.ends_with(" (deleted)")
{
return Some(false);
}
let running = std::fs::metadata(&link).ok()?;
Some(running.dev() == on_disk.dev() && running.ino() == on_disk.ino())
}
Platform::MacOs => {
let path = bsd_path(pid)?;
if path.canonicalize().ok()? != exe.canonicalize().ok()? {
return Some(false);
}
let start = bsd_info(pid)?.start;
Some((on_disk.ctime(), on_disk.ctime_nsec()) <= start)
}
}
}
pub fn linux_device(dev: u64) -> String {
let major = ((dev >> 8) & 0xfff) | ((dev >> 32) & !0xfff);
let minor = (dev & 0xff) | ((dev >> 12) & !0xff);
format!("{major:02x}:{minor:02x}")
}
pub fn flock_holder(cx: &Context, file: &Path) -> Option<u32> {
if cx.platform != Platform::Linux {
return None;
}
let meta = std::fs::metadata(file).ok()?;
let locks = std::fs::read_to_string(cx.root.join("proc/locks")).ok()?;
let id = format!("{}:{}", linux_device(meta.dev()), meta.ino());
locks.lines().find_map(|line| {
let fields: Vec<&str> = line.split_whitespace().collect();
match fields.as_slice() {
[_, "FLOCK", _, _, pid, file_id, ..] if *file_id == id => pid.parse().ok(),
_ => None,
}
})
}