Auto merge of #101077 - sunshowers:signal-mask-inherit, r=sunshowers
Change process spawning to inherit the parent's signal mask by default Previously, the signal mask was always reset when a child process is started. This breaks tools like `nohup` which expect `SIGHUP` to be blocked for all transitive processes. With this change, the default behavior changes to inherit the signal mask. This also changes the signal disposition for `SIGPIPE` to only be changed if the `#[unix_sigpipe]` attribute isn't set.
This commit is contained in:
commit
57e2c06a8d
8 changed files with 136 additions and 72 deletions
|
@ -1,5 +1,13 @@
|
||||||
//! NOTE: Keep these constants in sync with `library/std/src/sys/unix/mod.rs`!
|
//! NOTE: Keep these constants in sync with `library/std/src/sys/unix/mod.rs`!
|
||||||
|
|
||||||
|
/// The default value if `#[unix_sigpipe]` is not specified. This resolves
|
||||||
|
/// to `SIG_IGN` in `library/std/src/sys/unix/mod.rs`.
|
||||||
|
///
|
||||||
|
/// Note that `SIG_IGN` has been the Rust default since 2014. See
|
||||||
|
/// <https://github.com/rust-lang/rust/issues/62569>.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const DEFAULT: u8 = 0;
|
||||||
|
|
||||||
/// Do not touch `SIGPIPE`. Use whatever the parent process uses.
|
/// Do not touch `SIGPIPE`. Use whatever the parent process uses.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const INHERIT: u8 = 1;
|
pub const INHERIT: u8 = 1;
|
||||||
|
@ -15,8 +23,3 @@ pub const SIG_IGN: u8 = 2;
|
||||||
/// such as `head -n 1`.
|
/// such as `head -n 1`.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const SIG_DFL: u8 = 3;
|
pub const SIG_DFL: u8 = 3;
|
||||||
|
|
||||||
/// `SIG_IGN` has been the Rust default since 2014. See
|
|
||||||
/// <https://github.com/rust-lang/rust/issues/62569>.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const DEFAULT: u8 = SIG_IGN;
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ macro_rules! rtunwrap {
|
||||||
// `src/tools/tidy/src/pal.rs` for more info. On all other platforms, `sigpipe`
|
// `src/tools/tidy/src/pal.rs` for more info. On all other platforms, `sigpipe`
|
||||||
// has a value, but its value is ignored.
|
// has a value, but its value is ignored.
|
||||||
//
|
//
|
||||||
// Even though it is an `u8`, it only ever has 3 values. These are documented in
|
// Even though it is an `u8`, it only ever has 4 values. These are documented in
|
||||||
// `compiler/rustc_session/src/config/sigpipe.rs`.
|
// `compiler/rustc_session/src/config/sigpipe.rs`.
|
||||||
#[cfg_attr(test, allow(dead_code))]
|
#[cfg_attr(test, allow(dead_code))]
|
||||||
unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
|
unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
|
||||||
|
|
|
@ -163,17 +163,27 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
|
||||||
// See the other file for docs. NOTE: Make sure to keep them in
|
// See the other file for docs. NOTE: Make sure to keep them in
|
||||||
// sync!
|
// sync!
|
||||||
mod sigpipe {
|
mod sigpipe {
|
||||||
|
pub const DEFAULT: u8 = 0;
|
||||||
pub const INHERIT: u8 = 1;
|
pub const INHERIT: u8 = 1;
|
||||||
pub const SIG_IGN: u8 = 2;
|
pub const SIG_IGN: u8 = 2;
|
||||||
pub const SIG_DFL: u8 = 3;
|
pub const SIG_DFL: u8 = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
let handler = match sigpipe {
|
let (sigpipe_attr_specified, handler) = match sigpipe {
|
||||||
sigpipe::INHERIT => None,
|
sigpipe::DEFAULT => (false, Some(libc::SIG_IGN)),
|
||||||
sigpipe::SIG_IGN => Some(libc::SIG_IGN),
|
sigpipe::INHERIT => (true, None),
|
||||||
sigpipe::SIG_DFL => Some(libc::SIG_DFL),
|
sigpipe::SIG_IGN => (true, Some(libc::SIG_IGN)),
|
||||||
|
sigpipe::SIG_DFL => (true, Some(libc::SIG_DFL)),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
// The bootstrap compiler doesn't know about sigpipe::DEFAULT, and always passes in
|
||||||
|
// SIG_IGN. This causes some tests to fail because they expect SIGPIPE to be reset to
|
||||||
|
// default on process spawning (which doesn't happen if #[unix_sigpipe] is specified).
|
||||||
|
// Since we can't differentiate between the cases here, treat SIG_IGN as DEFAULT
|
||||||
|
// unconditionally.
|
||||||
|
if sigpipe_attr_specified && !(cfg!(bootstrap) && sigpipe == sigpipe::SIG_IGN) {
|
||||||
|
UNIX_SIGPIPE_ATTR_SPECIFIED.store(true, crate::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
if let Some(handler) = handler {
|
if let Some(handler) = handler {
|
||||||
rtassert!(signal(libc::SIGPIPE, handler) != libc::SIG_ERR);
|
rtassert!(signal(libc::SIGPIPE, handler) != libc::SIG_ERR);
|
||||||
}
|
}
|
||||||
|
@ -181,6 +191,26 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is set (up to once) in reset_sigpipe.
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "espidf",
|
||||||
|
target_os = "emscripten",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "horizon"
|
||||||
|
)))]
|
||||||
|
static UNIX_SIGPIPE_ATTR_SPECIFIED: crate::sync::atomic::AtomicBool =
|
||||||
|
crate::sync::atomic::AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "espidf",
|
||||||
|
target_os = "emscripten",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "horizon"
|
||||||
|
)))]
|
||||||
|
pub(crate) fn unix_sigpipe_attr_specified() -> bool {
|
||||||
|
UNIX_SIGPIPE_ATTR_SPECIFIED.load(crate::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
// SAFETY: must be called only once during runtime cleanup.
|
// SAFETY: must be called only once during runtime cleanup.
|
||||||
// NOTE: this is not guaranteed to run, for example when the program aborts.
|
// NOTE: this is not guaranteed to run, for example when the program aborts.
|
||||||
pub unsafe fn cleanup() {
|
pub unsafe fn cleanup() {
|
||||||
|
|
|
@ -39,10 +39,12 @@ cfg_if::cfg_if! {
|
||||||
// https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h
|
// https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(target_os = "android")] {
|
if #[cfg(target_os = "android")] {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub unsafe fn sigemptyset(set: *mut libc::sigset_t) -> libc::c_int {
|
pub unsafe fn sigemptyset(set: *mut libc::sigset_t) -> libc::c_int {
|
||||||
set.write_bytes(0u8, 1);
|
set.write_bytes(0u8, 1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub unsafe fn sigaddset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int {
|
pub unsafe fn sigaddset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -31,15 +31,18 @@ macro_rules! t {
|
||||||
ignore
|
ignore
|
||||||
)]
|
)]
|
||||||
fn test_process_mask() {
|
fn test_process_mask() {
|
||||||
|
// Test to make sure that a signal mask *does* get inherited.
|
||||||
|
fn test_inner(mut cmd: Command) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Test to make sure that a signal mask does not get inherited.
|
|
||||||
let mut cmd = Command::new(OsStr::new("cat"));
|
|
||||||
|
|
||||||
let mut set = mem::MaybeUninit::<libc::sigset_t>::uninit();
|
let mut set = mem::MaybeUninit::<libc::sigset_t>::uninit();
|
||||||
let mut old_set = mem::MaybeUninit::<libc::sigset_t>::uninit();
|
let mut old_set = mem::MaybeUninit::<libc::sigset_t>::uninit();
|
||||||
t!(cvt(sigemptyset(set.as_mut_ptr())));
|
t!(cvt(sigemptyset(set.as_mut_ptr())));
|
||||||
t!(cvt(sigaddset(set.as_mut_ptr(), libc::SIGINT)));
|
t!(cvt(sigaddset(set.as_mut_ptr(), libc::SIGINT)));
|
||||||
t!(cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, set.as_ptr(), old_set.as_mut_ptr())));
|
t!(cvt_nz(libc::pthread_sigmask(
|
||||||
|
libc::SIG_SETMASK,
|
||||||
|
set.as_ptr(),
|
||||||
|
old_set.as_mut_ptr()
|
||||||
|
)));
|
||||||
|
|
||||||
cmd.stdin(Stdio::MakePipe);
|
cmd.stdin(Stdio::MakePipe);
|
||||||
cmd.stdout(Stdio::MakePipe);
|
cmd.stdout(Stdio::MakePipe);
|
||||||
|
@ -58,14 +61,24 @@ fn test_process_mask() {
|
||||||
let _ = stdin_write.write(b"Hello");
|
let _ = stdin_write.write(b"Hello");
|
||||||
drop(stdin_write);
|
drop(stdin_write);
|
||||||
|
|
||||||
// Either EOF or failure (EPIPE) is okay.
|
// Exactly 5 bytes should be read.
|
||||||
let mut buf = [0; 5];
|
let mut buf = [0; 5];
|
||||||
if let Ok(ret) = stdout_read.read(&mut buf) {
|
let ret = t!(stdout_read.read(&mut buf));
|
||||||
assert_eq!(ret, 0);
|
assert_eq!(ret, 5);
|
||||||
}
|
assert_eq!(&buf, b"Hello");
|
||||||
|
|
||||||
t!(cat.wait());
|
t!(cat.wait());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A plain `Command::new` uses the posix_spawn path on many platforms.
|
||||||
|
let cmd = Command::new(OsStr::new("cat"));
|
||||||
|
test_inner(cmd);
|
||||||
|
|
||||||
|
// Specifying `pre_exec` forces the fork/exec path.
|
||||||
|
let mut cmd = Command::new(OsStr::new("cat"));
|
||||||
|
unsafe { cmd.pre_exec(Box::new(|| Ok(()))) };
|
||||||
|
test_inner(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crate::fmt;
|
||||||
use crate::io::{self, Error, ErrorKind};
|
use crate::io::{self, Error, ErrorKind};
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::num::NonZeroI32;
|
use crate::num::NonZeroI32;
|
||||||
use crate::ptr;
|
|
||||||
use crate::sys;
|
use crate::sys;
|
||||||
use crate::sys::cvt;
|
use crate::sys::cvt;
|
||||||
use crate::sys::process::process_common::*;
|
use crate::sys::process::process_common::*;
|
||||||
|
@ -310,7 +309,7 @@ impl Command {
|
||||||
//FIXME: Redox kernel does not support setgroups yet
|
//FIXME: Redox kernel does not support setgroups yet
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
if libc::getuid() == 0 && self.get_groups().is_none() {
|
if libc::getuid() == 0 && self.get_groups().is_none() {
|
||||||
cvt(libc::setgroups(0, ptr::null()))?;
|
cvt(libc::setgroups(0, crate::ptr::null()))?;
|
||||||
}
|
}
|
||||||
cvt(libc::setuid(u as uid_t))?;
|
cvt(libc::setuid(u as uid_t))?;
|
||||||
}
|
}
|
||||||
|
@ -326,24 +325,19 @@ impl Command {
|
||||||
// emscripten has no signal support.
|
// emscripten has no signal support.
|
||||||
#[cfg(not(target_os = "emscripten"))]
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
{
|
{
|
||||||
use crate::mem::MaybeUninit;
|
// Inherit the signal mask from the parent rather than resetting it (i.e. do not call
|
||||||
use crate::sys::cvt_nz;
|
// pthread_sigmask).
|
||||||
// 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::<libc::sigset_t>::uninit();
|
|
||||||
cvt(sigemptyset(set.as_mut_ptr()))?;
|
|
||||||
cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, set.as_ptr(), ptr::null_mut()))?;
|
|
||||||
|
|
||||||
|
// If #[unix_sigpipe] is specified, don't reset SIGPIPE to SIG_DFL.
|
||||||
|
// If #[unix_sigpipe] is not specified, reset SIGPIPE to SIG_DFL for backward compatibility.
|
||||||
|
//
|
||||||
|
// #[unix_sigpipe] is an opportunity to change the default here.
|
||||||
|
if !crate::sys::unix_sigpipe_attr_specified() {
|
||||||
#[cfg(target_os = "android")] // see issue #88585
|
#[cfg(target_os = "android")] // see issue #88585
|
||||||
{
|
{
|
||||||
let mut action: libc::sigaction = mem::zeroed();
|
let mut action: libc::sigaction = mem::zeroed();
|
||||||
action.sa_sigaction = libc::SIG_DFL;
|
action.sa_sigaction = libc::SIG_DFL;
|
||||||
cvt(libc::sigaction(libc::SIGPIPE, &action, ptr::null_mut()))?;
|
cvt(libc::sigaction(libc::SIGPIPE, &action, crate::ptr::null_mut()))?;
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
{
|
{
|
||||||
|
@ -353,6 +347,7 @@ impl Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for callback in self.get_closures().iter_mut() {
|
for callback in self.get_closures().iter_mut() {
|
||||||
callback()?;
|
callback()?;
|
||||||
|
@ -411,7 +406,7 @@ impl Command {
|
||||||
envp: Option<&CStringArray>,
|
envp: Option<&CStringArray>,
|
||||||
) -> io::Result<Option<Process>> {
|
) -> io::Result<Option<Process>> {
|
||||||
use crate::mem::MaybeUninit;
|
use crate::mem::MaybeUninit;
|
||||||
use crate::sys::{self, cvt_nz};
|
use crate::sys::{self, cvt_nz, unix_sigpipe_attr_specified};
|
||||||
|
|
||||||
if self.get_gid().is_some()
|
if self.get_gid().is_some()
|
||||||
|| self.get_uid().is_some()
|
|| self.get_uid().is_some()
|
||||||
|
@ -531,13 +526,24 @@ impl Command {
|
||||||
cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?;
|
cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut set = MaybeUninit::<libc::sigset_t>::uninit();
|
// Inherit the signal mask from this process rather than resetting it (i.e. do not call
|
||||||
cvt(sigemptyset(set.as_mut_ptr()))?;
|
// posix_spawnattr_setsigmask).
|
||||||
cvt_nz(libc::posix_spawnattr_setsigmask(attrs.0.as_mut_ptr(), set.as_ptr()))?;
|
|
||||||
cvt(sigaddset(set.as_mut_ptr(), libc::SIGPIPE))?;
|
// If #[unix_sigpipe] is specified, don't reset SIGPIPE to SIG_DFL.
|
||||||
cvt_nz(libc::posix_spawnattr_setsigdefault(attrs.0.as_mut_ptr(), set.as_ptr()))?;
|
// If #[unix_sigpipe] is not specified, reset SIGPIPE to SIG_DFL for backward compatibility.
|
||||||
|
//
|
||||||
|
// #[unix_sigpipe] is an opportunity to change the default here.
|
||||||
|
if !unix_sigpipe_attr_specified() {
|
||||||
|
let mut default_set = MaybeUninit::<libc::sigset_t>::uninit();
|
||||||
|
cvt(sigemptyset(default_set.as_mut_ptr()))?;
|
||||||
|
cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGPIPE))?;
|
||||||
|
cvt_nz(libc::posix_spawnattr_setsigdefault(
|
||||||
|
attrs.0.as_mut_ptr(),
|
||||||
|
default_set.as_ptr(),
|
||||||
|
))?;
|
||||||
|
flags |= libc::POSIX_SPAWN_SETSIGDEF;
|
||||||
|
}
|
||||||
|
|
||||||
flags |= libc::POSIX_SPAWN_SETSIGDEF | libc::POSIX_SPAWN_SETSIGMASK;
|
|
||||||
cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;
|
cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?;
|
||||||
|
|
||||||
// Make sure we synchronize access to the global `environ` resource
|
// Make sure we synchronize access to the global `environ` resource
|
||||||
|
|
|
@ -36,7 +36,7 @@ hello world
|
||||||
|
|
||||||
Set the `SIGPIPE` handler to `SIG_IGN` before invoking `fn main()`. This will result in `ErrorKind::BrokenPipe` errors if you program tries to write to a closed pipe. This is normally what you want if you for example write socket servers, socket clients, or pipe peers.
|
Set the `SIGPIPE` handler to `SIG_IGN` before invoking `fn main()`. This will result in `ErrorKind::BrokenPipe` errors if you program tries to write to a closed pipe. This is normally what you want if you for example write socket servers, socket clients, or pipe peers.
|
||||||
|
|
||||||
This is what libstd has done by default since 2014. Omitting `#[unix_sigpipe = "..."]` is the same as having `#[unix_sigpipe = "sig_ign"]`.
|
This is what libstd has done by default since 2014. (However, see the note on child processes below.)
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
|
@ -52,3 +52,11 @@ hello world
|
||||||
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', library/std/src/io/stdio.rs:1016:9
|
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', library/std/src/io/stdio.rs:1016:9
|
||||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Note on child processes
|
||||||
|
|
||||||
|
When spawning child processes, the legacy Rust behavior if `#[unix_sigpipe]` is not specified is to
|
||||||
|
reset `SIGPIPE` to `SIG_DFL`.
|
||||||
|
|
||||||
|
If `#[unix_sigpipe = "..."]` is specified, no matter what its value is, the signal disposition of
|
||||||
|
`SIGPIPE` is no longer reset. This means that the child inherits the parent's `SIGPIPE` behavior.
|
||||||
|
|
|
@ -23,9 +23,11 @@ pub fn assert_sigpipe_handler(expected_handler: SignalHandler) {
|
||||||
SignalHandler::Ignore => libc::SIG_IGN,
|
SignalHandler::Ignore => libc::SIG_IGN,
|
||||||
SignalHandler::Default => libc::SIG_DFL,
|
SignalHandler::Default => libc::SIG_DFL,
|
||||||
};
|
};
|
||||||
assert_eq!(prev, expected);
|
assert_eq!(prev, expected, "expected sigpipe value matches actual value");
|
||||||
|
|
||||||
// Unlikely to matter, but restore the old value anyway
|
// Unlikely to matter, but restore the old value anyway
|
||||||
unsafe { libc::signal(libc::SIGPIPE, prev); };
|
unsafe {
|
||||||
|
libc::signal(libc::SIGPIPE, prev);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue