about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTrevor Gross <t.gross35@gmail.com>2025-06-20 02:50:38 -0400
committerGitHub <noreply@github.com>2025-06-20 02:50:38 -0400
commite381a14b7cb07eb3be4da73a032cb8f4481a05b9 (patch)
treee7c8aaf3223ba74aabfdada24aec308ffcf20eab
parentc117ebefd23c47fa71252291f7fb597555c74daa (diff)
parentb3914945ae2d77770c25ed124a8450d82c8f7fe9 (diff)
downloadrust-e381a14b7cb07eb3be4da73a032cb8f4481a05b9.tar.gz
rust-e381a14b7cb07eb3be4da73a032cb8f4481a05b9.zip
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*`
-rw-r--r--library/std/src/os/unix/mod.rs3
-rw-r--r--library/std/src/os/unix/process.rs35
-rw-r--r--library/std/src/sys/pal/unix/linux/pidfd.rs6
-rw-r--r--library/std/src/sys/process/unix/fuchsia.rs5
-rw-r--r--library/std/src/sys/process/unix/unix.rs12
-rw-r--r--library/std/src/sys/process/unix/unsupported.rs6
-rw-r--r--library/std/src/sys/process/unix/vxworks.rs8
7 files changed, 67 insertions, 8 deletions
diff --git a/library/std/src/os/unix/mod.rs b/library/std/src/os/unix/mod.rs
index 5802b653965..78c957270c4 100644
--- a/library/std/src/os/unix/mod.rs
+++ b/library/std/src/os/unix/mod.rs
@@ -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)]
diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index 57ce3c5a4bf..3f4fe2e56ec 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -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]
diff --git a/library/std/src/sys/pal/unix/linux/pidfd.rs b/library/std/src/sys/pal/unix/linux/pidfd.rs
index 2d949ec9e91..47e9a616bfd 100644
--- a/library/std/src/sys/pal/unix/linux/pidfd.rs
+++ b/library/std/src/sys/pal/unix/linux/pidfd.rs
@@ -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,
             )
diff --git a/library/std/src/sys/process/unix/fuchsia.rs b/library/std/src/sys/process/unix/fuchsia.rs
index 017ab91797c..d71be510b6a 100644
--- a/library/std/src/sys/process/unix/fuchsia.rs
+++ b/library/std/src/sys/process/unix/fuchsia.rs
@@ -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;
diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs
index 4f595ac9a1c..1fe80c13b81 100644
--- a/library/std/src/sys/process/unix/unix.rs
+++ b/library/std/src/sys/process/unix/unix.rs
@@ -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> {
diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs
index e86561a5c5c..87403cd50f8 100644
--- a/library/std/src/sys/process/unix/unsupported.rs
+++ b/library/std/src/sys/process/unix/unsupported.rs
@@ -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()
     }
 
diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs
index f33b4a375da..51ae8c56bdb 100644
--- a/library/std/src/sys/process/unix/vxworks.rs
+++ b/library/std/src/sys/process/unix/vxworks.rs
@@ -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)
         }
     }