From e15af52a3d4007fa2eea54dd86bfaacca0e84fce Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 19 May 2024 22:40:39 -0400 Subject: [PATCH] scubainit: Restore the default SIGPIPE action Rust pre-main code may change the SIGPIPE disposition to ignore: * https://github.com/rust-lang/rust/issues/62569 * https://github.com/rust-lang/rust/issues/97889 We could use the nightly compiler flag -Zon-broken-pipe=inherit to disable this behavior. Instead, we take the simpler route and restore the default disposition ourselves. Fixes #254 --- scubainit/src/main.rs | 33 +++++++++++++++++++++++++++++++++ tests/test_main.py | 11 +++++++++++ 2 files changed, 44 insertions(+) diff --git a/scubainit/src/main.rs b/scubainit/src/main.rs index e10c2e5..0e26cb9 100644 --- a/scubainit/src/main.rs +++ b/scubainit/src/main.rs @@ -45,6 +45,8 @@ fn run_scubainit() -> Result<()> { setup_logging()?; info!("Looking Rusty!"); + restore_sigpipe_default()?; + let ctx = process_envvars()?; if let Some(ref user_info) = ctx.user_info { @@ -368,3 +370,34 @@ fn setup_logging() -> Result<()> { .verbosity(verbosity) .init()?) } + +// Restore the default SIGPIPE action. +// +// Rust pre-main code may change the SIGPIPE disposition to ignore: +// * https://github.com/rust-lang/rust/issues/62569 +// * https://github.com/rust-lang/rust/issues/97889 +// +// While scubainit doesn't rely on any specific SIGPIPE behavior, the dispositions of ignored +// signals are inherited through execve and Rust's pre-main code shouldn't influence the process +// executed by scuba. +// +// From man signal(7): +// +// Signal dispositions ... A child created via fork(2) inherits a copy of its parent's signal +// dispositions. During an execve(2), the dispositions of handled signals are reset to the +// default; the dispositions of ignored signals are left unchanged. +// +// This code may be unnecessary in the future if Rust's execve library function is changed to +// restore SIGPIPE. +fn restore_sigpipe_default() -> Result<()> { + // Set the SIGPIPE disposition to default (terminate). + // SAFETY: No signal handler is provided, only the default action, + // so no additional consideration is needed. + unsafe { + let result = libc::signal(libc::SIGPIPE, libc::SIG_DFL); + if result == libc::SIG_ERR { + return Err(std::io::Error::last_os_error().into()); + } + } + Ok(()) +} diff --git a/tests/test_main.py b/tests/test_main.py index 455c52a..bf3485f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -278,6 +278,17 @@ def test_redirect_stdin(self) -> None: assert_str_equalish(out, test_str) +class TestMainSignals(MainTest): + def test_sigpipe(self) -> None: + """Verify SIGPIPE is handled correctly""" + # See https://github.com/JonathonReinhart/scuba/issues/254 + SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}") + + out, err = run_scuba(["sh", "-c", "yes | echo abcd"]) + assert_str_equalish(out, "abcd") + assert_str_equalish(err, "") + + class TestMainUser(MainTest): def _test_user( self,