sicd: handle EPIPE and short writes at both write sites

write_all() loops until the full buffer is transferred — os.write's return value was
ignored at the payload write and in the pump loop, so a short write silently truncated
the stream. EPIPE at the payload write (child already dead) now reaps the child and
inherits its status instead of dying with a BrokenPipeError traceback; EPIPE in the pump
loop falls through to the same waitpid, which is standard `cmd | head` semantics.

Tests use deterministic fault injection rather than racing signals: a fork wrapper that
waits with waitid(WNOWAIT) so the child is provably dead but still reapable before the
parent writes, and an os.write cap of 7 bytes per call as the stand-in for a
signal-interrupted partial write.

Reviewed three times. Known gaps, tracked, NOT fixed here: signal-killed children still
collapse to exit 1 (no 128+WTERMSIG), and the pump loop's `except OSError: pass` is
broader than it should be.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Claude (noether)
2026-07-23 00:39:58 +02:00
parent 70103792df
commit 7f7f0c2ecb
2 changed files with 121 additions and 2 deletions
+20 -2
View File
@@ -38,6 +38,13 @@ def parse_netstring(data: bytes) -> tuple[bytes, bytes]:
return content, rest
def write_all(fd: int, data: bytes) -> None:
"""Write all bytes to fd, looping on short writes."""
while data:
n = os.write(fd, data)
data = data[n:]
def main() -> None:
# Read 5-byte preamble: magic + len32
try:
@@ -105,14 +112,25 @@ def main() -> None:
# Parent
os.close(r_fd)
# Write payload first
os.write(w_fd, payload)
try:
write_all(w_fd, payload)
except BrokenPipeError:
# Child died before we could write payload; reap and inherit its status
os.close(w_fd)
_, status = os.waitpid(pid, 0)
if os.WIFEXITED(status):
sys.exit(os.WEXITSTATUS(status))
else:
sys.exit(1)
# Then pump remaining stdin to child
try:
while True:
chunk = os.read(sys.stdin.fileno(), 65536)
if not chunk:
break
os.write(w_fd, chunk)
write_all(w_fd, chunk)
except BrokenPipeError:
pass
except OSError:
pass
os.close(w_fd)