use std::fs::File;
use std::io::Read;
use thiserror::Error;
#[cfg(feature = "serde")]
extern crate serde;
#[derive(Debug, Error)]
pub enum CalleeError {
#[error("IoError: {0}")]
Io(#[from] std::io::Error),
#[error("IoPathError: {0}")]
IoPath(String),
#[error("Error Parsing the PPID: {0}")]
PpidParseError(String),
}
type CalleeResult = Result<Callee, CalleeError>;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Callee(String, String);
impl Callee {
pub fn init() -> CalleeResult {
let ppid = Ppid::init()?;
let cmdline = CmdLine::from_ppid(&ppid)?;
let comm = Comm::from_ppid(&ppid)?;
Ok(Self(cmdline.as_str().to_owned(), comm.as_str().to_owned()))
}
pub fn comm(&self) -> &str {
&self.1
}
pub fn cmdline(&self) -> &str {
&self.0
}
pub fn info(&self) -> (&str, &str) {
(self.comm(), self.cmdline())
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Comm(String);
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct CmdLine(String);
impl Comm {
pub fn from_ppid(ppid: &Ppid) -> Result<Self, CalleeError> {
let location = format!("/proc/{}/comm", ppid.as_str());
let mut file =
File::open(&location).map_err(|e| CalleeError::IoPath(format!("{e}: {location}")))?;
let mut comm = String::new();
file.read_to_string(&mut comm)
.map_err(|e| CalleeError::IoPath(format!("{e}: {location}")))?;
let comm = comm.trim();
Ok(Self(comm.to_owned()))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl CmdLine {
pub fn from_ppid(ppid: &Ppid) -> Result<Self, CalleeError> {
let location = format!("/proc/{}/cmdline", ppid.as_str());
let mut file =
File::open(&location).map_err(|e| CalleeError::IoPath(format!("{e}: {location}")))?;
let mut cmdline = String::new();
file.read_to_string(&mut cmdline)?;
let cmdline = cmdline.replace('\0', " ");
let cmdline = cmdline.trim();
Ok(CmdLine(cmdline.to_owned()))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Ppid(String);
impl Ppid {
pub fn init() -> Result<Self, CalleeError> {
const PROC_STATUS_FILE: &str = "/proc/self/status";
let mut file = File::open(PROC_STATUS_FILE)
.map_err(|e| CalleeError::IoPath(format!("{e}: {PROC_STATUS_FILE}")))?;
let mut ppid = String::new();
file.read_to_string(&mut ppid)?;
let ppid: Ppid = ppid.parse()?;
Ok(ppid)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl std::str::FromStr for Ppid {
type Err = CalleeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let ppid: String = s.lines().skip(6).take(1).collect();
match ppid.split_once(':') {
Some((_ident, ppid)) => Ok(Ppid(ppid.trim_start().to_owned())),
None => Err(CalleeError::PpidParseError(format!(
"Could not parse the PPID from: {}",
s
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn procfile() -> &'static str {
r#"
Name: cat
Umask: 0022
State: R (running)
Tgid: 2764769
Ngid: 0
Pid: 2764769
PPid: 2095073
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 100 100 100 100
FDSize: 64
"#
}
#[test]
fn parse_ppid_from_str() {
let procfile = procfile();
let _ppid: Ppid = procfile.parse().unwrap();
}
#[test]
#[ignore]
fn native_ppid_works() {
Ppid::init().unwrap();
}
#[test]
#[ignore]
fn native_ppid_from_str_works() {
Ppid::init().unwrap();
}
#[test]
#[ignore]
fn init_callee_is_ok() {
Callee::init().unwrap();
}
}