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 https://github.com/rust-lang/rust/issues/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: https://github.com/rust-lang/rust/issues/97889
This commit is contained in:
bors 2022-09-02 21:08:08 +00:00
commit 8c6ce6b91b
46 changed files with 482 additions and 43 deletions

View File

@ -1,7 +1,7 @@
use rustc_hir::LangItem;
use rustc_middle::ty::subst::GenericArg;
use rustc_middle::ty::AssocKind;
use rustc_session::config::EntryFnType;
use rustc_session::config::{sigpipe, EntryFnType};
use rustc_span::symbol::Ident;
use crate::prelude::*;
@ -15,12 +15,12 @@ pub(crate) fn maybe_create_entry_wrapper(
is_jit: bool,
is_primary_cgu: bool,
) {
let (main_def_id, is_main_fn) = match tcx.entry_fn(()) {
let (main_def_id, (is_main_fn, sigpipe)) = match tcx.entry_fn(()) {
Some((def_id, entry_ty)) => (
def_id,
match entry_ty {
EntryFnType::Main => true,
EntryFnType::Start => false,
EntryFnType::Main { sigpipe } => (true, sigpipe),
EntryFnType::Start => (false, sigpipe::DEFAULT),
},
),
None => return,
@ -35,7 +35,7 @@ pub(crate) fn maybe_create_entry_wrapper(
return;
}
create_entry_fn(tcx, module, unwind_context, main_def_id, is_jit, is_main_fn);
create_entry_fn(tcx, module, unwind_context, main_def_id, is_jit, is_main_fn, sigpipe);
fn create_entry_fn(
tcx: TyCtxt<'_>,
@ -44,6 +44,7 @@ pub(crate) fn maybe_create_entry_wrapper(
rust_main_def_id: DefId,
ignore_lang_start_wrapper: bool,
is_main_fn: bool,
sigpipe: u8,
) {
let main_ret_ty = tcx.fn_sig(rust_main_def_id).output();
// Given that `main()` has no arguments,
@ -83,6 +84,7 @@ pub(crate) fn maybe_create_entry_wrapper(
bcx.switch_to_block(block);
let arg_argc = bcx.append_block_param(block, m.target_config().pointer_type());
let arg_argv = bcx.append_block_param(block, m.target_config().pointer_type());
let arg_sigpipe = bcx.ins().iconst(types::I8, sigpipe as i64);
let main_func_ref = m.declare_func_in_func(main_func_id, &mut bcx.func);
@ -143,7 +145,8 @@ pub(crate) fn maybe_create_entry_wrapper(
let main_val = bcx.ins().func_addr(m.target_config().pointer_type(), main_func_ref);
let func_ref = m.declare_func_in_func(start_func_id, &mut bcx.func);
let call_inst = bcx.ins().call(func_ref, &[main_val, arg_argc, arg_argv]);
let call_inst =
bcx.ins().call(func_ref, &[main_val, arg_argc, arg_argv, arg_sigpipe]);
bcx.inst_results(call_inst)[0]
} else {
// using user-defined start fn

View File

@ -389,15 +389,14 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let main_llfn = cx.get_fn_addr(instance);
let use_start_lang_item = EntryFnType::Start != entry_type;
let entry_fn = create_entry_fn::<Bx>(cx, main_llfn, main_def_id, use_start_lang_item);
let entry_fn = create_entry_fn::<Bx>(cx, main_llfn, main_def_id, entry_type);
return Some(entry_fn);
fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
cx: &'a Bx::CodegenCx,
rust_main: Bx::Value,
rust_main_def_id: DefId,
use_start_lang_item: bool,
entry_type: EntryFnType,
) -> Bx::Function {
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
// 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>>(
let i8pp_ty = cx.type_ptr_to(cx.type_i8p());
let (arg_argc, arg_argv) = get_argc_argv(cx, &mut bx);
let (start_fn, start_ty, args) = if use_start_lang_item {
let (start_fn, start_ty, args) = if let EntryFnType::Main { sigpipe } = entry_type {
let start_def_id = cx.tcx().require_lang_item(LangItem::Start, None);
let start_fn = cx.get_fn_addr(
ty::Instance::resolve(
@ -454,8 +453,13 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
.unwrap()
.unwrap(),
);
let start_ty = cx.type_func(&[cx.val_ty(rust_main), isize_ty, i8pp_ty], isize_ty);
(start_fn, start_ty, vec![rust_main, arg_argc, arg_argv])
let i8_ty = cx.type_i8();
let arg_sigpipe = bx.const_u8(sigpipe);
let start_ty =
cx.type_func(&[cx.val_ty(rust_main), isize_ty, i8pp_ty, i8_ty], isize_ty);
(start_fn, start_ty, vec![rust_main, arg_argc, arg_argv, arg_sigpipe])
} else {
debug!("using user-defined start fn");
let start_ty = cx.type_func(&[isize_ty, i8pp_ty], isize_ty);

View File

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

View File

@ -359,6 +359,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
),
// Entry point:
gated!(unix_sigpipe, Normal, template!(Word, NameValueStr: "inherit|sig_ign|sig_dfl"), ErrorFollowing, experimental!(unix_sigpipe)),
ungated!(start, Normal, template!(Word), WarnFollowing),
ungated!(no_start, CrateLevel, template!(Word), WarnFollowing),
ungated!(no_main, CrateLevel, template!(Word), WarnFollowing),

View File

@ -1314,7 +1314,7 @@ impl<'v> RootCollector<'_, 'v> {
/// the return type of `main`. This is not needed when
/// the user writes their own `start` manually.
fn push_extra_entry_roots(&mut self) {
let Some((main_def_id, EntryFnType::Main)) = self.entry_fn else {
let Some((main_def_id, EntryFnType::Main { .. })) = self.entry_fn else {
return;
};

View File

@ -2145,6 +2145,7 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
sym::automatically_derived,
sym::start,
sym::rustc_main,
sym::unix_sigpipe,
sym::derive,
sym::test,
sym::test_case,

View File

@ -1,11 +1,11 @@
use rustc_ast::{entry::EntryPointType, Attribute};
use rustc_ast::entry::EntryPointType;
use rustc_errors::struct_span_err;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
use rustc_hir::{ItemId, Node, CRATE_HIR_ID};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{DefIdTree, TyCtxt};
use rustc_session::config::{CrateType, EntryFnType};
use rustc_session::config::{sigpipe, CrateType, EntryFnType};
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol, DUMMY_SP};
@ -71,14 +71,12 @@ fn entry_point_type(ctxt: &EntryContext<'_>, id: ItemId, at_root: bool) -> Entry
}
}
fn err_if_attr_found(ctxt: &EntryContext<'_>, attrs: &[Attribute], sym: Symbol) {
fn err_if_attr_found(ctxt: &EntryContext<'_>, id: ItemId, sym: Symbol, details: &str) {
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym) {
ctxt.tcx
.sess
.struct_span_err(
attr.span,
&format!("`{}` attribute can only be used on functions", sym),
)
.struct_span_err(attr.span, &format!("`{}` attribute {}", sym, details))
.emit();
}
}
@ -87,14 +85,16 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
let at_root = ctxt.tcx.opt_local_parent(id.def_id) == Some(CRATE_DEF_ID);
match entry_point_type(ctxt, id, at_root) {
EntryPointType::None => (),
EntryPointType::None => {
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on `fn main()`");
}
_ if !matches!(ctxt.tcx.def_kind(id.def_id), DefKind::Fn) => {
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
err_if_attr_found(ctxt, attrs, sym::start);
err_if_attr_found(ctxt, attrs, sym::rustc_main);
err_if_attr_found(ctxt, id, sym::start, "can only be used on functions");
err_if_attr_found(ctxt, id, sym::rustc_main, "can only be used on functions");
}
EntryPointType::MainNamed => (),
EntryPointType::OtherMain => {
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on root `fn main()`");
ctxt.non_main_fns.push(ctxt.tcx.def_span(id.def_id));
}
EntryPointType::RustcMainAttr => {
@ -116,6 +116,7 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
}
}
EntryPointType::Start => {
err_if_attr_found(ctxt, id, sym::unix_sigpipe, "can only be used on `fn main()`");
if ctxt.start_fn.is_none() {
ctxt.start_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id())));
} else {
@ -136,8 +137,9 @@ fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
if let Some((def_id, _)) = visitor.start_fn {
Some((def_id.to_def_id(), EntryFnType::Start))
} else if let Some((def_id, _)) = visitor.attr_main_fn {
Some((def_id.to_def_id(), EntryFnType::Main))
} else if let Some((local_def_id, _)) = visitor.attr_main_fn {
let def_id = local_def_id.to_def_id();
Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx, def_id) }))
} else {
if let Some(main_def) = tcx.resolutions(()).main_def && let Some(def_id) = main_def.opt_fn_def_id() {
// non-local main imports are handled below
@ -161,13 +163,39 @@ fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId,
)
.emit();
}
return Some((def_id, EntryFnType::Main));
return Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx, def_id) }));
}
no_main_err(tcx, visitor);
None
}
}
fn sigpipe(tcx: TyCtxt<'_>, def_id: DefId) -> u8 {
if let Some(attr) = tcx.get_attr(def_id, sym::unix_sigpipe) {
match (attr.value_str(), attr.meta_item_list()) {
(Some(sym::inherit), None) => sigpipe::INHERIT,
(Some(sym::sig_ign), None) => sigpipe::SIG_IGN,
(Some(sym::sig_dfl), None) => sigpipe::SIG_DFL,
(_, Some(_)) => {
// Keep going so that `fn emit_malformed_attribute()` can print
// an excellent error message
sigpipe::DEFAULT
}
_ => {
tcx.sess
.struct_span_err(
attr.span,
"valid values for `#[unix_sigpipe = \"...\"]` are `inherit`, `sig_ign`, or `sig_dfl`",
)
.emit();
sigpipe::DEFAULT
}
}
} else {
sigpipe::DEFAULT
}
}
fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
let sp = tcx.def_span(CRATE_DEF_ID);
if *tcx.sess.parse_sess.reached_eof.borrow() {

View File

@ -36,6 +36,8 @@ use std::iter::{self, FromIterator};
use std::path::{Path, PathBuf};
use std::str::{self, FromStr};
pub mod sigpipe;
/// The different settings that the `-C strip` flag can have.
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub enum Strip {
@ -798,7 +800,15 @@ impl UnstableOptions {
// The type of entry function, so users can have their own entry functions
#[derive(Copy, Clone, PartialEq, Hash, Debug, HashStable_Generic)]
pub enum EntryFnType {
Main,
Main {
/// Specifies what to do with `SIGPIPE` before calling `fn main()`.
///
/// What values that are valid and what they mean must be in sync
/// across rustc and libstd, but we don't want it public in libstd,
/// so we take a bit of an unusual approach with simple constants
/// and an `include!()`.
sigpipe: u8,
},
Start,
}

View File

@ -0,0 +1,22 @@
//! NOTE: Keep these constants in sync with `library/std/src/sys/unix/mod.rs`!
/// Do not touch `SIGPIPE`. Use whatever the parent process uses.
#[allow(dead_code)]
pub const INHERIT: u8 = 1;
/// Change `SIGPIPE` to `SIG_IGN` so that failed writes results in `EPIPE`
/// that are eventually converted to `ErrorKind::BrokenPipe`.
#[allow(dead_code)]
pub const SIG_IGN: u8 = 2;
/// Change `SIGPIPE` to `SIG_DFL` so that the process is killed when trying
/// to write to a closed pipe. This is usually the desired behavior for CLI
/// apps that produce textual output that you want to pipe to other programs
/// such as `head -n 1`.
#[allow(dead_code)]
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;

View File

@ -823,6 +823,7 @@ symbols! {
infer_outlives_requirements,
infer_static_outlives_requirements,
inherent_associated_types,
inherit,
inlateout,
inline,
inline_const,
@ -1306,6 +1307,8 @@ symbols! {
should_panic,
shr,
shr_assign,
sig_dfl,
sig_ign,
simd,
simd_add,
simd_and,
@ -1524,6 +1527,7 @@ symbols! {
unit,
universal_impl_trait,
unix,
unix_sigpipe,
unlikely,
unmarked_api,
unpin,

View File

@ -444,7 +444,7 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
fn check_for_entry_fn(tcx: TyCtxt<'_>) {
match tcx.entry_fn(()) {
Some((def_id, EntryFnType::Main)) => check_main_fn_ty(tcx, def_id),
Some((def_id, EntryFnType::Main { .. })) => check_main_fn_ty(tcx, def_id),
Some((def_id, EntryFnType::Start)) => check_start_fn_ty(tcx, def_id),
_ => {}
}

View File

@ -72,10 +72,29 @@ 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 `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 <https://github.com/rust-lang/rust/issues/97889>
// 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) {
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 +126,7 @@ fn lang_start_internal(
main: &(dyn Fn() -> i32 + Sync + crate::panic::RefUnwindSafe),
argc: isize,
argv: *const *const u8,
sigpipe: u8,
) -> Result<isize, !> {
use crate::{mem, panic};
let rt_abort = move |e| {
@ -124,7 +144,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 +160,16 @@ fn lang_start<T: crate::process::Termination + 'static>(
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
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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() {}

View File

@ -44,12 +44,13 @@ 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) {
// 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
// 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) {
// 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 +153,31 @@ 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 `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 {
pub const INHERIT: u8 = 1;
pub const SIG_IGN: u8 = 2;
pub const SIG_DFL: u8 = 3;
}
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);
}
}
}
}

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,54 @@
# `unix_sigpipe`
The tracking issue for this feature is: [#97889]
[#97889]: https://github.com/rust-lang/rust/issues/97889
---
The `#[unix_sigpipe = "..."]` attribute on `fn main()` can be used to specify how libstd shall setup `SIGPIPE` on Unix platforms before invoking `fn main()`. This attribute is ignored on non-Unix targets. There are three variants:
* `#[unix_sigpipe = "inherit"]`
* `#[unix_sigpipe = "sig_dfl"]`
* `#[unix_sigpipe = "sig_ign"]`
## `#[unix_sigpipe = "inherit"]`
Leave `SIGPIPE` untouched before entering `fn main()`. Unless the parent process has changed the default `SIGPIPE` handler from `SIG_DFL` to something else, this will behave the same as `#[unix_sigpipe = "sig_dfl"]`.
## `#[unix_sigpipe = "sig_dfl"]`
Set the `SIGPIPE` handler to `SIG_DFL`. This will result in your program getting killed if it tries to write to a closed pipe. This is normally what you want if your program produces textual output.
### Example
```rust,no_run
#![feature(unix_sigpipe)]
#[unix_sigpipe = "sig_dfl"]
fn main() { loop { println!("hello world"); } }
```
```bash
% ./main | head -n 1
hello world
```
## `#[unix_sigpipe = "sig_ign"]`
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"]`.
### Example
```rust,no_run
#![feature(unix_sigpipe)]
#[unix_sigpipe = "sig_ign"]
fn main() { loop { println!("hello world"); } }
```
```bash
% ./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
```

View File

@ -0,0 +1,31 @@
#![feature(rustc_private)]
extern crate libc;
/// So tests don't have to bring libc in scope themselves
pub enum SignalHandler {
Ignore,
Default,
}
/// Helper to assert that [`libc::SIGPIPE`] has the expected signal handler.
pub fn assert_sigpipe_handler(expected_handler: SignalHandler) {
#[cfg(unix)]
#[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "horizon",
target_os = "android",
)))]
{
let prev = unsafe { libc::signal(libc::SIGPIPE, libc::SIG_IGN) };
let expected = match expected_handler {
SignalHandler::Ignore => libc::SIG_IGN,
SignalHandler::Default => libc::SIG_DFL,
};
assert_eq!(prev, expected);
// Unlikely to matter, but restore the old value anyway
unsafe { libc::signal(libc::SIGPIPE, prev); };
}
}

