Skip to main content

pidfd_util/
pidfd_ext.rs

1// SPDX-FileCopyrightText: 2026 The pidfd-util-rs authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::PidFd;
5use super::lowlevel::{PidFdCreds, PidFdGetNamespace};
6use super::lowlevel::{
7    pidfd_get_cgroupid, pidfd_get_creds, pidfd_get_inode_id, pidfd_get_namespace, pidfd_get_pid,
8    pidfd_get_ppid, pidfd_getfd, pidfd_open, pidfd_send_signal,
9};
10use std::io;
11use std::os::fd::OwnedFd;
12
13pub use nix::sched::CloneFlags;
14
15/// Extension trait providing additional operations on [`PidFd`].
16///
17/// This trait extends the basic `PidFd` functionality with methods for querying process
18/// information, manipulating namespaces, and accessing remote file descriptors.
19pub trait PidFdExt {
20    /// Creates a `PidFd` for the current process.
21    fn from_self() -> io::Result<PidFd>;
22
23    /// Creates a `PidFd` from a process ID. Calling this is highly discouraged as it is racy and
24    /// the resulting pidfd might not refer to the expected process.
25    ///
26    /// See the crate documentation for more details on how to obtain a pidfd in a race-free way.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if the process does not exist or if pidfd creation fails.
31    fn from_pid(pid: i32) -> io::Result<PidFd>;
32
33    /// Gets the process ID of the process referred to by the pidfd.
34    fn get_pid(&self) -> io::Result<i32>;
35
36    /// Gets the process ID of the parent process referred to by the pidfd.
37    fn get_ppid(&self) -> io::Result<i32>;
38
39    /// Gets a unique identifier of the process referred to by the pidfd.
40    ///
41    /// Returns a unique 64-bit identifier that, like a pidfd, will never be reused.
42    /// This ID can be used to safely identify a process without risk of confusion
43    /// with a different process, even after the original process exits.
44    ///
45    /// This is useful when you need to identify a process but cannot use the pidfd directly,
46    /// such as:
47    /// - Writing process identifiers to logs
48    /// - Passing process identifiers to other processes where sending file descriptors is difficult
49    /// - Storing process identifiers in data structures where holding file descriptors is impractical
50    ///
51    /// # Errors
52    ///
53    /// Returns `ErrorKind::Unsupported` if the kernel doesn't support retrieving a unique process ID.
54    fn get_id(&self) -> io::Result<u64>;
55
56    /// Gets the credentials (UIDs and GIDs) of the process referred to by the pidfd.
57    ///
58    /// Returns real, effective, saved, and filesystem UID/GID values.
59    /// Requires Linux 6.9+ (uses pidfd ioctl).
60    ///
61    /// # Errors
62    ///
63    /// Returns `ErrorKind::Unsupported` if the kernel doesn't support the required ioctl.
64    fn get_creds(&self) -> io::Result<PidFdCreds>;
65
66    /// Gets the cgroup ID of the process.
67    ///
68    /// Requires Linux 6.9+ (uses pidfd ioctl).
69    ///
70    /// # Errors
71    ///
72    /// Returns `ErrorKind::Unsupported` if the kernel doesn't support the required ioctl.
73    fn get_cgroupid(&self) -> io::Result<u64>;
74
75    /// Gets a file descriptor to a namespace of type `ns` of the process referred to by the pidfd.
76    ///
77    /// The returned file descriptor can be used with `setns()` to enter the namespace.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if the namespace type is not supported or if the ioctl fails.
82    fn get_namespace(&self, ns: &PidFdGetNamespace) -> io::Result<OwnedFd>;
83
84    /// Executes a function with protection against PID reuse.
85    ///
86    /// This method verifies that the PID hasn't changed before and after executing
87    /// the function `func`, protecting against race conditions where the process exits
88    /// and the PID is reused between checks.
89    ///
90    ///
91    /// # Errors
92    ///
93    /// Returns `ErrorKind::NotFound` if the PID changed during execution.
94    ///
95    /// # Examples
96    ///
97    /// ```no_run
98    /// #![cfg_attr(feature = "nightly", feature(linux_pidfd))]
99    /// use pidfd_util::{PidFd, PidFdExt};
100    /// use std::fs;
101    ///
102    /// # fn main() -> std::io::Result<()> {
103    /// let pidfd = PidFd::from_pid(1234)?;
104    /// let result = pidfd.access_proc(|| {
105    ///     // This operation is protected against PID reuse
106    ///     fs::read_to_string("/proc/1234/status")
107    /// })?;
108    /// # Ok(())
109    /// # }
110    /// ```
111    fn access_proc<R, F: FnOnce() -> R>(&self, func: F) -> io::Result<R>;
112
113    /// Sends a signal (e.g., `libc::SIGTERM`) to the process referred to by the pidfd.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if the signal cannot be sent (e.g., insufficient permissions).
118    fn send_signal(&self, signal: i32) -> io::Result<()>;
119
120    /// Moves the calling process into the namespaces of the process referred to by the pidfd.
121    ///
122    /// The `ns` argument is a bit mask, specifying the namespaces to enter into.
123    /// This is equivalent to calling `setns()` with this pidfd and the specified namespace flags.
124    /// After this call, the current thread will be in the specified namespace(s) of the target process.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if `setns()` fails (e.g., insufficient permissions).
129    fn set_namespace(&self, ns: CloneFlags) -> io::Result<()>;
130
131    /// Installs a duplicate of a file descriptor from the process referred to by the pidfd in the current
132    /// process.
133    ///
134    /// This allows accessing file descriptors from another process. The returned
135    /// file descriptor refers to the same open file description as the file descriptor
136    /// with the number `target_fd` in the target process.
137    ///
138    /// Requires `CAP_SYS_PTRACE` or `PTRACE_MODE_ATTACH_REALCREDS` permissions.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if permissions are insufficient or the target FD doesn't exist.
143    fn get_remote_fd(&self, target_fd: i32) -> io::Result<OwnedFd>;
144}
145
146impl PidFdExt for PidFd {
147    fn from_self() -> io::Result<PidFd> {
148        Self::from_pid(std::process::id().try_into().unwrap())
149    }
150
151    fn from_pid(pid: i32) -> io::Result<PidFd> {
152        pidfd_open(pid as libc::pid_t).map(PidFd::from)
153    }
154
155    fn get_pid(&self) -> io::Result<i32> {
156        pidfd_get_pid(self)
157    }
158
159    fn get_ppid(&self) -> io::Result<i32> {
160        pidfd_get_ppid(self)
161    }
162
163    fn get_id(&self) -> io::Result<u64> {
164        pidfd_get_inode_id(self)
165    }
166
167    fn get_creds(&self) -> io::Result<PidFdCreds> {
168        pidfd_get_creds(self)
169    }
170
171    fn get_cgroupid(&self) -> io::Result<u64> {
172        pidfd_get_cgroupid(self)
173    }
174
175    fn get_namespace(&self, ns: &PidFdGetNamespace) -> io::Result<OwnedFd> {
176        pidfd_get_namespace(self, ns)
177    }
178
179    fn access_proc<R, F: FnOnce() -> R>(&self, func: F) -> io::Result<R> {
180        let pid = self.get_pid()?;
181        let result = func();
182        let pid_after = self.get_pid()?;
183
184        if pid != pid_after {
185            return Err(io::ErrorKind::NotFound.into());
186        }
187
188        Ok(result)
189    }
190
191    fn send_signal(&self, signal: i32) -> io::Result<()> {
192        pidfd_send_signal(self, signal)
193    }
194
195    fn set_namespace(&self, ns: CloneFlags) -> io::Result<()> {
196        Ok(nix::sched::setns(self, ns)?)
197    }
198
199    fn get_remote_fd(&self, target_fd: i32) -> io::Result<OwnedFd> {
200        pidfd_getfd(self, target_fd)
201    }
202}