Skip to content

Commit

Permalink
Implement pidfd APIs for Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
oxalica committed Nov 10, 2022
1 parent 20df092 commit 78c23aa
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 1 deletion.
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();
}

0 comments on commit 78c23aa

Please sign in to comment.