diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d122ab10..02749be1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). ([#1841](https://github.com/nix-rust/nix/pull/1841)) - Added `eaccess()` on FreeBSD, DragonFly and Linux (glibc and musl). ([#1842](https://github.com/nix-rust/nix/pull/1842)) +- Added `pidfd_{open,getfd,send_signal}()` on Linux + ([#1859](https://github.com/nix-rust/nix/pull/1859)) ### Changed diff --git a/Cargo.toml b/Cargo.toml index e09f758a25..5d31ae78fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ memoffset = { version = "0.6.3", optional = true } default = [ "acct", "aio", "dir", "env", "event", "feature", "fs", "hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue", - "net", "personality", "poll", "process", "pthread", "ptrace", "quota", + "net", "personality", "pidfd", "poll", "process", "pthread", "ptrace", "quota", "reboot", "resource", "sched", "signal", "socket", "term", "time", "ucontext", "uio", "user", "zerocopy", ] @@ -60,6 +60,7 @@ mount = ["uio"] mqueue = ["fs"] net = ["socket"] personality = [] +pidfd = ["process"] poll = [] pthread = [] ptrace = ["process"] diff --git a/src/lib.rs b/src/lib.rs index 6b82125761..1d42bd4400 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ //! * `mqueue` - POSIX message queues //! * `net` - Networking-related functionality //! * `personality` - Set the process execution domain +//! * `pidfd` - Linux's PID file descriptors //! * `poll` - APIs like `poll` and `select` //! * `process` - Stuff relating to running processes //! * `pthread` - POSIX threads diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 2065059de8..dd4e2e1b58 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -68,6 +68,12 @@ feature! { pub mod personality; } +#[cfg(target_os = "linux")] +feature! { + #![feature = "pidfd"] + pub mod pidfd; +} + feature! { #![feature = "pthread"] pub mod pthread; diff --git a/src/sys/pidfd.rs b/src/sys/pidfd.rs new file mode 100644 index 0000000000..3f153b5655 --- /dev/null +++ b/src/sys/pidfd.rs @@ -0,0 +1,72 @@ +//! Interfaces for Linux's PID file descriptors. + +use std::os::unix::io::RawFd; +use crate::Result; +use crate::errno::Errno; +use crate::unistd::Pid; + +libc_bitflags!( + /// Options that change the behavior of [`pidfd_open`]. + pub struct PidFdOpenFlag: libc::c_uint { + /// Return a nonblocking file descriptor. (since Linux 5.10) + /// + /// If the process referred to by the file descriptor has not yet terminated, + /// then an attempt to wait on the file descriptor using [`waitid(2)`] will + /// immediately return the error `EAGAIN` rather than blocking. + /// + /// [`waitid(2)`]: https://man7.org/linux/man-pages/man2/waitid.2.html + PIDFD_NONBLOCK; + } +); + +/// Obtain a file descriptor that refers to a process. (since Linux 5.3) +/// +/// The `pidfd_open(2)` creates a file descriptor that refers to the process +/// whose PID is specified in pid. The file descriptor is returned as the function +/// result; the close-on-exec flag is set on the file descriptor. +/// +/// For more information, see [`pidfd_open(2)`]. +/// +/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html +pub fn pidfd_open(pid: Pid, flags: PidFdOpenFlag) -> Result { + let ret = unsafe { + libc::syscall(libc::SYS_pidfd_open, pid, flags) + }; + Errno::result(ret).map(|r| r as RawFd) +} + +/// Obtain a duplicate of another process's file descriptor. (since Linux 5.6) +/// +/// The `pidfd_getfd(2)` system call allocates a new file descriptor in the calling +/// process. This new file descriptor is a duplicate of an existing file descriptor, +/// `target_fd`, in the process referred to by the PID file descriptor pidfd. +/// +/// For more information, see [`pidfd_getfd(2)`]. +/// +/// [`pidfd_getfd(2)`]: https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html +pub fn pidfd_getfd(pidfd: RawFd, target_fd: RawFd) -> Result { + let ret = unsafe { + libc::syscall(libc::SYS_pidfd_getfd, pidfd, target_fd, 0) + }; + Errno::result(ret).map(|r| r as RawFd) +} + +/// Send a signal to a process specified by a file descriptor. +/// +/// The `pidfd_send_signal(2)` system call sends the signal sig to the target process +/// referred to by pidfd, a PID file descriptor that refers to a process. +/// +/// For more information, see [`pidfd_send_signal(2)`]. +/// +/// [`pidfd_send_signal(2)`]: https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html +#[cfg(feature = "signal")] +pub fn pidfd_send_signal>>(pidfd: RawFd, signal: T, sig_info: Option<&libc::siginfo_t>) -> Result<()> { + let signal = match signal.into() { + Some(signal) => signal as libc::c_int, + _ => 0, + }; + let ret = unsafe { + libc::syscall(libc::SYS_pidfd_send_signal, pidfd, signal, sig_info, 0) + }; + Errno::result(ret).map(drop) +} diff --git a/test/sys/mod.rs b/test/sys/mod.rs index 20312120a6..0da5e54225 100644 --- a/test/sys/mod.rs +++ b/test/sys/mod.rs @@ -45,6 +45,8 @@ mod test_wait; mod test_epoll; #[cfg(target_os = "linux")] mod test_inotify; +#[cfg(target_os = "linux")] +mod test_pidfd; mod test_pthread; #[cfg(any( target_os = "android", diff --git a/test/sys/test_pidfd.rs b/test/sys/test_pidfd.rs new file mode 100644 index 0000000000..9ceb22602d --- /dev/null +++ b/test/sys/test_pidfd.rs @@ -0,0 +1,69 @@ +use nix::poll::{poll, PollFd, PollFlags}; +use nix::sys::pidfd::*; +use nix::sys::signal::*; +use nix::unistd::*; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::unix::prelude::{AsRawFd, FromRawFd}; + +#[test] +fn test_pidfd_open() { + let pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap(); + close(pidfd).unwrap(); +} + +#[test] +fn test_pidfd_getfd() { + let pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap(); + + let mut tempfile = tempfile::tempfile().unwrap(); + tempfile.write_all(b"hello").unwrap(); + tempfile.seek(SeekFrom::Start(0)).unwrap(); + + let tempfile2 = pidfd_getfd(pidfd, tempfile.as_raw_fd()).unwrap(); + let mut tempfile2 = unsafe { std::fs::File::from_raw_fd(tempfile2) }; + + // Drop the original file. Since `tempfile2` should hold the same file, it would not be deleted. + drop(tempfile); + let mut buf = String::new(); + tempfile2.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "hello"); + + close(pidfd).unwrap(); +} + +#[test] +fn test_pidfd_poll_send_signal() { + let me_pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap(); + + let child = match unsafe { fork() }.expect("Error: Fork Failed") { + ForkResult::Child => { + sleep(1); + unsafe { libc::_exit(42) } + } + ForkResult::Parent { child } => child, + }; + + let child_pidfd = pidfd_open(child, PidFdOpenFlag::empty()).unwrap(); + let mut poll_fds = [ + PollFd::new(me_pidfd, PollFlags::POLLIN), + PollFd::new(child_pidfd, PollFlags::POLLIN), + ]; + + // Timeout. + assert_eq!(poll(&mut poll_fds, 100).unwrap(), 0); + // Both parent and child are running. + assert!(!poll_fds[0].revents().unwrap().contains(PollFlags::POLLIN)); + assert!(!poll_fds[1].revents().unwrap().contains(PollFlags::POLLIN)); + + pidfd_send_signal(child_pidfd, Signal::SIGINT, None).unwrap(); + + // Child pidfd is ready. + assert_eq!(poll(&mut poll_fds, 100).unwrap(), 1); + // Parent is still running. + assert!(!poll_fds[0].revents().unwrap().contains(PollFlags::POLLIN)); + // Child is dead. + assert!(poll_fds[1].revents().unwrap().contains(PollFlags::POLLIN)); + + close(me_pidfd).unwrap(); + close(child_pidfd).unwrap(); +}