async_pidfd/lib.rs
1//! Process file descriptors (`pidfd`) for Linux
2//! ============================================
3//!
4//! Process file descriptors (`pidfd`) provide a race-free way to manage processes
5//! on Linux, maintaining a persistent reference to a process using a file
6//! descriptor rather than a numeric process ID (PID) that could be reused after
7//! the process exits.
8//!
9//! This crate only works on Linux; if you need support for other platforms, or for
10//! older Linux kernels, see
11//! [async-process](https://crates.io/crates/async-process).
12//!
13//! `async-pidfd` provides Rust support for pidfd, and supports managing processes
14//! both synchronously (via the `PidFd` type) and asynchronously (via the
15//! `AsyncPidFd` type).
16//!
17//! Sync - `PidFd`
18//! --------------
19//!
20//! The `PidFd` type manages processes synchronously. Use `PidFd::from_pid` to
21//! construct a `PidFd` from a process ID, such as from
22//! [`Child::id`](https://doc.rust-lang.org/std/process/struct.Child.html#method.id)
23//! in the standard library. (Note that the portable `Child::id` function returns
24//! process IDs as `u32`, rather than as a `libc::pid_t`, necessitating a cast.)
25//!
26//! ```rust
27//! use std::os::unix::process::ExitStatusExt;
28//! use std::process::{Command, ExitStatus};
29//!
30//! use async_pidfd::PidFd;
31//!
32//! fn main() -> std::io::Result<()> {
33//! let child = Command::new("/bin/true").spawn()?;
34//! let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
35//! let status = pidfd.wait()?.status();
36//! assert_eq!(status.code(), Some(0));
37//!
38//! let child = Command::new("/bin/sh").arg("-c").arg("kill -9 $$").spawn()?;
39//! let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
40//! let status = pidfd.wait()?.status();
41//! assert_eq!(status.signal(), Some(9));
42//!
43//! Ok(())
44//! }
45//! ```
46//!
47//! `PidFd::wait` returns information about an exited process via the `ExitInfo`
48//! structure. `ExitInfo` includes a `libc::siginfo_t` indicating how the process
49//! exited (including the exit code if it exited normally, or the signal if it was
50//! killed by a signal), and a `libc::rusage` describing the resource usage of the
51//! process and its children. `libc::siginfo_t` has complex semantics; to get a
52//! [`std::process::ExitStatus`](https://doc.rust-lang.org/std/process/struct.ExitStatus.html)
53//! instead, you can call `.status()` on an `ExitInfo`.
54//!
55//! Note that while opening the PID for an arbitrary process can potentially race
56//! with the exit of that process, opening the PID for a child process that you
57//! have not yet waited on is safe, as the process ID will not get reused until you
58//! wait on the process (or block `SIGCHLD`).
59//!
60//! If you only want to use the synchronous `PidFd` type, you can use `async-pidfd`
61//! with `default-features = false` in `Cargo.toml` to remove async-related
62//! dependencies.
63//!
64//! Async - `AsyncPidFd`
65//! --------------------
66//!
67//! The `AsyncPidFd` type manages processes asynchronously, based on the
68//! [`async-io`](https://docs.rs/async-io/) crate by Stjepan Glavina. `async-io`
69//! provides an `Async` wrapper that makes it easy to turn any synchronous type
70//! based on a file descriptor into an asynchronous type; the resulting
71//! asynchronous code uses `epoll` to wait for all the file descriptors
72//! concurrently.
73//!
74//! `AsyncPidFd` wraps an `Async<PidFd>` and provides the same API as `PidFd`, but
75//! with an `async` version of the `wait` function.
76//!
77//! ```rust
78//! use std::os::unix::process::ExitStatusExt;
79//! use std::process::{Command, ExitStatus};
80//!
81//! use async_pidfd::AsyncPidFd;
82//! use futures_lite::future;
83//!
84//! async fn async_spawn_and_status(cmd: &mut Command) -> std::io::Result<ExitStatus> {
85//! let child = cmd.spawn()?;
86//! let pidfd = AsyncPidFd::from_pid(child.id() as libc::pid_t)?;
87//! Ok(pidfd.wait().await?.status())
88//! }
89//!
90//! fn main() -> std::io::Result<()> {
91//! future::block_on(async {
92//! let (status1, status2) = future::try_zip(
93//! async_spawn_and_status(&mut Command::new("/bin/true")),
94//! async_spawn_and_status(&mut Command::new("/bin/false")),
95//! )
96//! .await?;
97//! assert_eq!(status1.code(), Some(0));
98//! assert_eq!(status2.code(), Some(1));
99//! Ok(())
100//! })
101//! }
102//! ```
103#![forbid(missing_docs)]
104
105#[cfg(not(target_os = "linux"))]
106compile_error!("pidfd only works on Linux");
107
108use std::io;
109use std::mem::MaybeUninit;
110use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
111use std::os::unix::process::ExitStatusExt;
112use std::process::ExitStatus;
113
114#[cfg(feature = "async")]
115use async_io::Async;
116
117fn syscall_result(ret: libc::c_long) -> io::Result<libc::c_long> {
118 if ret == -1 {
119 Err(io::Error::last_os_error())
120 } else {
121 Ok(ret)
122 }
123}
124
125// pidfd_open sets `O_CLOEXEC` by default, without requiring a flag.
126fn pidfd_open(pid: libc::pid_t, flags: libc::c_uint) -> io::Result<OwnedFd> {
127 let ret = syscall_result(unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags) })?;
128 Ok(unsafe { OwnedFd::from_raw_fd(ret as RawFd) })
129}
130
131// Use the raw waitid syscall, which additionally provides the rusage argument.
132fn waitid_pidfd(pidfd: BorrowedFd) -> io::Result<(libc::siginfo_t, libc::rusage)> {
133 let mut siginfo = MaybeUninit::uninit();
134 let mut rusage = MaybeUninit::uninit();
135 unsafe {
136 syscall_result(libc::syscall(
137 libc::SYS_waitid,
138 libc::P_PIDFD,
139 pidfd,
140 siginfo.as_mut_ptr(),
141 libc::WEXITED,
142 rusage.as_mut_ptr(),
143 ))?;
144 Ok((siginfo.assume_init(), rusage.assume_init()))
145 }
146}
147
148/// A process file descriptor.
149pub struct PidFd(OwnedFd);
150
151impl PidFd {
152 /// Create a process file descriptor from a PID.
153 ///
154 /// You can get a process ID from `std::process::Child` by calling `Child::id`.
155 ///
156 /// As long as this process has not yet waited on the child process, and has not blocked
157 /// `SIGCHLD`, the process ID will not get reused, so it does not matter if the child process
158 /// has already exited.
159 pub fn from_pid(pid: libc::pid_t) -> io::Result<Self> {
160 Ok(Self(pidfd_open(pid, 0)?))
161 }
162
163 /// Wait for the process to complete.
164 pub fn wait(&self) -> io::Result<ExitInfo> {
165 let (siginfo, rusage) = waitid_pidfd(self.0.as_fd())?;
166 Ok(ExitInfo { siginfo, rusage })
167 }
168}
169
170impl AsRawFd for PidFd {
171 fn as_raw_fd(&self) -> RawFd {
172 self.0.as_raw_fd()
173 }
174}
175
176impl AsFd for PidFd {
177 fn as_fd(&self) -> BorrowedFd<'_> {
178 self.0.as_fd()
179 }
180}
181
182/// Information about an exited process.
183pub struct ExitInfo {
184 /// Information about how the process exited.
185 pub siginfo: libc::siginfo_t,
186 /// Resource usage for the process and its children.
187 pub rusage: libc::rusage,
188}
189
190impl ExitInfo {
191 /// Returns the exit status of the process.
192 pub fn status(&self) -> ExitStatus {
193 let si_status = unsafe { self.siginfo.si_status() };
194 let raw_status = if self.siginfo.si_code == libc::CLD_EXITED {
195 si_status << 8
196 } else {
197 si_status
198 };
199 ExitStatus::from_raw(raw_status)
200 }
201}
202
203/// Asynchronous version of `PidFd`.
204#[cfg(feature = "async")]
205pub struct AsyncPidFd(Async<PidFd>);
206
207#[cfg(feature = "async")]
208impl AsyncPidFd {
209 /// Create a process file descriptor from a PID.
210 ///
211 /// You can get a process ID from `std::process::Child` by calling `Child::id`.
212 ///
213 /// As long as this process has not yet waited on the child process, and has not blocked
214 /// `SIGCHLD`, the process ID will not get reused, so it does not matter if the child process
215 /// has already exited.
216 pub fn from_pid(pid: libc::pid_t) -> io::Result<Self> {
217 Ok(Self(Async::new(PidFd::from_pid(pid)?)?))
218 }
219
220 /// Wait for the process to complete.
221 pub async fn wait(&self) -> io::Result<ExitInfo> {
222 self.0.readable().await?;
223 self.0.get_ref().wait()
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use super::*;
230 use std::process::Command;
231
232 fn spawn_and_status(cmd: &mut Command) -> io::Result<ExitStatus> {
233 let child = cmd.spawn()?;
234 let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
235 Ok(pidfd.wait()?.status())
236 }
237
238 #[test]
239 fn test() -> io::Result<()> {
240 let status = spawn_and_status(&mut Command::new("/bin/true"))?;
241 assert_eq!(status.code(), Some(0));
242 assert_eq!(status.signal(), None);
243 let status = spawn_and_status(&mut Command::new("/bin/false"))?;
244 assert_eq!(status.code(), Some(1));
245 assert_eq!(status.signal(), None);
246 let status = spawn_and_status(Command::new("/bin/sh").arg("-c").arg("kill -9 $$"))?;
247 assert_eq!(status.code(), None);
248 assert_eq!(status.signal(), Some(9));
249 Ok(())
250 }
251
252 fn assert_echild(ret: io::Result<ExitInfo>) {
253 if let Err(e) = ret {
254 assert_eq!(e.raw_os_error(), Some(libc::ECHILD));
255 } else {
256 panic!("Expected an error!");
257 }
258 }
259
260 #[test]
261 fn test_wait_twice() -> io::Result<()> {
262 let child = Command::new("/bin/true").spawn()?;
263 let pidfd = PidFd::from_pid(child.id() as libc::pid_t)?;
264 let status = pidfd.wait()?.status();
265 assert!(status.success());
266 let ret = pidfd.wait();
267 assert_echild(ret);
268 Ok(())
269 }
270
271 #[cfg(feature = "async")]
272 async fn async_spawn_and_status(cmd: &mut Command) -> io::Result<ExitStatus> {
273 let child = cmd.spawn()?;
274 let pidfd = AsyncPidFd::from_pid(child.id() as libc::pid_t)?;
275 Ok(pidfd.wait().await?.status())
276 }
277
278 #[cfg(feature = "async")]
279 #[test]
280 fn test_async() -> io::Result<()> {
281 use futures_lite::future;
282 future::block_on(async {
283 let (status1, status2) = future::try_zip(
284 async_spawn_and_status(&mut Command::new("/bin/true")),
285 async_spawn_and_status(&mut Command::new("/bin/false")),
286 )
287 .await?;
288 assert_eq!(status1.code(), Some(0));
289 assert_eq!(status2.code(), Some(1));
290 Ok(())
291 })
292 }
293
294 #[cfg(feature = "async")]
295 #[test]
296 fn test_async_concurrent() -> io::Result<()> {
297 use futures_lite::future::{self, FutureExt};
298 future::block_on(async {
299 let status = async_spawn_and_status(
300 Command::new("/bin/sh")
301 .arg("-c")
302 .arg("read line")
303 .stdin(std::process::Stdio::piped()),
304 )
305 .or(async_spawn_and_status(&mut Command::new("/bin/false")))
306 .await?;
307 assert_eq!(status.code(), Some(1));
308 Ok(())
309 })
310 }
311
312 #[cfg(feature = "async")]
313 #[test]
314 fn test_async_wait_twice() -> io::Result<()> {
315 futures_lite::future::block_on(async {
316 let child = Command::new("/bin/true").spawn()?;
317 let pidfd = AsyncPidFd::from_pid(child.id() as libc::pid_t)?;
318 let status = pidfd.wait().await?.status();
319 assert!(status.success());
320 let ret = pidfd.wait().await;
321 assert_echild(ret);
322 Ok(())
323 })
324 }
325}