1use std::alloc::{Layout, alloc_zeroed, dealloc};
8use std::io;
9use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
10use std::os::unix::ffi::OsStrExt;
11#[cfg(not(feature = "nightly"))]
12use std::os::unix::process::ExitStatusExt;
13#[cfg(not(feature = "nightly"))]
14use std::process::ExitStatus;
15use std::sync::atomic;
16
17const PID_FS_MAGIC: i64 = 0x50494446;
18
19#[repr(u8)]
20#[derive(PartialEq)]
21enum Supported {
22 Unknown = 0,
23 Yes = 1,
24 No = 2,
25}
26
27struct AtomicSupported(atomic::AtomicU8);
28
29impl AtomicSupported {
30 const fn new(supported: Supported) -> Self {
31 Self(atomic::AtomicU8::new(supported as u8))
32 }
33
34 fn load(&self) -> Supported {
35 match self.0.load(atomic::Ordering::Relaxed) {
36 0 => Supported::Unknown,
37 1 => Supported::Yes,
38 2 => Supported::No,
39 _ => panic!(),
40 }
41 }
42
43 fn store(&self, supported: Supported) {
44 self.0.store(supported as u8, atomic::Ordering::Relaxed);
45 }
46}
47
48trait IsMinusOne {
49 fn is_minus_one(&self) -> bool;
50}
51
52macro_rules! impl_is_minus_one {
53 ($($t:ident)*) => ($(impl IsMinusOne for $t {
54 fn is_minus_one(&self) -> bool {
55 *self == -1
56 }
57 })*)
58}
59
60impl_is_minus_one! { i8 i16 i32 i64 isize }
61
62fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
63 if t.is_minus_one() {
64 Err(io::Error::last_os_error())
65 } else {
66 Ok(t)
67 }
68}
69
70fn ioctl_unsupported(e: nix::Error) -> nix::Error {
71 match e {
72 nix::Error::EOPNOTSUPP
73 | nix::Error::ENOTTY
74 | nix::Error::ENOSYS
75 | nix::Error::EAFNOSUPPORT
76 | nix::Error::EPFNOSUPPORT
77 | nix::Error::EPROTONOSUPPORT
78 | nix::Error::ESOCKTNOSUPPORT
79 | nix::Error::ENOPROTOOPT => nix::Error::EOPNOTSUPP,
80 e => e,
81 }
82}
83
84const PIDFS_IOCTL_MAGIC: u8 = 0xFF;
85
86const PIDFS_IOCTL_GET_CGROUP_NAMESPACE: u8 = 1;
87const PIDFS_IOCTL_GET_IPC_NAMESPACE: u8 = 2;
88const PIDFS_IOCTL_GET_MNT_NAMESPACE: u8 = 3;
89const PIDFS_IOCTL_GET_NET_NAMESPACE: u8 = 4;
90const PIDFS_IOCTL_GET_PID_NAMESPACE: u8 = 5;
91const PIDFS_IOCTL_GET_PID_FOR_CHILDREN_NAMESPACE: u8 = 6;
92const PIDFS_IOCTL_GET_TIME_NAMESPACE: u8 = 7;
93const PIDFS_IOCTL_GET_TIME_FOR_CHILDREN_NAMESPACE: u8 = 8;
94const PIDFS_IOCTL_GET_USER_NAMESPACE: u8 = 9;
95const PIDFS_IOCTL_GET_UTS_NAMESPACE: u8 = 10;
96const PIDFS_IOCTL_GET_INFO: u8 = 11;
97
98#[non_exhaustive]
103pub enum PidFdGetNamespace {
104 Cgroup,
106 Ipc,
108 Mnt,
110 Net,
112 Pid,
114 PidForChildren,
116 Time,
118 TimeForChildren,
120 User,
122 Uts,
124}
125
126impl PidFdGetNamespace {
127 fn as_ioctl(&self) -> u8 {
128 match self {
129 PidFdGetNamespace::Cgroup => PIDFS_IOCTL_GET_CGROUP_NAMESPACE,
130 PidFdGetNamespace::Ipc => PIDFS_IOCTL_GET_IPC_NAMESPACE,
131 PidFdGetNamespace::Mnt => PIDFS_IOCTL_GET_MNT_NAMESPACE,
132 PidFdGetNamespace::Net => PIDFS_IOCTL_GET_NET_NAMESPACE,
133 PidFdGetNamespace::Pid => PIDFS_IOCTL_GET_PID_NAMESPACE,
134 PidFdGetNamespace::PidForChildren => PIDFS_IOCTL_GET_PID_FOR_CHILDREN_NAMESPACE,
135 PidFdGetNamespace::Time => PIDFS_IOCTL_GET_TIME_NAMESPACE,
136 PidFdGetNamespace::TimeForChildren => PIDFS_IOCTL_GET_TIME_FOR_CHILDREN_NAMESPACE,
137 PidFdGetNamespace::User => PIDFS_IOCTL_GET_USER_NAMESPACE,
138 PidFdGetNamespace::Uts => PIDFS_IOCTL_GET_UTS_NAMESPACE,
139 }
140 }
141}
142
143pub fn pidfd_get_namespace<Fd: AsFd>(pidfd: &Fd, ns: &PidFdGetNamespace) -> io::Result<OwnedFd> {
144 unsafe {
151 let fd = cvt(libc::ioctl(
152 pidfd.as_fd().as_raw_fd(),
153 nix::request_code_none!(PIDFS_IOCTL_MAGIC, ns.as_ioctl()),
154 ))?;
155 Ok(OwnedFd::from_raw_fd(fd))
156 }
157}
158
159#[non_exhaustive]
160struct PidfdInfoFlags;
161
162impl PidfdInfoFlags {
163 pub const PID: u64 = 1 << 0;
165 #[allow(dead_code)]
167 pub const CREDS: u64 = 1 << 1;
168 #[allow(dead_code)]
170 pub const CGROUPID: u64 = 1 << 2;
171 #[allow(dead_code)]
173 pub const EXIT: u64 = 1 << 3;
174}
175
176#[derive(Debug, Default)]
177#[repr(C)]
178struct PidfdInfo {
179 mask: u64,
180 cgroupid: u64,
181 pid: u32,
182 tgid: u32,
183 ppid: u32,
184 ruid: u32,
185 rgid: u32,
186 euid: u32,
187 egid: u32,
188 suid: u32,
189 sgid: u32,
190 fsuid: u32,
191 fsgid: u32,
192 exit_code: i32,
193}
194
195nix::ioctl_readwrite!(
196 pidfd_get_info_ioctl,
197 PIDFS_IOCTL_MAGIC,
198 PIDFS_IOCTL_GET_INFO,
199 PidfdInfo
200);
201
202fn pidfd_get_info<Fd: AsFd>(pidfd: &Fd, flags: u64) -> io::Result<PidfdInfo> {
203 assert_eq!(64, std::mem::size_of::<PidfdInfo>());
204
205 static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
206
207 let supported = SUPPORTED.load();
208 if supported == Supported::No {
209 return Err(io::ErrorKind::Unsupported.into());
210 }
211
212 let mut info = PidfdInfo {
213 mask: flags,
214 ..Default::default()
215 };
216
217 let r = unsafe { pidfd_get_info_ioctl(pidfd.as_fd().as_raw_fd(), &raw mut info) }
222 .map_err(ioctl_unsupported);
223
224 if let Err(e) = r {
225 if e == nix::Error::EOPNOTSUPP {
226 SUPPORTED.store(Supported::No);
227 }
228 return Err(io::Error::from_raw_os_error(e as i32));
229 } else if supported == Supported::Unknown {
230 SUPPORTED.store(Supported::Yes);
231 }
232
233 assert!(info.mask & flags == flags);
234 Ok(info)
235}
236
237pub fn pidfd_open(pid: libc::pid_t) -> io::Result<OwnedFd> {
238 unsafe {
244 let fd = cvt(libc::syscall(
245 libc::SYS_pidfd_open,
246 pid as libc::pid_t,
247 0 as libc::c_uint,
248 ))?;
249 Ok(OwnedFd::from_raw_fd(fd as libc::c_int))
250 }
251}
252
253pub fn pidfd_send_signal<Fd: AsFd>(pidfd: &Fd, signal: libc::c_int) -> io::Result<()> {
254 cvt(unsafe {
260 libc::syscall(
261 libc::SYS_pidfd_send_signal,
262 pidfd.as_fd().as_raw_fd() as libc::c_int,
263 signal as libc::c_int,
264 std::ptr::null::<()>() as *const libc::siginfo_t,
265 0 as libc::c_uint,
266 )
267 })
268 .map(drop)
269}
270
271#[cfg(not(feature = "nightly"))]
272fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus {
273 let status = unsafe { siginfo.si_status() };
276
277 match siginfo.si_code {
278 libc::CLD_EXITED => ExitStatus::from_raw((status & 0xff) << 8),
279 libc::CLD_KILLED => ExitStatus::from_raw(status),
280 libc::CLD_DUMPED => ExitStatus::from_raw(status | 0x80),
281 libc::CLD_CONTINUED => ExitStatus::from_raw(0xffff),
282 libc::CLD_STOPPED | libc::CLD_TRAPPED => {
283 ExitStatus::from_raw(((status & 0xff) << 8) | 0x7f)
284 }
285 _ => unreachable!("waitid() should only return the above codes"),
286 }
287}
288
289#[cfg(not(feature = "nightly"))]
290pub fn pidfd_wait<Fd: AsFd>(pidfd: &Fd) -> io::Result<ExitStatus> {
291 let mut siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() };
294
295 cvt(unsafe {
302 libc::waitid(
303 libc::P_PIDFD,
304 pidfd.as_fd().as_raw_fd() as u32,
305 &raw mut siginfo,
306 libc::WEXITED,
307 )
308 })?;
309 Ok(from_waitid_siginfo(siginfo))
310}
311
312#[cfg(not(feature = "nightly"))]
313pub fn pidfd_try_wait<Fd: AsFd>(pidfd: &Fd) -> io::Result<Option<ExitStatus>> {
314 let mut siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() };
317
318 cvt(unsafe {
325 libc::waitid(
326 libc::P_PIDFD,
327 pidfd.as_fd().as_raw_fd() as u32,
328 &mut siginfo,
329 libc::WEXITED | libc::WNOHANG,
330 )
331 })?;
332
333 if unsafe { siginfo.si_pid() } == 0 {
336 Ok(None)
337 } else {
338 Ok(Some(from_waitid_siginfo(siginfo)))
339 }
340}
341
342fn pidfd_get_pid_fdinfo<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
343 use std::fs::read_to_string;
344
345 let raw = pidfd.as_fd().as_raw_fd();
346 let fdinfo = read_to_string(format!("/proc/self/fdinfo/{raw}"))?;
347 let pidline = fdinfo
348 .split('\n')
349 .find(|s| s.starts_with("Pid:"))
350 .ok_or(io::ErrorKind::Unsupported)?;
351 Ok(pidline
352 .split('\t')
353 .next_back()
354 .ok_or(io::ErrorKind::Unsupported)?
355 .parse::<i32>()
356 .map_err(|_| io::ErrorKind::Unsupported)?)
357}
358
359pub fn pidfd_get_pid<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
360 match pidfd_get_info(pidfd, PidfdInfoFlags::PID) {
361 Ok(info) => Ok(info.pid as i32),
362 Err(e) if e.kind() == io::ErrorKind::Unsupported => pidfd_get_pid_fdinfo(pidfd),
363 Err(e) => Err(e),
364 }
365}
366
367pub fn pidfd_get_ppid<Fd: AsFd>(pidfd: &Fd) -> io::Result<i32> {
368 pidfd_get_info(pidfd, PidfdInfoFlags::PID).map(|info| info.ppid as i32)
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub struct PidFdCreds {
378 pub ruid: u32,
380 pub rgid: u32,
382 pub euid: u32,
384 pub egid: u32,
386 pub suid: u32,
388 pub sgid: u32,
390 pub fsuid: u32,
392 pub fsgid: u32,
394}
395
396pub fn pidfd_get_creds<Fd: AsFd>(pidfd: &Fd) -> io::Result<PidFdCreds> {
397 pidfd_get_info(pidfd, PidfdInfoFlags::CREDS).map(|info| PidFdCreds {
398 ruid: info.ruid,
399 rgid: info.rgid,
400 euid: info.euid,
401 egid: info.egid,
402 suid: info.suid,
403 sgid: info.sgid,
404 fsuid: info.fsuid,
405 fsgid: info.fsgid,
406 })
407}
408
409pub fn pidfd_get_cgroupid<Fd: AsFd>(pidfd: &Fd) -> io::Result<u64> {
410 pidfd_get_info(pidfd, PidfdInfoFlags::PID).map(|info| info.cgroupid)
411}
412
413pub fn pidfd_is_on_pidfs() -> io::Result<bool> {
414 use nix::sys::statfs::fstatfs;
415
416 static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
417
418 match SUPPORTED.load() {
419 Supported::Unknown => (),
420 Supported::Yes => return Ok(true),
421 Supported::No => return Ok(false),
422 }
423
424 let self_pidfd = pidfd_open(std::process::id().try_into().unwrap())?;
425
426 let fsstat = fstatfs(self_pidfd)?;
427 if fsstat.filesystem_type().0 == PID_FS_MAGIC {
428 SUPPORTED.store(Supported::Yes);
429 Ok(true)
430 } else {
431 SUPPORTED.store(Supported::No);
432 Ok(false)
433 }
434}
435
436#[allow(dead_code)]
437pub struct FileHandle {
438 pub mount_id: i32,
439 pub handle_type: i32,
440 pub handle: Vec<u8>,
441}
442
443pub fn name_to_handle_at<Fd: AsFd>(
444 fd: &Fd,
445 path: &std::path::Path,
446 flags: i32,
447) -> io::Result<FileHandle> {
448 #[repr(C)]
449 #[derive(Default)]
450 pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
451
452 #[repr(C)]
453 #[derive(Default)]
454 struct file_handle {
455 handle_bytes: libc::c_uint,
456 handle_type: libc::c_int,
457 f_handle: __IncompleteArrayField<libc::c_uchar>,
458 }
459
460 static SUPPORTED: AtomicSupported = AtomicSupported::new(Supported::Unknown);
461
462 let supported = SUPPORTED.load();
463 if supported == Supported::No {
464 return Err(io::ErrorKind::Unsupported.into());
465 }
466
467 let mut handle = file_handle::default();
468 let mut mount_id = 0;
469 let mut path = path.as_os_str().as_bytes().to_owned();
470 path.push(0);
471
472 #[allow(clippy::unnecessary_cast)]
480 let err = cvt(unsafe {
481 libc::syscall(
482 libc::SYS_name_to_handle_at,
483 fd.as_fd().as_raw_fd() as libc::c_int,
484 path.as_ptr() as *const libc::c_char,
485 &raw mut handle as *mut file_handle,
486 &raw mut mount_id as *mut libc::c_int,
487 flags,
488 ) as libc::c_int
489 })
490 .unwrap_err();
491
492 if err.raw_os_error().unwrap() == libc::EOPNOTSUPP {
493 SUPPORTED.store(Supported::No);
494 } else if supported == Supported::Unknown {
495 SUPPORTED.store(Supported::Yes);
496 }
497
498 if err.raw_os_error().unwrap() != libc::EOVERFLOW || handle.handle_bytes == 0 {
499 return Err(err);
500 }
501
502 loop {
503 let layout = Layout::new::<file_handle>();
504 let buf_layout =
505 Layout::array::<libc::c_uchar>(handle.handle_bytes.try_into().unwrap()).unwrap();
506 let (layout, buf_offset) = layout.extend(buf_layout).unwrap();
507 let layout = layout.pad_to_align();
508
509 let buf = unsafe { alloc_zeroed(layout) };
512 let mut new_handle: Box<file_handle> = unsafe { Box::from_raw(buf as _) };
515 new_handle.handle_bytes = handle.handle_bytes;
516 new_handle.handle_type = handle.handle_type;
517
518 #[allow(clippy::unnecessary_cast)]
523 let res = cvt(unsafe {
524 libc::syscall(
525 libc::SYS_name_to_handle_at,
526 fd.as_fd().as_raw_fd() as libc::c_int,
527 path.as_ptr() as *const libc::c_char,
528 &raw mut *new_handle as *mut file_handle,
529 &raw mut mount_id as *mut libc::c_int,
530 flags,
531 ) as libc::c_int
532 });
533
534 handle.handle_bytes = new_handle.handle_bytes;
535 handle.handle_type = new_handle.handle_type;
536 Box::leak(new_handle);
538
539 match res {
540 Err(e) if e.raw_os_error().unwrap() == libc::EOVERFLOW => (),
541 Err(e) => {
542 unsafe { dealloc(buf, layout) };
545 return Err(e);
546 }
547 Ok(_) => {
548 let h = {
549 let f_handle = unsafe {
555 std::slice::from_raw_parts(
556 buf.offset(buf_offset.try_into().unwrap()),
557 handle.handle_bytes.try_into().unwrap(),
558 )
559 };
560
561 FileHandle {
562 mount_id,
563 handle_type: handle.handle_type,
564 handle: f_handle.to_vec(),
565 }
566 };
567
568 unsafe { dealloc(buf, layout) };
571
572 return Ok(h);
573 }
574 }
575 }
576}
577
578pub fn pidfd_get_inode_id<Fd: AsFd>(pidfd: &Fd) -> io::Result<u64> {
579 use nix::sys::stat::fstat;
580
581 if !pidfd_is_on_pidfs()? {
582 return Err(io::ErrorKind::Unsupported.into());
583 }
584
585 match name_to_handle_at(
586 &pidfd.as_fd(),
587 std::path::Path::new(""),
588 libc::AT_EMPTY_PATH,
589 ) {
590 Err(e) if e.kind() == io::ErrorKind::Unsupported => (),
591 Err(e) => return Err(e),
592 Ok(h) => return Ok(u64::from_ne_bytes(h.handle.try_into().unwrap())),
593 }
594
595 let stat = fstat(pidfd)?;
596
597 if std::mem::size_of_val(&stat.st_ino) != 8 {
599 return Err(io::ErrorKind::Unsupported.into());
600 }
601
602 Ok(stat.st_ino)
603}
604
605pub fn pidfd_getfd<Fd: AsFd>(pidfd: &Fd, targetfd: i32) -> io::Result<OwnedFd> {
606 unsafe {
613 let fd = cvt(libc::syscall(
614 libc::SYS_pidfd_getfd,
615 pidfd.as_fd().as_raw_fd() as libc::c_int,
616 targetfd as libc::c_int,
617 0 as libc::c_uint,
618 ) as libc::c_int)?;
619 Ok(OwnedFd::from_raw_fd(fd as libc::c_int))
620 }
621}