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
[email protected]
http://lists.gnu.org/mailman/listinfo/bug-coreutils