Skip to content

Commit ea6e6dc

Browse files
committed
Auto merge of #97802 - Enselic:add-no_ignore_sigkill-feature, r=joshtriplett
Support `#[unix_sigpipe = "inherit|sig_dfl"]` on `fn main()` to prevent ignoring `SIGPIPE` When enabled, programs don't have to explicitly handle `ErrorKind::BrokenPipe` any longer. Currently, the program ```rust fn main() { loop { println!("hello world"); } } ``` will print an error if used with a short-lived pipe, e.g. % ./main | head -n 1 hello world 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 by enabling `#[unix_sigpipe = "sig_dfl"]` like this ```rust #![feature(unix_sigpipe)] #[unix_sigpipe = "sig_dfl"] fn main() { loop { println!("hello world"); } } ``` there is no error, because `SIGPIPE` will not be ignored and thus the program will be killed appropriately: % ./main | head -n 1 hello world The current libstd behaviour of ignoring `SIGPIPE` before `fn main()` can be explicitly requested by using `#[unix_sigpipe = "sig_ign"]`. With `#[unix_sigpipe = "inherit"]`, no change at all is made to `SIGPIPE`, which typically means the behaviour will be the same as `#[unix_sigpipe = "sig_dfl"]`. See rust-lang/rust#62569 and referenced issues for discussions regarding the `SIGPIPE` problem itself See the [this](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Proposal.3A.20First.20step.20towards.20solving.20the.20SIGPIPE.20problem) Zulip topic for more discussions, including about this PR. Tracking issue: rust-lang/rust#97889
2 parents 24fc95c + a86df17 commit ea6e6dc

File tree

7 files changed

+63
-14
lines changed

7 files changed

+63
-14
lines changed

