[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: EPIPE in standard utils ([issue41])

From: Tom Lord
Subject: Re: EPIPE in standard utils ([issue41])
Date: Tue, 4 Mar 2003 13:39:44 -0800 (PST)

    > In my opinion, it is incorrect for a shell utility (such as sed or any
    > of the textutils) to respond to EPIPE for stdout (and, arguably, any
    > other output file) by

    >       1) printing a message on stderr
    >       2) exiting with non-0 status

    It would be quite a nuisance to do anything else,
    so the question is, is there sufficient reason to bother?
    What harm results if the program does this?

This is tricky to answer because a bunch of issues have gotten jumbled
up.   Please bear with me.

Let's call the behavior I proposed above the "non-error EPIPE"
proposal.  I'll expand below on four points:

1) Eggert's reply sent me paging through various kernel sources and docs
   and cleared up the bug in my thinking (I had a misconception about
   when SIGPIPE could be delivered).   To solve the immediate bug
   that some of my users have reported does _not_ require the
   non-error EPIPE behavior:  my bad.

2) Another proposal, called it the "explicit SIGPIPE SIG_DFL"
   proposal, says that the various utils should explicitly set a
   SIG_DFL handler for SIGPIPE on start-up if they are written in a
   style that assumes SIGPIPE should be received and cause an
   immediate, non-0 exit.  This would be a comparatively easy change to
   make and it would mean that the utils are more defensively
   programmed, at least from the traditional unix perspetive.

3) The timing of signal delivery in POSIX is weakly specified.  
   If certain additional assumptions about signal delivery timing
   are not made, beyond what the standard says, then the 
   non-error EPIPE proposal is important.

4) SIGPIPE and EPIPE handling in traditional unix is a poorly designed
   corner case that interacts badly with traditional shell design.
   The non-error EPIPE proposal (and equivalent variations on it) are
   the only robust way I see to handle programming with pipes, and
   thus should still be considered for adoption as a convention for
   GNU systems.

* The originally reported bug and its implications

  The bug that gave rise to the thread was that on some systems, shell
  scripts containing idioms such as:

        if test -z "`sed .... | head -1`" ; then

  were sometimes (not even consistently) generating (obviously
  undesirable) stderr output from sed of the form:

        sed: Couldn't close {standard output}: Broken pipe

  which is the generic response of sed to any error in ck_fclose.  The
  glitchiness of the bug reinforced my misconception about signal

  Similar bug reports have been generated regarding other utils besides

  But, apparently, either the shell executing this code is sometimes
  execing sed with SIGPIPE ignored (which is not the right thing to
  do) -- or the users reporting this are using a buggy kernel.  So,
  I was wrong -- these bug reports don't provide a reason to implement
  the non-error EPIPE proposal.

* the explicit SIGPIPE SIG_DFL proposal

   The various utils could arguably be improved if they explicitly
   restore SIGPIPE to SIG_DFL when they start.  The argument is
   that programs execing them shouldn't be assuming that they
   handle EPIPE gracefully and thus have no legitimate reason for
   execing them with SIGPIPE ignored; the utils are written with
   the assumption that SIGPIPE has SIG_DFL behavior but have at
   best weak legitimate reason to assume it has that handler when
   they start.  Thus, explicitly restoring SIGPIPE to SIG_DFL is
   just defensive programming (it would have prevented the originally 
   reported bugs, even if they are caused by a buggy shell).

* the formal guarantees of signal delivery are weak

  POSIX appears to be deliberately non-specific about the timing of
  signals (IEEE 1003.1-1990 B. "Signal Generation and
  Delivery"), though the idea of an "imprecise SIGPIPE" (one that's
  delivered _after_ later system calls) was not anticipated and
  presumably not intended.  Rather everyone (except me, until recently
  :-) assumes that all pending signals generated by a system call will
  be delivered no later than during the return from that system call.
  If that's not right -- if delivery of non-blocked, non-ignored
  SIGPIPE _can_ somehow wind up being delayed past the point at which
  a subsequent system call has effect, then I'd have to go back to at
  least part (1) of the non-error EPIPE proposal. (Which, I agree,
  would be inconvenient but it would be the only way, under that
  circumstance, to not have glitchy shell scripts).

  I've only looked into BSD variants and Linux with regard to signal
  timing.   Both are notably quite strict about delivering all pending
  signals on entry to and exit from system calls.  So this is probably
  a purely hypothetical concern.

* Traditional SIGPIPE and EPIPE handling is inerhently non-robust

  Part (2) of the non-error EIPE proposal (about exit status) is nearly
  orthogonal to everything else.  It's mostly a moot point in normal
  shell scripts (since the exit status of non-final programs in
  pipelines is, unfortunately, nearly irrelevant) -- I was thinking
  about more modern shells in which the exit statuses of non-final
  pipeline programs might be collected and significant. 

  There is an intersting (I think) design problem still lurking that
  supports my original opinion about (2) (and therefore, really, (1) as

  Exit status of non-leading programs in pipelines is a weak spot of
  shell programming.  Using `sh' variants, those statuses are ignored,
  presumably in anticipation that a SIGPIPE death is a more common
  case than than some kind of "real" error that you'd want, for
  example, to trip a `set -e' exit from the shell.  When you need an
  absolutely robust script, with status checking for all the programs
  in what is logically a pipeline, you don't (in traditional sh) have
  the option of using a simple pipeline -- you have to resort to some
  ugly idioms (and thus the traditional design discourages robust
  programming by precluding simple programming in robust applications).

  It isn't hard to imagine a more modern shell design in which the exit
  status of all programs in a pipeline are collected and significant, at
  least optionally.   That would make it possible to detect and handle
  errors from all of the programs in a simple pipeline, not just the
  last one.   Using the notation "|+" for this, one might write:

        set -e

        sort "$somefile" |+ head -1 > ,first-one

  expecting the script to fail if "$somefile" can not be opened or
  the `sort' fails for some other reason.

  But what if `sort' will die of a SIGPIPE?  Should that cause the
  script to fail?   Well, if the non-error EPIPE proposal had effect,
  then a useful answer is "yes, it should cause the script to fail"
  but if the non-error EPIPE proposal does not have effect, then
  there is no robust answer.

  Consider that `sort' may get SIGPIPE or EPIPE for two very distinct 
  reasons.   (1) it might get that error because the consumer for its
  stdout has stopped reading.  This is an ordinary and expected
  condition and means only that `sort' is free to exit at will.   (2)
  it might get that error because it has forked a subprocess to help
  with the sorting and that subprocess has died prematurely;  this is
  a real error and should cause the script to fail.

  So, I think it's useful for utilities to distinguish between
  anticipated non-error EPIPE returns (or SIGPIPE deliveries) and
  unanticipated EPIPE/SIGPIPE events -- and to reflect the difference
  between the two in their exit status and stderr output.
  This would be a step in the direction of making GNU better than
  traditional unix.

  I admit -- it's a pretty esoteric point.  It might be a more
  pressing one if the idea of a shell that collects all exit statuses
  in a pipeline were further developed and seen to be useful (or not).
  Personally, I think that's a good direction to go in -- at least
  to put some design thinking into.  The pipeline/software tools style
  of programming has a lot of nice performance and ease-of-use
  charateristics that modern hw and kernels just keep making more and
  more applicable.   SCSH gives a good existence proof that sh is very
  far from the last word in shell programming.  While we're in there,
  we could think about other (related) improvements such as replacing 
  the stream-RDB tools with new versions that have more robust field
  syntaxes and more consistent options and defaults.


reply via email to

[Prev in Thread] Current Thread [Next in Thread]