From 7ea2b92c17a86ea68a1f55fa42e0931f1151ddc9 Mon Sep 17 00:00:00 2001 From: Pablo Garcia Date: Mon, 17 Aug 2026 22:59:36 +0200 Subject: [PATCH 1/3] head: propagate a write error from the -v filename header The two header writes around it use ?, but the filename went out via print_verbatim(file).unwrap(). A short name stays buffered and its error surfaces later at the checked flush; a name longer than the stdout buffer forces a flush mid-write, so the failure lands in the unwrap and aborts instead of being reported. Fixes #13264 --- src/uu/head/src/head.rs | 2 +- tests/by-util/test_head.rs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 61082204734..4a2c6f26952 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -461,7 +461,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 90a9829aeb8..5a760d53826 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -1074,6 +1074,29 @@ fn test_head_rejects_directory_through_symlink() { /// Regression for #11972: a symlink that points to a regular file must /// still be readable by head (the fd-based check must distinguish the /// fd's mode, not the symlink's). +#[test] +#[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/dev) not visible")] +fn test_verbose_header_write_error_long_filename() { + use std::fs::File; + + let dev_full = + File::create("/dev/full").expect("Failed to open /dev/full - test must run on Linux"); + + // A filename longer than the stdout buffer forces the header write to flush + // mid-write, so the failure surfaces inside the filename write rather than + // at the next checked one. + let long_path = format!("/dev/{}null", "./".repeat(512)); + + new_ucmd!() + .arg("-v") + .arg(long_path) + .set_stdout(dev_full) + .fails() + .code_is(1) + .stderr_contains("No space left on device"); +} + #[test] #[cfg(unix)] fn test_head_follows_symlink_to_regular_file() { From 6dfca6f8bd179f7941b24ece1a58fbaa19fea6d8 Mon Sep 17 00:00:00 2001 From: Pablo Garcia Date: Wed, 19 Aug 2026 17:16:49 +0200 Subject: [PATCH 2/3] chore: retrigger CI (canceled/timeout jobs) From 0b1bad8d841d58d83f82a0ccc46fc02f7ff3d92f Mon Sep 17 00:00:00 2001 From: Pablo Garcia Date: Wed, 19 Aug 2026 17:48:47 +0200 Subject: [PATCH 3/3] head: keep the #11972 regression comment with its test Move the #13887 verbose-header write-error regression test to after test_head_follows_symlink_to_regular_file so the #11972 doc comment stays attached to the test it documents (review feedback). --- tests/by-util/test_head.rs | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 5a760d53826..1ad450a1d4b 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -1050,6 +1050,31 @@ fn test_unreadable_file_prints_no_header() { .stderr_contains("cannot open 'unreadable' for reading: Permission denied"); } +/// Regression for #13887: writing the `==> filename <==` verbose header to a +/// full/closed stdout must surface the write error instead of panicking inside +/// `print_verbatim(...).unwrap()`. A filename longer than the stdout buffer +/// forces the header write to flush mid-write so the failure surfaces inside +/// the filename write rather than at the next checked one. +#[test] +#[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/dev) not visible")] +fn test_verbose_header_write_error_long_filename() { + use std::fs::File; + + let dev_full = + File::create("/dev/full").expect("Failed to open /dev/full - test must run on Linux"); + + let long_path = format!("/dev/{}null", "./".repeat(512)); + + new_ucmd!() + .arg("-v") + .arg(long_path) + .set_stdout(dev_full) + .fails() + .code_is(1) + .stderr_contains("No space left on device"); +} + /// Regression for #11972: head must reject directories detected on the /// open fd, not via a separate `Path::is_dir()` call. A symlink that /// resolves to a directory must still be rejected — verifying the fd @@ -1074,29 +1099,6 @@ fn test_head_rejects_directory_through_symlink() { /// Regression for #11972: a symlink that points to a regular file must /// still be readable by head (the fd-based check must distinguish the /// fd's mode, not the symlink's). -#[test] -#[cfg(target_os = "linux")] -#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/dev) not visible")] -fn test_verbose_header_write_error_long_filename() { - use std::fs::File; - - let dev_full = - File::create("/dev/full").expect("Failed to open /dev/full - test must run on Linux"); - - // A filename longer than the stdout buffer forces the header write to flush - // mid-write, so the failure surfaces inside the filename write rather than - // at the next checked one. - let long_path = format!("/dev/{}null", "./".repeat(512)); - - new_ucmd!() - .arg("-v") - .arg(long_path) - .set_stdout(dev_full) - .fails() - .code_is(1) - .stderr_contains("No space left on device"); -} - #[test] #[cfg(unix)] fn test_head_follows_symlink_to_regular_file() {