View File

@ -0,0 +1,4 @@
#![feature(unix_sigpipe)]
#![unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute cannot be used at crate level
fn main() {}

View File

@ -0,0 +1,13 @@
error: `unix_sigpipe` attribute cannot be used at crate level
--> $DIR/unix_sigpipe-crate.rs:2:1
|
LL | #![unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: perhaps you meant to use an outer attribute
|
LL | #[unix_sigpipe = "inherit"]
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error

View File

@ -0,0 +1,5 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe = "sig_ign"]
#[unix_sigpipe = "inherit"] //~ error: multiple `unix_sigpipe` attributes
fn main() {}

View File

@ -0,0 +1,14 @@
error: multiple `unix_sigpipe` attributes
--> $DIR/unix_sigpipe-duplicates.rs:4:1
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
|
note: attribute also specified here
--> $DIR/unix_sigpipe-duplicates.rs:3:1
|
LL | #[unix_sigpipe = "sig_ign"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,13 @@
// run-pass
// aux-build:sigpipe-utils.rs
#![feature(unix_sigpipe)]
#[unix_sigpipe = "sig_ign"]
fn main() {
extern crate sigpipe_utils;
// #[unix_sigpipe = "sig_ign"] is active, so the legacy behavior of ignoring
// SIGPIPE shall be in effect
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Ignore);
}

