diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 09b2bfe39f094..202eca2c5c041 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -179,6 +179,107 @@ pub trait CommandExt: Sealed { /// ``` #[stable(feature = "process_set_process_group", since = "1.64.0")] fn process_group(&mut self, pgroup: i32) -> &mut process::Command; + + /// Blocks the given signal for the child process at the time it is started. + /// + /// The set of blocked signals for a process is known as its signal mask. + /// Use this method to block some signals. + /// + /// This method corresponds to calling [`sigaddset`] with the given signal. + /// + /// # Notes + /// + /// Rust's current default is to not block any signals in child processes. This may change in + /// the future to inheriting the current process's signal mask. + /// + /// This method is idempotent: blocking a signal that's already blocked results in + /// success and has no effect. + /// + /// Blocking some signals like `SIGSEGV` can lead to undefined behavior in + /// the child. See the [`pthread_sigmask`] man page for more information. + /// + /// # Errors + /// + /// Returns an `InvalidInput` error if the signal is invalid. + /// + /// # Examples + /// + /// Start a process with `SIGINT` blocked: + /// + /// ```no_run + /// #![feature(process_sigmask)] + /// # + /// use std::process::Command; + /// use std::os::unix::process::CommandExt; + /// + /// Command::new("sleep") + /// .arg("10") + /// // On most platforms, SIGINT is signal 2. + /// .block_signal(2)? + /// .spawn()?; + /// # + /// # Ok::<_, Box>(()) + /// ``` + /// + /// [`sigaddset`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaddset.html + /// [`pthread_sigmask`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html + #[unstable(feature = "process_sigmask", issue = "none")] + fn block_signal(&mut self, signal: i32) -> io::Result<&mut process::Command>; + + /// Unblocks the given signal for the child process at the time it is started. + /// + /// The set of blocked signals for a process is known as its signal mask. + /// Use this method to unblock a signal. + /// + /// This method corresponds to calling [`sigdelset`] with the given signal. + /// + /// # Notes + /// + /// Rust's current default is to not block any signals in child processes. This may change in + /// the future to inheriting the current process's signal mask. + /// + /// This method is idempotent: unblocking a signal that's already unblocked results in + /// success and has no effect. + /// + /// # Errors + /// + /// Returns an `InvalidInput` error if the signal is invalid. + /// + /// # Examples + /// + /// Start a process with `SIGHUP` unblocked: + /// + /// ```no_run + /// #![feature(process_sigmask)] + /// # + /// use std::process::Command; + /// use std::os::unix::process::CommandExt; + /// + /// Command::new("sleep") + /// .arg("10") + /// // On most platforms, SIGHUP is signal 1. + /// .unblock_signal(1)? + /// .spawn()?; + /// # + /// # Ok::<_, Box>(()) + /// ``` + /// + /// [`sigdelset`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigdelset.html + /// [`pthread_sigmask`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html + #[unstable(feature = "process_sigmask", issue = "none")] + fn unblock_signal(&mut self, signal: i32) -> io::Result<&mut process::Command>; + + /// Returns true if a signal will be blocked in the child process at the time it is started. + /// + /// This method corresponds to calling [`sigismember`] with the given signal. + /// + /// # Errors + /// + /// Returns an `InvalidInput` error if the signal is invalid. + /// + /// [`sigismember`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigismember.html + #[unstable(feature = "process_sigmask", issue = "none")] + fn will_block_signal(&self, signal: i32) -> io::Result; } #[stable(feature = "rust1", since = "1.0.0")] @@ -224,6 +325,20 @@ impl CommandExt for process::Command { self.as_inner_mut().pgroup(pgroup); self } + + fn block_signal(&mut self, signal: i32) -> io::Result<&mut process::Command> { + self.as_inner_mut().signal_mask()?.insert(signal)?; + Ok(self) + } + + fn unblock_signal(&mut self, signal: i32) -> io::Result<&mut process::Command> { + self.as_inner_mut().signal_mask()?.remove(signal)?; + Ok(self) + } + + fn will_block_signal(&self, signal: i32) -> io::Result { + self.as_inner().get_signal_mask()?.contains(signal) + } } /// Unix-specific extensions to [`process::ExitStatus`] and diff --git a/library/std/src/sys/unix/process/mod.rs b/library/std/src/sys/unix/process/mod.rs index 3701510f3a428..ad04c49aa76eb 100644 --- a/library/std/src/sys/unix/process/mod.rs +++ b/library/std/src/sys/unix/process/mod.rs @@ -1,4 +1,4 @@ -pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes}; +pub use self::process_common::{Command, CommandArgs, ExitCode, SignalSet, Stdio, StdioPipes}; pub use self::process_inner::{ExitStatus, ExitStatusError, Process}; pub use crate::ffi::OsString as EnvKey; pub use crate::sys_common::process::CommandEnvs; diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index dc071c85cdfdb..e1188ed7c7495 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -9,9 +9,11 @@ use crate::fmt; use crate::io; use crate::path::Path; use crate::ptr; +use crate::sync::OnceLock; use crate::sys::fd::FileDesc; use crate::sys::fs::File; use crate::sys::pipe::{self, AnonPipe}; +use crate::sys::{cvt, cvt_nz}; use crate::sys_common::process::{CommandEnv, CommandEnvs}; use crate::sys_common::IntoInner; @@ -152,6 +154,7 @@ pub struct Command { #[cfg(target_os = "linux")] create_pidfd: bool, pgroup: Option, + signal_mask: OnceLock, } // Create a new type for argv, so that we can make it `Send` and `Sync` @@ -196,6 +199,49 @@ pub enum Stdio { Fd(FileDesc), } +pub struct SignalSet { + pub sigset: libc::sigset_t, +} + +impl SignalSet { + #[allow(dead_code)] + pub fn new_from_current() -> io::Result { + let mut set = crate::mem::MaybeUninit::::uninit(); + // pthread_sigmask returns the errno rather than setting it directly. + unsafe { + cvt_nz(libc::pthread_sigmask(libc::SIG_BLOCK, ptr::null(), set.as_mut_ptr()))?; + Ok(Self { sigset: set.assume_init() }) + } + } + + pub fn empty() -> io::Result { + let mut set = crate::mem::MaybeUninit::::uninit(); + unsafe { + cvt(sigemptyset(set.as_mut_ptr()))?; + Ok(Self { sigset: set.assume_init() }) + } + } + + pub fn insert(&mut self, signal: i32) -> io::Result<&mut Self> { + unsafe { + cvt(sigaddset(&mut self.sigset, signal))?; + } + Ok(self) + } + + pub fn remove(&mut self, signal: i32) -> io::Result<&mut Self> { + unsafe { + cvt(sigdelset(&mut self.sigset, signal))?; + } + Ok(self) + } + + pub fn contains(&self, signal: i32) -> io::Result { + let contains = unsafe { cvt(sigismember(&self.sigset, signal))? }; + Ok(contains != 0) + } +} + impl Command { #[cfg(not(target_os = "linux"))] pub fn new(program: &OsStr) -> Command { @@ -216,6 +262,7 @@ impl Command { stdout: None, stderr: None, pgroup: None, + mask: None, } } @@ -239,6 +286,7 @@ impl Command { stderr: None, create_pidfd: false, pgroup: None, + signal_mask: OnceLock::new(), } } @@ -277,6 +325,10 @@ impl Command { pub fn pgroup(&mut self, pgroup: pid_t) { self.pgroup = Some(pgroup); } + pub fn signal_mask(&mut self) -> io::Result<&mut SignalSet> { + self.signal_mask.get_or_try_init(SignalSet::empty)?; + Ok(self.signal_mask.get_mut().unwrap()) + } #[cfg(target_os = "linux")] pub fn create_pidfd(&mut self, val: bool) { @@ -344,6 +396,10 @@ impl Command { pub fn get_pgroup(&self) -> Option { self.pgroup } + #[allow(dead_code)] + pub fn get_signal_mask(&self) -> io::Result<&SignalSet> { + self.signal_mask.get_or_try_init(SignalSet::empty) + } pub fn get_closures(&mut self) -> &mut Vec io::Result<()> + Send + Sync>> { &mut self.closures diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 75bb92437fd92..0375169beb71d 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -326,18 +326,10 @@ impl Command { // emscripten has no signal support. #[cfg(not(target_os = "emscripten"))] { - use crate::mem::MaybeUninit; use crate::sys::cvt_nz; - // Reset signal handling so the child process starts in a - // standardized state. libstd ignores SIGPIPE, and signal-handling - // libraries often set a mask. Child processes inherit ignored - // signals and the signal mask from their parent, but most - // UNIX programs do not reset these things on their own, so we - // need to clean things up now to avoid confusing the program - // we're about to run. - let mut set = MaybeUninit::::uninit(); - cvt(sigemptyset(set.as_mut_ptr()))?; - cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, set.as_ptr(), ptr::null_mut()))?; + // Set the signal mask. + let signal_mask = self.get_signal_mask()?; + cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, &signal_mask.sigset, ptr::null_mut()))?; #[cfg(target_os = "android")] // see issue #88585 { @@ -529,11 +521,12 @@ impl Command { cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?; } - let mut set = MaybeUninit::::uninit(); - cvt(sigemptyset(set.as_mut_ptr()))?; - cvt_nz(libc::posix_spawnattr_setsigmask(attrs.0.as_mut_ptr(), set.as_ptr()))?; - cvt(sigaddset(set.as_mut_ptr(), libc::SIGPIPE))?; - cvt_nz(libc::posix_spawnattr_setsigdefault(attrs.0.as_mut_ptr(), set.as_ptr()))?; + let signal_mask = self.get_signal_mask()?; + cvt_nz(libc::posix_spawnattr_setsigmask(attrs.0.as_mut_ptr(), &signal_mask.sigset))?; + + let mut sig_default = SignalSet::empty()?; + sig_default.insert(libc::SIGPIPE)?; + cvt_nz(libc::posix_spawnattr_setsigdefault(attrs.0.as_mut_ptr(), &sig_default.sigset))?; flags |= libc::POSIX_SPAWN_SETSIGDEF | libc::POSIX_SPAWN_SETSIGMASK; cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;