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

pidfd_getfd, pid_open and pidfd_send_signal #1868

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/1868.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Added `pidfd_getfd` on Linux.
- Added `pid_open` on Linux.
- Added `pidfd_send_signal` on Linux.
3 changes: 3 additions & 0 deletions src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,6 @@ feature! {
#![feature = "time"]
pub mod timer;
}

#[cfg(all(target_os = "linux", feature = "signal", feature = "process"))]
pub mod pidfd;
115 changes: 115 additions & 0 deletions src/sys/pidfd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! pidfd related functionality

use crate::errno::Errno;
use crate::sys::signal::Signal;
use crate::unistd::Pid;
use crate::Result;
use std::convert::TryFrom;
use std::os::unix::io::{AsFd, AsRawFd, FromRawFd, OwnedFd};

/// Allocates a new file descriptor in the calling process. This new file descriptor is a duplicate
/// of an existing file descriptor, `target`, in the process referred to by the PID file descriptor
/// `pid`.
///
/// The duplicate file descriptor refers to the same open file description (see
/// [open(2)](https://man7.org/linux/man-pages/man2/open.2.html)) as the original file descriptor in
/// the process referred to by `pid`. The two file descriptors thus share file status flags and
/// file offset. Furthermore, operations on the underlying file object (for example, assigning an
/// address to a socket object using [bind(2)](https://man7.org/linux/man-pages/man2/bind.2.html))
/// can equally be performed via the duplicate file descriptor.
///
/// The close-on-exec flag ([`libc::FD_CLOEXEC`]; see
/// [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html)) is set on the returned file
/// descriptor.
///
/// Permission to duplicate another process's file descriptor is governed by a ptrace access mode
/// PTRACE_MODE_ATTACH_REALCREDS check (see
/// [ptrace(2)](https://man7.org/linux/man-pages/man2/ptrace.2.html)).
pub fn pidfd_getfd<PFd: AsFd, TFd: AsFd>(
pid: PFd,
target: TFd,
) -> Result<OwnedFd> {
#[allow(clippy::useless_conversion)] // Not useless on all OSes
match unsafe {
libc::syscall(
libc::SYS_pidfd_getfd,
pid.as_fd().as_raw_fd(),
target.as_fd().as_raw_fd(),
0,
)
} {
-1 => Err(Errno::last()),
fd @ 0.. => {
Ok(unsafe { OwnedFd::from_raw_fd(i32::try_from(fd).unwrap()) })
}
_ => unreachable!(),
}
}

/// 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.
///
/// If `nonblock == true` returns a nonblocking file descriptor. 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.
pub fn pid_open(pid: Pid, nonblock: bool) -> Result<OwnedFd> {
#[allow(clippy::useless_conversion)] // Not useless on all OSes
match unsafe {
libc::syscall(
libc::SYS_pidfd_open,
pid,
if nonblock { libc::PIDFD_NONBLOCK } else { 0 },
)
} {
-1 => Err(Errno::last()),
fd @ 0.. => {
Ok(unsafe { OwnedFd::from_raw_fd(i32::try_from(fd).unwrap()) })
}
_ => unreachable!(),
}
}

/// Sends the signal `sig` to the target process referred to by `pid`, a PID file descriptor that
/// refers to a process.
///
/// If the info argument is some [`libc::siginfo_t`] buffer, that buffer should be populated as
/// described in [rt_sigqueueinfo(2)](https://man7.org/linux/man-pages/man2/rt_sigqueueinfo.2.html).
///
/// If the info argument is `None`, this is equivalent to specifying a pointer to a `siginfo_t`
/// buffer whose fields match the values that are implicitly supplied when a signal is sent using
/// [`crate::sys::signal::kill`]:
///
/// - `si_signo` is set to the signal number;
/// - `si_errno` is set to 0;
/// - `si_code` is set to SI_USER;
/// - `si_pid` is set to the caller's PID; and
/// - `si_uid` is set to the caller's real user ID.
///
/// The calling process must either be in the same PID namespace as the process referred to by
/// pidfd, or be in an ancestor of that namespace.
pub fn pidfd_send_signal<Fd: AsFd>(
pid: Fd,
sig: Signal,
info: Option<libc::siginfo_t>,
) -> Result<()> {
let info = match info {
Some(i) => &i,
None => std::ptr::null(),
};
match unsafe {
libc::syscall(
libc::SYS_pidfd_send_signal,
pid.as_fd().as_raw_fd(),
sig as i32,
info,
0u32,
)
} {
-1 => Err(Errno::last()),
0 => Ok(()),
_ => unreachable!(),
}
}
3 changes: 3 additions & 0 deletions test/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@ mod test_statfs;
target_os = "haiku"
)))]
mod test_resource;

#[cfg(all(target_os = "linux", feature = "signal", feature = "process"))]
pub mod test_pidfd;
28 changes: 28 additions & 0 deletions test/sys/test_pidfd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use nix::{
sys::{
pidfd::{pid_open, pidfd_send_signal},
signal::Signal,
signalfd::SigSet,
wait::waitpid,
},
unistd::{fork, ForkResult},
};

#[test]
fn test_pidfd_send_signal() {
match unsafe { fork().unwrap() } {
ForkResult::Parent { child } => {
// Send SIGUSR1
let pid_fd = pid_open(child, false).unwrap();
pidfd_send_signal(pid_fd, Signal::SIGUSR1, None).unwrap();
// Wait for child to exit.
waitpid(child, None).unwrap();
}
ForkResult::Child => {
// Wait for SIGUSR1
let mut mask = SigSet::empty();
mask.add(Signal::SIGUSR1);
assert_eq!(mask.wait().unwrap(), Signal::SIGUSR1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion is done in child process. So even if it fails, the main process would still succeed, which is not what we want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the parent needs to assert on the exit code of the child process? How would you suggest doing this?

}
}
}