coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 2/2] tee: add --write-error to control handling of closed pipes


From: Pádraig Brady
Subject: [PATCH 2/2] tee: add --write-error to control handling of closed pipes
Date: Tue, 17 Feb 2015 02:44:12 +0000

tee is very often used with pipes and this gives better control
when writing to them.  There are 3 classes of file descriptors
that tee can write to: files(1), pipes(2), and early close pipes(3).
Handling write errors to 1 & 2 is supported at present with the caveat
that failure writing to any pipe will terminate tee immediately.
Handling write errors to type 3 is not currently supported.
To improve the supported combinations we add these options:

 --write-error=warn
   Warn if error writing any output including pipes.
   Allows continued writing to still open files/pipes.
   Exit status is failure if any output had error.
 --write-error=warn-nopipe, -p
   Warn if error writing any output except pipes.
   Allows continued writing to still open files/pipes.
   Exit status is failure if any non pipe output had error.
 --write-error=exit
   Exit if error writing any output including pipes.
 --write-error=exit-nopipe
   Exit if error writing any output except pipes.

Use the "nopipe" variants when files are of types 1 and 3, otherwise
use the standard variants with types 1 and 2.  A caveat with the above
scheme is that a combination of pipe types (2 & 3) is not supported
robustly.  I.e. if you use the "nopipe" variants when using both type
2 and 3 pipes, then any "real" errors on type 2 pipes will not be
diagnosed.
  Note also a general issue with type 3 pipes that are not on tee's
stdout, is that shell constructs don't allow to distinguish early
close from real failures.  For example `tee >(head -n1) | grep -m1 ..`
can't distinguish between an error or an early close in "head" pipe,
while the fail on the grep part of the pipe is distinguished
independently from the resulting pipe errors.  This is a general
issue with the >() construct, rather than with tee itself.

* NEWS: Mention the new feature.
* doc/coreutils.texi (tee invocation): Describe the new option.
* src/tee.c (usage): Likewise.
(main): With --write-error ignore SIGPIPE, and handle
the various exit, diagnostics combinations.
* tests/misc/tee.sh: Tess all the new options.
Fixes http://bugs.gnu.org/11540
---
 NEWS               |  2 ++
 doc/coreutils.texi | 26 ++++++++++++++++++++++
 src/tee.c          | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 tests/misc/tee.sh  | 36 ++++++++++++++++++++++++++++++
 4 files changed, 126 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index b6795aa..0ccadde 100644
--- a/NEWS
+++ b/NEWS
@@ -58,6 +58,8 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   sync no longer ignores arguments, and syncs each specified file, or with the
   --file-system option, the file systems associated with each specified file.
 
+  tee accepts a new --write-error option to control operation with pipes.
+
 ** Changes in behavior
 
   df no longer suppresses separate exports of the same remote device, as
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index bb652ac..4f2935f 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -13200,6 +13200,32 @@ them.
 @opindex --ignore-interrupts
 Ignore interrupt signals.
 
+@item -p
+@itemx --write-error[=@var{mode}]
+@opindex -p
+@opindex --write-error
+Select the behavior with write errors on the outputs,
+where @var{mode} is one of the following:
+
+@table @samp
+@item warn
+Warn on error writing any output, including pipes.
+Writing is continued to still open files/pipes.
+Exit status indicates failure if any output has an error.
+
+@item warn-nopipe
+Warn on error writing any output, except pipes.
+Writing is continued to still open files/pipes.
+Exit status indicates failure if any non pipe output had an error.
+This is the default @var{mode} when not specified.
+
+@item exit
+Exit on error writing any output, including pipes.
+
+@item exit-nopipe
+Exit on error writing any output, except pipes.
+@end table
+
 @end table
 
 The @command{tee} command is useful when you happen to be transferring a large
diff --git a/src/tee.c b/src/tee.c
index bfe1b69..4f185d6 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -22,6 +22,7 @@
 #include <getopt.h>
 
 #include "system.h"
+#include "argmatch.h"
 #include "error.h"
 #include "fadvise.h"
 #include "stdio--.h"
@@ -43,15 +44,38 @@ static bool append;
 /* If true, ignore interrupts. */
 static bool ignore_interrupts;
 
