callee 0.1.4

A library to show information about the calling process.
Documentation
//! `callee` is a Rust library that provides functionality to retrieve information about the calling process.
//!
//! This library allows you to access various attributes of the calling process, such as the command-line arguments and process name.
//! It provides a simple interface to retrieve and work with this process metadata.
//!
//! # Usage
//!
//! To use `callee`, add it as a dependency in your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! callee = "0"
//! ```
//!
//! Then, you can initialize a `Callee` instance to retrieve information about the calling process:
//!
//! ```rust
//! use callee::Callee;
//!
//! let callee = Callee::init().unwrap();
//! println!("Callee: {:?}", callee);
//! println!("Comm: {}", callee.comm());
//! println!("Cmdline: {}", callee.cmdline());
//! ```
//!
//! # Features
//!
//! - Retrieve the command-line arguments and process name of the calling process.
//! - Simplify process introspection and monitoring in Rust applications.
//! - Works on Unix-like systems with access to the `/proc` file system.
//!
//! We hope `callee` simplifies your process introspection tasks and provides valuable insights into the calling process.
//! Feel free to explore the documentation and examples for more information.
//!
//! Your feedback and contributions are welcome!
//!
use std::fs::File;
use std::io::Read;
use thiserror::Error;

#[cfg(feature = "serde")]
extern crate serde;

#[derive(Debug, Error)]
// The general Callee error type
pub enum CalleeError {
    /// Io Error
    #[error("IoError: {0}")]
    Io(#[from] std::io::Error),
    /// Io Path Error
    #[error("IoPathError: {0}")]
    IoPath(String),
    #[error("Error Parsing the PPID: {0}")]
    PpidParseError(String),
}

type CalleeResult = Result<Callee, CalleeError>;

/// Represents information about the calling process.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Callee(String, String);

impl Callee {
    /// Initializes a `Callee` instance by retrieving information about the calling process.
    ///
    /// # Errors
    /// Returns an error if it does not have access to the proc filesystem.
    ///
    /// # Example
    ///
    /// ```
    /// use callee::Callee;
    ///
    /// let callee = Callee::init().unwrap();
    /// println!("Callee: {:?}", callee);
    /// println!("Comm: {}", callee.comm());
    /// println!("Cmdline: {}", callee.cmdline());
    /// ```
    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()))
    }
    /// Get the comm attribute out of `[Callee]`
    pub fn comm(&self) -> &str {
        &self.1
    }
    /// Get the cmdline attribute out of `[Callee]`
    pub fn cmdline(&self) -> &str {
        &self.0
    }
    /// Returns both the comm attribute, and the cmdline attribute
    /// (Comm, Cmdline)
    pub fn info(&self) -> (&str, &str) {
        (self.comm(), self.cmdline())
    }
}

/// Represents the `comm` attribute of the currently running process.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Comm(String);

/// Represents the `cmdline` attribute of the currently running process.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct CmdLine(String);

/// The Comm of the currently running process.
impl Comm {
    /// Returns the Comm of the currently running process.
    ///
    /// Needs access to the proc file system: `/proc/self/status`
    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()
    }
}
/// The CmdLine of the currently running process.
impl CmdLine {
    /// Returns the Comm of the currently running process.
    ///
    /// Needs access to the proc file system: `/proc/self/status`
    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()
    }
}

/// Represents the Parent Process ID (PPID) of the currently running process.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Ppid(String);

/// The PPID Parent Process ID of the currently running process.
impl Ppid {
    /// Initializes a `[Ppid]` instance by retrieving the Parent Process ID (PPID) of the currently running process.
    ///
    /// # Errors
    /// Returns an error if it does not have access to the proc filesystem.
    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;

    /// Parses the Parent Process ID (PPID) from a string.
    ///
    /// # Example
    ///
    /// ```
    /// use callee::Ppid;
    ///
    /// let ppid: Ppid = "PPid: 2095073".parse().unwrap();
    /// assert_eq!(ppid.as_str(), "2095073");
    /// ```
    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 {
        // Contents of a sample proc file
        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]
    // Ignored: Needs native binary to run.
    fn native_ppid_works() {
        Ppid::init().unwrap();
    }
    #[test]
    #[ignore]
    // Ignored: Needs native binary to run.
    fn native_ppid_from_str_works() {
        Ppid::init().unwrap();
    }
    // #[test]
    // #[ignore]
    // // Ignored: Needs native binary to run.
    // fn parse_cmdline_from_ppid() {
    //     let procfile = procfile();
    //     let ppid: Ppid = procfile.parse().unwrap();
    //     CmdLine::from_ppid(&ppid).unwrap();
    // }
    // #[test]
    // #[ignore]
    // // Ignored: Needs native binary to run.
    // fn parse_comm_from_ppid() {
    //     let procfile = procfile();
    //     let ppid: Ppid = procfile.parse().unwrap();
    //     Comm::from_ppid(&ppid).unwrap();
    // }
    #[test]
    #[ignore]
    // Ignored: Needs native binary to run.
    fn init_callee_is_ok() {
        Callee::init().unwrap();
    }
}