use std::env;
use std::ffi::OsString;
use std::io;
use std::mem;
use std::mem::MaybeUninit;
use std::mem::size_of;
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use libc::c_int;
use libc::pid_t;
use crate::helpers::syscall;
use crate::libproc::bindings::audit_token_t;
use crate::libproc::bindings::proc_pidpath_audittoken;
use crate::libproc::bindings::{
PROC_PIDLISTFDS, PROC_PIDLISTTHREADS, PROC_PIDPATHINFO, PROC_PIDPATHINFO_MAXSIZE,
PROC_PIDREGIONINFO, PROC_PIDREGIONPATHINFO, PROC_PIDTASKALLINFO, PROC_PIDTASKINFO,
PROC_PIDTBSDINFO, PROC_PIDTHREADINFO, PROC_PIDTHREADPATHINFO, PROC_PIDVNODEPATHINFO,
PROC_PIDWORKQUEUEINFO, proc_libversion, proc_name, proc_pidinfo, proc_pidpath,
proc_regionfilename,
};
use crate::libproc::bsd_info::BSDInfo;
use crate::libproc::kinfo::{KProcInfo, KinfoProc};
use crate::libproc::processes;
use crate::libproc::task_info::{TaskAllInfo, TaskInfo};
use crate::libproc::thread_info::ThreadInfo;
use crate::libproc::work_queue_info::WorkQueueInfo;
use libc::c_void;
#[repr(u32)]
pub enum PidInfoFlavor {
ListFDs = PROC_PIDLISTFDS,
TaskAllInfo = PROC_PIDTASKALLINFO,
TBSDInfo = PROC_PIDTBSDINFO,
TaskInfo = PROC_PIDTASKINFO,
ThreadInfo = PROC_PIDTHREADINFO,
ListThreads = PROC_PIDLISTTHREADS,
RegionInfo = PROC_PIDREGIONINFO,
RegionPathInfo = PROC_PIDREGIONPATHINFO,
VNodePathInfo = PROC_PIDVNODEPATHINFO,
ThreadPathInfo = PROC_PIDTHREADPATHINFO,
PathInfo = PROC_PIDPATHINFO,
WorkQueueInfo = PROC_PIDWORKQUEUEINFO,
}
pub trait ListPIDInfo {
type Item;
fn flavor() -> PidInfoFlavor;
}
#[derive(Copy, Clone)]
pub enum ProcType {
ProcAllPIDS = 1,
ProcPGRPOnly = 2,
ProcTTYOnly = 3,
ProcUIDOnly = 4,
ProcRUIDOnly = 5,
ProcPPIDOnly = 6,
}
pub trait PIDInfo {
fn flavor() -> PidInfoFlavor;
}
#[allow(clippy::large_enum_variant)]
pub enum PidInfo {
ListFDs(Vec<i32>),
TaskAllInfo(TaskAllInfo),
TBSDInfo(BSDInfo),
TaskInfo(TaskInfo),
ThreadInfo(ThreadInfo),
ListThreads(Vec<i32>),
RegionInfo(String),
RegionPathInfo(String),
VNodePathInfo(String),
ThreadPathInfo(String),
PathInfo(String),
WorkQueueInfo(WorkQueueInfo),
}
pub struct ListThreads;
impl ListPIDInfo for ListThreads {
type Item = u64;
fn flavor() -> PidInfoFlavor {
PidInfoFlavor::ListThreads
}
}
impl From<ProcType> for processes::ProcFilter {
fn from(proc_type: ProcType) -> Self {
use processes::ProcFilter;
match proc_type {
ProcType::ProcAllPIDS => ProcFilter::All,
ProcType::ProcPGRPOnly => ProcFilter::ByProgramGroup { pgrpid: 0 },
ProcType::ProcTTYOnly => ProcFilter::ByTTY { tty: 0 },
ProcType::ProcUIDOnly => ProcFilter::ByUID { uid: 0 },
ProcType::ProcRUIDOnly => ProcFilter::ByRealUID { ruid: 0 },
ProcType::ProcPPIDOnly => ProcFilter::ByParentProcess { ppid: 0 },
}
}
}
#[allow(clippy::missing_errors_doc)]
#[deprecated(
since = "0.13.0",
note = "Please use `libproc::processes::pids_by_type()` instead."
)]
pub fn listpids(proc_types: ProcType) -> Result<Vec<u32>, io::Error> {
processes::pids_by_type(proc_types.into())
}
#[allow(clippy::missing_errors_doc)]
#[deprecated(
since = "0.13.0",
note = "Please use `libproc::processes::pids_by_type_and_path()` instead."
)]
pub fn listpidspath(proc_types: ProcType, path: &str) -> Result<Vec<u32>, io::Error> {
processes::pids_by_type_and_path(proc_types.into(), &PathBuf::from(path), false, false)
}
pub fn pidinfo<T: PIDInfo>(pid: i32, arg: u64) -> Result<T, io::Error> {
use std::mem::MaybeUninit;
use crate::helpers::syscall;
if pid == 0 && !am_root() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Cannot request information about kernel task (pid=0) unless running as root",
));
}
let flavor = T::flavor() as i32;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = size_of::<T>();
let mut pidinfo = MaybeUninit::<T>::uninit();
#[allow(clippy::pedantic)]
let buffer_ptr = &mut pidinfo as *mut _ as *mut c_void;
let bytes_read = syscall::cvt_positive(unsafe {
proc_pidinfo(pid, flavor, arg, buffer_ptr, buffer_size as _)
})? as usize;
if bytes_read < buffer_size {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("Insufficient bytes read: {} < {}", bytes_read, buffer_size),
));
}
Ok(unsafe { pidinfo.assume_init() })
}
#[cfg(any(target_os = "macos", doc))]
pub fn regionfilename(pid: i32, address: u64) -> Result<OsString, io::Error> {
use crate::helpers::syscall;
let mut buf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = buf.as_mut_ptr().cast::<c_void>();
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = buf.capacity() as u32;
let ret = syscall::cvt_positive(unsafe {
proc_regionfilename(pid, address, buffer_ptr, buffer_size)
})? as usize;
unsafe { buf.set_len(ret) };
Ok(OsString::from_vec(buf))
}
pub fn pidpath(pid: i32) -> Result<PathBuf, io::Error> {
let mut buf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = buf.as_mut_ptr().cast::<c_void>();
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = buf.capacity() as u32;
let ret =
syscall::cvt_positive(unsafe { proc_pidpath(pid, buffer_ptr, buffer_size as _) })? as usize;
unsafe { buf.set_len(ret) };
Ok(PathBuf::from(OsString::from_vec(buf)))
}
pub fn pidpath_audittoken(mut audit_token: audit_token_t) -> Result<PathBuf, io::Error> {
let mut buf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = buf.as_mut_ptr().cast::<c_void>();
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = buf.capacity() as u32;
let ret =
syscall::cvt_positive(unsafe { proc_pidpath_audittoken(&mut audit_token, buffer_ptr, buffer_size as _) })? as usize;
unsafe { buf.set_len(ret) };
Ok(PathBuf::from(OsString::from_vec(buf)))
}
pub fn libversion() -> Result<(i32, i32), io::Error> {
let mut major = 0;
let mut minor = 0;
let ret: i32;
unsafe {
ret = proc_libversion(&raw mut major, &raw mut minor);
};
if ret == 0 {
Ok((major, minor))
} else {
Err(io::Error::last_os_error())
}
}
pub fn name(pid: i32) -> Result<OsString, io::Error> {
let mut namebuf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = namebuf.as_ptr() as *mut c_void;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = namebuf.capacity() as u32;
let ret = syscall::cvt_positive(unsafe { proc_name(pid, buffer_ptr, buffer_size) })? as usize;
unsafe { namebuf.set_len(ret) };
Ok(OsString::from_vec(namebuf))
}
pub fn listpidinfo<T: ListPIDInfo>(pid: i32, max_len: usize) -> Result<Vec<T::Item>, io::Error> {
let flavor = T::flavor() as i32;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let buffer_size = size_of::<T::Item>() as c_int * max_len as c_int;
let mut buffer = (0..max_len)
.map(|_| MaybeUninit::<T::Item>::uninit())
.collect::<Vec<_>>();
let buffer_ptr = buffer.as_mut_ptr().cast::<c_void>();
let ret =
syscall::cvt_positive(unsafe { proc_pidinfo(pid, flavor, 0, buffer_ptr, buffer_size) })?;
#[allow(clippy::cast_sign_loss)]
let actual_len = ret as usize / size_of::<T::Item>();
buffer.truncate(actual_len);
Ok(buffer
.into_iter()
.map(|x| unsafe { x.assume_init() })
.collect())
}
pub fn kproc_info_raw(pid: i32) -> Result<KinfoProc, io::Error> {
let mut mib: [c_int; 4] = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
let mut info = MaybeUninit::<KinfoProc>::zeroed();
let mut size = mem::size_of::<KinfoProc>();
let ret = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
4,
std::ptr::addr_of_mut!(info).cast::<c_void>(),
&raw mut size,
std::ptr::null_mut(),
0,
)
};
if ret != 0 {
return Err(io::Error::last_os_error());
}
if size == 0 {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("No process found with pid {pid}"),
));
}
Ok(unsafe { info.assume_init() })
}
pub fn kproc_info(pid: i32) -> Result<KProcInfo, io::Error> {
kproc_info_raw(pid).map(|raw| KProcInfo::from(&raw))
}
pub fn pidcwd(_pid: pid_t) -> Result<PathBuf, String> {
Err("pidcwd is not implemented for macos".into())
}
pub fn cwdself() -> Result<PathBuf, String> {
env::current_dir().map_err(|e| e.to_string())
}
#[must_use]
pub fn am_root() -> bool {
unsafe { libc::getuid() == 0 }
}
#[cfg(test)]
#[allow(clippy::cast_possible_wrap)]
mod test {
use std::env;
use std::path::Path;
use std::process;
use rustix::path::Arg;
use crate::libproc::bsd_info::BSDInfo;
use crate::libproc::file_info::ListFDs;
use crate::libproc::task_info::TaskAllInfo;
use super::am_root;
use super::{ListThreads, libversion, listpidinfo, pidinfo};
use super::{cwdself, name, pidpath};
use crate::libproc::task_info::TaskInfo;
use crate::libproc::thread_info::ThreadInfo;
use crate::libproc::work_queue_info::WorkQueueInfo;
#[test]
fn pidinfo_test() {
let pid = process::id() as i32;
match pidinfo::<BSDInfo>(pid, 0) {
Ok(info) => assert_eq!(info.pbi_pid as i32, pid),
Err(e) => panic!("Error retrieving BSDInfo: {}", e),
}
}
#[test]
fn pidinfo_kernel_task_test() {
if am_root() {
let pid = 0;
match pidinfo::<BSDInfo>(pid, 0) {
Ok(info) => {
println!("BSDInfo: {info:?}");
assert_eq!(info.pbi_pid as i32, pid);
}
Err(e) => panic!("Error retrieving BSDInfo: {}", e),
}
}
}
#[test]
fn taskinfo_test() {
let pid = process::id() as i32;
match pidinfo::<TaskInfo>(pid, 0) {
Ok(info) => assert!(info.pti_virtual_size > 0),
Err(e) => panic!("Error retrieving TaskInfo: {}", e),
}
}
#[test]
fn taskallinfo_test() {
let pid = process::id() as i32;
match pidinfo::<TaskAllInfo>(pid, 0) {
Ok(info) => assert!(info.ptinfo.pti_virtual_size > 0),
Err(e) => panic!("Error retrieving TaskAllInfo: {}", e),
}
}
#[test]
fn threadinfo_test() {
let pid = process::id() as i32;
let task_info = pidinfo::<TaskAllInfo>(pid, 0).expect("Failed to get TaskAllInfo");
#[allow(clippy::cast_sign_loss)]
let thread_count = task_info.ptinfo.pti_threadnum as usize;
let thread_ids =
listpidinfo::<ListThreads>(pid, thread_count).expect("Failed to get thread list");
assert!(!thread_ids.is_empty(), "Thread list should not be empty");
let first_thread_id = thread_ids[0];
match pidinfo::<ThreadInfo>(pid, first_thread_id) {
Ok(info) => assert!(info.pth_run_state > 0),
Err(e) => panic!("Error retrieving ThreadInfo: {}", e),
}
}
#[test]
fn workqueueinfo_test() {
let pid = process::id() as i32;
match pidinfo::<WorkQueueInfo>(pid, 0) {
Ok(info) => assert!(info.pwq_nthreads > 0),
Err(e) if e.raw_os_error().unwrap() == libc::ESRCH => {
}
Err(e) => panic!("Error retrieving WorkQueueInfo: {}", e),
}
}
#[test]
#[allow(clippy::cast_sign_loss)]
fn listpidinfo_test() {
let pid = process::id() as i32;
if let Ok(info) = pidinfo::<TaskAllInfo>(pid, 0) {
if let Ok(threads) = listpidinfo::<ListThreads>(pid, info.ptinfo.pti_threadnum as usize)
{
assert!(!threads.is_empty());
}
if let Ok(fds) = listpidinfo::<ListFDs>(pid, info.pbsd.pbi_nfiles as usize) {
assert!(!fds.is_empty());
}
}
}
#[test]
fn libversion_test() {
libversion().expect("libversion() failed");
}
#[test]
fn name_test() {
if am_root() {
assert!(
&name(process::id() as i32)
.expect("Could not get the process name")
.as_str()
.unwrap()
.starts_with("libproc"),
"Incorrect process name"
);
} else {
println!("Cannot run 'name_test' on macos unless run as root");
}
}
#[test]
fn pidpath_test_unknown_pid_test() {
match pidpath(-1) {
Ok(path) => panic!(
"It found the path of process with ID = -1 (path = {}), that's not possible\n",
path.display()
),
Err(e) => assert!(e.raw_os_error().unwrap() == libc::ESRCH),
}
}
#[test]
fn pidpath_test() {
assert_eq!(
Path::new("/sbin/launchd"),
pidpath(1).expect("pidpath() failed")
);
}
#[test]
fn cwd_self_test() {
assert_eq!(
env::current_dir().expect("Could not get current directory"),
cwdself().expect("cwdself() failed")
);
}
#[test]
fn am_root_test() {
if am_root() {
println!("You are root");
} else {
println!("You are not root");
}
}
}