Skip to content

Commit aa263f8

Browse files
committed
Implement pidfd APIs for Linux
1 parent 20df092 commit aa263f8

File tree

6 files changed

+152
-1
lines changed

6 files changed

+152
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ memoffset = { version = "0.6.3", optional = true }
3939
default = [
4040
"acct", "aio", "dir", "env", "event", "feature", "fs",
4141
"hostname", "inotify", "ioctl", "kmod", "mman", "mount", "mqueue",
42-
"net", "personality", "poll", "process", "pthread", "ptrace", "quota",
42+
"net", "personality", "pidfd", "poll", "process", "pthread", "ptrace", "quota",
4343
"reboot", "resource", "sched", "signal", "socket", "term", "time",
4444
"ucontext", "uio", "user", "zerocopy",
4545
]
@@ -60,6 +60,7 @@ mount = ["uio"]
6060
mqueue = ["fs"]
6161
net = ["socket"]
6262
personality = []
63+
pidfd = ["process"]
6364
poll = []
6465
pthread = []
6566
ptrace = ["process"]

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
//! * `mqueue` - POSIX message queues
2424
//! * `net` - Networking-related functionality
2525
//! * `personality` - Set the process execution domain
26+
//! * `pidfd` - Linux's PID file descriptors
2627
//! * `poll` - APIs like `poll` and `select`
2728
//! * `process` - Stuff relating to running processes
2829
//! * `pthread` - POSIX threads

src/sys/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ feature! {
6868
pub mod personality;
6969
}
7070

