kill_tree 0.2.4

🌳 Kill Tree is a library designed to terminate a specified process and all its child processes recursively, operating independently of other commands like kill or taskkill.
Documentation
#[derive(Debug)]
pub enum Error {
    InvalidProcessId {
        process_id: ProcessId,
        reason: String,
    },
    InvalidCast {
        reason: String,
        source: std::num::TryFromIntError,
    },
    InvalidProcEntry {
        process_id: ProcessId,
        path: String,
        reason: String,
        source: Option<std::num::ParseIntError>,
    },
    Io(std::io::Error),
    #[cfg(windows)]
    Windows(windows::core::Error),
    #[cfg(unix)]
    Unix(nix::Error),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Error::InvalidProcessId { process_id, reason } => {
                write!(f, "Invalid process id: {process_id}. Reason: {reason}")
            }
            Error::InvalidCast { reason, source } => {
                write!(f, "Invalid cast. Reason: {reason}. Source: {source}")
            }
            Error::InvalidProcEntry {
                process_id,
                path,
                reason,
                source,
            } => write!(
                f,
                "Invalid proc entry. Process id: {process_id}. Path: {path}. Reason: {reason}. Source: {source:?}"
            ),
            Error::Io(e) => write!(f, "I/O error: {e}"),
            #[cfg(windows)]
            Error::Windows(e) => write!(f, "Windows error: {e}"),
            #[cfg(unix)]
            Error::Unix(e) => write!(f, "Unix error: {e}"),
        }
    }
}

impl std::error::Error for Error {}

impl From<std::io::Error> for Error {
    fn from(error: std::io::Error) -> Self {
        Self::Io(error)
    }
}

#[cfg(windows)]
impl From<::windows::core::Error> for Error {
    fn from(error: ::windows::core::Error) -> Self {
        Self::Windows(error)
    }
}

#[cfg(unix)]
impl From<nix::Error> for Error {
    fn from(error: nix::Error) -> Self {
        Self::Unix(error)
    }
}

pub type Result<T> = std::result::Result<T, Error>;

pub type ProcessId = u32;
pub type ParentProcessId = ProcessId;

#[derive(Debug)]
pub(crate) struct ProcessInfo {
    pub(crate) process_id: ProcessId,
    pub(crate) parent_process_id: ParentProcessId,
    pub(crate) name: String,
}

pub(crate) type ChildProcessId = ProcessId;
pub(crate) type ChildProcessIds = Vec<ChildProcessId>;
pub(crate) type ProcessIds = Vec<ProcessId>;
pub(crate) type ProcessInfos = Vec<ProcessInfo>;
pub(crate) type ChildProcessIdMap = std::collections::HashMap<ProcessId, ChildProcessIds>;
pub(crate) type ProcessInfoMap = std::collections::HashMap<ProcessId, ProcessInfo>;
pub(crate) type ChildProcessIdMapFilter = fn(&ProcessInfo) -> bool;

pub(crate) trait Killable {
    fn kill(&self, process_id: ProcessId) -> Result<KillOutput>;
}

pub(crate) trait KillableBuildable {
    fn new_killable(&self, config: &Config) -> Result<impl Killable>;
}

#[derive(Debug)]
pub(crate) enum KillOutput {
    Killed {
        process_id: ProcessId,
    },
    MaybeAlreadyTerminated {
        process_id: ProcessId,
        source: Error,
    },
}

#[derive(Debug)]
pub enum Output {
    Killed {
        process_id: ProcessId,
        parent_process_id: ParentProcessId,
        name: String,
    },
    MaybeAlreadyTerminated {
        process_id: ProcessId,
        source: Error,
    },
}

pub type Outputs = Vec<Output>;

#[derive(Debug)]
pub struct Config {
    pub signal: String,
    pub include_target: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            signal: "SIGTERM".to_string(),
            include_target: true,
        }
    }
}

#[cfg(feature = "blocking")]
pub(crate) mod blocking {
    use super::{ProcessInfos, Result};

    pub(crate) trait ProcessInfosProvidable {
        fn get_process_infos(&self) -> Result<ProcessInfos>;
    }
}

#[cfg(feature = "tokio")]
pub(crate) mod tokio {
    use super::{ProcessInfos, Result};

    pub(crate) trait ProcessInfosProvidable {
        async fn get_process_infos(&self) -> Result<ProcessInfos>;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn error_display_invalid_process_id() {
        let error = Error::InvalidProcessId {
            process_id: 0,
            reason: "reason".to_string(),
        };
        assert_eq!(format!("{error}"), "Invalid process id: 0. Reason: reason");
    }

    #[test]
    fn error_display_invalid_cast() {
        let error = Error::InvalidCast {
            reason: "reason".to_string(),
            source: u32::try_from(-1).unwrap_err(),
        };
        assert_eq!(
            format!("{error}"),
            "Invalid cast. Reason: reason. Source: out of range integral type conversion attempted"
        );
    }

    #[test]
    fn error_display_invalid_proc_entry() {
        let error = Error::InvalidProcEntry {
            process_id: 0,
            path: "/proc/0".to_string(),
            reason: "reason".to_string(),
            source: Some("source".parse::<u32>().unwrap_err()),
        };
        assert_eq!(
            format!("{error}"),
            "Invalid proc entry. Process id: 0. Path: /proc/0. Reason: reason. Source: Some(ParseIntError { kind: InvalidDigit })"
        );
    }

    #[test]
    fn error_display_io() {
        let error = Error::Io(std::io::Error::new(std::io::ErrorKind::Other, "error"));
        assert_eq!(format!("{error}"), "I/O error: error");
    }

    #[cfg(windows)]
    #[test]
    fn error_display_windows() {
        let error = Error::Windows(windows::core::Error::OK);
        assert_eq!(
            format!("{error}"),
            "Windows error: The operation completed successfully. (0x00000000)"
        );
    }

    #[cfg(unix)]
    #[test]
    fn error_display_unix() {
        let error = Error::Unix(nix::Error::EPERM);
        assert_eq!(
            format!("{error}"),
            "Unix error: EPERM: Operation not permitted"
        );
    }

    #[test]
    fn from_io_error() {
        let error = std::io::Error::new(std::io::ErrorKind::Other, "error");
        let error = Error::from(error);
        assert_eq!(format!("{error}"), "I/O error: error");
    }

    #[cfg(windows)]
    #[test]
    fn from_windows_error() {
        let error = windows::core::Error::OK;
        let error = Error::from(error);
        assert_eq!(
            format!("{error}"),
            "Windows error: The operation completed successfully. (0x00000000)"
        );
    }

    #[cfg(unix)]
    #[test]
    fn from_unix_error() {
        let error = nix::Error::EPERM;
        let error = Error::from(error);
        assert_eq!(
            format!("{error}"),
            "Unix error: EPERM: Operation not permitted"
        );
    }

    #[test]
    fn default_config() {
        let config = Config::default();
        assert_eq!(config.signal, "SIGTERM");
        assert!(config.include_target);
    }
}