View File

@ -0,0 +1,14 @@
// run-pass
// aux-build:sigpipe-utils.rs
#![feature(unix_sigpipe)]
#[unix_sigpipe = "inherit"]
fn main() {
extern crate sigpipe_utils;
// #[unix_sigpipe = "inherit"] is active, so SIGPIPE shall NOT be ignored,
// instead the default handler shall be installed. (We assume that the
// process that runs these tests have the default handler.)
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Default);
}

View File

@ -0,0 +1,4 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe(inherit)] //~ error: malformed `unix_sigpipe` attribute input
fn main() {}

View File

@ -0,0 +1,15 @@
error: malformed `unix_sigpipe` attribute input
--> $DIR/unix_sigpipe-list.rs:3:1
|
LL | #[unix_sigpipe(inherit)]
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: the following are the possible correct uses
|
LL | #[unix_sigpipe = "inherit|sig_ign|sig_dfl"]
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LL | #[unix_sigpipe]
| ~~~~~~~~~~~~~~~
error: aborting due to previous error

View File

@ -0,0 +1,6 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute can only be used on `fn main()`
fn f() {}
fn main() {}

View File

@ -0,0 +1,8 @@
error: `unix_sigpipe` attribute can only be used on `fn main()`
--> $DIR/unix_sigpipe-non-main-fn.rs:3:1
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,8 @@
#![feature(unix_sigpipe)]
mod m {
#[unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute can only be used on root `fn main()`
fn main() {}
}
fn main() {}

