From 77a25efe1e0b8e62a54f94a022236bd6c9144e96 Mon Sep 17 00:00:00 2001 From: sehkone Date: Sun, 9 Aug 2026 11:55:06 +0900 Subject: [PATCH] Stop the landing test racing the shell's exit `PUT_FILE_SCRIPT` refuses a directory at the destination in its first four lines, before `cat > "$tmp"`. The shell is then gone with the read end of its stdin closed, while the test helper is writing the contents and unwrapping the result. Which of the two happens first is a race, and losing it fails the test with `BrokenPipe` from the harness rather than from anything the test is about. The refusal under test is the exit status and the message on stderr. A write nobody was ever going to read is not part of it, so an early close is taken as one outcome of a run and every other write error still panics. The pipe is closed explicitly before the wait, because the paths that do read stdin sit in `cat` until they see EOF. Closes #44 --- src/executor.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index 91fc08a..0ca2e51 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -2985,14 +2985,28 @@ exec "$@" /// permits — on exactly the terms [`current_meta`] uses for the /// native path. They are passed numerically so the script needs no /// passwd entry for the account CI happens to run as. + /// + /// A refused destination is refused before `cat > "$tmp"` — a + /// directory at the destination in the script's first four lines — + /// so on those paths the shell exits without ever reading stdin. + /// Whether these bytes reach the pipe buffer before that happens is + /// a race, and `BrokenPipe` is the side of it that says the script + /// refused early rather than that anything went wrong. The verdict + /// is the exit status and the stderr the caller asserts on, so it + /// is taken as one outcome of a run; every other write error still + /// panics. fn run_landing_script(dest: &Path, contents: &[u8], mode: u32) -> std::process::Output { let mut child = spawn_landing_script(dest, mode); - child - .stdin - .take() - .expect("stdin is piped") - .write_all(contents) - .expect("write contents"); + let mut stdin = child.stdin.take().expect("stdin is piped"); + match stdin.write_all(contents) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::BrokenPipe => {} + Err(error) => panic!("write contents: {error}"), + } + // Explicitly, and before the wait: the paths that do read stdin + // sit in `cat` until they see EOF, and holding this open past + // here would hang them. + drop(stdin); child.wait_with_output().expect("the script should finish") }