>From 8935a5cfde598e1eebfc857ebbf9563ecabe490e Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Thu, 16 Aug 2018 13:00:14 +0200 Subject: [PATCH] tee: handle EAGAIN returned from fwrite() 'tee' expected the output file descriptors to operate in blocking mode but this assumption might be invalidated by other programs connected to the same terminal, as in the following example: $ telnet ... | tee log_file telnet calls ioctl(stdin, FIONBIO, [1]), which causes the O_NONBLOCK flag to be set on tee's output, which is connected to the same terminal. This patch has zero impact unless EAGAIN returns from fwrite(). In that case 'tee' waits for the underlying file descriptor to become writable again and then it writes the remaining data. --- src/tee.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/tee.c b/src/tee.c index dae0f1e50..e657a12ec 100644 --- a/src/tee.c +++ b/src/tee.c @@ -17,6 +17,8 @@ /* Mike Parker, Richard M. Stallman, and David MacKenzie */ #include +#include +#include #include #include #include @@ -176,6 +178,48 @@ main (int argc, char **argv) return ok ? EXIT_SUCCESS : EXIT_FAILURE; } +/* wrapper of fwrite() that handles EAGAIN properly in case the O_NONBLOCK + flag was set on the output file descriptor */ +static bool +write_buf (const char *buf, ssize_t size, FILE *f) +{ + for (;;) + { + struct pollfd pfd; + const size_t written = fwrite (buf, 1, size, f); + size -= written; + assert (size >= 0); + if (size <= 0) + /* everything written */ + return true; + + if (errno != EAGAIN) + /* non-recoverable write error */ + return false; + + /* update position in the write buffer */ + buf += written; + + /* obtain file descriptor we are writing to */ + pfd.fd = fileno (f); + if (pfd.fd == -1) + goto fail; + + /* wait for the file descriptor to become writable */ + pfd.events = POLLOUT; + pfd.revents = 0; + if (poll (&pfd, 1, -1) != 1) + goto fail; + if (!(pfd.revents & POLLOUT)) + goto fail; + } + +fail: + /* preserve the original errno value in case we failed to handle EAGAIN */ + errno = EAGAIN; + return false; +} + /* Copy the standard input into each of the NFILES files in FILES and into the standard output. As a side effect, modify FILES[-1]. Return true if successful. */ @@ -238,7 +282,7 @@ tee_files (int nfiles, char **files) Standard output is the first one. */ for (i = 0; i <= nfiles; i++) if (descriptors[i] - && fwrite (buffer, bytes_read, 1, descriptors[i]) != 1) + && !write_buf (buffer, bytes_read, descriptors[i])) { int w_errno = errno; bool fail = errno != EPIPE || (output_error == output_error_exit -- 2.17.1