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 {}