Rollup merge of #141990 - Qelxiros:141975-unix_send_signal, r=ChrisDenton,tgross35

Implement send_signal for unix child processes

Tracking issue: rust-lang/rust#141975

There are two main differences between my implementation and the Public API section of the tracking issue. ~First, `send_signal` requires a mutable reference, like `Child::kill`.~ Second, `ChildExt` has `Sealed` as a supertrait, bringing it more in line with other extension traits like `CommandExt`.

try-job: `dist-various*`
try-job: `test-various*`
This commit is contained in:
Trevor Gross 2025-06-20 02:50:38 -04:00 committed by GitHub
commit e381a14b7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 67 additions and 8 deletions

View file

@ -116,6 +116,9 @@ pub mod prelude {
#[stable(feature = "rust1", since = "1.0.0")]
pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
#[doc(no_inline)]
#[unstable(feature = "unix_send_signal", issue = "141975")]
pub use super::process::ChildExt;
#[doc(no_inline)]
#[stable(feature = "rust1", since = "1.0.0")]
pub use super::process::{CommandExt, ExitStatusExt};
#[doc(no_inline)]

View file

@ -378,6 +378,41 @@ impl ExitStatusExt for process::ExitStatusError {
}
}
#[unstable(feature = "unix_send_signal", issue = "141975")]
pub trait ChildExt: Sealed {
/// Sends a signal to a child process.
///
/// # Errors
///
/// This function will return an error if the signal is invalid. The integer values associated
/// with signals are implemenation-specific, so it's encouraged to use a crate that provides
/// posix bindings.
///
/// # Examples
///
/// ```rust
/// #![feature(unix_send_signal)]
///
/// use std::{io, os::unix::process::ChildExt, process::{Command, Stdio}};
///
/// use libc::SIGTERM;
///
/// fn main() -> io::Result<()> {
/// let child = Command::new("cat").stdin(Stdio::piped()).spawn()?;
/// child.send_signal(SIGTERM)?;
/// Ok(())
/// }
/// ```
fn send_signal(&self, signal: i32) -> io::Result<()>;
}
#[unstable(feature = "unix_send_signal", issue = "141975")]
impl ChildExt for process::Child {
fn send_signal(&self, signal: i32) -> io::Result<()> {
self.handle.send_signal(signal)
}
}
#[stable(feature = "process_extensions", since = "1.2.0")]
impl FromRawFd for process::Stdio {
#[inline]

View file

@ -13,11 +13,15 @@ pub(crate) struct PidFd(FileDesc);
impl PidFd {
pub fn kill(&self) -> io::Result<()> {
self.send_signal(libc::SIGKILL)
}
pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
cvt(unsafe {
libc::syscall(
libc::SYS_pidfd_send_signal,
self.0.as_raw_fd(),
libc::SIGKILL,
signal,
crate::ptr::null::<()>(),
0,
)

View file

@ -152,6 +152,11 @@ impl Process {
Ok(())
}
pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
// Fuchsia doesn't have a direct equivalent for signals
unimplemented!()
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
let mut proc_info: zx_info_process_t = Default::default();
let mut actual: size_t = 0;

View file

@ -963,9 +963,13 @@ impl Process {
self.pid as u32
}
pub fn kill(&mut self) -> io::Result<()> {
pub fn kill(&self) -> io::Result<()> {
self.send_signal(libc::SIGKILL)
}
pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
// If we've already waited on this process then the pid can be recycled
// and used for another process, and we probably shouldn't be killing
// and used for another process, and we probably shouldn't be signaling
// random processes, so return Ok because the process has exited already.
if self.status.is_some() {
return Ok(());
@ -973,9 +977,9 @@ impl Process {
#[cfg(target_os = "linux")]
if let Some(pid_fd) = self.pidfd.as_ref() {
// pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too
return pid_fd.kill();
return pid_fd.send_signal(signal);
}
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {

View file

@ -40,7 +40,11 @@ impl Process {
0
}
pub fn kill(&mut self) -> io::Result<()> {
pub fn kill(&self) -> io::Result<()> {
unsupported()
}
pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
unsupported()
}

View file

@ -146,14 +146,18 @@ impl Process {
self.pid as u32
}
pub fn kill(&mut self) -> io::Result<()> {
pub fn kill(&self) -> io::Result<()> {
self.send_signal(libc::SIGKILL)
}
pub fn send_signal(&self, signal: i32) -> io::Result<()> {
// If we've already waited on this process then the pid can be recycled
// and used for another process, and we probably shouldn't be killing
// random processes, so return Ok because the process has exited already.
if self.status.is_some() {
Ok(())
} else {
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
}
}