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