use std::path;
#[cfg(target_os = "linux")]
use std::{fs, io};
#[derive(Debug, PartialEq)]
#[cfg(target_os = "linux")]
pub struct Process {
pub(crate) pid: i32,
}
impl Process {
#[cfg(target_os = "linux")]
pub fn try_new(pid: i32) -> Result<Self, ProcessError> {
let mem_handle = path::PathBuf::from(format!("/proc/{pid}/mem"));
if !mem_handle.is_file() {
return Err(ProcessError::AbsentProcess);
}
Ok(Self { pid })
}
#[cfg(target_os = "linux")]
fn mem_path(&self) -> path::PathBuf {
path::PathBuf::from(format!("/proc/{}/mem", self.pid))
}
#[cfg(target_os = "linux")]
pub(crate) fn mem_file(&self, is_write: bool) -> io::Result<fs::File> {
if is_write {
fs::OpenOptions::new().write(true).open(self.mem_path())
} else {
fs::OpenOptions::new().read(true).open(self.mem_path())
}
}
}
#[derive(Debug, PartialEq, Error)]
pub enum ProcessError {
#[error("Process with given id does not exist or you lack permission to view processes.")]
AbsentProcess,
}
#[cfg(test)]
mod tests {
use std::{
env,
io::{self, BufRead},
process,
};
use super::*;
struct DummyProcess {
child: process::Child,
pub pid: i32,
}
impl Drop for DummyProcess {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
#[fixture]
fn dummy_process() -> DummyProcess {
let cwd = env::current_dir().expect("Could not get the current working directory.");
let dummy_process_dir = cwd.join("dummy_process");
let build_status = process::Command::new("cargo")
.arg("build")
.current_dir(&dummy_process_dir)
.status()
.expect("Failed to run dummy_process compilation.");
assert!(build_status.success(), "Failed to build dummy_process");
let bin_path = dummy_process_dir.join("target/debug/dummy_process");
let mut child = process::Command::new(bin_path)
.stdout(process::Stdio::piped())
.spawn()
.expect("Could not spawn dummy_process.");
let port = {
let stdout = child.stdout.as_mut().expect("Failed to get stdout.");
let reader = io::BufReader::new(stdout);
let port_line = reader
.lines()
.next()
.expect("Port line does not exist.")
.expect("Could not read port line of stdout.");
port_line
.trim()
.split(":")
.last()
.expect("Could not read port part from stdout.")
.parse::<u16>()
.expect("Could not parse port from stdout.")
};
let pid = {
let body = reqwest::blocking::get(format!("http://127.0.0.1:{port}/pid"))
.expect("Could not request PID of dummy process.");
body.text()
.expect("Could not decode the charset of PID of dummy process.")
.parse::<i32>()
.expect("Could not parse PID of dummy process")
};
DummyProcess { child, pid }
}
#[rstest]
#[cfg(target_os = "linux")]
fn test_absent_process() {
let process_id = i32::MAX;
let process = Process::try_new(process_id);
assert_eq!(process, Err(ProcessError::AbsentProcess));
}
#[rstest]
#[cfg(target_os = "linux")]
fn test_present_process(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid);
assert_eq!(
process,
Ok(Process {
pid: dummy_process.pid,
})
);
}
}