71+
#[cfg(target_os = "linux")]
72+
feature! {
73+
#![feature = "pidfd"]
74+
pub mod pidfd;
75+
}
76+
7177
feature! {
7278
#![feature = "pthread"]
7379
pub mod pthread;

src/sys/pidfd.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Interfaces for Linux's PID file descriptors.
2+
3+
use std::os::unix::io::RawFd;
4+
use crate::Result;
5+
use crate::errno::Errno;
6+
use crate::unistd::Pid;
7+
8+
libc_bitflags!(
9+
/// Options that change the behavior of [`pidfd_open`].
10+
pub struct PidFdOpenFlag: libc::c_uint {
11+
/// Return a nonblocking file descriptor. (since Linux 5.10)
12+
///
13+
/// If the process referred to by the file descriptor has not yet terminated,
14+
/// then an attempt to wait on the file descriptor using [`waitid(2)`] will
15+
/// immediately return the error `EAGAIN` rather than blocking.
16+
///
17+
/// [`waitid(2)`]: https://man7.org/linux/man-pages/man2/waitid.2.html
18+
PIDFD_NONBLOCK;
19+
}
20+
);
21+
22+
/// Obtain a file descriptor that refers to a process. (since Linux 5.3)
23+
///
24+
/// The `pidfd_open(2)` creates a file descriptor that refers to the process
25+
/// whose PID is specified in pid. The file descriptor is returned as the function
26+
/// result; the close-on-exec flag is set on the file descriptor.
27+
///
28+
/// For more information, see [`pidfd_open(2)`].
29+
///
30+
/// [`pidfd_open(2)`]: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
31+
pub fn pidfd_open(pid: Pid, flags: PidFdOpenFlag) -> Result<RawFd> {
32+
let ret = unsafe {
33+
libc::syscall(libc::SYS_pidfd_open, pid, flags)
34+
};
35+
Errno::result(ret).map(|r| r as RawFd)
36+
}
37+
38+
/// Obtain a duplicate of another process's file descriptor. (since Linux 5.6)
39+
///
40+
/// The `pidfd_getfd(2)` system call allocates a new file descriptor in the calling
41+
/// process. This new file descriptor is a duplicate of an existing file descriptor,
42+
/// `target_fd`, in the process referred to by the PID file descriptor pidfd.
43+
///
44+
/// For more information, see [`pidfd_getfd(2)`].
45+
///
46+
/// [`pidfd_getfd(2)`]: https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html
47+
pub fn pidfd_getfd(pidfd: RawFd, target_fd: RawFd) -> Result<RawFd> {
48+
let ret = unsafe {
49+
libc::syscall(libc::SYS_pidfd_getfd, pidfd, target_fd, 0)
50+
};
51+
Errno::result(ret).map(|r| r as RawFd)
52+
}
53+
54+
/// Send a signal to a process specified by a file descriptor.
55+
///
56+
/// The `pidfd_send_signal(2)` system call sends the signal sig to the target process
57+
/// referred to by pidfd, a PID file descriptor that refers to a process.
58+
///
59+
/// For more information, see [`pidfd_send_signal(2)`].
60+
///
61+
/// [`pidfd_send_signal(2)`]: https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html
62+
#[cfg(feature = "signal")]
63+
pub fn pidfd_send_signal<T: Into<Option<crate::sys::signal::Signal>>>(pidfd: RawFd, signal: T, sig_info: Option<&libc::siginfo_t>) -> Result<()> {
64+
let signal = match signal.into() {
65+
Some(signal) => signal as libc::c_int,
66+
_ => 0,
67+
};
68+
let ret = unsafe {
69+
libc::syscall(libc::SYS_pidfd_send_signal, pidfd, signal, sig_info, 0)
70+
};
71+
Errno::result(ret).map(drop)
72+
}

test/sys/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ mod test_wait;
4545
mod test_epoll;
4646
#[cfg(target_os = "linux")]
4747
mod test_inotify;
48+
#[cfg(target_os = "linux")]
49+
mod test_pidfd;
4850
mod test_pthread;
4951
#[cfg(any(
5052
target_os = "android",

test/sys/test_pidfd.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use nix::poll::{poll, PollFd, PollFlags};
2+
use nix::sys::pidfd::*;
3+
use nix::sys::signal::*;
4+
use nix::unistd::*;
5+
use std::io::{Read, Seek, SeekFrom, Write};
6+
use std::os::unix::prelude::{AsRawFd, FromRawFd};
7+
8+
#[test]
9+
fn test_pidfd_open() {
10+
let pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap();
11+
close(pidfd).unwrap();
12+
}
13+
14+
#[test]
15+
fn test_pidfd_getfd() {
16+
let pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap();
17+
18+
let mut tempfile = tempfile::tempfile().unwrap();
19+
tempfile.write_all(b"hello").unwrap();
20+
tempfile.seek(SeekFrom::Start(0)).unwrap();
21+
22+
let tempfile2 = pidfd_getfd(pidfd, tempfile.as_raw_fd()).unwrap();
23+
let mut tempfile2 = unsafe { std::fs::File::from_raw_fd(tempfile2) };
24+
25+
// Drop the original file. Since `tempfile2` should hold the same file, it would not be deleted.
26+
drop(tempfile);
27+
let mut buf = String::new();
28+
tempfile2.read_to_string(&mut buf).unwrap();
29+
assert_eq!(buf, "hello");
30+
31+
close(pidfd).unwrap();
32+
}
33+
34+
#[test]
35+
fn test_pidfd_poll_send_signal() {
36+
let me_pidfd = pidfd_open(getpid(), PidFdOpenFlag::empty()).unwrap();
37+
38+
let child = match unsafe { fork() }.expect("Error: Fork Failed") {
39+
ForkResult::Child => {
40+
sleep(1);
41+
unsafe { libc::_exit(42) }
42+
}
43+
ForkResult::Parent { child } => child,
44+
};
45+
46+
let child_pidfd = pidfd_open(child, PidFdOpenFlag::empty()).unwrap();
47+
let mut poll_fds = [
48+
PollFd::new(me_pidfd, PollFlags::POLLIN),
49+
PollFd::new(child_pidfd, PollFlags::POLLIN),
50+
];
51+
52+
// Timeout.
53+
assert_eq!(poll(&mut poll_fds, 100).unwrap(), 0);
54+
// Both parent and child are running.
55+
assert!(!poll_fds[0].revents().unwrap().contains(PollFlags::POLLIN));
56+
assert!(!poll_fds[1].revents().unwrap().contains(PollFlags::POLLIN));
57+
58+
pidfd_send_signal(child_pidfd, Signal::SIGINT, None).unwrap();
59+
60+
// Child pidfd is ready.
61+
assert_eq!(poll(&mut poll_fds, 100).unwrap(), 1);
62+
// Parent is still running.
63+
assert!(!poll_fds[0].revents().unwrap().contains(PollFlags::POLLIN));
64+
// Child is dead.
65+
assert!(poll_fds[1].revents().unwrap().contains(PollFlags::POLLIN));
66+
67+
close(me_pidfd).unwrap();
68+
close(child_pidfd).unwrap();
69+
}

0 commit comments

Comments
 (0)