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}