Rollup merge of #156539 - jmillikin:unix-childext-killpg, r=nia-e

Add `ChildExt::kill_process_group`

ACP: https://github.com/rust-lang/libs-team/issues/791
Tracking issue: https://github.com/rust-lang/rust/issues/156537
This commit is contained in:
Jonathan Brouwer
2026-05-14 00:35:39 +02:00
committed by GitHub
6 changed files with 115 additions and 0 deletions
+70
View File
@@ -420,6 +420,68 @@ pub trait ChildExt: Sealed {
/// }
/// ```
fn send_signal(&self, signal: i32) -> io::Result<()>;
/// Sends a signal to a child process's process group.
///
/// # Errors
///
/// This function will return an error if the signal is invalid or if the
/// child process does not have a process group. The integer values
/// associated with signals are implementation-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, CommandExt}, process::{Command, Stdio}};
///
/// use libc::SIGTERM;
///
/// fn main() -> io::Result<()> {
/// # if cfg!(not(all(target_vendor = "apple", not(target_os = "macos")))) {
/// let child = Command::new("cat")
/// .stdin(Stdio::piped())
/// .process_group(0)
/// .spawn()?;
/// child.send_process_group_signal(SIGTERM)?;
/// # }
/// Ok(())
/// }
/// ```
#[unstable(feature = "unix_send_signal", issue = "141975")]
fn send_process_group_signal(&self, signal: i32) -> io::Result<()>;
/// Forces the child process's process group to exit.
///
/// This is analogous to [`Child::kill`] but applies to every process in
/// the child process's process group.
///
/// Use [`CommandExt::process_group`] to assign a child process to an
/// existing process group, or to make it the leader of a new process group.
/// By default spawned processes are in the parent's process group.
///
/// # Examples
///
/// ```rust
/// #![feature(unix_kill_process_group)]
///
/// use std::{os::unix::process::{ChildExt, CommandExt}, process::{Command, Stdio}};
///
/// fn main() -> std::io::Result<()> {
/// let mut child = Command::new("cat")
/// .stdin(Stdio::piped())
/// .process_group(0)
/// .spawn()?;
/// child.kill_process_group()?;
/// Ok(())
/// }
/// ```
///
/// [`Child::kill`]: process::Child::kill
#[unstable(feature = "unix_kill_process_group", issue = "156537")]
fn kill_process_group(&mut self) -> io::Result<()>;
}
#[unstable(feature = "unix_send_signal", issue = "141975")]
@@ -427,6 +489,14 @@ impl ChildExt for process::Child {
fn send_signal(&self, signal: i32) -> io::Result<()> {
self.handle.send_signal(signal)
}
fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
self.handle.send_process_group_signal(signal)
}
fn kill_process_group(&mut self) -> io::Result<()> {
self.handle.send_process_group_signal(libc::SIGKILL)
}
}
#[stable(feature = "process_extensions", since = "1.2.0")]
@@ -95,6 +95,21 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
.map(drop)
}
pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
// since kernel 6.9
// https://lore.kernel.org/all/20240210-chihuahua-hinzog-3945b6abd44a@brauner/
cvt(unsafe {
libc::syscall(
libc::SYS_pidfd_send_signal,
self.0.as_raw_fd(),
signal,
crate::ptr::null::<()>(),
libc::PIDFD_SIGNAL_PROCESS_GROUP,
)
})
.map(drop)
}
pub fn wait(&self) -> io::Result<ExitStatus> {
let r = self.waitid(libc::WEXITED)?;
match r {
@@ -158,6 +158,11 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
unimplemented!()
}
pub fn send_process_group_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;
+13
View File
@@ -1002,6 +1002,19 @@ pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
}
pub(crate) fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
// See note in `send_signal` regarding recycled PIDs.
if self.status.is_some() {
return Ok(());
}
#[cfg(target_os = "linux")]
if let Some(pid_fd) = self.pidfd.as_ref() {
// The `PIDFD_SIGNAL_PROCESS_GROUP` flag requires kernel >= 6.9
return pid_fd.send_process_group_signal(signal);
}
cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop)
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
use crate::sys::cvt_r;
if let Some(status) = self.status {
@@ -49,6 +49,10 @@ pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
unsupported()
}
pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> {
unsupported()
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
unsupported()
}
@@ -161,6 +161,14 @@ pub fn send_signal(&self, signal: i32) -> io::Result<()> {
}
}
pub fn send_process_group_signal(&self, signal: i32) -> io::Result<()> {
// See note in `send_signal` regarding recycled PIDs.
if self.status.is_some() {
return Ok(());
}
cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop)
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
use crate::sys::cvt_r;
if let Some(status) = self.status {