memfd_exec/
process.rs

1//! This basically implements Process from:
2//! <https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/process/process_unix.rs>
3
4use libc::c_int;
5use std::fmt::{Debug, Formatter, Result as FmtResult};
6use std::io::{Error, Result};
7
8use libc::pid_t;
9
10use crate::cvt::{cvt, cvt_r};
11
12pub struct Process {
13    pid: pid_t,
14    status: Option<ExitStatus>,
15}
16
17impl Process {
18    pub unsafe fn new(pid: pid_t) -> Self {
19        // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned.
20        Process { pid, status: None }
21    }
22
23    pub fn id(&self) -> u32 {
24        self.pid as u32
25    }
26
27    pub fn kill(&mut self) -> Result<()> {
28        // If we've already waited on this process then the pid can be recycled
29        // and used for another process, and we probably shouldn't be killing
30        // random processes, so just return an error.
31        if self.status.is_some() {
32            Err(Error::new(
33                std::io::ErrorKind::InvalidInput,
34                "invalid argument: can't kill an exited process",
35            ))
36        } else {
37            cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
38        }
39    }
40
41    pub fn wait(&mut self) -> Result<ExitStatus> {
42        if let Some(status) = self.status {
43            return Ok(status);
44        }
45        let mut status = 0 as c_int;
46        cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
47        self.status = Some(ExitStatus::new(status));
48        Ok(ExitStatus::new(status))
49    }
50
51    pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
52        if let Some(status) = self.status {
53            return Ok(Some(status));
54        }
55        let mut status = 0 as c_int;
56        let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?;
57        if pid == 0 {
58            Ok(None)
59        } else {
60            self.status = Some(ExitStatus::new(status));
61            Ok(Some(ExitStatus::new(status)))
62        }
63    }
64}
65
66/// Describes the result of a process after it has terminated.
67#[derive(PartialEq, Eq, Clone, Copy)]
68pub struct ExitStatus(c_int);
69
70impl Debug for ExitStatus {
71    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
72        f.debug_tuple("unix_wait_status").field(&self.0).finish()
73    }
74}
75
76impl ExitStatus {
77    pub(crate) fn new(status: c_int) -> ExitStatus {
78        ExitStatus(status)
79    }
80
81    fn exited(&self) -> bool {
82        libc::WIFEXITED(self.0)
83    }
84
85    /// Was termination successful? Returns a Result.
86    pub fn exit_ok(&self) -> Result<()> {
87        // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0.  This is
88        // true on all actual versions of Unix, is widely assumed, and is specified in SuS
89        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html .  If it is not
90        // true for a platform pretending to be Unix, the tests (our doctests, and also
91        // procsss_unix/tests.rs) will spot it.  `ExitStatusError::code` assumes this too.
92        #[allow(clippy::useless_conversion)]
93        match c_int::try_from(self.0) {
94            /* was nonzero */
95            Ok(failure) => Err(Error::new(
96                std::io::ErrorKind::Other,
97                format!("process exited with status {}", failure),
98            )),
99            /* was zero, couldn't convert */
100            Err(_) => Ok(()),
101        }
102    }
103
104    /// Was termination successful?
105    ///
106    /// Signal termination is not considered a success, and success is defined
107    /// as a zero exit status.
108    pub fn success(&self) -> bool {
109        self.exit_ok().is_ok()
110    }
111
112    /// Returns the exit code of the process, if any.
113    ///
114    /// In Unix terms the return value is the exit status:
115    /// the value passed to exit, if the process finished by calling exit.
116    /// Note that on Unix the exit status is truncated to 8 bits, and that
117    /// values that didn’t come from a program’s call to exit may be invented
118    /// by the runtime system (often, for example, 255, 254, 127 or 126).
119    ///
120    /// This will return None if the process was terminated by a signal.
121    /// ExitStatusExt is an extension trait for extracting any such signal,
122    /// and other details, from the ExitStatus.
123    pub fn code(&self) -> Option<i32> {
124        self.exited().then(|| libc::WEXITSTATUS(self.0))
125    }
126
127    /// If the process was terminated by a signal, returns that signal.
128    ///
129    /// In other words, if WIFSIGNALED, this returns WTERMSIG.
130    pub fn signal(&self) -> Option<i32> {
131        libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0))
132    }
133
134    /// If the process was terminated by a signal, says whether it dumped core.
135    pub fn core_dumped(&self) -> bool {
136        libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0)
137    }
138
139    /// If the process was stopped by a signal, returns that signal.
140    ///
141    /// In other words, if WIFSTOPPED, this returns WSTOPSIG.
142    /// This is only possible if the status came from a wait system call
143    /// which was passed WUNTRACED, and was then converted into an ExitStatus.
144    pub fn stopped_signal(&self) -> Option<i32> {
145        libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0))
146    }
147
148    /// Whether the process was continued from a stopped status.
149    ///
150    /// Ie, WIFCONTINUED. This is only possible if the status came from a
151    /// wait system call which was passed WCONTINUED, and was then converted
152    /// into an ExitStatus.
153    pub fn continued(&self) -> bool {
154        libc::WIFCONTINUED(self.0)
155    }
156
157    /// Returns the underlying raw wait status.
158    ///
159    /// The returned integer is a wait status, not an exit status.
160    #[allow(clippy::wrong_self_convention)]
161    pub fn into_raw(&self) -> c_int {
162        self.0
163    }
164}
165
166/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying.
167impl From<c_int> for ExitStatus {
168    fn from(a: c_int) -> ExitStatus {
169        ExitStatus(a)
170    }
171}
172
173#[derive(PartialEq, Eq, Clone, Copy)]
174pub struct ExitStatusError(c_int);
175
176impl From<ExitStatusError> for ExitStatus {
177    fn from(val: ExitStatusError) -> Self {
178        ExitStatus(val.0)
179    }
180}
181
182impl Debug for ExitStatusError {
183    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
184        f.debug_tuple("unix_wait_status").field(&self.0).finish()
185    }
186}
187
188impl ExitStatusError {}