On Fri, 9 Dec 2022, Carl Edquist wrote:

On Fri, 9 Dec 2022, Arsen Arsenović wrote:

... Also, i suspect that the pipe_check option can be disabled if the _input_ is a regular file (or block device), since (i think) these always show up as ready for reading. (This check would only need to be done once for fd 0 at program start.)

Yes, there's no point poll-driving those, since it'll be always readable, up to EOF, and never hesitate to bring more data. It might just end up being a no-op if used in current form (but I haven't tried).

Well currently if the input is a regular file, poll() immediately returns POLLIN, along with any potential errors for the output. But yes the net effective behavior is the same as if the poll() call had been skipped.

In a way i don't think it's so necessary (as it's just an optimization, and it's only relevant if the user calls tee with -P/--pipe-check despite stdin being a regular file or block device), but anyway my attached patch (0002-tee-skip-pipe-checks-if-input-is-always-ready-for-re.patch) adds logic to disable pipe_check mode in this case. (Though in order not to change the SIGPIPE semantics, -P still implies -p.)


Carl
From fb3227b0e252b897f60053a6ffae48eb6ec19e62 Mon Sep 17 00:00:00 2001
From: Carl Edquist <edqu...@cs.wisc.edu>
Date: Sat, 10 Dec 2022 10:00:12 -0600
Subject: [PATCH 1/2] tee: only fstat outputs if pipe_check is active

* src/tee.c (tee_files): avoid populating out_is_pipe array if
pipe_check is not enabled.
---
 src/tee.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/tee.c b/src/tee.c
index 482967a..1f5c171 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -313,10 +313,12 @@ tee_files (int nfiles, char **files)
      In both arrays, entry 0 corresponds to standard output.  */
 
   descriptors = xnmalloc (nfiles + 1, sizeof *descriptors);
-  out_is_pipe = xnmalloc (nfiles + 1, sizeof *out_is_pipe);
+  if (pipe_check)
+    out_is_pipe = xnmalloc (nfiles + 1, sizeof *out_is_pipe);
   files--;
   descriptors[0] = stdout;
-  out_is_pipe[0] = is_pipe (fileno (descriptors[0]));
+  if (pipe_check)
+    out_is_pipe[0] = is_pipe (fileno (descriptors[0]));
   files[0] = bad_cast (_("standard output"));
   setvbuf (stdout, NULL, _IONBF, 0);
   n_outputs++;
@@ -325,7 +327,8 @@ tee_files (int nfiles, char **files)
     {
       /* Do not treat "-" specially - as mandated by POSIX.  */
       descriptors[i] = fopen (files[i], mode_string);
-      out_is_pipe[i] = is_pipe (fileno (descriptors[i]));
+      if (pipe_check)
+        out_is_pipe[i] = is_pipe (fileno (descriptors[i]));
       if (descriptors[i] == NULL)
         {
           error (output_error == output_error_exit
@@ -397,7 +400,8 @@ tee_files (int nfiles, char **files)
       }
 
   free (descriptors);
-  free (out_is_pipe);
+  if (pipe_check)
+    free (out_is_pipe);
 
   return ok;
 }
-- 
2.9.0

From 07853a6624ca805497ad5438dfc3092c3fdf41bc Mon Sep 17 00:00:00 2001
From: Carl Edquist <edqu...@cs.wisc.edu>
Date: Sat, 10 Dec 2022 10:21:48 -0600
Subject: [PATCH 2/2] tee: skip pipe checks if input is always ready for
 reading

Regular files and block devices are always ready for reading, even
at EOF where they just return 0 bytes.  Thus if the input is one of
these, it's never necessary to poll outputs, since we will never be
in a situation stuck waiting for input while all remaining outputs
are broken pipes.

Note that -P/--pipe-check still implies -p in this case, otherwise
a SIGPIPE would kill the process early.

* src/tee.c (always_ready): helper function to test an fd file type.
(main): disable pipe_check if STDIN is always ready for reading.
---
 src/tee.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/src/tee.c b/src/tee.c
index 1f5c171..f09324e 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -47,6 +47,7 @@
 #define IOPOLL_ERROR         -3
 
 static bool tee_files (int nfiles, char **files);
+static bool always_ready (int fd);
 
 /* If true, append to output files rather than truncating them. */
 static bool append;
@@ -184,6 +185,11 @@ main (int argc, char **argv)
   if (output_error != output_error_sigpipe)
     signal (SIGPIPE, SIG_IGN);
 
+  /* No need to poll outputs if input is always ready for reading.  */
+
+  if (pipe_check && always_ready (STDIN_FILENO))
+	  pipe_check = false;
+
   /* Do *not* warn if tee is given no file arguments.
      POSIX requires that it work when given no arguments.  */
 
@@ -276,13 +282,25 @@ fail_output(FILE **descriptors, char **files, int i)
   return fail;
 }
 
-/* Return true if fd refers to a pipe */
+/* Return true if fd refers to a pipe.  */
 
 static bool
 is_pipe(int fd)
 {
 	struct stat st;
-	return fstat (fd, &st) == 0 && S_ISFIFO(st.st_mode);
+	return fstat (fd, &st) == 0 && S_ISFIFO (st.st_mode);
+}
+
+
+/* Return true if fd is always ready for reading,
+   ie, it is a regular file or block device.  */
+
+static bool
+always_ready(int fd)
+{
+	struct stat st;
+	return fstat (fd, &st) == 0 && (S_ISREG (st.st_mode) ||
+                                    S_ISBLK (st.st_mode));
 }
 
 /* Copy the standard input into each of the NFILES files in FILES
-- 
2.9.0

Reply via email to