View File

@ -0,0 +1,8 @@
error: `unix_sigpipe` attribute can only be used on root `fn main()`
--> $DIR/unix_sigpipe-non-root-main.rs:4:5
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,9 @@
// run-pass
// aux-build:sigpipe-utils.rs
fn main() {
extern crate sigpipe_utils;
// SIGPIPE shall be ignored since #[unix_sigpipe = "..."] is not used
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Ignore);
}

View File

@ -0,0 +1,13 @@
// run-pass
// aux-build:sigpipe-utils.rs
#![feature(unix_sigpipe)]
fn main() {
extern crate sigpipe_utils;
// Only #![feature(unix_sigpipe)] is enabled, not #[unix_sigpipe = "..."].
// This shall not change any behavior, so we still expect SIGPIPE to be
// ignored
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Ignore);
}

View File

@ -0,0 +1,15 @@
// run-pass
// aux-build:sigpipe-utils.rs
#![feature(unix_sigpipe)]
#![feature(rustc_attrs)]
#[unix_sigpipe = "sig_dfl"]
#[rustc_main]
fn rustc_main() {
extern crate sigpipe_utils;
// #[unix_sigpipe = "sig_dfl"] is active, so SIGPIPE handler shall be
// SIG_DFL. Note that we have a #[rustc_main], but it should still work.
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Default);
}

