mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 06:44:35 +00:00
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:
commit
8c6ce6b91b
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
22
compiler/rustc_session/src/config/sigpipe.rs
Normal file
22
compiler/rustc_session/src/config/sigpipe.rs
Normal 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;
|
@ -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,
|
||||
|
@ -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),
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
54
src/doc/unstable-book/src/language-features/unix-sigpipe.md
Normal file
54
src/doc/unstable-book/src/language-features/unix-sigpipe.md
Normal 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
|
||||
```
|
@ -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); };
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#![feature(unix_sigpipe)]
|
||||
#![unix_sigpipe = "inherit"] //~ error: `unix_sigpipe` attribute cannot be used at crate level
|
||||
|
||||
fn main() {}
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
#![feature(unix_sigpipe)]
|
||||
|
||||
#[unix_sigpipe = "sig_ign"]
|
||||
#[unix_sigpipe = "inherit"] //~ error: multiple `unix_sigpipe` attributes
|
||||
fn main() {}
|
@ -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
|
||||
|
13
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs
Normal file
13
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-error.rs
Normal 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);
|
||||
}
|
14
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs
Normal file
14
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-inherit.rs
Normal 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);
|
||||
}
|
4
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs
Normal file
4
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#![feature(unix_sigpipe)]
|
||||
|
||||
#[unix_sigpipe(inherit)] //~ error: malformed `unix_sigpipe` attribute input
|
||||
fn main() {}
|
15
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr
Normal file
15
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-list.stderr
Normal 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
|
||||
|
@ -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() {}
|
@ -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
|
||||
|
@ -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() {}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
13
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs
Normal file
13
src/test/ui/attributes/unix_sigpipe/unix_sigpipe-sig_dfl.rs
Normal 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);
|
||||
}
|
@ -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 }
|
@ -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
|
||||
|
@ -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() {}
|
@ -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
|
||||
|
@ -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() {}
|
@ -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
|
||||
|
4
src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs
Normal file
4
src/test/ui/attributes/unix_sigpipe/unix_sigpipe.rs
Normal 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() {}
|
8
src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr
Normal file
8
src/test/ui/attributes/unix_sigpipe/unix_sigpipe.stderr
Normal 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
|
||||
|
4
src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs
Normal file
4
src/test/ui/feature-gates/feature-gate-unix_sigpipe.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#![crate_type = "bin"]
|
||||
|
||||
#[unix_sigpipe = "inherit"] //~ the `#[unix_sigpipe]` attribute is an experimental feature
|
||||
fn main () {}
|
12
src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr
Normal file
12
src/test/ui/feature-gates/feature-gate-unix_sigpipe.stderr
Normal 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`.
|
Loading…
Reference in New Issue
Block a user