about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-09-02 21:08:08 +0000
committerbors <bors@rust-lang.org>2022-09-02 21:08:08 +0000
commit8c6ce6b91b172f77c795a74bfeaf74b865146b3f (patch)
tree5e620d34e0d80a748c87d4daddf1e73a15c39e80
parent9ba169a73acfa9c9875b76eec09e9a91cc6246df (diff)
parent3810d4a3680d825448f5034262cc6ad63586b02e (diff)
downloadrust-8c6ce6b91b172f77c795a74bfeaf74b865146b3f.tar.gz
rust-8c6ce6b91b172f77c795a74bfeaf74b865146b3f.zip
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
-rw-r--r--compiler/rustc_codegen_cranelift/src/main_shim.rs15
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs16
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs1
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs2
-rw-r--r--compiler/rustc_passes/src/check_attr.rs1
-rw-r--r--compiler/rustc_passes/src/entry.rs56
-rw-r--r--compiler/rustc_session/src/config.rs12
-rw-r--r--compiler/rustc_session/src/config/sigpipe.rs22
-rw-r--r--compiler/rustc_span/src/symbol.rs4
-rw-r--r--compiler/rustc_typeck/src/lib.rs2
-rw-r--r--library/std/src/rt.rs31
-rw-r--r--library/std/src/sys/hermit/mod.rs2
-rw-r--r--library/std/src/sys/sgx/mod.rs2
-rw-r--r--library/std/src/sys/solid/mod.rs2
-rw-r--r--library/std/src/sys/unix/mod.rs36
-rw-r--r--library/std/src/sys/unsupported/common.rs2
-rw-r--r--library/std/src/sys/windows/mod.rs2
-rw-r--r--src/doc/unstable-book/src/language-features/unix-sigpipe.md54
-rw-r--r--src/test/ui/attributes/unix_sigpipe/auxiliary/sigpipe-utils.rs31
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.rs4
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.stderr13
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.rs5
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.stderr14
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs13
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs14
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs4
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr15
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.rs6
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.stderr8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.rs8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.stderr8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-not-used.rs9
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-only-feature.rs13
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-rustc_main.rs15
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs13
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.rs6
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.stderr8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.rs6
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.stderr8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.rs4
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.stderr8
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs4
-rw-r--r--src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr8
-rw-r--r--src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs4
-rw-r--r--src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr12
46 files changed, 482 insertions, 43 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/main_shim.rs b/compiler/rustc_codegen_cranelift/src/main_shim.rs
index c67b6e98b32..3c024a84d90 100644
--- a/compiler/rustc_codegen_cranelift/src/main_shim.rs
+++ b/compiler/rustc_codegen_cranelift/src/main_shim.rs
@@ -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
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 4c6be3f9108..6e482062383 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -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);
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index e09c3ccbc75..f71b3d59e2c 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -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.
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 0c88379d498..e359d50c4e9 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -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),
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 6ec5e9e113d..4e37da0dedb 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -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;
         };
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index f376da29bd9..100adedfb50 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -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,
diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs
index 7381019a620..cd10170d3ba 100644
--- a/compiler/rustc_passes/src/entry.rs
+++ b/compiler/rustc_passes/src/entry.rs
@@ -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() {
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index d4d29288f7f..7fa4294e5cf 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -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,
 }
 
diff --git a/compiler/rustc_session/src/config/sigpipe.rs b/compiler/rustc_session/src/config/sigpipe.rs
new file mode 100644
index 00000000000..a5c94118a47
--- /dev/null
+++ b/compiler/rustc_session/src/config/sigpipe.rs
@@ -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;
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index be954334313..64b919587e8 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -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,
diff --git a/compiler/rustc_typeck/src/lib.rs b/compiler/rustc_typeck/src/lib.rs
index 8e910441a5d..ac66819d283 100644
--- a/compiler/rustc_typeck/src/lib.rs
+++ b/compiler/rustc_typeck/src/lib.rs
@@ -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),
         _ => {}
     }
diff --git a/library/std/src/rt.rs b/library/std/src/rt.rs
index 663537a05fa..98f6cc7aa3e 100644
--- a/library/std/src/rt.rs
+++ b/library/std/src/rt.rs
@@ -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
 }
diff --git a/library/std/src/sys/hermit/mod.rs b/library/std/src/sys/hermit/mod.rs
index 60b7a973cc2..61da096ae16 100644
--- a/library/std/src/sys/hermit/mod.rs
+++ b/library/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/library/std/src/sys/sgx/mod.rs b/library/std/src/sys/sgx/mod.rs
index 696400670e0..b1d32929ecf 100644
--- a/library/std/src/sys/sgx/mod.rs
+++ b/library/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/library/std/src/sys/solid/mod.rs b/library/std/src/sys/solid/mod.rs
index 778a589d1b7..5867979a2a7 100644
--- a/library/std/src/sys/solid/mod.rs
+++ b/library/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/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs
index 3a375093099..c84e292eac1 100644
--- a/library/std/src/sys/unix/mod.rs
+++ b/library/std/src/sys/unix/mod.rs
@@ -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);
+            }
+        }
     }
 }
 
