1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use std::{io, process, string};

/// Result type of this crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Error raised when a process manager failed to kill hanged process after timeout. It is platform-specific.
#[cfg(unix)]
pub type KillError = nix::Error;

/// Error raised when a process manager failed to kill hanged process after timeout. It is platform-specific.
#[cfg(windows)]
pub type KillError = winapi::shared::minwindef::DWORD;

/// Error type of this crate.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// IO error that might happen during command / process execution.
    #[error("IO error: {0}")]
    IoError(io::Error),
    /// Error raised when a process exits with a non-zero exit code.
    #[error("Process exited with non-zero code: {:#?}. Output: {:#?}", .code, .output)]
    NonZeroExitCode {
        /// Exit code of a process. Might be absent on Unix systems when a process was terminated by a signal.
        code: Option<i32>,
        /// [`Output`](std::process::Output) of the exited process
        output: process::Output,
    },
    /// Error raised when a child process does not return its identifier,
    /// which means it does not exist at operating system level,
    /// which is unexpected in the context of this program.
    #[error("Process does not exist.")]
    ProcessDoesNotExist,
    /// When a process manager failed to kill hanged child process, there is a zombie process left hanging around.
    /// This error provides details, such as process id and an error, so user could handle cleaning manually.
    #[cfg(unix)]
    #[error("Process with pid {pid} hanged and we were unable to kill it. Error: {err}", pid = .pid, err = .err)]
    Zombie {
        /// Process id of the hanged process.
        pid: u32,
        /// Error raised on attempt to terminate the hanged process.
        err: KillError,
    },
    /// When a process manager failed to kill hanged child process, there is a zombie process left hanging around.
    /// This error provides details, such as process id and an error, so user could handle cleaning manually.
    #[cfg(windows)]
    #[error("Process with pid {pid} hanged and we were unable to kill it. Error: {err}", pid = .pid, err = .err)]
    Zombie {
        /// Process id of the hanged process.
        pid: u32,
        /// Error raised on attempt to terminate the hanged process.
        err: KillError,
    },
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Self::IoError(err)
    }
}

impl From<string::FromUtf8Error> for Error {
    fn from(err: string::FromUtf8Error) -> Self {
        Self::IoError(io::Error::new(io::ErrorKind::InvalidInput, err))
    }
}

impl From<process::Output> for Error {
    fn from(output: process::Output) -> Self {
        if output.status.success() {
            panic!("Failed to convert command output to error because the command succeeded. Output: {:#?}", output);
        }
        Self::NonZeroExitCode {
            code: output.status.code(),
            output,
        }
    }
}