diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index f8bad5aa83..1bc256d673 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore nopipe +// spell-checker:ignore nopipe EFBIG Fsize SIGXFSZ XFSZ #![allow(clippy::borrow_as_ptr)] @@ -262,6 +262,37 @@ mod linux_only { use uutests::at_and_ucmd; use uutests::new_ucmd; + #[test] + fn test_tee_append_short_write_before_file_size_error() { + use rustix::process::Resource; + use uutests::util::TestScenario; + + const FILE_LIMIT: u64 = 4137; + + let content: Vec = (0..100_000).map(|n| (n % 251) as u8).collect(); + let scene = TestScenario::new("tee"); + let at = &scene.fixtures; + at.write_bytes("input", &content); + + // Append mode prevents splice from writing to the file, naturally + // exercising write_all: the file-size limit allows a prefix, then the + // retry fails. Ignore SIGXFSZ only in the child so it reports EFBIG + // without changing signal handling in other concurrently running tests. + scene + .cmd("sh") + .args(&["-c", "trap '' XFSZ; exec \"$@\"", "sh"]) + .arg(&scene.bin_path) + .args(&["tee", "-a", "limited"]) + .set_stdin(at.open("input")) + .set_stdout(Stdio::piped()) + .limit(Resource::Fsize, FILE_LIMIT, FILE_LIMIT) + .fails_with_code(1) + .stdout_is_bytes(&content) + .stderr_is("tee: limited: File too large\n"); + + assert_eq!(at.read_bytes("limited"), content[..FILE_LIMIT as usize]); + } + fn make_broken_pipe() -> std::io::PipeWriter { let (read, write) = std::io::pipe().expect("Failed to create pipe"); // Drop the read end of the pipe diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 53d3e9694d..7b9cfbf868 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -239,6 +239,12 @@ grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs -r "${SED} # we should not regress our project just to match what GNU is going. # So, do some changes on the fly +# strace only injects faults into traced calls. Include splice to force tee's +# stdout path to use read/write and exercise short-write retries. The +# replacement stops matching after the first edit, avoiding duplicate options. +sed -i 's/-e trace=write /-e trace=write,splice -e fault=splice /' \ + tests/tee/short-write.sh + sed -i -e "s|removed directory 'a/'|removed directory 'a'|g" tests/rm/v-slash.sh # 'rel' doesn't exist. Our implementation is giving a better message.