diff --git a/library/std/src/sys/unsupported/common.rs b/library/std/src/sys/unsupported/common.rs
index 4c9ade4a8c7..5cd9e57de19 100644
--- a/library/std/src/sys/unsupported/common.rs
+++ b/library/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/library/std/src/sys/windows/mod.rs b/library/std/src/sys/windows/mod.rs
index 340cae4066b..eab9b961279 100644
--- a/library/std/src/sys/windows/mod.rs
+++ b/library/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
diff --git a/src/doc/unstable-book/src/language-features/unix-sigpipe.md b/src/doc/unstable-book/src/language-features/unix-sigpipe.md
new file mode 100644
index 00000000000..aa39b6eb288
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/unix-sigpipe.md
@@ -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
+```
diff --git a/src/test/ui/attributes/unix_sigpipe/auxiliary/sigpipe-utils.rs b/src/test/ui/attributes/unix_sigpipe/auxiliary/sigpipe-utils.rs
new file mode 100644
index 00000000000..e8b4fe7ae52
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/auxiliary/sigpipe-utils.rs
@@ -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); };
+    }
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.rs
new file mode 100644
index 00000000000..d6d020c52b2
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.rs
@@ -0,0 +1,4 @@
+#![feature(unix_sigpipe)]
+#![unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute cannot be used at crate level
+
+fn main() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.stderr
new file mode 100644
index 00000000000..a1fb4d6787c
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-crate.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.rs
new file mode 100644
index 00000000000..294cb38526b
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.rs
@@ -0,0 +1,5 @@
+#![feature(unix_sigpipe)]
+
+#[unix_sigpipe = "sig_ign"]
+#[unix_sigpipe = "inherit"] //~ error: multiple `unix_sigpipe` attributes
+fn main() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.stderr
new file mode 100644
index 00000000000..2362c17a090
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-duplicates.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs
new file mode 100644
index 00000000000..0a42a5b5ef1
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs
new file mode 100644
index 00000000000..4f864807752
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs
new file mode 100644
index 00000000000..b5ebc07a043
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs
@@ -0,0 +1,4 @@
+#![feature(unix_sigpipe)]
+
+#[unix_sigpipe(inherit)] //~ error: malformed `unix_sigpipe` attribute input
+fn main() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr
new file mode 100644
index 00000000000..59a87e13918
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.rs
new file mode 100644
index 00000000000..cde6719fc9c
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.rs
@@ -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() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.stderr
new file mode 100644
index 00000000000..c4b81118c9f
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-main-fn.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.rs
new file mode 100644
index 00000000000..16f7276398e
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.rs
@@ -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() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.stderr
new file mode 100644
index 00000000000..a04f605edc2
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-non-root-main.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-not-used.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-not-used.rs
new file mode 100644
index 00000000000..100b4ce9f74
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-not-used.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-only-feature.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-only-feature.rs
new file mode 100644
index 00000000000..b5adc2e5572
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-only-feature.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-rustc_main.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-rustc_main.rs
new file mode 100644
index 00000000000..6befb9e9565
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-rustc_main.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs
new file mode 100644
index 00000000000..238c0d57a68
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs
@@ -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);
+}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.rs
new file mode 100644
index 00000000000..64fd5ec4f0e
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.rs
@@ -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 }
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.stderr
new file mode 100644
index 00000000000..2c9ce479b6c
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-start.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.rs
new file mode 100644
index 00000000000..a5e47cfebc8
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.rs
@@ -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() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.stderr
new file mode 100644
index 00000000000..c56ee60bb2e
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-struct.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.rs
new file mode 100644
index 00000000000..4ec25de00ec
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.rs
@@ -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() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.stderr
new file mode 100644
index 00000000000..a66e45aa210
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe-wrong.stderr
@@ -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
+
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs
new file mode 100644
index 00000000000..7bf1c7350c3
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs
@@ -0,0 +1,4 @@
+#![feature(unix_sigpipe)]
+
+#[unix_sigpipe] //~ error: valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl`
+fn main() {}
diff --git a/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr
new file mode 100644
index 00000000000..1b1eda825aa
--- /dev/null
+++ b/src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr
@@ -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
+
diff --git a/src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs b/src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs
new file mode 100644
index 00000000000..46dc3f6cc17
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs
@@ -0,0 +1,4 @@
+#![crate_type = "bin"]
+
+#[unix_sigpipe = "inherit"] //~ the `#[unix_sigpipe]` attribute is an experimental feature
+fn main () {}
diff --git a/src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr b/src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr
new file mode 100644
index 00000000000..cf3284467f7
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr
@@ -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`.