1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
https://cgit.git.savannah.gnu.org/cgit/coreutils.git/commit/?id=0d6fcb99d691d920961938e61c43478566ef626e
From 0d6fcb99d691d920961938e61c43478566ef626e Mon Sep 17 00:00:00 2001
From: Collin Funk <collin.funk1@gmail.com>
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;
|