[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v3] tee: handle EAGAIN returned from fwrite/fclose()
From: |
Kamil Dudka |
Subject: |
[PATCH v3] tee: handle EAGAIN returned from fwrite/fclose() |
Date: |
Tue, 11 Sep 2018 15:05:20 +0200 |
'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/fclose().
In that case 'tee' waits for the underlying file descriptor to become
writable again and then it writes the remaining data.
---
src/tee.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 73 insertions(+), 2 deletions(-)
diff --git a/src/tee.c b/src/tee.c
index dae0f1e50..264560e81 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -17,6 +17,8 @@
/* Mike Parker, Richard M. Stallman, and David MacKenzie */
#include <config.h>
+#include <assert.h>
+#include <poll.h>
#include <sys/types.h>
#include <signal.h>
#include <getopt.h>
@@ -176,6 +178,75 @@ main (int argc, char **argv)
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
}
+/* On EAGAIN, wait for the underlying file descriptor to become writable.
+ Return true, if EAGAIN has been successfully handled. */
+static bool
+handle_eagain_on_write (FILE *f)
+{
+ struct pollfd pfd;
+ if (errno != EAGAIN)
+ /* non-recoverable write error */
+ return false;
+
+ /* obtain the 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;
+
+ /* successfully waited for the descriptor to become writable */
+ clearerr(f);
+ return true;
+
+fail:
+ errno = EAGAIN;
+ return false;
+}
+
+/* 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 (;;)
+ {
+ const size_t written = fwrite (buf, 1, size, f);
+ size -= written;
+ assert (size >= 0);
+ if (size <= 0)
+ /* everything written */
+ return true;
+
+ if (!handle_eagain_on_write (f))
+ return false;
+
+ /* update position in the write buffer */
+ buf += written;
+ }
+}
+
+/* wrapper of fclose() that handles EAGAIN properly in case the O_NONBLOCK
+ flag was set on the output file descriptor */
+static bool
+close_file (FILE *f)
+{
+ for (;;)
+ {
+ if (fclose (f) == 0)
+ return true;
+
+ if (!handle_eagain_on_write (f))
+ 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 +309,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
@@ -266,7 +337,7 @@ tee_files (int nfiles, char **files)
/* Close the files, but not standard output. */
for (i = 1; i <= nfiles; i++)
- if (descriptors[i] && fclose (descriptors[i]) != 0)
+ if (descriptors[i] && !close_file (descriptors[i]))
{
error (0, errno, "%s", quotef (files[i]));
ok = false;
--
2.17.1