diff --git a/Cargo.toml b/Cargo.toml index ef673cb..cec0bb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,8 @@ unused_qualifications = "warn" all = { level = "warn", priority = -1 } cargo = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } +print_stdout = "warn" # restriction: forbid print/println macros +print_stderr = "warn" # restriction: forbid eprint/eprintln macros use_self = "warn" # nursery lint cargo_common_metadata = "allow" # 3240 multiple_crate_versions = "allow" # 2882 diff --git a/src/main.rs b/src/main.rs index 1852fb6..437126f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod cli; mod utils; use std::env::args_os; +use std::io::{self, Write}; use bumpalo::Bump; use clap::Parser as _; @@ -45,7 +46,11 @@ fn uu_main() -> Result<()> { return Ok(()); } }; - println!("---\n{ast}"); + if let Err(e) = writeln!(io::stdout(), "---\n{ast}") + && e.kind() != io::ErrorKind::BrokenPipe + { + exit_err(Some(format!("awk: error writing to standard output: {e}"))); + } dbg!(arena.chunk_capacity()); // for token in lex { diff --git a/src/utils.rs b/src/utils.rs index 721aa95..ae92746 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ // files that was distributed with this source code. use std::fmt::{Debug, Display}; +use std::io::{self, Write}; use std::panic::{UnwindSafe, catch_unwind, set_hook, take_hook}; use std::process::exit; @@ -73,7 +74,7 @@ pub fn exit_with(res: Result>, impl Display + Debug>) pub fn exit_err(err: Option) -> ! { if let Some(err) = err { - eprintln!("{err}"); + let _ = writeln!(io::stderr(), "{err}"); } exit(EXIT_FAILURE) } diff --git a/tests/by-util/test_awk.rs b/tests/by-util/test_awk.rs index 5ff71e2..9841b8c 100644 --- a/tests/by-util/test_awk.rs +++ b/tests/by-util/test_awk.rs @@ -14,3 +14,29 @@ fn print_first_field() { fn no_args_fails_code_one() { ucmd().fails_with_code(1); } + +// Regression test for issue #5: writing to /dev/full must not panic. +#[cfg(target_os = "linux")] +#[test] +fn write_to_dev_full_does_not_panic() { + use std::fs::OpenOptions; + use std::process::{Command, Stdio}; + + let dev_full = match OpenOptions::new().write(true).open("/dev/full") { + Ok(f) => f, + Err(_) => return, // /dev/full not available; skip. + }; + let output = Command::new(super::TESTS_BINARY) + .arg("BEGIN { print 1 }") + .stdout(Stdio::from(dev_full)) + .stderr(Stdio::piped()) + .output() + .expect("failed to spawn awk"); + // Must not panic (panic exits with code 2). + assert_ne!(output.status.code(), Some(2)); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !stderr.contains("panicked"), + "awk panicked on write to /dev/full: stderr={stderr}" + ); +}