Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pidfd APIs for Linux #1859

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand All @@ -60,6 +60,7 @@ mount = ["uio"]
mqueue = ["fs"]
net = ["socket"]
personality = []
pidfd = ["process"]
poll = []
pthread = []
ptrace = ["process"]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ feature! {
pub mod personality;
}

#[cfg(target_os = "linux")]
feature! {
#![feature = "pidfd"]
pub mod pidfd;
}

feature! {
#![feature = "pthread"]
pub mod pthread;
Expand Down
72 changes: 72 additions & 0 deletions src/sys/pidfd.rs
Original file line number Diff line number Diff line change
@@ -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<RawFd> {
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<RawFd> {
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<T: Into<Option<crate::sys::signal::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)
}
2 changes: 2 additions & 0 deletions test/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
69 changes: 69 additions & 0 deletions test/sys/test_pidfd.rs
Original file line number Diff line number Diff line change
@@ -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();
}