extern crate libc;
#[cfg(target_os = "macos")]
use std::mem;
#[cfg(target_os = "linux")]
use std::ffi::CString;
#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[cfg(target_os = "macos")]
use std::ptr;
use std::env;
#[cfg(target_os = "linux")]
use libc::PATH_MAX;
use libc::pid_t;
use crate::libproc::bsd_info::BSDInfo;
use crate::libproc::helpers;
use crate::libproc::task_info::{TaskAllInfo, TaskInfo};
use crate::libproc::thread_info::ThreadInfo;
use crate::libproc::work_queue_info::WorkQueueInfo;
#[cfg(target_os = "linux")]
use self::libc::{c_char, readlink};
#[cfg(target_os = "macos")]
use self::libc::{c_int, c_void, c_char};
#[cfg(target_os = "macos")]
use std::ffi::CString;
#[cfg(target_os = "macos")]
const MAXPATHLEN: usize = 1024;
#[cfg(target_os = "macos")]
const PROC_PIDPATHINFO_MAXSIZE: usize = 4 * MAXPATHLEN;
#[derive(Copy, Clone)]
pub enum ProcType {
ProcAllPIDS = 1,
ProcPGRPOnly = 2,
ProcTTYOnly = 3,
ProcUIDOnly = 4,
ProcRUIDOnly = 5,
ProcPPIDOnly = 6,
}
pub trait PIDInfo: Default {
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>),
TaskAllInfo(TaskAllInfo),
TBSDInfo(BSDInfo),
TaskInfo(TaskInfo),
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")]
#[link(name = "proc", kind = "dylib")]
extern {
fn proc_listpids(proc_type: u32, typeinfo: u32, buffer: *mut c_void, buffersize: u32) -> c_int;
fn proc_listpidspath(proc_type: u32, typeinfo: u32, path: * const c_char, pathflags: u32, buffer: *mut c_void, buffersize: u32) -> c_int;
fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *mut c_void, buffersize: c_int) -> c_int;
fn proc_name(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int;
fn proc_libversion(major: *mut c_int, minor: *mut c_int) -> c_int;
fn proc_pidpath(pid: c_int, buffer: *mut c_void, buffersize: u32) -> c_int;
fn proc_regionfilename(pid: c_int, address: u64, buffer: *mut c_void, buffersize: u32) -> c_int;
}
#[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 u32) };
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.to_string()))?;
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 u32)
};
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 = T::default();
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);
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);
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);
};
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)).unwrap();
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);
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")]
fn procfile_field(filename: &str, fieldname: &str) -> Result<String, String> {
const SEPARATOR: &str = ":";
let lineheader = format!("{}{}", fieldname, SEPARATOR);
let file = File::open(filename).map_err(|_| format!("Could not open /proc file '{}'", filename))?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line.map_err(|_| "Could not read file contents")?;
if line.starts_with(&lineheader) {
let parts: Vec<&str> = line.split(SEPARATOR).collect();
return Ok(parts[1].trim().to_owned());
}
}
Err(format!("Could not find the field named '{}' in the /proc FS file name '{}'", fieldname, filename))
}
#[cfg(target_os = "linux")]
pub fn name(pid: i32) -> Result<String, String> {
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, procfile_field};
use crate::libproc::proc_pid::ProcType;
use super::am_root;
#[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(err) => assert!(false, "Error retrieving process info: {}", err)
};
}
#[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().unwrap();
}
#[test]
fn listpids_test() {
let pids = listpids(ProcType::ProcAllPIDS).unwrap();
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(err) => assert!(false, "Error retrieving process name: {}", err)
}
} 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) => assert!(false, "It found the path of process Pwith 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).unwrap());
}
#[test]
fn cwd_self_test() {
assert_eq!(env::current_dir().unwrap(), cwdself().unwrap());
}
#[cfg(target_os = "linux")]
#[test]
fn pidcwd_of_self_test() {
assert_eq!(env::current_dir().unwrap(), pidcwd(process::id() as i32).unwrap());
}
#[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!(procfile_field("/proc/1/status", "invalid").is_err());
}
}
#[test]
#[cfg(target_os = "macos")]
fn listpidspath_test() {
let pids = super::listpidspath(ProcType::ProcAllPIDS, "/").unwrap();
assert!(pids.len() > 1);
}
}