extern crate libc;
use std::env;
#[cfg(target_os = "linux")]
use std::ffi::CString;
#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "macos")]
use std::mem;
use std::path::PathBuf;
#[cfg(target_os = "macos")]
use std::ptr;
use libc::pid_t;
#[cfg(target_os = "linux")]
use libc::PATH_MAX;
#[cfg(target_os = "macos")]
use crate::libproc::bsd_info::BSDInfo;
use crate::libproc::helpers;
#[cfg(target_os = "macos")]
use crate::libproc::task_info::{TaskAllInfo, TaskInfo};
#[cfg(target_os = "macos")]
use crate::libproc::thread_info::ThreadInfo;
use crate::libproc::work_queue_info::WorkQueueInfo;
#[cfg(target_os = "macos")]
use crate::osx_libproc_bindings::{
proc_libversion, proc_listpids, proc_listpidspath, proc_name, proc_pidinfo, proc_pidpath,
proc_regionfilename, PROC_PIDPATHINFO_MAXSIZE,
};
#[cfg(target_os = "macos")]
use self::libc::{c_char, c_void};
#[cfg(target_os = "linux")]
use self::libc::{c_char, readlink};
#[cfg(target_os = "macos")]
use std::ffi::CString;
#[derive(Copy, Clone)]
pub enum ProcType {
ProcAllPIDS = 1,
ProcPGRPOnly = 2,
ProcTTYOnly = 3,
ProcUIDOnly = 4,
ProcRUIDOnly = 5,
ProcPPIDOnly = 6,
}
pub trait PIDInfo {
fn flavor() -> PidInfoFlavor;
}
pub enum PidInfoFlavor {
ListFDs = 1,
TaskAllInfo = 2,
TBSDInfo = 3,
TaskInfo = 4,
ThreadInfo = 5,
ListThreads = 6,
RegionInfo = 7,
RegionPathInfo = 8,
VNodePathInfo = 9,
ThreadPathInfo = 10,
PathInfo = 11,
WorkQueueInfo = 12,
}
#[allow(clippy::large_enum_variant)]
pub enum PidInfo {
ListFDs(Vec<i32>),
#[cfg(target_os = "macos")]
TaskAllInfo(TaskAllInfo),
#[cfg(target_os = "macos")]
TBSDInfo(BSDInfo),
#[cfg(target_os = "macos")]
TaskInfo(TaskInfo),
#[cfg(target_os = "macos")]
ThreadInfo(ThreadInfo),
ListThreads(Vec<i32>),
RegionInfo(String),
RegionPathInfo(String),
VNodePathInfo(String),
ThreadPathInfo(String),
PathInfo(String),
WorkQueueInfo(WorkQueueInfo),
}
pub trait ListPIDInfo {
type Item;
fn flavor() -> PidInfoFlavor;
}
pub struct ListThreads;
impl ListPIDInfo for ListThreads {
type Item = u64;
fn flavor() -> PidInfoFlavor {
PidInfoFlavor::ListThreads
}
}
#[cfg(target_os = "macos")]
pub fn listpids(proc_types: ProcType) -> Result<Vec<u32>, String> {
let buffer_size = unsafe { proc_listpids(proc_types as u32, 0, ptr::null_mut(), 0) };
if buffer_size <= 0 {
return Err(helpers::get_errno_with_message(buffer_size));
}
let capacity = buffer_size as usize / mem::size_of::<u32>();
let mut pids: Vec<u32> = Vec::with_capacity(capacity);
let buffer_ptr = pids.as_mut_ptr() as *mut c_void;
let ret = unsafe { proc_listpids(proc_types as u32, 0, buffer_ptr, buffer_size as i32) };
if ret <= 0 {
Err(helpers::get_errno_with_message(ret))
} else {
let items_count = ret as usize / mem::size_of::<u32>() - 1;
unsafe {
pids.set_len(items_count);
}
Ok(pids)
}
}
#[cfg(target_os = "linux")]
pub fn listpids(proc_types: ProcType) -> Result<Vec<u32>, String> {
match proc_types {
ProcType::ProcAllPIDS => {
let mut pids = Vec::<u32>::new();
let proc_dir = fs::read_dir("/proc").map_err(|e| format!("Could not read '/proc': {}", e))?;
for entry in proc_dir {
let path = entry.map_err(|_| "Couldn't get /proc/ filename")?.path();
let filename = path.file_name();
if let Some(name) = filename {
if let Some(n) = name.to_str() {
if let Ok(pid) = n.parse::<u32>() {
pids.push(pid);
}
}
}
}
Ok(pids)
}
_ => Err("Unsupported ProcType".to_owned())
}
}
#[cfg(target_os = "macos")]
pub fn listpidspath(proc_types: ProcType, path: &str) -> Result<Vec<u32>, String> {
let c_path = CString::new(path).map_err(|_| "CString::new failed".to_string())?;
let buffer_size = unsafe {
proc_listpidspath(proc_types as u32, 0, c_path.as_ptr() as * const c_char, 0, ptr::null_mut(), 0)
};
if buffer_size <= 0 {
return Err(helpers::get_errno_with_message(buffer_size));
}
let capacity = buffer_size as usize / mem::size_of::<u32>();
let mut pids: Vec<u32> = Vec::with_capacity(capacity);
let buffer_ptr = pids.as_mut_ptr() as *mut c_void;
let ret = unsafe {
proc_listpidspath(
proc_types as u32,
0,
c_path.as_ptr() as *const c_char,
0,
buffer_ptr,
buffer_size as i32,
)
};
if ret <= 0 {
Err(helpers::get_errno_with_message(ret))
} else {
let items_count = ret as usize / mem::size_of::<u32>() - 1;
unsafe {
pids.set_len(items_count);
}
Ok(pids)
}
}
#[cfg(target_os = "macos")]
pub fn pidinfo<T: PIDInfo>(pid: i32, arg: u64) -> Result<T, String> {
let flavor = T::flavor() as i32;
let buffer_size = mem::size_of::<T>() as i32;
let mut pidinfo = unsafe { std::mem::zeroed() };
let buffer_ptr = &mut pidinfo as *mut _ as *mut c_void;
let ret: i32;
unsafe {
ret = proc_pidinfo(pid, flavor, arg, buffer_ptr, buffer_size);
};
if ret <= 0 {
Err(helpers::get_errno_with_message(ret))
} else {
Ok(pidinfo)
}
}
#[cfg(not(target_os = "macos"))]
pub fn pidinfo<T: PIDInfo>(_pid: i32, _arg: u64) -> Result<T, String> {
unimplemented!()
}
#[cfg(target_os = "macos")]
pub fn regionfilename(pid: i32, address: u64) -> Result<String, String> {
let mut buf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = buf.as_mut_ptr() as *mut c_void;
let buffer_size = buf.capacity() as u32;
let ret: i32;
unsafe {
ret = proc_regionfilename(pid, address, buffer_ptr, buffer_size);
};
helpers::check_errno(ret, &mut buf)
}
#[cfg(not(target_os = "macos"))]
pub fn regionfilename(_pid: i32, _address: u64) -> Result<String, String> {
Err("'regionfilename' not implemented on linux".to_owned())
}
#[cfg(target_os = "macos")]
pub fn pidpath(pid: i32) -> Result<String, String> {
let mut buf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = buf.as_mut_ptr() as *mut c_void;
let buffer_size = buf.capacity() as u32;
let ret: i32;
unsafe {
ret = proc_pidpath(pid, buffer_ptr, buffer_size as _);
};
helpers::check_errno(ret, &mut buf)
}
#[cfg(target_os = "linux")]
pub fn pidpath(pid: i32) -> Result<String, String> {
let exe_path = CString::new(format!("/proc/{}/exe", pid))
.map_err(|_| "Could not create CString")?;
let mut buf: Vec<u8> = Vec::with_capacity(PATH_MAX as usize - 1);
let buffer_ptr = buf.as_mut_ptr() as *mut c_char;
let buffer_size = buf.capacity();
let ret = unsafe {
readlink(exe_path.as_ptr(), buffer_ptr, buffer_size)
};
helpers::check_errno(ret as i32, &mut buf)
}
#[cfg(target_os = "macos")]
pub fn libversion() -> Result<(i32, i32), String> {
let mut major = 0;
let mut minor = 0;
let ret: i32;
unsafe {
ret = proc_libversion(&mut major, &mut minor);
};
if ret == 0 {
Ok((major, minor))
} else {
Err(helpers::get_errno_with_message(ret))
}
}
#[cfg(not(target_os = "macos"))]
pub fn libversion() -> Result<(i32, i32), String> {
Err("Linux does not use a library, so no library version number".to_owned())
}
#[cfg(target_os = "macos")]
pub fn name(pid: i32) -> Result<String, String> {
let mut namebuf: Vec<u8> = Vec::with_capacity((PROC_PIDPATHINFO_MAXSIZE - 1) as _);
let buffer_ptr = namebuf.as_ptr() as *mut c_void;
let buffer_size = namebuf.capacity() as u32;
let ret: i32;
unsafe {
ret = proc_name(pid, buffer_ptr, buffer_size);
};
if ret <= 0 {
Err(helpers::get_errno_with_message(ret))
} else {
unsafe {
namebuf.set_len(ret as usize);
}
match String::from_utf8(namebuf) {
Ok(name) => Ok(name),
Err(e) => Err(format!("Invalid UTF-8 sequence: {}", e))
}
}
}
#[cfg(target_os = "linux")]
pub fn name(pid: i32) -> Result<String, String> {
helpers::procfile_field(&format!("/proc/{}/status", pid), "Name")
}
#[cfg(target_os = "macos")]
pub fn listpidinfo<T: ListPIDInfo>(pid: i32, max_len: usize) -> Result<Vec<T::Item>, String> {
let flavor = T::flavor() as i32;
let buffer_size = mem::size_of::<T::Item>() as i32 * max_len as i32;
let mut buffer = Vec::<T::Item>::with_capacity(max_len);
let buffer_ptr = unsafe {
buffer.set_len(max_len);
buffer.as_mut_ptr() as *mut c_void
};
let ret: i32;
unsafe {
ret = proc_pidinfo(pid, flavor, 0, buffer_ptr, buffer_size);
};
if ret <= 0 {
Err(helpers::get_errno_with_message(ret))
} else {
let actual_len = ret as usize / mem::size_of::<T::Item>();
buffer.truncate(actual_len);
Ok(buffer)
}
}
#[cfg(not(target_os = "macos"))]
pub fn listpidinfo<T: ListPIDInfo>(_pid: i32, _max_len: usize) -> Result<Vec<T::Item>, String> {
unimplemented!()
}
#[cfg(target_os = "macos")]
pub fn pidcwd(_pid: pid_t) -> Result<PathBuf, String> {
Err("pidcwd is not implemented for macos".into())
}
#[cfg(target_os = "linux")]
pub fn pidcwd(pid: pid_t) -> Result<PathBuf, String> {
fs::read_link(format!("/proc/{}/cwd", pid)).map_err(|e| {
e.to_string()
})
}
pub fn cwdself() -> Result<PathBuf, String> {
env::current_dir().map_err(|e| e.to_string())
}
#[cfg(target_os = "macos")]
pub fn am_root() -> bool {
unsafe { libc::getuid() == 0 }
}
#[cfg(target_os = "linux")]
pub fn am_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
#[cfg(test)]
mod test {
#[cfg(target_os = "linux")]
use std::process;
use std::env;
#[cfg(target_os = "macos")]
use crate::libproc::bsd_info::BSDInfo;
#[cfg(target_os = "macos")]
use crate::libproc::file_info::ListFDs;
#[cfg(target_os = "macos")]
use crate::libproc::task_info::TaskAllInfo;
#[cfg(target_os = "macos")]
use super::{libversion, listpidinfo, ListThreads, pidinfo};
use super::{name, cwdself, listpids, pidpath};
#[cfg(target_os = "linux")]
use super::pidcwd;
use crate::libproc::proc_pid::ProcType;
use super::am_root;
#[cfg(target_os = "linux")]
use crate::libproc::helpers;
#[cfg(target_os = "macos")]
use crate::libproc::task_info::TaskInfo;
#[cfg(target_os = "macos")]
use crate::libproc::thread_info::ThreadInfo;
#[cfg(target_os = "macos")]
use crate::libproc::work_queue_info::WorkQueueInfo;
#[cfg(target_os = "macos")]
#[test]
fn pidinfo_test() {
use std::process;
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)
};
}
#[cfg(target_os = "macos")]
#[test]
fn taskinfo_test() {
use std::process;
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)
};
}
#[cfg(target_os = "macos")]
#[test]
fn taskallinfo_test() {
use std::process;
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)
};
}
#[ignore]
#[cfg(target_os = "macos")]
#[test]
fn threadinfo_test() {
use std::process;
let pid = process::id() as i32;
match pidinfo::<ThreadInfo>(pid, 0) {
Ok(info) => assert!(info.pth_user_time > 0),
Err(e) => panic!("Error retrieving ThreadInfo: {}", e)
};
}
#[ignore]
#[cfg(target_os = "macos")]
#[test]
fn workqueueinfo_test() {
use std::process;
let pid = process::id() as i32;
match pidinfo::<WorkQueueInfo>(pid, 0) {
Ok(info) => assert!(info.pwq_nthreads > 0),
Err(_) => panic!("Error retrieving WorkQueueInfo")
};
}
#[cfg(target_os = "macos")]
#[test]
fn listpidinfo_test() {
use std::process;
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]
#[cfg(target_os = "macos")]
fn libversion_test() {
libversion().expect("libversion() failed");
}
#[test]
fn listpids_test() {
let pids = listpids(ProcType::ProcAllPIDS).expect("Could not list pids");
assert!(pids.len() > 1);
}
#[test]
#[cfg(target_os = "linux")]
fn listpids_invalid_type_test() {
assert!(listpids(ProcType::ProcPGRPOnly).is_err());
}
#[test]
fn name_test() {
#[cfg(target_os = "linux")]
let expected_name = "systemd";
#[cfg(target_os = "macos")]
let expected_name = "launchd";
if am_root() || cfg!(target_os = "linux") {
match name(1) {
Ok(name) => assert_eq!(expected_name, name),
Err(_) => panic!("Error retrieving process name")
}
} else {
println!("Cannot run 'name_test' on macos unless run as root");
}
}
#[test]
fn pidpath_test_unknown_pid_test() {
#[cfg(target_os = "macos")]
let error_message = "No such process";
#[cfg(target_os = "linux")]
let error_message = "No such file or directory";
match pidpath(-1) {
Ok(path) => panic!("It found the path of process with ID = -1 (path = {}), that's not possible\n", path),
Err(message) => assert!(message.contains(error_message)),
}
}
#[test]
#[cfg(target_os = "macos")]
fn pidpath_test() {
assert_eq!("/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"));
}
#[cfg(target_os = "linux")]
#[test]
fn pidcwd_of_self_test() {
assert_eq!(env::current_dir().expect("Could not get current directory"),
pidcwd(process::id() as i32).expect("pidcwd() failed"));
}
#[test]
fn am_root_test() {
if am_root() {
println!("You are root");
} else {
println!("You are not root");
}
}
#[test]
#[cfg(target_os = "linux")]
fn procfile_field_test() {
if am_root() {
assert!(helpers::procfile_field("/proc/1/status", "invalid").is_err());
}
}
#[test]
#[cfg(target_os = "macos")]
fn listpidspath_test() {
let pids = super::listpidspath(ProcType::ProcAllPIDS, "/")
.expect("listpidspath() failed");
assert!(pids.len() > 1);
}
}