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;