Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 19 Mar 2003 22:27:57 +1100 (EST)
From:      Bruce Evans <bde@zeta.org.au>
To:        Peter Pentchev <roam@ringlet.net>
Cc:        Luigi Rizzo <rizzo@icir.org>, "" <net@FreeBSD.ORG>, Tristan Goode <tgoode@iprimus.com.au>
Subject:   Re: write(2) SIGPIPE on a closed socket?
Message-ID:  <20030319215212.V8375@gamplex.bde.org>
In-Reply-To: <20030319094506.GB27330@straylight.oblivion.bg>
References:  <20030319093002.GT468@straylight.oblivion.bg> <20030319013748.A84035@xorpc.icir.org> <20030319094506.GB27330@straylight.oblivion.bg>

next in thread | previous in thread | raw e-mail | index | archive | help
On Wed, 19 Mar 2003, Peter Pentchev wrote:

> On Wed, Mar 19, 2003 at 01:37:48AM -0800, Luigi Rizzo wrote:
> > On Wed, Mar 19, 2003 at 11:30:02AM +0200, Peter Pentchev wrote:
> > ...
> > > dnscache) getting a SIGPIPE when attempting to write to an incoming
> > > connection's socket.  Presumably, the client closed the connection in
> > ...
> > > The question: if the client closed the socket, shouldn't a write(2)
> > > return -1 with errno == EPIPE before sending a SIGPIPE?  Does anyone
> >
> > well, what would "before" mean ? the system sends a signal when
> > the error is detected, not after an arbitrary amount of time
> > to give the user a chance of handling the return from the syscall.
>
> Well, the documented behavior of write(2) - and the one I have seen on
> many cases when writing to a socket or a FIFO - is that the first time a
> write is attempted after the fd is no longer available for writing,
> write(2) returns -1 and sets errno to EPIPE.  This is the way read(2)
> behaves, too; this is the way programs are written to accommodate -
> dnscache does a check for write(2) returning -1, and closes the
> connection if this condition is detected.
>
> IMHO, this is way more logical - the system call should first try to
> return an error, so the application can detect a problem *immediately*,
> not asynchronously via signal handlers setting flags and such.  I - and
> many others, apparently - have come to depend on the fact that the first
> read(2) or write(2) operation on a closed socket will return -1, and
> only if I am foolish enough to attempt a second one will the system send
> me a signal (isn't this the whole purpose of SIGPIPE - forcibly
> terminate foolish applications which do not honor errors signalled by
> return code?).

I think you are just depending on a race bug for closing the socket.
The signal should be delivered asynchronously _before_ write() returns.
FreeBSD's write(2) is missing documentation of SIGPIPE.  POSIX gives
more details of course.  It says that EPIPE is returned when a pipe
_was_ attempted on a socket that _is_ shut down or no longer connected,
and that a SIGPIPE _is_ generated in this case.  I'm not sure how much
causality should be read from the tenses in the wording.

FreeBSD clearly delivers SIGPIPE before returning from write() (unless
SIGPIPE is blocked) for non-sockets.  From sys_generic.c:dofilewrite():

% 	if ((error = fo_write(fp, &auio, td->td_ucred, flags, td))) {
% 		if (auio.uio_resid != cnt && (error == ERESTART ||
% 		    error == EINTR || error == EWOULDBLOCK))
% 			error = 0;
% 		/* Socket layer is responsible for issuing SIGPIPE. */
% 		if (error == EPIPE && fp->f_type != DTYPE_SOCKET) {
% 			PROC_LOCK(td->td_proc);
% 			psignal(td->td_proc, SIGPIPE);
  			^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This gets delivered ASAP.

% 			PROC_UNLOCK(td->td_proc);
% 		}
% 	}

Similarly in for sockets except in the SO_NOSIGPIPE case, unless I am
misreading uipc_syscalls.c:sendit():

% 	error = so->so_proto->pr_usrreqs->pru_sosend(so, to, &auio, 0, control,
% 						     flags, td);
% 	if (error) {
% 		if (auio.uio_resid != len && (error == ERESTART ||
% 		    error == EINTR || error == EWOULDBLOCK))
% 			error = 0;
% 		/* Generation of SIGPIPE can be controlled per socket */
% 		if (error == EPIPE && !(so->so_options & SO_NOSIGPIPE)) {
% 			PROC_LOCK(td->td_proc);
% 			psignal(td->td_proc, SIGPIPE);
  			^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
% 			PROC_UNLOCK(td->td_proc);
% 		}
% 	}

This code seems to have an interesting version of mishandling short
i/o counts (auio.uio_resid != len && auio.ui_resid != 0).  Short i/o
counts involving an interrupt are normally the only ones handled
correctly (by the ERESTART/EINTR/EWOULDBLOCK fixup).  However, in the
case of EPIPE/SIGPIPE, we only notice the SIGPIPE after we do the
fixup, so we never do the fixup and always return an error instead of
the short i/o count.

Bruce

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-net" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20030319215212.V8375>