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}