mod kinfo_proc;
mod lock;
mod procstat;
mod ptrace;
use libc::{lwpid_t, pid_t};
use read_process_memory::{CopyAddress, ProcessHandle};
use std::convert::TryInto;
use std::sync::{Arc, Mutex, Weak};
use super::{Error, ProcessMemory};
use crate::freebsd::lock::ProcessLock;
pub type Pid = pid_t;
pub type Tid = lwpid_t;
pub struct Process {
pub pid: Pid,
lock: Arc<Mutex<Weak<ProcessLock>>>,
}
pub struct Thread {
pub tid: lwpid_t,
pid: pid_t,
active: bool,
lock: Arc<Mutex<Weak<ProcessLock>>>,
}
fn process_lock(pid: Pid, container: &Mutex<Weak<ProcessLock>>) -> Result<Arc<ProcessLock>, Error> {
let mut mutex_lock = container.lock().unwrap();
if let Some(ref lock) = Weak::upgrade(&mutex_lock) {
return Ok(Arc::clone(lock));
}
let lock = Arc::new(ProcessLock::new(pid)?);
*mutex_lock = Arc::downgrade(&lock);
Ok(lock)
}
impl Process {
pub fn new(pid: Pid) -> Result<Process, Error> {
Ok(Process {
pid,
lock: Arc::new(Mutex::new(Weak::new())),
})
}
pub fn exe(&self) -> Result<String, Error> {
let filename = procstat::exe(self.pid)?;
if filename.is_empty() {
return Err(Error::Other("Failed to get process executable name".into()));
}
Ok(filename)
}
pub fn cwd(&self) -> Result<String, Error> {
Ok(procstat::cwd(self.pid)?)
}
pub fn threads(&self) -> Result<Vec<Thread>, Error> {
let threads = procstat::threads_info(self.pid)?;
let result = threads.iter().map(|th| Thread {
tid: th.ki_tid,
active: th.ki_stat == 2,
pid: self.pid,
lock: Arc::clone(&self.lock),
});
Ok(result.collect())
}
pub fn lock(&self) -> Result<Arc<ProcessLock>, Error> {
process_lock(self.pid, &self.lock)
}
pub fn cmdline(&self) -> Result<Vec<String>, Error> {
unsafe {
let mib: [i32; 4] = [
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_ARGS,
self.pid,
];
let args: [u8; 65536] = std::mem::zeroed();
let size: usize = std::mem::size_of_val(&args);
let ret = libc::sysctl(
&mib as *const _ as *mut _,
4,
&args as *const _ as *mut _,
&size as *const _ as *mut _,
std::ptr::null_mut(),
0,
);
if ret < 0 {
return Err(Error::IOError(std::io::Error::last_os_error()));
}
let mut ret = Vec::new();
for arg in args[..size].split(|b| *b == 0) {
let arg = String::from_utf8(arg.to_vec())
.map_err(|e| Error::Other(format!("Failed to convert utf8 {}", e)))?;
ret.push(arg);
}
Ok(ret)
}
}
pub fn child_processes(&self) -> Result<Vec<(Pid, Pid)>, Error> {
let processes = procstat::processes()?;
Ok(crate::filter_child_pids(self.pid, &processes))
}
pub fn unwinder(&self) -> Result<(), Error> {
unimplemented!("No unwinding yet!")
}
}
impl Thread {
pub fn id(&self) -> Result<lwpid_t, Error> {
Ok(self.tid)
}
pub fn active(&self) -> Result<bool, Error> {
Ok(self.active)
}
pub fn lock(&self) -> Result<Arc<ProcessLock>, Error> {
process_lock(self.pid, &self.lock)
}
}
impl ProcessMemory for Process {
fn read(&self, addr: usize, buf: &mut [u8]) -> Result<(), Error> {
let handle: ProcessHandle = self.pid.try_into()?;
Ok(handle.copy_address(addr, buf)?)
}
}
#[cfg(test)]
mod tests {
use libc::pid_t;
use log::warn;
use mark_flaky_tests::flaky;
use std::process::{Child, Command};
use std::{thread, time};
use super::{Error, Process};
struct DroppableProcess {
inner: Child,
}
impl Drop for DroppableProcess {
fn drop(&mut self) {
if let Err(e) = self.inner.kill() {
warn!("Failed to kill process: {}", e);
}
}
}
const PERL_PROGRAM: &str = r#"
use threads;
my $sleeping = async { sleep; };
my $running = async { while(true) {} };
map { $_->join } ($sleeping, $running);
"#;
const EXECUTABLE: &str = "/usr/local/bin/perl";
const CWD: &str = "/usr/local/share";
fn trace_perl_program(program: &str) -> Result<(Process, DroppableProcess), Error> {
let wait_time = time::Duration::from_millis(50);
Command::new(EXECUTABLE)
.current_dir(CWD)
.args(&["-e", program])
.spawn()
.and_then(|child| {
let pid = child.id() as pid_t;
let result = (
Process::new(pid).unwrap(),
DroppableProcess { inner: child },
);
thread::sleep(wait_time);
Ok(result)
})
.map_err(|err| err.into())
}
#[test]
fn test_threads() {
let threads = trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| process.threads())
.expect("test failed!");
let active_count = threads.iter().filter(|x| x.active().unwrap()).count();
assert_eq!(threads.len(), 3); assert_eq!(active_count, 1);
}
#[test]
fn test_thread_lock_unlock() {
trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| {
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_some());
if let Some(thread) = active_thread {
let _lock = thread.lock();
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_none());
}
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_some());
Ok(())
})
.expect("test failed!");
}
#[test]
fn test_exe() {
trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| {
assert_eq!(process.exe()?, EXECUTABLE);
Ok(())
})
.unwrap();
}
#[test]
fn test_cwd() {
trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| {
assert_eq!(process.cwd()?, CWD);
Ok(())
})
.unwrap();
}
#[test]
fn test_process_lock() {
trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| {
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_some());
if let Some(_) = active_thread {
let _lock = process.lock();
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_none());
}
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_some());
Ok(())
})
.expect("test failed!");
}
#[flaky]
fn test_process_and_thread_lock() {
trace_perl_program(PERL_PROGRAM)
.and_then(|(process, _p)| {
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_some());
if let Some(_thread) = active_thread {
let _lock = process.lock()?;
let _thread_lock = active_thread.unwrap().lock()?;
let threads = process.threads()?;
let active_thread = threads.iter().find(|x| x.active().unwrap());
assert!(active_thread.is_none());
}
Ok(())
})
.expect("test failed!");
}
}