Skip to content

Commit 8c6ce6b

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 #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: #97889
2 parents 9ba169a + 3810d4a commit 8c6ce6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+482
-43
lines changed

compiler/rustc_codegen_cranelift/src/main_shim.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustc_hir::LangItem;
22
use rustc_middle::ty::subst::GenericArg;
33
use rustc_middle::ty::AssocKind;
4-
use rustc_session::config::EntryFnType;
4+
use rustc_session::config::{sigpipe, EntryFnType};
55
use rustc_span::symbol::Ident;
66

77
use crate::prelude::*;
@@ -15,12 +15,12 @@ pub(crate) fn maybe_create_entry_wrapper(
1515
is_jit: bool,
1616
is_primary_cgu: bool,
1717
) {
18-
let (main_def_id, is_main_fn) = match tcx.entry_fn(()) {
18+
let (main_def_id, (is_main_fn, sigpipe)) = match tcx.entry_fn(()) {
1919
Some((def_id, entry_ty)) => (
2020
def_id,
2121
match entry_ty {
22-
EntryFnType::Main => true,
23-
EntryFnType::Start => false,
22+
EntryFnType::Main { sigpipe } => (true, sigpipe),
23+
EntryFnType::Start => (false, sigpipe::DEFAULT),
2424
},
2525
),
2626
None => return,
@@ -35,7 +35,7 @@ pub(crate) fn maybe_create_entry_wrapper(
3535
return;
3636
}
3737

38-
create_entry_fn(tcx, module, unwind_context, main_def_id, is_jit, is_main_fn);
38+
create_entry_fn(tcx, module, unwind_context, main_def_id, is_jit, is_main_fn, sigpipe);
3939

4040
fn create_entry_fn(
4141
tcx: TyCtxt<'_>,
@@ -44,6 +44,7 @@ pub(crate) fn maybe_create_entry_wrapper(
4444
rust_main_def_id: DefId,
4545
ignore_lang_start_wrapper: bool,
4646
is_main_fn: bool,
47+
sigpipe: u8,
4748
) {
4849
let main_ret_ty = tcx.fn_sig(rust_main_def_id).output();
4950
// Given that `main()` has no arguments,
@@ -83,6 +84,7 @@ pub(crate) fn maybe_create_entry_wrapper(
8384
bcx.switch_to_block(block);
8485
let arg_argc = bcx.append_block_param(block, m.target_config().pointer_type());
8586
let arg_argv = bcx.append_block_param(block, m.target_config().pointer_type());
87+
let arg_sigpipe = bcx.ins().iconst(types::I8, sigpipe as i64);
8688

8789
let main_func_ref = m.declare_func_in_func(main_func_id, &mut bcx.func);
8890

@@ -143,7 +145,8 @@ pub(crate) fn maybe_create_entry_wrapper(
143145
let main_val = bcx.ins().func_addr(m.target_config().pointer_type(), main_func_ref);
144146

145147
let func_ref = m.declare_func_in_func(start_func_id, &mut bcx.func);
146-
let call_inst = bcx.ins().call(func_ref, &[main_val, arg_argc, arg_argv]);
148+
let call_inst =
149+
bcx.ins().call(func_ref, &[main_val, arg_argc, arg_argv, arg_sigpipe]);
147150
bcx.inst_results(call_inst)[0]
148151
} else {
149152
// using user-defined start fn

compiler/rustc_codegen_ssa/src/base.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,14 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
389389

390390
let main_llfn = cx.get_fn_addr(instance);
391391

392-
let use_start_lang_item = EntryFnType::Start != entry_type;
393-
let entry_fn = create_entry_fn::<Bx>(cx, main_llfn, main_def_id, use_start_lang_item);
392+
let entry_fn = create_entry_fn::<Bx>(cx, main_llfn, main_def_id, entry_type);
394393
return Some(entry_fn);
395394

396395
fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
397396
cx: &'a Bx::CodegenCx,
398397
rust_main: Bx::Value,
399398
rust_main_def_id: DefId,
400-
use_start_lang_item: bool,
399+
entry_type: EntryFnType,
401400
) -> Bx::Function {
402401
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
403402
// depending on whether the target needs `argc` and `argv` to be passed in.
@@ -442,7 +441,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
442441
let i8pp_ty = cx.type_ptr_to(cx.type_i8p());
443442
let (arg_argc, arg_argv) = get_argc_argv(cx, &mut bx);
444443

445-
let (start_fn, start_ty, args) = if use_start_lang_item {
444+
let (start_fn, start_ty, args) = if let EntryFnType::Main { sigpipe } = entry_type {
446445
let start_def_id = cx.tcx().require_lang_item(LangItem::Start, None);
447446
let start_fn = cx.get_fn_addr(
448447
ty::Instance::resolve(
@@ -454,8 +453,13 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
454453
.unwrap()
455454
.unwrap(),
456455
);
457-
let start_ty = cx.type_func(&[cx.val_ty(rust_main), isize_ty, i8pp_ty], isize_ty);
458-
(start_fn, start_ty, vec![rust_main, arg_argc, arg_argv])
456+
457+
let i8_ty = cx.type_i8();
458+
let arg_sigpipe = bx.const_u8(sigpipe);
459+
460+
let start_ty =
461+
cx.type_func(&[cx.val_ty(rust_main), isize_ty, i8pp_ty, i8_ty], isize_ty);
462+
(start_fn, start_ty, vec![rust_main, arg_argc, arg_argv, arg_sigpipe])
459463
} else {
460464
debug!("using user-defined start fn");
461465
let start_ty = cx.type_func(&[isize_ty, i8pp_ty], isize_ty);

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ declare_features! (
519519
/// Allows creation of instances of a struct by moving fields that have
520520
/// not changed from prior instances of the same struct (RFC #2528)
521521
(active, type_changing_struct_update, "1.58.0", Some(86555), None),
522+
/// Enables rustc to generate code that instructs libstd to NOT ignore SIGPIPE.
523+
(active, unix_sigpipe, "CURRENT_RUSTC_VERSION", Some(97889), None),
522524
/// Allows unsized fn parameters.
523525
(active, unsized_fn_params, "1.49.0", Some(48055), None),
524526
/// Allows unsized rvalues at arguments and parameters.

compiler/rustc_feature/src/builtin_attrs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
359359
),
360360

361361
// Entry point:
362+
gated!(unix_sigpipe, Normal, template!(Word, NameValueStr: "inherit|sig_ign|sig_dfl"), ErrorFollowing, experimental!(unix_sigpipe)),
362363
ungated!(start, Normal, template!(Word), WarnFollowing),
363364
ungated!(no_start, CrateLevel, template!(Word), WarnFollowing),
364365
ungated!(no_main, CrateLevel, template!(Word), WarnFollowing),

compiler/rustc_monomorphize/src/collector.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ impl<'v> RootCollector<'_, 'v> {
13141314
/// the return type of `main`. This is not needed when
13151315
/// the user writes their own `start` manually.
13161316
fn push_extra_entry_roots(&mut self) {
1317-
let Some((main_def_id, EntryFnType::Main)) = self.entry_fn else {
1317+
let Some((main_def_id, EntryFnType::Main { .. })) = self.entry_fn else {
13181318
return;
13191319
};
13201320

compiler/rustc_passes/src/check_attr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,7 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
21452145
sym::automatically_derived,
21462146
sym::start,
21472147
sym::rustc_main,
2148+
sym::unix_sigpipe,
21482149
sym::derive,
21492150
sym::test,
21502151
sym::test_case,

compiler/rustc_passes/src/entry.rs

+42-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use rustc_ast::{entry::EntryPointType, Attribute};
1+
use rustc_ast::entry::EntryPointType;
22
use rustc_errors::struct_span_err;
33
use rustc_hir::def::DefKind;
44
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
55
use rustc_hir::{ItemId, Node, CRATE_HIR_ID};
66
use rustc_middle::ty::query::Providers;
77
use rustc_middle::ty::{DefIdTree, TyCtxt};
8-
use rustc_session::config::{CrateType, EntryFnType};
8+
use rustc_session::config::{sigpipe, CrateType, EntryFnType};
99
use rustc_session::parse::feature_err;
1010
use rustc_span::symbol::sym;
1111
use rustc_span::{Span, Symbol, DUMMY_SP};
@@ -71,14 +71,12 @@ fn entry_point_type(ctxt: &EntryContext<'_>, id: ItemId, at_root: bool) -> Entry
7171
}
7272
}
7373

74-
fn err_if_attr_found(ctxt: &EntryContext<'_>, attrs: &[Attribute], sym: Symbol) {
74+
fn err_if_attr_found(ctxt: &EntryContext<'_>, id: ItemId, sym: Symbol, details: &str) {
75+
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
7576
if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym) {
7677
ctxt.tcx
7778
.sess
78-
.struct_span_err(
79-
attr.span,
80-
&format!("`{}` attribute can only be used on functions", sym),
81-
)
79+
.struct_span_err(attr.span, &format!("`{}` attribute {}", sym, details))
8280
.emit();
8381
}
8482
}
@@ -87,14 +85,16 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
8785
let at_root = ctxt.tcx.opt_local_parent(id.def_id) == Some(CRATE_DEF_ID);
8886

8987
match entry_point_type(ctxt, id, at_root) {
90-
EntryPointType::None => (),
88+
EntryPointType::None => {
89+
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on `fn main()`");
90+
}
9191
_ if !matches!(ctxt.tcx.def_kind(id.def_id), DefKind::Fn) => {
92-
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
93-
err_if_attr_found(ctxt, attrs, sym::start);
94-
err_if_attr_found(ctxt, attrs, sym::rustc_main);
92+
err_if_attr_found(ctxt, id, sym::start, "can only be used on functions");
93+
err_if_attr_found(ctxt, id, sym::rustc_main, "can only be used on functions");
9594
}
9695
EntryPointType::MainNamed => (),
9796
EntryPointType::OtherMain => {
97+
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on root `fn main()`");
9898
ctxt.non_main_fns.push(ctxt.tcx.def_span(id.def_id));
9999
}
100100
EntryPointType::RustcMainAttr => {
@@ -116,6 +116,7 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
116116
}
117117
}
118118
EntryPointType::Start => {
119+
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on `fn main()`");
119120
if ctxt.start_fn.is_none() {
120121
ctxt.start_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id())));
121122
} else {
@@ -136,8 +137,9 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
136137
fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
137138
if let Some((def_id, _)) = visitor.start_fn {
138139
Some((def_id.to_def_id(), EntryFnType::Start))
139-
} else if let Some((def_id, _)) = visitor.attr_main_fn {
140-
Some((def_id.to_def_id(), EntryFnType::Main))
140+
} else if let Some((local_def_id, _)) = visitor.attr_main_fn {
141+
let def_id = local_def_id.to_def_id();
142+
Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx, def_id) }))
141143
} else {
142144
if let Some(main_def) = tcx.resolutions(()).main_def && let Some(def_id) = main_def.opt_fn_def_id() {
143145
// non-local main imports are handled below
@@ -161,13 +163,39 @@ fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId,
161163
)
162164
.emit();
163165
}
164-
return Some((def_id, EntryFnType::Main));
166+
return Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx, def_id) }));
165167
}
166168
no_main_err(tcx, visitor);
167169
None
168170
}
169171
}
170172

