unix_exec_output_catcher/
lib.rs

1//! This library lets you execute a child process and catch its output (stdout and stderr).
2//! This is useful if you want to use the output from a specific command and transform it
3//! in your program.
4//!
5//! ⚠️ Difference to std::process::Command 🚨
6//! `std::process::Command` does the same in the standard library but **with one exception**:
7//! My library gives you access to `stdout`, `stderr`, **and** `"stdcombined"`. This way you get all
8//! output lines in the order they appeared. That's the unique feature of this crate.
9
10use derive_more::Display;
11use std::rc::Rc;
12
13#[macro_use]
14extern crate log;
15
16mod child;
17pub mod error;
18mod exec;
19mod libc_util;
20mod pipe;
21mod reader;
22
23pub use exec::fork_exec_and_catch;
24
25/// Holds the information from the executed process. It depends on the `strategy` option of
26/// [`crate::fork_exec_and_catch`] how the output is structured.
27///
28/// The strategy results in the following two kinds of outputs:
29/// * `stdout_lines` and `stderr_lines` are correct but `stdcombined_lines` is only
30///   maybe in correct order
31/// * or `stdout_lines` and `stderr_lines` are `None`, but `stdcombined_lines` is in correct order
32#[derive(Debug)]
33pub struct ProcessOutput {
34    /// Exit code of the process. 0 is success, >1 is error.
35    /// See https://man7.org/linux/man-pages/man3/errno.3.html
36    exit_code: i32,
37    /// * `None` for [`crate::OCatchStrategy::StdCombined`]
38    /// * `Some` for [`crate::OCatchStrategy::StdSeparately`]
39    stdout_lines: Option<Vec<Rc<String>>>,
40    /// * `None` for [`crate::OCatchStrategy::StdCombined`]
41    /// * `Some` for [`crate::OCatchStrategy::StdSeparately`]
42    stderr_lines: Option<Vec<Rc<String>>>,
43    /// * All output lines in correct order for [`crate::OCatchStrategy::StdCombined`]
44    /// * All output lines in not guaranteed correct order for [`crate::OCatchStrategy::StdSeparately`]
45    stdcombined_lines: Vec<Rc<String>>,
46    /// The strategy that was used. See [`crate::OCatchStrategy::StdSeparately`].
47    strategy: OCatchStrategy,
48}
49
50impl ProcessOutput {
51    /// Constructor.
52    fn new(
53        stdout_lines: Option<Vec<Rc<String>>>,
54        stderr_lines: Option<Vec<Rc<String>>>,
55        stdcombined_lines: Vec<Rc<String>>,
56        exit_code: i32,
57        strategy: OCatchStrategy,
58    ) -> Self {
59        Self {
60            stdout_lines,
61            stderr_lines,
62            stdcombined_lines,
63            exit_code,
64            strategy,
65        }
66    }
67
68    /// Getter for `stdout_lines`. This is only available if [`OCatchStrategy::StdSeparately`] was used.
69    pub fn stdout_lines(&self) -> Option<&Vec<Rc<String>>> {
70        self.stdout_lines.as_ref()
71    }
72    /// Getter for `stderr_lines`. This is only available if [`OCatchStrategy::StdSeparately`] was used.
73    pub fn stderr_lines(&self) -> Option<&Vec<Rc<String>>> {
74        self.stderr_lines.as_ref()
75    }
76    /// Getter for `stdcombined_lines`. The correctness of the ordering depends on the used [`OCatchStrategy`].
77    pub fn stdcombined_lines(&self) -> &Vec<Rc<String>> {
78        &self.stdcombined_lines
79    }
80    /// Getter for `exit_code` of the executed child process.
81    pub fn exit_code(&self) -> i32 {
82        self.exit_code
83    }
84    /// Getter for the used [`OCatchStrategy`].
85    pub fn strategy(&self) -> OCatchStrategy {
86        self.strategy
87    }
88}
89
90/// Determines the strategy that is used to get STDOUT, STDERR, and "STDCOMBINED".
91/// Both has advantages and disadvantages.
92#[derive(Debug, Display, Copy, Clone)]
93pub enum OCatchStrategy {
94    /// Catches all output lines of STDOUT and STDERR in correct order on a line
95    /// by line base. There is no way to find out STDOUT-only or STDERR-only lines.
96    StdCombined,
97    /// Catches all output lines from STDOUT and STDERR separately. There is also a
98    /// "STDCOMBINED" vector, but the order is not 100% correct.  It's only approximately correct
99    /// on a best effort base. If between each STDOUT/STDERR-alternating output is ≈100µs
100    /// (a few thousand cycles) it should be definitely fine, but there is no guarantee for that.
101    /// Also the incorrectness is not deterministic. This is because
102    /// STDOUT and STDERR are two separate streams. Scheduling and buffering result in
103    /// different results.
104    StdSeparately,
105}
106
107#[cfg(test)]
108mod tests {
109
110    // use super::*;
111
112    // RUst tests doesn't work with fork, dup2 and other fun :)
113    // weird output.. use the test binary instead!
114    /*#[test]
115    fn test_fork_exec_and_catch() {
116        let res = fork_exec_and_catch("echo", vec!["echo", "hallo"]);
117
118        println!("{:#?}", res);
119        // fork_exec_and_catch("sysctl", vec!["sysctl", "-a"]);
120    }*/
121}