https://cgit.git.savannah.gnu.org/cgit/coreutils.git/commit/?id=0d6fcb99d691d920961938e61c43478566ef626e From 0d6fcb99d691d920961938e61c43478566ef626e Mon Sep 17 00:00:00 2001 From: Collin Funk Date: Mon, 18 May 2026 20:40:28 -0700 Subject: tee: fix infinite loop when write returns EAGAIN and short write errors * NEWS: Mention the bug fixes. * THANKS.in: Add Bernhard M. Wiedemann for reporting the bugs. * src/iopoll.c (close_wait): Remove function. (write_wait): Don't call wait_for_nonblocking_write if write is successful. Handle errors more robustly. * src/iopoll.h (close_wait): Remove declaration. * src/tee.c (tee_files): Use close instead of close_wait. * tests/tee/short-write.sh: New test for the bug. * tests/tee/write-eagain.sh: Likewise. * tests/local.mk (all_tests): Add the new tests. Fixes https://bugs.gnu.org/81060 --- src/iopoll.c | 59 +++++++++++++++++++++++++++++------------------ src/iopoll.h | 1 - src/tee.c | 2 +- 8 files changed, 111 insertions(+), 25 deletions(-) create mode 100755 tests/tee/short-write.sh create mode 100755 tests/tee/write-eagain.sh diff --git a/src/iopoll.c b/src/iopoll.c index de20bc8d9..0bd2d6bf6 100644 --- a/src/iopoll.c +++ b/src/iopoll.c @@ -194,17 +194,6 @@ wait_for_nonblocking_write (int fd) return true; } -/* wrapper for close() that also waits for FD if non blocking. */ - -extern bool -close_wait (int fd) -{ - while (wait_for_nonblocking_write (fd)) - ; - return close (fd) == 0; -} - - /* wrapper for write() that also waits for FD if non blocking. */ extern bool @@ -212,19 +201,43 @@ write_wait (int fd, void const *buffer, size_t size) { unsigned char const *buf = buffer; - while (true) + do { - ssize_t written = write (fd, buf, size); - if (written < 0) - written = 0; - - size -= written; - if (size <= 0) /* everything written */ - return true; - - if (! wait_for_nonblocking_write (fd)) - return false; + const ssize_t written = write (fd, buf, size); + /* POSIX says that calling write with SIZE of zero may detect and + return errors. If no error occurs, or write makes no attempt + to detect errors, then write returns zero with no other + results. write_fail will return successfully in this case. */ + if (written == 0) + { + if (size == 0) + return true; + else + { + /* If SIZE is greater than zero and write returns zero, + treat it as an error. Some buggy drivers behave this + way. See src/dd.c and Gnulib's lib/full-write.c for + more details. */ + errno = ENOSPC; + return false; + } + } - buf += written; + if (written < 0) + { + /* Return an error if write detected one with a SIZE of zero. + Otherwise, if SIZE is greater than zero, fail if it does + not become writable. */ + if (size == 0 || ! wait_for_nonblocking_write (fd)) + return false; + } + else + { + buf += written; + size -= written; + } } + while (0 < size); + + return true; } diff --git a/src/iopoll.h b/src/iopoll.h index 1711fdab9..a1561b9ff 100644 --- a/src/iopoll.h +++ b/src/iopoll.h @@ -5,5 +5,4 @@ int iopoll (int fdin, int fdout, bool block); bool iopoll_input_ok (int fdin); bool iopoll_output_ok (int fdout); -bool close_wait (int fd); bool write_wait (int fd, void const *buffer, size_t size); diff --git a/src/tee.c b/src/tee.c index 32a18e340..fba6ae089 100644 --- a/src/tee.c +++ b/src/tee.c @@ -329,7 +329,7 @@ tee_files (int nfiles, char **files, bool pipe_check) /* Close the files, but not standard output. */ for (int i = 1; i <= nfiles; i++) - if (0 <= descriptors[i] && ! close_wait (descriptors[i])) + if (0 <= descriptors[i] && close (descriptors[i]) < 0) { error (0, errno, "%s", quotef (files[i])); ok = false;