unix_exec_output_catcher/
exec.rs

1//! Utility functions for exec.
2
3use crate::child::ChildProcess;
4use crate::error::UECOError;
5use crate::libc_util::{libc_ret_to_result, LibcSyscall};
6use crate::pipe::CatchPipes;
7use crate::reader::{OutputReader, SimpleOutputReader, SimultaneousOutputReader};
8use crate::OCatchStrategy;
9use crate::ProcessOutput;
10use std::ffi::CString;
11use std::sync::{Arc, Mutex};
12
13/// Wrapper around [`libc::execvp`].
14/// * `executable` Path or name of executable without null (\0).
15/// * `args` vector of args without null (\0). Remember that the
16///          first real arg starts at index 1. index 0 is usually
17///          the name of the executable. See:
18///          https://unix.stackexchange.com/questions/315812/why-does-argv-include-the-program-name
19pub fn exec(executable: &str, args: Vec<&str>) -> Result<(), UECOError> {
20    // panics if the string contains a \0 (null)
21    let executable = CString::new(executable).expect("Executable must not contain null!");
22    let executable = executable.as_c_str();
23
24    // Build array of null terminated C-strings array
25    let args = args
26        .iter()
27        .map(|s| CString::new(*s).expect("Arg not contain null!"))
28        .collect::<Vec<CString>>();
29    // Build null terminated array with pointers null terminated c-strings
30    let mut args_nl = args
31        .iter()
32        .map(|cs| cs.as_ptr())
33        .collect::<Vec<*const libc::c_char>>();
34    args_nl.push(std::ptr::null());
35
36    let ret = unsafe { libc::execvp(executable.as_ptr(), args_nl.as_ptr()) };
37    let res = libc_ret_to_result(ret, LibcSyscall::Execvp);
38
39    res
40}
41
42/// Executes a program in a child process and returns the output of STDOUT and STDERR
43/// line by line in a vector. Be aware that this is blocking and static! So if your
44/// executable produces 1GB of output text, the data of the vectors of the returned structs
45/// is also 1GB in size. If the program doesn't terminate, this function will neither.
46///
47/// This lib/function works fine for commands like "$ sysctl -a" or "$ ls -la".
48///
49/// ⚠️ Difference to std::process::Command 🚨
50/// `std::process::Command` does the same in the standard library but **with one exception**:
51/// My library gives you access to stdout, stderr, **and "stdcombined"**. This way you get all output
52/// lines in the order they appeared. That's the unique feature of this crate.
53///
54///
55/// * `executable` Path or name of executable without null (\0). Lookup in $PATH happens automatically.
56/// * `args` vector of args, each without null (\0). Remember that the
57///          first real arg starts at index 1. index 0 is usually
58///          the name of the executable. See:
59///          https://unix.stackexchange.com/questions/315812/why-does-argv-include-the-program-name
60/// * `strategy` Specify how accurate the `"STDCOMBINED` vecor is. See [`crate::OCatchStrategy`] for
61///              more information.
62pub fn fork_exec_and_catch(
63    executable: &str,
64    args: Vec<&str>,
65    strategy: OCatchStrategy,
66) -> Result<ProcessOutput, UECOError> {
67    let cp = CatchPipes::new(strategy)?;
68    let child = match strategy {
69        OCatchStrategy::StdCombined => setup_and_execute_strategy_combined(executable, args, cp),
70        OCatchStrategy::StdSeparately => {
71            setup_and_execute_strategy_separately(executable, args, cp)
72        }
73    };
74    let mut child = child?;
75    child.dispatch()?;
76    let output = match strategy {
77        OCatchStrategy::StdCombined => SimpleOutputReader::new(&mut child).read_all_bl(),
78        OCatchStrategy::StdSeparately => {
79            SimultaneousOutputReader::new(Arc::new(Mutex::new(child))).read_all_bl()
80        }
81    };
82    output
83}
84
85/// Setups up parent and child process and executes everything. Obtains the output
86/// using the [`crate::OCatchStrategy::StdCombined`]-strategy.
87fn setup_and_execute_strategy_combined(
88    executable: &str,
89    args: Vec<&str>,
90    cp: CatchPipes,
91) -> Result<ChildProcess, UECOError> {
92    let pipe = if let CatchPipes::Combined(pipe) = cp {
93        pipe
94    } else {
95        panic!("Wrong CatchPipe-variant")
96    };
97    let pipe = Arc::new(Mutex::new(pipe));
98    let pipe_closure = pipe.clone();
99    // gets called after fork() after
100    let child_setup = move || {
101        let mut pipe_closure = pipe_closure.lock().unwrap();
102        pipe_closure.mark_as_child_process()?;
103        pipe_closure.connect_to_stdout()?;
104        pipe_closure.connect_to_stderr()?;
105        Ok(())
106    };
107    let pipe_closure = pipe.clone();
108    let parent_setup = move || {
109        let mut pipe_closure = pipe_closure.lock().unwrap();
110        pipe_closure.mark_as_parent_process()?;
111        Ok(())
112    };
113    let child = ChildProcess::new(
114        executable,
115        args,
116        Box::new(child_setup),
117        Box::new(parent_setup),
118        pipe.clone(),
119        pipe,
120    );
121    Ok(child)
122}
123
124/// Setups up parent and child process and executes everything. Obtains the output
125/// using the [`crate::OCatchStrategy::StdSeparately`]-strategy.
126fn setup_and_execute_strategy_separately(
127    executable: &str,
128    args: Vec<&str>,
129    cp: CatchPipes,
130) -> Result<ChildProcess, UECOError> {
131    let (stdout_pipe, stderr_pipe) = if let CatchPipes::Separately { stdout, stderr } = cp {
132        (stdout, stderr)
133    } else {
134        panic!("Wrong CatchPipe-variant")
135    };
136    let stdout_pipe = Arc::new(Mutex::new(stdout_pipe));
137    let stderr_pipe = Arc::new(Mutex::new(stderr_pipe));
138    let stdout_pipe_closure = stdout_pipe.clone();
139    let stderr_pipe_closure = stderr_pipe.clone();
140    // gets called after fork() after
141    let child_setup = move || {
142        let mut stdout_pipe_closure = stdout_pipe_closure.lock().unwrap();
143        let mut stderr_pipe_closure = stderr_pipe_closure.lock().unwrap();
144        stdout_pipe_closure.mark_as_child_process()?;
145        stderr_pipe_closure.mark_as_child_process()?;
146        stdout_pipe_closure.connect_to_stdout()?;
147        stderr_pipe_closure.connect_to_stderr()?;
148        Ok(())
149    };
150    let stdout_pipe_closure = stdout_pipe.clone();
151    let stderr_pipe_closure = stderr_pipe.clone();
152    let parent_setup = move || {
153        let mut stdout_pipe_closure = stdout_pipe_closure.lock().unwrap();
154        let mut stderr_pipe_closure = stderr_pipe_closure.lock().unwrap();
155        stdout_pipe_closure.mark_as_parent_process()?;
156        stderr_pipe_closure.mark_as_parent_process()?;
157        Ok(())
158    };
159    let child = ChildProcess::new(
160        executable,
161        args,
162        Box::new(child_setup),
163        Box::new(parent_setup),
164        stdout_pipe,
165        stderr_pipe,
166    );
167    Ok(child)
168}