173+
fn sigpipe(tcx: TyCtxt<'_>, def_id: DefId) -> u8 {
174+
if let Some(attr) = tcx.get_attr(def_id, sym::unix_sigpipe) {
175+
match (attr.value_str(), attr.meta_item_list()) {
176+
(Some(sym::inherit), None) => sigpipe::INHERIT,
177+
(Some(sym::sig_ign), None) => sigpipe::SIG_IGN,
178+
(Some(sym::sig_dfl), None) => sigpipe::SIG_DFL,
179+
(_, Some(_)) => {
180+
// Keep going so that `fn emit_malformed_attribute()` can print
181+
// an excellent error message
182+
sigpipe::DEFAULT
183+
}
184+
_ => {
185+
tcx.sess
186+
.struct_span_err(
187+
attr.span,
188+
"valid values for `#[unix_sigpipe = \"...\"]` are `inherit`, `sig_ign`, or `sig_dfl`",
189+
)
190+
.emit();
191+
sigpipe::DEFAULT
192+
}
193+
}
194+
} else {
195+
sigpipe::DEFAULT
196+
}
197+
}
198+
171199
fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
172200
let sp = tcx.def_span(CRATE_DEF_ID);
173201
if *tcx.sess.parse_sess.reached_eof.borrow() {

compiler/rustc_session/src/config.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ use std::iter::{self, FromIterator};
3636
use std::path::{Path, PathBuf};
3737
use std::str::{self, FromStr};
3838

39+
pub mod sigpipe;
40+
3941
/// The different settings that the `-C strip` flag can have.
4042
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
4143
pub enum Strip {
@@ -798,7 +800,15 @@ impl UnstableOptions {
798800
// The type of entry function, so users can have their own entry functions
799801
#[derive(Copy, Clone, PartialEq, Hash, Debug, HashStable_Generic)]
800802
pub enum EntryFnType {
801-
Main,
803+
Main {
804+
/// Specifies what to do with `SIGPIPE` before calling `fn main()`.
805+
///
806+
/// What values that are valid and what they mean must be in sync
807+
/// across rustc and libstd, but we don't want it public in libstd,
808+
/// so we take a bit of an unusual approach with simple constants
809+
/// and an `include!()`.
810+
sigpipe: u8,
811+
},
802812
Start,
803813
}
804814

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! NOTE: Keep these constants in sync with `library/std/src/sys/unix/mod.rs`!
2+
3+
/// Do not touch `SIGPIPE`. Use whatever the parent process uses.
4+
#[allow(dead_code)]
5+
pub const INHERIT: u8 = 1;
6+
7+
/// Change `SIGPIPE` to `SIG_IGN` so that failed writes results in `EPIPE`
8+
/// that are eventually converted to `ErrorKind::BrokenPipe`.
9+
#[allow(dead_code)]
10+
pub const SIG_IGN: u8 = 2;
11+
12+
/// Change `SIGPIPE` to `SIG_DFL` so that the process is killed when trying
13+
/// to write to a closed pipe. This is usually the desired behavior for CLI
14+
/// apps that produce textual output that you want to pipe to other programs
15+
/// such as `head -n 1`.
16+
#[allow(dead_code)]
17+
pub const SIG_DFL: u8 = 3;
18+
19+
/// `SIG_IGN` has been the Rust default since 2014. See
20+
/// <https://github.com/rust-lang/rust/issues/62569>.
21+
#[allow(dead_code)]
22+
pub const DEFAULT: u8 = SIG_IGN;

compiler/rustc_span/src/symbol.rs

+4
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ symbols! {
823823
infer_outlives_requirements,
824824
infer_static_outlives_requirements,
825825
inherent_associated_types,
826+
inherit,
826827
inlateout,
827828
inline,
828829
inline_const,
@@ -1306,6 +1307,8 @@ symbols! {
13061307
should_panic,
13071308
shr,
13081309
shr_assign,
1310+
sig_dfl,
1311+
sig_ign,
13091312
simd,
13101313
simd_add,
13111314
simd_and,
@@ -1524,6 +1527,7 @@ symbols! {
15241527
unit,
15251528
universal_impl_trait,
15261529
unix,
1530+
unix_sigpipe,
15271531
unlikely,
15281532
unmarked_api,
15291533
unpin,

compiler/rustc_typeck/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
444444

445445
fn check_for_entry_fn(tcx: TyCtxt<'_>) {
446446
match tcx.entry_fn(()) {
447-
Some((def_id, EntryFnType::Main)) => check_main_fn_ty(tcx, def_id),
447+
Some((def_id, EntryFnType::Main { .. })) => check_main_fn_ty(tcx, def_id),
448448
Some((def_id, EntryFnType::Start)) => check_start_fn_ty(tcx, def_id),
449449
_ => {}
450450
}

library/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
}

0 commit comments

Comments
 (0)