Jim Meyering wrote:
> If there is an EPIPE error, IMHO, close_stream must diagnose it.

Well, then here is an amended patch (just for coreutils: 0001 and 0002,
with a single ChangeLog entry) for the 'tee' program.

The idea behind the patch is as follows:

SIGPIPE is an optimization through which the readers of a pipe can communicate
to the writers of the pipe "save your effort, no one is interested in your
output any more".

Suppose "tee /some/pipe-or-socket" is invoked. The 'tee' process has one
input and two outputs (stdout and /some/pipe-or-socket). If any of the output
destinations are shut down, the 'tee' process will get a SIGPIPE signal during
the next write() call. According to POSIX, 'tee' must terminate in this
situation.

Viewing SIGPIPE as an optimization, a different behaviour is useful:
When the first of the output destinations is shut down, 'tee' continues
to forward the input to the second destination. When the second destination
is shut down as well, _then_ there's no point for more input, and 'tee'
can terminate itself.

The new option '-p' implements this behaviour.

The reason for patch 0002 is to handle the case that some output destination
shuts down immediately after the input is terminated. In this case you
don't want a diagnostic; it's better to let the 'tee' process die from a
SIGPIPE in this case. This is the purpose of the line
   signal (SIGPIPE, SIG_DFL);

Bruno

From af4129d7520276b678539299799a177856e3ecdd Mon Sep 17 00:00:00 2001
From: Bruno Haible <[EMAIL PROTECTED]>
Date: Sun, 31 Aug 2008 17:35:16 +0200
Subject: [PATCH]         New tee option -p.
         * src/tee.c (ignore_sigpipe): New variable.
         (long_options): Add option -p.
         (usage): Document option -p.
         (main): Handle option -p.
         (tee_files): When option -p is specified, ignore SIGPIPE write errors
         until the last open output descriptor is closed.
         * doc/coreutils.texi (tee invocation): Document option -p.

---
 doc/coreutils.texi |   11 +++++++++
 src/tee.c          |   60 +++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 3a04176..f81c35a 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -11369,6 +11369,17 @@ them.
 @opindex --ignore-interrupts
 Ignore interrupt signals.
 
[EMAIL PROTECTED] -p
[EMAIL PROTECTED] --ignore-sigpipe
[EMAIL PROTECTED] -p
[EMAIL PROTECTED] --ignore-sigpipe
+Ignore failed writes to pipes with no readers.  By default, when standard
+output or one of the given files refers to a pipe with no reading processes,
+the operating system will kill the @command{tee} process with signal
[EMAIL PROTECTED], thus terminating the output to the other files.  When the
+option @samp{-p} is specified, the @command{tee} process will continue
+writing to the other specified files.
+
 @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 4e46aab..42eb689 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -41,10 +41,14 @@ static bool append;
 /* If true, ignore interrupts. */
 static bool ignore_interrupts;
 
