From 1eddc93a3671c1385a08ce5c112399546ba09b65 Mon Sep 17 00:00:00 2001 From: Kanishk Sachan Date: Sat, 4 Jul 2026 09:43:46 +0100 Subject: [PATCH] head: fix panic on write error when printing a long -v filename header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When head prints the ==> <== header for -v/multi-file mode, the filename was written with print_verbatim(file).unwrap(). If stdout is a full device (ENOSPC) and the filename is longer than the internal LineWriter buffer (~1024 bytes), the write_all inside print_verbatim flushes mid-write, the write error is returned to .unwrap(), and the process panics (exit 134). A short filename stays entirely within the buffer, so its error only surfaces at the next ?-checked write — those fail gracefully. The asymmetry meant only long filenames triggered the panic. Fix: replace .unwrap() with ? so the error is propagated to the enclosing closure, which already returns UResult<()>, and is handled the same way as the surrounding write!/writeln! calls. Adds a regression test using /dev/full and a >1024-byte path built from repeated "./" segments that the OS resolves to /dev/null. Fixes #13264 --- src/uu/head/src/head.rs | 2 +- tests/by-util/test_head.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 955b3334dfd..0fc28fb6911 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -460,7 +460,7 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { writeln!(stdout)?; } write!(stdout, "==> ")?; - print_verbatim(file).unwrap(); + print_verbatim(file)?; writeln!(stdout, " <==")?; first = false; } diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 0f4e9dae5dd..61b3e1735ff 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -1002,6 +1002,35 @@ fn test_directory_header_with_multiple_files_zero_output() { /// GNU `head` prints the `==> name <==` header only after the file is /// successfully opened. A file that exists but cannot be opened (e.g. no read /// permission) must therefore produce only an error and no header. +/// Regression test for #13264: writing a long `-v` filename header to a +/// full device must not panic. The `print_verbatim` call used `.unwrap()` +/// which panicked when stdout's internal buffer was flushed mid-write and +/// returned an error. A short filename stays buffered and its error surfaces +/// at a later `?`-checked write, so we need a path longer than the buffer +/// (~1024 bytes) to trigger the flush inside `print_verbatim`. +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_verbose_long_filename_write_error_does_not_panic() { + use std::fs::OpenOptions; + + // Build a path > 1024 bytes that still resolves to an existing file. + // Repeated "./" segments are verbatim in the header but collapsed by the OS. + let long_path = format!("/dev/{}null", "./".repeat(512)); + assert!(long_path.len() > 1024); + + let dev_full = OpenOptions::new() + .write(true) + .open("/dev/full") + .unwrap(); + + // Must not panic (which would exit 134). Any non-zero exit code is fine. + new_ucmd!() + .arg("-v") + .arg(&long_path) + .set_stdout(dev_full) + .fails(); +} + #[cfg(unix)] #[test] fn test_unreadable_file_prints_no_header() {