View File

@ -0,0 +1,13 @@
// run-pass
// aux-build:sigpipe-utils.rs
#![feature(unix_sigpipe)]
#[unix_sigpipe = "sig_dfl"]
fn main() {
extern crate sigpipe_utils;
// #[unix_sigpipe = "sig_dfl"] is active, so SIGPIPE shall NOT be ignored, instead
// the default handler shall be installed
sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Default);
}

View File

@ -0,0 +1,6 @@
#![feature(start)]
#![feature(unix_sigpipe)]
#[start]
#[unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute can only be used on `fn main()`
fn custom_start(argc: isize, argv: *const *const u8) -> isize { 0 }

View File

@ -0,0 +1,8 @@
error: `unix_sigpipe` attribute can only be used on `fn main()`
--> $DIR/unix_sigpipe-start.rs:5:1
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,6 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute can only be used on `fn main()`
struct S;
fn main() {}

View File

@ -0,0 +1,8 @@
error: `unix_sigpipe` attribute can only be used on `fn main()`
--> $DIR/unix_sigpipe-struct.rs:3:1
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,4 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe = "wrong"] //~ error: valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl`
fn main() {}

View File

@ -0,0 +1,8 @@
error: valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl`
--> $DIR/unix_sigpipe-wrong.rs:3:1
|
LL | #[unix_sigpipe = "wrong"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,4 @@
#![feature(unix_sigpipe)]
#[unix_sigpipe] //~ error: valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl`
fn main() {}

View File

@ -0,0 +1,8 @@
error: valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl`
--> $DIR/unix_sigpipe.rs:3:1
|
LL | #[unix_sigpipe]
| ^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,4 @@
#![crate_type = "bin"]
#[unix_sigpipe = "inherit"] //~ the `#[unix_sigpipe]` attribute is an experimental feature
fn main () {}

View File

@ -0,0 +1,12 @@
error[E0658]: the `#[unix_sigpipe]` attribute is an experimental feature
--> $DIR/feature-gate-unix_sigpipe.rs:3:1
|
LL | #[unix_sigpipe = "inherit"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #97889 <https://github.com/rust-lang/rust/issues/97889> for more information
= help: add `#![feature(unix_sigpipe)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.