+/* If true, ignore failed writes to pipes with no readers. */
+static bool ignore_sigpipe;
+
 static struct option const long_options[] =
 {
   {"append", no_argument, NULL, 'a'},
   {"ignore-interrupts", no_argument, NULL, 'i'},
+  {"ignore-sigpipe", no_argument, NULL, 'p'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -64,6 +68,7 @@ Copy standard input to each FILE, and also to standard output.\n\
 \n\
   -a, --append              append to the given FILEs, do not overwrite\n\
   -i, --ignore-interrupts   ignore interrupt signals\n\
+  -p, --ignore-sigpipe      ignore failed writes to pipes with no readers\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -93,7 +98,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)
 	{
@@ -105,6 +110,10 @@ main (int argc, char **argv)
 	  ignore_interrupts = true;
 	  break;
 
+	case 'p':
+	  ignore_sigpipe = true;
+	  break;
+
 	case_GETOPT_HELP_CHAR;
 
 	case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
@@ -117,6 +126,9 @@ main (int argc, char **argv)
   if (ignore_interrupts)
     signal (SIGINT, SIG_IGN);
 
+  if (ignore_sigpipe)
+    signal (SIGPIPE, SIG_IGN);
+
   /* Do *not* warn if tee is given no file arguments.
      POSIX requires that it work when given no arguments.  */
 
@@ -135,6 +147,7 @@ static bool
 tee_files (int nfiles, const char **files)
 {
   FILE **descriptors;
+  size_t num_open_descriptors;
   char buffer[BUFSIZ];
   ssize_t bytes_read;
   int i;
@@ -161,6 +174,7 @@ tee_files (int nfiles, const char **files)
   descriptors[0] = stdout;
   files[0] = _("standard output");
   setvbuf (stdout, NULL, _IONBF, 0);
+  num_open_descriptors = 1;
 
   for (i = 1; i <= nfiles; i++)
     {
@@ -173,7 +187,10 @@ tee_files (int nfiles, const char **files)
 	  ok = false;
 	}
       else
-	setvbuf (descriptors[i], NULL, _IONBF, 0);
+	{
+	  setvbuf (descriptors[i], NULL, _IONBF, 0);
+	  num_open_descriptors++;
+	}
     }
 
   while (1)
@@ -192,9 +209,41 @@ tee_files (int nfiles, const char **files)
 	if (descriptors[i]
 	    && fwrite (buffer, bytes_read, 1, descriptors[i]) != 1)
 	  {
-	    error (0, errno, "%s", files[i]);
-	    descriptors[i] = NULL;
-	    ok = false;
+	    if (ignore_sigpipe && errno == EPIPE)
+	      {
+		/* Could not write to a pipe with no readers.
+		   Close the stream.  */
+		fclose (descriptors[i]);
+		/* Close also the underlying file descriptor, to avoid an
+		   error message from close_stdout.  */
+		if (fileno (descriptors[i]) >= 0)
+		  close (fileno (descriptors[i]));
+		descriptors[i] = NULL;
+		if (--num_open_descriptors == 0)
+		  {
+		    /* All output descriptors are closed.  We have no reason
+		       to continue reading input any more.  Raise a SIGPIPE
+		       and terminate.  */
+		    sigset_t sigpipe_set;
+
+		    sigemptyset (&sigpipe_set);
+		    sigaddset (&sigpipe_set, SIGPIPE);
+		    sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
+
+		    signal (SIGPIPE, SIG_DFL);
+
+		    raise (SIGPIPE);
+
+		    /* raise didn't terminate? So force a termination.  */
+		    goto done;
+		  }
+	      }
+	    else
+	      {
+		error (0, errno, "%s", files[i]);
+		descriptors[i] = NULL;
+		ok = false;
+	      }
 	  }
     }
 
@@ -213,6 +262,7 @@ tee_files (int nfiles, const char **files)
 	ok = false;
       }
 
+ done:
   free (descriptors);
 
   return ok;
-- 
1.5.6.3

From 24d31878d2b2dc614c31a0f23e6255d0e233c8d8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[EMAIL PROTECTED]>
Date: Wed, 3 Sep 2008 01:06:03 +0200
Subject: [PATCH] Restore the default SIGPIPE handler before exiting.

---
 src/tee.c |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/src/tee.c b/src/tee.c
index 42eb689..34c3891 100644
--- a/src/tee.c
+++ b/src/tee.c
@@ -133,6 +133,13 @@ main (int argc, char **argv)
      POSIX requires that it work when given no arguments.  */
 
   ok = tee_files (argc - optind, (const char **) &argv[optind]);
+
+  /* Restore the default SIGPIPE signal handling before exiting.  From this
+     point on, we prefer to get a SIGPIPE signal to an EPIPE error, since
+     close_stdout interprets EPIPE as a failure condition.  */
+  if (ignore_sigpipe)
+    signal (SIGPIPE, SIG_DFL);
+
   if (close (STDIN_FILENO) != 0)
     error (EXIT_FAILURE, errno, _("standard input"));
 
-- 
1.5.6.3

_______________________________________________
Bug-coreutils mailing list
Bug-coreutils@gnu.org
http://lists.gnu.org/mailman/listinfo/bug-coreutils

Reply via email to