Hi Jim,

On 2008-07-01 you replied to a proposed new 'tee' option that makes it ignore
SIGPIPE in 
<http://lists.gnu.org/archive/html/bug-coreutils/2008-07/msg00005.html>:
> Thanks for the patch, but I'm reluctant to use it
> in part because it covers only the write syscalls deriving
> from tee's explicit fwrite call.  It does not handle
> an EPIPE failure that comes of a close_stdout-induced
> write syscall, so you'd still get the offending diagnostics
> some of the time.

Right. close_stdout and more generally close_stream should be changed to
handle an EPIPE failure. An EPIPE errno value means that the kernel is telling
the program "This pipe/socket has no readers any more. You can stop writing
to it." But in close_stream we are already stopping the output to this
pipe/socket. There's no point in signalling an error about this situation.

Also, if the reader process terminated only a moment later, the fflush and
fclose would succeed, and the output would land in the kernel's pipe buffer
and be discarded at that place.

I'm therefore proposing
  - a gnulib patch to ignore EPIPE in close_stream,
  - a coreutils patch to add option '-p' to 'tee'. Revised so that when _all_
    output destinations of 'tee' have no readers any more, 'tee' terminates
    with SIGPIPE (like all reasonable filter programs that have only 1 output
    destination do).

Both patches are attached.

Bruno

2008-08-31  Bruno Haible  <[EMAIL PROTECTED]>

	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.

--- src/tee.c.orig	2008-08-31 17:22:12.000000000 +0200
+++ src/tee.c	2008-08-31 17:15:15.000000000 +0200
@@ -41,10 +41,14 @@
 /* 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 @@
 \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 @@
   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 @@
 	  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 @@
   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 @@
 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 @@
   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 @@
 	  ok = false;
 	}
       else
-	setvbuf (descriptors[i], NULL, _IONBF, 0);
+	{
+	  setvbuf (descriptors[i], NULL, _IONBF, 0);
+	  num_open_descriptors++;
+	}
     }
 
   while (1)
@@ -192,9 +209,41 @@
 	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 @@
 	ok = false;
       }
 
+ done:
   free (descriptors);
 
   return ok;
--- doc/coreutils.texi.orig	2008-08-31 17:22:12.000000000 +0200
+++ doc/coreutils.texi	2008-08-31 17:21:55.000000000 +0200
@@ -11369,6 +11369,17 @@
 @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
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

2008-08-31  Bruno Haible  <[EMAIL PROTECTED]>

	* lib/close-stream.c (close_stream): Ignore error EPIPE from fclose.

--- lib/close-stream.c.orig	2008-08-31 17:18:56.000000000 +0200
+++ lib/close-stream.c	2008-08-31 17:14:12.000000000 +0200
@@ -1,6 +1,6 @@
 /* Close a stream, with nicer error checking than fclose's.
 
-   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2006, 2007 Free
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2006, 2007, 2008 Free
    Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
@@ -57,14 +57,20 @@
   bool fclose_fail = (fclose (stream) != 0);
 
   /* Return an error indication if there was a previous failure or if
-     fclose failed, with one exception: ignore an fclose failure if
-     there was no previous error, no data remains to be flushed, and
-     fclose failed with EBADF.  That can happen when a program like cp
-     is invoked like this `cp a b >&-' (i.e., with standard output
-     closed) and doesn't generate any output (hence no previous error
-     and nothing to be flushed).  */
+     fclose failed, with two exceptions:
+       - Ignore an fclose failure if there was no previous error, no data
+	 remains to be flushed, and fclose failed with EBADF.  That can
+	 happen when a program like cp is invoked like this `cp a b >&-'
+	 (i.e., with standard output closed) and doesn't generate any
+	 output (hence no previous error and nothing to be flushed).
+       - Ignore an fclose failure due to EPIPE.  That can happen when a
+	 program blocks or ignores SIGPIPE, and the output pipe or socket
+	 has no readers now.  The EPIPE tells us that we should stop writing
+	 to this output.  That's what we are doing anyway here, in
+	 close_stream.  */
 
-  if (prev_fail || (fclose_fail && (some_pending || errno != EBADF)))
+  if (prev_fail
+      || (fclose_fail && (some_pending || errno != EBADF) && errno != EPIPE))
     {
       if (! fclose_fail)
 	errno = 0;
_______________________________________________
Bug-coreutils mailing list
Bug-coreutils@gnu.org
http://lists.gnu.org/mailman/listinfo/bug-coreutils

Reply via email to