use std::ffi::CString;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
pub(crate) fn may_be_running(pid: u32) -> bool {
let Ok(pid) = libc::pid_t::try_from(pid) else {
return true;
};
let probed = unsafe { libc::kill(pid, 0) };
probed == 0 || io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
pub(crate) fn can_run(path: &Path) -> bool {
let Ok(name) = CString::new(path.as_os_str().as_bytes()) else {
return false;
};
let executable = unsafe { libc::access(name.as_ptr(), libc::X_OK) } == 0;
executable && std::fs::metadata(path).is_ok_and(|found| found.is_file())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_process_is_running_and_one_that_exited_is_not() {
assert!(may_be_running(std::process::id()));
let exited = {
let mut child = std::process::Command::new("true").spawn().unwrap();
let pid = child.id();
child.wait().unwrap();
pid
};
assert!(!may_be_running(exited));
}
#[test]
fn a_program_is_a_regular_file_this_user_may_execute() {
use std::os::unix::fs::PermissionsExt;
let dir = std::env::temp_dir().join(format!("pitboard-can-run-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("a scratch directory");
let made = |name: &str, mode: u32| {
let path = dir.join(name);
std::fs::write(&path, "#!/bin/sh\n").expect("a file");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))
.expect("its mode");
path
};
let everyone = made("everyone", 0o755);
let mine = made("mine", 0o700);
let theirs = made("theirs", 0o611);
let plain = made("plain", 0o644);
let folder = dir.join("folder");
std::fs::create_dir(&folder).expect("a directory");
let link = dir.join("link");
std::os::unix::fs::symlink(&mine, &link).expect("a link");
let dangling = dir.join("dangling");
std::os::unix::fs::symlink(dir.join("gone"), &dangling).expect("a link");
assert!(can_run(&everyone));
assert!(can_run(&mine));
assert!(can_run(&link), "a link to a program is that program");
assert!(!can_run(&plain));
assert!(!can_run(&folder), "a directory is not a program");
assert!(!can_run(&dangling));
assert!(!can_run(&dir.join("absent")));
if unsafe { libc::geteuid() } != 0 {
assert!(
!can_run(&theirs),
"only its group and others may execute it"
);
}
let _ = std::fs::remove_dir_all(&dir);
}
}