+enum write_error
+  {
+    write_error_sigpipe,      /* traditional behavior, sigpipe enabled.  */
+    write_error_warn,         /* warn on EPIPE, but continue.  */
+    write_error_warn_nopipe,  /* ignore EPIPE, continue.  */
+    write_error_exit,         /* exit on any write error.  */
+    write_error_exit_nopipe   /* exit on any write error except EPIPE.  */
+  };
+
+static enum write_error write_error;
+
 static struct option const long_options[] =
 {
   {"append", no_argument, NULL, 'a'},
   {"ignore-interrupts", no_argument, NULL, 'i'},
+  {"write-error", optional_argument, NULL, 'p'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
 };
 
+static char const *const write_error_args[] =
+{
+  "warn", "warn-nopipe", "exit", "exit-nopipe", NULL
+};
+static enum write_error const write_error_types[] =
+{
+  write_error_warn, write_error_warn_nopipe,
+  write_error_exit, write_error_exit_nopipe
+};
+ARGMATCH_VERIFY (write_error_args, write_error_types);
+
 void
 usage (int status)
 {
@@ -66,12 +90,27 @@ Copy standard input to each FILE, and also to standard 
output.\n\
   -a, --append              append to the given FILEs, do not overwrite\n\
   -i, --ignore-interrupts   ignore interrupt signals\n\
 "), stdout);
+      fputs (_("\
+  -p, --write-error[=MODE]  behavior on write error.  See MODE details below\n\
+"), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       fputs (_("\
 \n\
 If a FILE is -, copy again to standard output.\n\
 "), stdout);
+      fputs (_("\
+\n\
+MODE determines behavior with write errors on the outputs:\n\
+  'warn'         diagnose errors writing to any output\n\
+  'warn-nopipe'  diagnose errors writing to any output not a pipe\n\
+  'exit'         exit on error writing to any output\n\
+  'exit-nopipe'  exit on error writing to any output not a pipe\n\
+The default MODE for the -p option is 'warn-nopipe'.\n\
+The default operation when --write-error is not specified, is to\n\
+exit immediately on error writing to a pipe, and diagnose errors\n\
+writing to non pipe outputs.\n\
+"), stdout);
       emit_ancillary_info (PROGRAM_NAME);
     }
   exit (status);
@@ -94,7 +133,7 @@ main (int argc, char **argv)
   append = false;
   ignore_interrupts = false;
 
-  while ((optc = getopt_long (argc, argv, "ai", long_options, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "aip", long_options, NULL)) != -1)
     {
       switch (optc)
         {
@@ -106,6 +145,14 @@ main (int argc, char **argv)
           ignore_interrupts = true;
           break;
 
+        case 'p':
+          if (optarg)
+            write_error = XARGMATCH ("--write-error", optarg, write_error_args,
+                                     write_error_types);
+          else
+            write_error = write_error_warn_nopipe;
+          break;
+
         case_GETOPT_HELP_CHAR;
 
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
@@ -118,6 +165,9 @@ main (int argc, char **argv)
   if (ignore_interrupts)
     signal (SIGINT, SIG_IGN);
 
+  if (write_error != write_error_sigpipe)
+    signal (SIGPIPE, SIG_IGN);
+
   /* Do *not* warn if tee is given no file arguments.
      POSIX requires that it work when given no arguments.  */
 
@@ -198,11 +248,20 @@ tee_files (int nfiles, const char **files)
         if (descriptors[i]
             && fwrite (buffer, bytes_read, 1, descriptors[i]) != 1)
           {
-            error (0, errno, "%s", files[i]);
+            int w_errno = errno;
+            bool fail = errno != EPIPE || (write_error == write_error_exit
+                                           || write_error == write_error_warn);
             if (descriptors[i] == stdout)
               clearerr (stdout); /* Avoid redundant close_stdout diagnostic.  
*/
+            if (fail)
+              {
+                error (write_error == write_error_exit
+                       || write_error == write_error_exit_nopipe,
+                       w_errno, "%s", files[i]);
+              }
             descriptors[i] = NULL;
-            ok = false;
+            if (fail)
+              ok = false;
             n_outputs--;
           }
 
diff --git a/tests/misc/tee.sh b/tests/misc/tee.sh
index 1043bec..79b1dc1 100755
--- a/tests/misc/tee.sh
+++ b/tests/misc/tee.sh
@@ -40,4 +40,40 @@ if test -w /dev/full && test -c /dev/full; then
   test $(wc -l < err) = 2 || { cat err; fail=1; }
 fi
 
+
+# Ensure tee honors --write-error modes
+mkfifo_or_skip_ fifo
+read_fifo() { timeout 10 dd count=1 if=fifo of=/dev/null status=none & }
+
+# Determine platform sigpipe exit status
+read_fifo
+yes >fifo
+pipe_status=$?
+
+# Default operation is to exit silently on SIGPIPE
+read_fifo
+yes | returns_ $pipe_status timeout 10 tee - 2>err >fifo || fail=1
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+
+# With -p, SIGPIPE is suppressed, exit 0 for EPIPE when all outputs finished
+read_fifo
+yes | timeout 10 tee -p - 2>err >fifo || fail=1
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+
+# With --write-error=warn, exit 1 for EPIPE when all outputs finished
+read_fifo
+yes | returns_ 1 timeout 10 tee --write-error=warn - 2>err >fifo || fail=1
+test $(wc -l < err) = 2 || { cat err; fail=1; }
+
+# With --write-error=exit, exit 1 immediately for EPIPE
+read_fifo
+yes | returns_ 1 timeout 10 tee --write-error=exit - 2>err >fifo || fail=1
+test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+# With --write-error=exit-nopipe, exit 0 for EPIPE
+read_fifo
+yes | timeout 10 tee --write-error=exit-nopipe - 2>err >fifo || fail=1
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+
+wait
 Exit $fail
-- 
2.1.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]