unix_exec_output_catcher/
child.rs

1//! Childprocess related abstractions.
2
3use crate::error::UECOError;
4use crate::exec::exec;
5use crate::libc_util::{libc_ret_to_result, LibcSyscall};
6use crate::pipe::Pipe;
7use std::fmt::Debug;
8use std::sync::{Arc, Mutex};
9
10/// The state in that a child process can be.
11#[derive(Debug, PartialEq, Copy, Clone)]
12pub enum ProcessState {
13    /// Waiting for dispatch.
14    Ready,
15    /// Dispatched.
16    Running,
17    /// Finished with error code 0.
18    FinishedSuccess,
19    /// Finished with error code != 0.
20    FinishedError(i32),
21}
22
23/// Abstraction over a child process.
24pub struct ChildProcess {
25    /// String of the executable. Can also be a name
26    /// that will be looked up inside $PATH during execution.
27    executable: String,
28    /// All args of the program including args[0] that refers to
29    /// the name of the binary.
30    args: Vec<String>,
31    /// Once the process has been dispatched/forked, the pid of the child
32    /// is set here.
33    pid: Option<libc::pid_t>,
34    /// Once the process exited, the exit code stands here.
35    exit_code: Option<i32>,
36    /// The current process state.
37    state: ProcessState,
38    /// Reference to the pipe where STDOUT gets redirected.
39    stdout_pipe: Arc<Mutex<Pipe>>,
40    /// Reference to the pipe where STDERR gets redirected.
41    stderr_pipe: Arc<Mutex<Pipe>>,
42    /// Code that should be executed in child after fork() but before exec().
43    child_after_dispatch_before_exec_fn: Box<dyn Send + FnMut() -> Result<(), UECOError>>,
44    /// Code that should be executed in parent after fork()
45    parent_after_dispatch_fn: Box<dyn Send + FnMut() -> Result<(), UECOError>>,
46}
47
48impl ChildProcess {
49    /// Constructor.
50    /// * `executable` executable or path to executable
51    /// * `args` Args vector. First real arg starts at index 1.
52    /// * `child_after_dispatch_before_exec_fn` Code that should be executed in child after fork() but before exec().
53    /// * `parent_after_dispatch_fn` Code that should be executed in parent after fork()
54    /// * `stdout_pipe` Reference to the pipe where STDOUT gets redirected.
55    /// * `stderr_pipe` Reference to the pipe where STDERR gets redirected.
56    pub fn new(
57        executable: &str,
58        args: Vec<&str>,
59        child_after_dispatch_before_exec_fn: Box<dyn Send + FnMut() -> Result<(), UECOError>>,
60        parent_after_dispatch_fn: Box<dyn Send + FnMut() -> Result<(), UECOError>>,
61        stdout_pipe: Arc<Mutex<Pipe>>,
62        stderr_pipe: Arc<Mutex<Pipe>>,
63    ) -> Self {
64        ChildProcess {
65            executable: executable.to_string(),
66            args: args.iter().map(|s| s.to_string()).collect::<Vec<String>>(),
67            pid: None,
68            exit_code: None,
69            state: ProcessState::Ready,
70            child_after_dispatch_before_exec_fn,
71            parent_after_dispatch_fn,
72            stdout_pipe,
73            stderr_pipe,
74        }
75    }
76
77    /// Forks the process. This mean child and parent will run from that
78    /// point concurrently.
79    pub fn dispatch(&mut self) -> Result<libc::pid_t, UECOError> {
80        self.state = ProcessState::Running;
81        let pid = unsafe { libc::fork() };
82        // unwrap error, if pid == -1
83        libc_ret_to_result(pid, LibcSyscall::Fork)?;
84
85        trace!("forked successfully");
86
87        if pid == 0 {
88            // child process
89            trace!("Hello from Child!");
90            let res: Result<(), UECOError> = (self.child_after_dispatch_before_exec_fn)();
91            res?;
92            exec(
93                &self.executable,
94                self.args.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
95            )?;
96            // here be dragons (after exec())
97            // only happens if exec failed; otherwise at this point
98            // the address space of the process is replaced by the new program
99            Err(UECOError::Unknown)
100        } else {
101            // parent process
102            trace!("Hello from parent!");
103            self.pid.replace(pid);
104            let res: Result<(), UECOError> = (self.parent_after_dispatch_fn)();
105            res?;
106            Ok(pid)
107        }
108    }
109
110    /// Check process state nonblocking from parent.
111    pub fn check_state_nbl(&mut self) -> ProcessState {
112        if self.state != ProcessState::Running {
113            return self.state;
114        }
115
116        let wait_flags = libc::WNOHANG;
117        let mut status_code: libc::c_int = 0;
118        let status_code_ptr = &mut status_code as *mut libc::c_int;
119
120        let ret = unsafe { libc::waitpid(self.pid.unwrap(), status_code_ptr, wait_flags) };
121        libc_ret_to_result(ret, LibcSyscall::Waitpid).unwrap();
122
123        // IDE doesn't find this functions but they exist
124
125        // I'm not sure on this one..
126        // maybe my assumption is wrong and "child process started"
127        // is actually printed when the process is finished.
128        // But this is not important for the problem here.
129
130        // process didn't started running yet
131        if ret == 0 {
132            trace!("Child process not started yet");
133            return self.state; // RUNNING
134        } else if ret == self.pid.unwrap() {
135            trace!("Child process started");
136        }
137
138        // returns true if the child terminated normally
139        let exited_normally: bool = libc::WIFEXITED(status_code);
140        // returns true if the child was terminated by signal
141        let exited_by_signal: bool = libc::WIFSIGNALED(status_code);
142        // exit code (0 = success, or > 1 = error)
143        let exit_code: libc::c_int = libc::WEXITSTATUS(status_code);
144
145        if exited_normally || exited_by_signal {
146            self.exit_code.replace(exit_code);
147            if exit_code == 0 {
148                self.state = ProcessState::FinishedSuccess;
149            } else {
150                self.state = ProcessState::FinishedError(exit_code);
151            }
152        }
153
154        self.state
155    }
156
157    /// Getter for exit code.
158    pub fn exit_code(&self) -> Option<i32> {
159        self.exit_code
160    }
161    /// Getter for stdout_pipe.
162    pub fn stdout_pipe(&self) -> &Arc<Mutex<Pipe>> {
163        &self.stdout_pipe
164    }
165    /// Getter for stderr_pipe.
166    pub fn stderr_pipe(&self) -> &Arc<Mutex<Pipe>> {
167        &self.stderr_pipe
168    }
169}