* 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
'shred' no longer blocks when opening a FIFO that has no readers.
[This bug was present in "the beginning".]
+ 'tee' no longer loops infinitely after writing all output if a write call sets
+ errno to EAGAIN.
+ [bug introduced in coreutils-9.11]
+
+ 'tee' no longer treats short writes as errors.
+ [bug introduced in coreutils-9.11]
+
'unexpand -t' no longer overflows a heap buffer, for tab values > SIZE_MAX/16.
[bug introduced in coreutils-9.11]
Bernd Melchers melchers@cis.fu-berlin.de
Bernhard Baehr bernhard.baehr@gmx.de
Bernhard Gabler bernhard@uni-koblenz.de
+Bernhard M. Wiedemann bwiedemann@suse.de
Bernhard Rosenkraenzer bero@redhat.de
Bert Deknuydt Bert.Deknuydt@esat.kuleuven.ac.be
Bert Wesarg bert.wesarg@googlemail.com
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
{
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;
}
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);
/* 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;
tests/tac/tac-2-nonseekable.sh \
tests/tail/tail.pl \
tests/tee/append.sh \
+ tests/tee/short-write.sh \
tests/tee/tee.sh \
+ tests/tee/write-eagain.sh \
tests/test/test-N.sh \
tests/test/test-diag.pl \
tests/test/test-file.sh \
--- /dev/null
+#!/bin/sh
+# Test 'tee' when a write is short.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tee
+require_strace_ write
+
+printf 'abcdef' >file1-exp || framework_failure_
+printf 'f' >out-exp || framework_failure_
+
+# In coreutils-9.11, a short write would be treated as an error.
+strace -qqq -o /dev/null --trace-fds=1 -e trace=write \
+ -e inject=write:retval=1:when=1..5 tee file1 >out 2>err <file1-exp || fail=1
+compare file1-exp file1 || fail=1
+compare out-exp out || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
--- /dev/null
+#!/bin/sh
+# Test 'tee' when a write fails with errno set to EAGAIN.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tee
+require_strace_ write
+
+# In coreutils-9.11 the following test would infinite loop.
+echo a >exp || framework_failure_
+timeout 10 strace -qqq -o /dev/null -e trace-fds=3 \
+ -e inject=write:error=EAGAIN:when=1 tee file1 <exp >out 2>err || fail=1
+compare exp file1 || fail=1
+compare exp out || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail