From d6e72ee25be54453aac1064166baf3a71aaebe2d Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Tue, 5 Jul 2022 19:56:22 +0200 Subject: [PATCH 1/4] Support `#[unix_sigpipe = "inherit|sig_dfl|sig_ign"]` on `fn main()` This makes it possible to instruct libstd to never touch the signal handler for `SIGPIPE`, which makes programs pipeable by default (e.g. with `./your-program | head -n 1`) without `ErrorKind::BrokenPipe` errors. --- std/src/rt.rs | 12 +++++++++--- std/src/sys/hermit/mod.rs | 2 +- std/src/sys/sgx/mod.rs | 2 +- std/src/sys/solid/mod.rs | 2 +- std/src/sys/unix/mod.rs | 29 +++++++++++++++++++++++------ std/src/sys/unsupported/common.rs | 2 +- std/src/sys/windows/mod.rs | 2 +- 7 files changed, 37 insertions(+), 14 deletions(-) diff --git a/std/src/rt.rs b/std/src/rt.rs index 663537a05..7cf3d7d41 100644 --- a/std/src/rt.rs +++ b/std/src/rt.rs @@ -73,9 +73,9 @@ macro_rules! rtunwrap { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. #[cfg_attr(test, allow(dead_code))] -unsafe fn init(argc: isize, argv: *const *const u8) { +unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { unsafe { - sys::init(argc, argv); + sys::init(argc, argv, sigpipe); let main_guard = sys::thread::guard::init(); // Next, set up the current Thread with the guard information we just @@ -107,6 +107,7 @@ fn lang_start_internal( main: &(dyn Fn() -> i32 + Sync + crate::panic::RefUnwindSafe), argc: isize, argv: *const *const u8, + sigpipe: u8, ) -> Result { use crate::{mem, panic}; let rt_abort = move |e| { @@ -124,7 +125,7 @@ fn lang_start_internal( // prevent libstd from accidentally introducing a panic to these functions. Another is from // user code from `main` or, more nefariously, as described in e.g. issue #86030. // SAFETY: Only called once during runtime initialization. - panic::catch_unwind(move || unsafe { init(argc, argv) }).map_err(rt_abort)?; + panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?; let ret_code = panic::catch_unwind(move || panic::catch_unwind(main).unwrap_or(101) as isize) .map_err(move |e| { mem::forget(e); @@ -140,11 +141,16 @@ fn lang_start( main: fn() -> T, argc: isize, argv: *const *const u8, + #[cfg(not(bootstrap))] sigpipe: u8, ) -> isize { let Ok(v) = lang_start_internal( &move || crate::sys_common::backtrace::__rust_begin_short_backtrace(main).report().to_i32(), argc, argv, + #[cfg(bootstrap)] + 2, // Temporary inlining of sigpipe::DEFAULT until bootstrap stops being special + #[cfg(not(bootstrap))] + sigpipe, ); v } diff --git a/std/src/sys/hermit/mod.rs b/std/src/sys/hermit/mod.rs index 60b7a973c..61da096ae 100644 --- a/std/src/sys/hermit/mod.rs +++ b/std/src/sys/hermit/mod.rs @@ -98,7 +98,7 @@ pub extern "C" fn __rust_abort() { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(argc: isize, argv: *const *const u8) { +pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { let _ = net::init(); args::init(argc, argv); } diff --git a/std/src/sys/sgx/mod.rs b/std/src/sys/sgx/mod.rs index 696400670..b1d32929e 100644 --- a/std/src/sys/sgx/mod.rs +++ b/std/src/sys/sgx/mod.rs @@ -47,7 +47,7 @@ pub mod locks { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(argc: isize, argv: *const *const u8) { +pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { unsafe { args::init(argc, argv); } diff --git a/std/src/sys/solid/mod.rs b/std/src/sys/solid/mod.rs index 778a589d1..5867979a2 100644 --- a/std/src/sys/solid/mod.rs +++ b/std/src/sys/solid/mod.rs @@ -56,7 +56,7 @@ pub mod locks { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(_argc: isize, _argv: *const *const u8) {} +pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} // SAFETY: must be called only once during runtime cleanup. pub unsafe fn cleanup() {} diff --git a/std/src/sys/unix/mod.rs b/std/src/sys/unix/mod.rs index 3a3750930..e11201f60 100644 --- a/std/src/sys/unix/mod.rs +++ b/std/src/sys/unix/mod.rs @@ -44,12 +44,12 @@ pub mod thread_parker; pub mod time; #[cfg(target_os = "espidf")] -pub fn init(argc: isize, argv: *const *const u8) {} +pub fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {} #[cfg(not(target_os = "espidf"))] // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(argc: isize, argv: *const *const u8) { +pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // The standard streams might be closed on application startup. To prevent // std::io::{stdin, stdout,stderr} objects from using other unrelated file // resources opened later, we reopen standards streams when they are closed. @@ -61,8 +61,9 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) { // want! // // Hence, we set SIGPIPE to ignore when the program starts up in order - // to prevent this problem. - reset_sigpipe(); + // to prevent this problem. Add `#[unix_sigpipe = "..."]` above `fn main()` to + // alter this behavior. + reset_sigpipe(sigpipe); stack_overflow::init(); args::init(argc, argv); @@ -151,9 +152,25 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) { } } - unsafe fn reset_sigpipe() { + unsafe fn reset_sigpipe(#[allow(unused_variables)] sigpipe: u8) { #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "horizon")))] - rtassert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR); + { + // We don't want to add this as a public type to libstd, nor do we want to + // duplicate the code, so we choose to include this compiler file like this. + mod sigpipe { + include!("../../../../../compiler/rustc_session/src/config/sigpipe.rs"); + } + + let handler = match sigpipe { + sigpipe::INHERIT => None, + sigpipe::SIG_IGN => Some(libc::SIG_IGN), + sigpipe::SIG_DFL => Some(libc::SIG_DFL), + _ => unreachable!(), + }; + if let Some(handler) = handler { + rtassert!(signal(libc::SIGPIPE, handler) != libc::SIG_ERR); + } + } } } diff --git a/std/src/sys/unsupported/common.rs b/std/src/sys/unsupported/common.rs index 4c9ade4a8..5cd9e57de 100644 --- a/std/src/sys/unsupported/common.rs +++ b/std/src/sys/unsupported/common.rs @@ -6,7 +6,7 @@ pub mod memchr { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(_argc: isize, _argv: *const *const u8) {} +pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. diff --git a/std/src/sys/windows/mod.rs b/std/src/sys/windows/mod.rs index a9846a484..510f470bf 100644 --- a/std/src/sys/windows/mod.rs +++ b/std/src/sys/windows/mod.rs @@ -48,7 +48,7 @@ cfg_if::cfg_if! { // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -pub unsafe fn init(_argc: isize, _argv: *const *const u8) { +pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) { stack_overflow::init(); // Normally, `thread::spawn` will call `Thread::set_name` but since this thread already From 1dec40f84a383e6ec91e3677ffcf6306fa2184f6 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Wed, 31 Aug 2022 18:04:15 +0200 Subject: [PATCH 2/4] unix_sigpipe: Inline compiler sigpipe constants in std --- std/src/sys/unix/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/std/src/sys/unix/mod.rs b/std/src/sys/unix/mod.rs index e11201f60..d701f950e 100644 --- a/std/src/sys/unix/mod.rs +++ b/std/src/sys/unix/mod.rs @@ -155,10 +155,16 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { unsafe fn reset_sigpipe(#[allow(unused_variables)] sigpipe: u8) { #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "horizon")))] { - // We don't want to add this as a public type to libstd, nor do we want to - // duplicate the code, so we choose to include this compiler file like this. + // We don't want to add this as a public type to libstd, nor do we + // want to `include!` a file from the compiler (which would break + // Miri and xargo for example), so we choose to duplicate these + // constants from `compiler/rustc_session/src/config/sigpipe.rs`. + // See the other file for docs. NOTE: Make sure to keep them in + // sync! mod sigpipe { - include!("../../../../../compiler/rustc_session/src/config/sigpipe.rs"); + pub const INHERIT: u8 = 1; + pub const SIG_IGN: u8 = 2; + pub const SIG_DFL: u8 = 3; } let handler = match sigpipe { From 0e792b3842ba9af261de90c8ed5c1e0da5658f7a Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Wed, 31 Aug 2022 18:11:48 +0200 Subject: [PATCH 3/4] unix_sigpipe: Add docs for `init()` `sigpipe` param --- std/src/rt.rs | 2 ++ std/src/sys/unix/mod.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/std/src/rt.rs b/std/src/rt.rs index 7cf3d7d41..b3f6f8295 100644 --- a/std/src/rt.rs +++ b/std/src/rt.rs @@ -72,6 +72,8 @@ macro_rules! rtunwrap { // Runs before `main`. // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. +// The extra parameter `sigpipe` allows rustc to generate code that instructs std whether +// or not to ignore `SIGPIPE`. #[cfg_attr(test, allow(dead_code))] unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { unsafe { diff --git a/std/src/sys/unix/mod.rs b/std/src/sys/unix/mod.rs index d701f950e..2856dfcb3 100644 --- a/std/src/sys/unix/mod.rs +++ b/std/src/sys/unix/mod.rs @@ -49,6 +49,8 @@ pub fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {} #[cfg(not(target_os = "espidf"))] // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. +// The extra parameter `sigpipe` allows rustc to generate code that instructs std whether +// or not to ignore `SIGPIPE`. pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // The standard streams might be closed on application startup. To prevent // std::io::{stdin, stdout,stderr} objects from using other unrelated file From a86df1727bf07aabea494d90c3ea4e94e6e75cfa Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Thu, 1 Sep 2022 06:43:03 +0200 Subject: [PATCH 4/4] unix_sigpipe: Make `sigpipe` param docs long-form --- std/src/rt.rs | 21 +++++++++++++++++++-- std/src/sys/unix/mod.rs | 3 +-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/std/src/rt.rs b/std/src/rt.rs index b3f6f8295..98f6cc7aa 100644 --- a/std/src/rt.rs +++ b/std/src/rt.rs @@ -72,8 +72,25 @@ macro_rules! rtunwrap { // Runs before `main`. // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -// The extra parameter `sigpipe` allows rustc to generate code that instructs std whether -// or not to ignore `SIGPIPE`. +// +// # The `sigpipe` parameter +// +// Since 2014, the Rust runtime on Unix has set the `SIGPIPE` handler to +// `SIG_IGN`. Applications have good reasons to want a different behavior +// though, so there is a `#[unix_sigpipe = "..."]` attribute on `fn main()` that +// can be used to select how `SIGPIPE` shall be setup (if changed at all) before +// `fn main()` is called. See +// for more info. +// +// The `sigpipe` parameter to this function gets its value via the code that +// rustc generates to invoke `fn lang_start()`. The reason we have `sigpipe` for +// all platforms and not only Unix, is because std is not allowed to have `cfg` +// directives as this high level. See the module docs in +// `src/tools/tidy/src/pal.rs` for more info. On all other platforms, `sigpipe` +// has a value, but its value is ignored. +// +// Even though it is an `u8`, it only ever has 3 values. These are documented in +// `compiler/rustc_session/src/config/sigpipe.rs`. #[cfg_attr(test, allow(dead_code))] unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { unsafe { diff --git a/std/src/sys/unix/mod.rs b/std/src/sys/unix/mod.rs index 2856dfcb3..c84e292ea 100644 --- a/std/src/sys/unix/mod.rs +++ b/std/src/sys/unix/mod.rs @@ -49,8 +49,7 @@ pub fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {} #[cfg(not(target_os = "espidf"))] // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. -// The extra parameter `sigpipe` allows rustc to generate code that instructs std whether -// or not to ignore `SIGPIPE`. +// See `fn init()` in `library/std/src/rt.rs` for docs on `sigpipe`. pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // The standard streams might be closed on application startup. To prevent // std::io::{stdin, stdout,stderr} objects from using other unrelated file