#![allow(
dead_code,
reason = "unused when the mock-loader feature selects the DLL backend instead"
)]
use std::time::Duration;
const NAME_CAP: usize = 256;
const PROC_ALL_PIDS: u32 = 1;
pub(crate) fn name_of(pid: i32) -> Option<String> {
let mut buf = [0u8; NAME_CAP];
let len = unsafe { libc::proc_name(pid, buf.as_mut_ptr().cast(), NAME_CAP as u32) };
if len <= 0 {
return None;
}
let len = (len as usize).min(NAME_CAP);
String::from_utf8(buf[..len].to_vec()).ok()
}
pub(crate) fn all_pids() -> Vec<i32> {
let bytes = unsafe { libc::proc_listpids(PROC_ALL_PIDS, 0, std::ptr::null_mut(), 0) };
if bytes <= 0 {
return Vec::new();
}
let count = (bytes as usize / size_of::<i32>()) + 64;
let mut pids = vec![0i32; count];
let written = unsafe {
libc::proc_listpids(
PROC_ALL_PIDS,
0,
pids.as_mut_ptr().cast(),
(count * size_of::<i32>()) as i32,
)
};
if written <= 0 {
return Vec::new();
}
pids.truncate(written as usize / size_of::<i32>());
pids.retain(|p| *p > 0);
pids
}
fn name_matches(actual: &str, wanted: &str) -> bool {
let wanted = wanted.strip_suffix(".exe").unwrap_or(wanted);
actual.eq_ignore_ascii_case(wanted)
}
pub(crate) fn find(name_or_pid: &str) -> Option<i32> {
if let Ok(pid) = name_or_pid.parse::<i32>() {
if pid > 0 && exists(pid) {
return Some(pid);
}
}
all_pids()
.into_iter()
.find(|pid| name_of(*pid).is_some_and(|n| name_matches(&n, name_or_pid)))
}
pub(crate) fn exists(pid: i32) -> bool {
if unsafe { libc::kill(pid, 0) } == 0 {
return has_info(pid);
}
std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
fn has_info(pid: i32) -> bool {
let size = size_of::<libc::proc_bsdinfo>();
let mut info: libc::proc_bsdinfo = unsafe { std::mem::zeroed() };
let written = unsafe {
libc::proc_pidinfo(
pid,
libc::PROC_PIDTBSDINFO,
0,
std::ptr::from_mut(&mut info).cast(),
size as i32,
)
};
written as usize == size && info.pbi_status != libc::SZOMB
}
pub(crate) fn close(pid: i32) -> bool {
unsafe {
if libc::kill(pid, libc::SIGTERM) != 0 {
return false;
}
}
let deadline = std::time::Instant::now() + Duration::from_secs(2);
while std::time::Instant::now() < deadline {
if !exists(pid) {
return true;
}
std::thread::sleep(Duration::from_millis(50));
}
unsafe { libc::kill(pid, libc::SIGKILL) == 0 }
}
pub(crate) fn set_priority(pid: i32, autoit_priority: i32) -> bool {
let nice = match autoit_priority {
0 => 19, 1 => 10, 2 => 0, 3 => -5, 4 => -10, 5 => -20, _ => return false,
};
unsafe { libc::setpriority(libc::PRIO_PROCESS, pid as libc::id_t, nice) == 0 }
}
pub(crate) fn is_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_process_is_findable_by_its_own_pid_and_name() {
let me = std::process::id() as i32;
assert!(exists(me));
let name = name_of(me).expect("our own process has a name");
assert!(!name.is_empty());
assert_eq!(find(&me.to_string()), Some(me));
}
#[test]
fn a_pid_that_cannot_exist_is_absent() {
assert!(!exists(9_999_999));
assert!(name_of(9_999_999).is_none());
}
#[test]
fn the_process_list_contains_us_and_launchd() {
let pids = all_pids();
assert!(pids.contains(&(std::process::id() as i32)));
assert!(pids.contains(&1));
}
#[test]
fn windows_spelling_is_accepted() {
assert!(name_matches("Chrome", "chrome.exe"));
assert!(name_matches("Chrome", "Chrome"));
assert!(name_matches("Chrome", "CHROME"));
assert!(!name_matches("Chrome", "Chromium"));
}
#[test]
fn priorities_map_to_niceness_the_right_way_round() {
let me = std::process::id() as i32;
assert!(set_priority(me, 1));
let nice = unsafe { libc::getpriority(libc::PRIO_PROCESS, me as libc::id_t) };
assert_eq!(nice, 10);
assert!(!set_priority(me, 99));
}
#[test]
fn a_child_process_can_be_found_and_closed() {
let mut child = std::process::Command::new("/bin/sleep")
.arg("30")
.spawn()
.expect("spawn sleep");
let pid = child.id() as i32;
assert!(exists(pid));
assert_eq!(name_of(pid).as_deref(), Some("sleep"));
assert!(close(pid));
assert!(!exists(pid));
let _ = child.wait();
assert!(!exists(pid));
}
#[test]
fn another_users_process_reads_as_running() {
assert!(exists(1));
}
}