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}