std/src/rt.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,29 @@ macro_rules! rtunwrap {
7272
// Runs before `main`.
7373
// SAFETY: must be called only once during runtime initialization.
7474
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
75+
//
76+
// # The `sigpipe` parameter
77+
//
78+
// Since 2014, the Rust runtime on Unix has set the `SIGPIPE` handler to
79+
// `SIG_IGN`. Applications have good reasons to want a different behavior
80+
// though, so there is a `#[unix_sigpipe = "..."]` attribute on `fn main()` that
81+
// can be used to select how `SIGPIPE` shall be setup (if changed at all) before
82+
// `fn main()` is called. See <https://github.com/rust-lang/rust/issues/97889>
83+
// for more info.
84+
//
85+
// The `sigpipe` parameter to this function gets its value via the code that
86+
// rustc generates to invoke `fn lang_start()`. The reason we have `sigpipe` for
87+
// all platforms and not only Unix, is because std is not allowed to have `cfg`
88+
// directives as this high level. See the module docs in
89+
// `src/tools/tidy/src/pal.rs` for more info. On all other platforms, `sigpipe`
90+
// has a value, but its value is ignored.
91+
//
92+
// Even though it is an `u8`, it only ever has 3 values. These are documented in
93+
// `compiler/rustc_session/src/config/sigpipe.rs`.
7594
#[cfg_attr(test, allow(dead_code))]
76-
unsafe fn init(argc: isize, argv: *const *const u8) {
95+
unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
7796
unsafe {
78-
sys::init(argc, argv);
97+
sys::init(argc, argv, sigpipe);
7998

8099
let main_guard = sys::thread::guard::init();
81100
// Next, set up the current Thread with the guard information we just
@@ -107,6 +126,7 @@ fn lang_start_internal(
107126
main: &(dyn Fn() -> i32 + Sync + crate::panic::RefUnwindSafe),
108127
argc: isize,
109128
argv: *const *const u8,
129+
sigpipe: u8,
110130
) -> Result<isize, !> {
111131
use crate::{mem, panic};
112132
let rt_abort = move |e| {
@@ -124,7 +144,7 @@ fn lang_start_internal(
124144
// prevent libstd from accidentally introducing a panic to these functions. Another is from
125145
// user code from `main` or, more nefariously, as described in e.g. issue #86030.
126146
// SAFETY: Only called once during runtime initialization.
127-
panic::catch_unwind(move || unsafe { init(argc, argv) }).map_err(rt_abort)?;
147+
panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?;
128148
let ret_code = panic::catch_unwind(move || panic::catch_unwind(main).unwrap_or(101) as isize)
129149
.map_err(move |e| {
130150
mem::forget(e);
@@ -140,11 +160,16 @@ fn lang_start<T: crate::process::Termination + 'static>(
140160
main: fn() -> T,
141161
argc: isize,
142162
argv: *const *const u8,
163+
#[cfg(not(bootstrap))] sigpipe: u8,
143164
) -> isize {
144165
let Ok(v) = lang_start_internal(
145166
&move || crate::sys_common::backtrace::__rust_begin_short_backtrace(main).report().to_i32(),
146167
argc,
147168
argv,
169+
#[cfg(bootstrap)]
170+
2, // Temporary inlining of sigpipe::DEFAULT until bootstrap stops being special
171+
#[cfg(not(bootstrap))]
172+
sigpipe,
148173
);
149174
v
150175
}

std/src/sys/hermit/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub extern "C" fn __rust_abort() {
9898

9999
// SAFETY: must be called only once during runtime initialization.
100100
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
101-
pub unsafe fn init(argc: isize, argv: *const *const u8) {
101+
pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
102102
let _ = net::init();
103103
args::init(argc, argv);
104104
}

std/src/sys/sgx/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub mod locks {
4747

4848
// SAFETY: must be called only once during runtime initialization.
4949
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
50-
pub unsafe fn init(argc: isize, argv: *const *const u8) {
50+
pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {
5151
unsafe {
5252
args::init(argc, argv);
5353
}

std/src/sys/solid/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub mod locks {
5656

5757
// SAFETY: must be called only once during runtime initialization.
5858
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
59-
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
59+
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {}
6060

6161
// SAFETY: must be called only once during runtime cleanup.
6262
pub unsafe fn cleanup() {}

std/src/sys/unix/mod.rs

+30-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ pub mod thread_parker;
4444
pub mod time;
4545

4646
#[cfg(target_os = "espidf")]
47-
pub fn init(argc: isize, argv: *const *const u8) {}
47+
pub fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) {}
4848

4949
#[cfg(not(target_os = "espidf"))]
5050
// SAFETY: must be called only once during runtime initialization.
5151
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
52-
pub unsafe fn init(argc: isize, argv: *const *const u8) {
52+
// See `fn init()` in `library/std/src/rt.rs` for docs on `sigpipe`.
53+
pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
5354
// The standard streams might be closed on application startup. To prevent
5455
// std::io::{stdin, stdout,stderr} objects from using other unrelated file
5556
// resources opened later, we reopen standards streams when they are closed.
@@ -61,8 +62,9 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
6162
// want!
6263
//
6364
// Hence, we set SIGPIPE to ignore when the program starts up in order
64-
// to prevent this problem.
65-
reset_sigpipe();
65+
// to prevent this problem. Add `#[unix_sigpipe = "..."]` above `fn main()` to
66+
// alter this behavior.
67+
reset_sigpipe(sigpipe);
6668

6769
stack_overflow::init();
6870
args::init(argc, argv);
@@ -151,9 +153,31 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
151153
}
152154
}
153155

154-
unsafe fn reset_sigpipe() {
156+
unsafe fn reset_sigpipe(#[allow(unused_variables)] sigpipe: u8) {
155157
#[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "horizon")))]
156-
rtassert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR);
158+
{
159+
// We don't want to add this as a public type to libstd, nor do we
160+
// want to `include!` a file from the compiler (which would break
161+
// Miri and xargo for example), so we choose to duplicate these
162+
// constants from `compiler/rustc_session/src/config/sigpipe.rs`.
163+
// See the other file for docs. NOTE: Make sure to keep them in
164+
// sync!
165+
mod sigpipe {
166+
pub const INHERIT: u8 = 1;
167+
pub const SIG_IGN: u8 = 2;
168+
pub const SIG_DFL: u8 = 3;
169+
}
170+
171+
let handler = match sigpipe {
172+
sigpipe::INHERIT => None,
173+
sigpipe::SIG_IGN => Some(libc::SIG_IGN),
174+
sigpipe::SIG_DFL => Some(libc::SIG_DFL),
175+
_ => unreachable!(),
176+
};
177+
if let Some(handler) = handler {
178+
rtassert!(signal(libc::SIGPIPE, handler) != libc::SIG_ERR);
179+
}
180+
}
157181
}
158182
}
159183

std/src/sys/unsupported/common.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub mod memchr {
66

77
// SAFETY: must be called only once during runtime initialization.
88
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
9-
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
9+
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {}
1010

1111
// SAFETY: must be called only once during runtime cleanup.
1212
// NOTE: this is not guaranteed to run, for example when the program aborts.

std/src/sys/windows/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ cfg_if::cfg_if! {
4848

4949
// SAFETY: must be called only once during runtime initialization.
5050
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
51-
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
51+
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {
5252
stack_overflow::init();
5353

5454
// Normally, `thread::spawn` will call `Thread::set_name` but since this thread already

0 commit comments

Comments
 (0)