From 04689f0483a0ef1f27ab16fe521191d5e7cf1112 Mon Sep 17 00:00:00 2001 From: Carl Edquist Date: Thu, 15 Dec 2022 06:10:33 -0600 Subject: [PATCH 1/2] iopoll: broken pipe detection while waiting for input When a program's output becomes a broken pipe, future attempts to write to that ouput will fail (SIGPIPE/EPIPE). Once it is known that all future write attepts will fail (due to broken pipes), in many cases it becomes pointless to wait for further input for slow devices like ttys. Ideally, a program could use this information to exit early once it is known that future writes will fail. Introduce iopoll() to wait on a pair of fds (input & output) for input to become ready or output to become a broken pipe. This is relevant when input is intermittent (a tty, pipe, or socket); but if input is always ready (a regular file or block device), then a read() will not block, and write failures for a broken pipe will happen normally. Introduce iopoll_input_ok() to check whether an input fd is relevant for iopoll(). Experimentally, broken pipes are only detectable immediately for pipes, but not sockets. Errors for other file types will be detected in the usual way, on write failure. Introduce iopoll_output_ok() to check whether an output fd is suitable for iopoll() -- namely, whether it is a pipe. iopoll() is best implemented with a native poll(2) where possible, but fall back to a select(2)-based implementation platforms where there are portability issues. See also discussion in tail.c. In general, adding a call to iopoll() before a read() in filter programs also allows broken pipes to "propagate" backwards in a shell pipeline. * src/iopoll.c, src/iopoll.h (iopoll): New function implementing broken pipe detection on output while waiting for input. (IOPOLL_BROKEN_OUTPUT, IOPOLL_ERROR): Return codes for iopoll(). (IOPOLL_USES_POLL): Macro for poll() vs select() implementation. (iopoll_input_ok): New function to check whether an input fd is relevant for iopoll(). (iopoll_output_ok): New function to check whether an input fd is suitable for iopoll(). * src/local.mk (noinst_HEADERS): add src/iopoll.h. --- src/iopoll.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/iopoll.h | 6 ++++ src/local.mk | 1 + 3 files changed, 122 insertions(+) create mode 100644 src/iopoll.c create mode 100644 src/iopoll.h diff --git a/src/iopoll.c b/src/iopoll.c new file mode 100644 index 0000000..e6f311e --- /dev/null +++ b/src/iopoll.c @@ -0,0 +1,115 @@ +/* iopoll.c -- broken pipe detection (while waiting for input) + Copyright (C) 1989-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Written by Carl Edquist in collaboration with Arsen Arsenović. */ + +#include + + /* poll(2) is needed on AIX (where 'select' gives a readable + event immediately) and Solaris (where 'select' never gave + a readable event). Also use poll(2) on systems we know work + and/or are already using poll (linux). */ + +#define IOPOLL_USES_POLL \ + defined _AIX || defined __sun || defined __APPLE__ || \ + defined __linux__ || defined __ANDROID__ + +#if IOPOLL_USES_POLL +#include +#else +#include +#endif + +#include "error.h" +#include "system.h" +#include "iopoll.h" +#include "isapipe.h" + + +/* Wait for fdin to become ready for reading or fdout to become a broken pipe. + Return 0 if fdin can be read() without blocking, or IOPOLL_BROKEN_OUTPUT if + fdout becomes a broken pipe, otherwise IOPOLL_ERROR if there is a poll() + or select() error. */ + +#if IOPOLL_USES_POLL + +extern int +iopoll(int fdin, int fdout) +{ + struct pollfd pfds[2] = {{fdin, POLLIN, 0}, {fdout, 0, 0}}; + + while (poll (pfds, 2, -1) > 0 || errno == EINTR) + { + if (errno == EINTR) + continue; + if (pfds[0].revents) /* input available or pipe closed indicating EOF; */ + return 0; /* should now be able to read() without blocking */ + if (pfds[1].revents) /* POLLERR, POLLHUP (or POLLNVAL) */ + return IOPOLL_BROKEN_OUTPUT; /* output error or broken pipe */ + } + return IOPOLL_ERROR; /* poll error */ +} + +#else /* fall back to select()-based implementation */ + +extern int +iopoll(int fdin, int fdout) +{ + int nfds = (fdin > fdout ? fdin : fdout) + 1; + fd_set rfds; + FD_ZERO (&rfds); + FD_SET (fdin, &rfds); + FD_SET (fdout, &rfds); + + /* If fdout has an error condition (like a broken pipe) it will be seen + as ready for reading. Assumes fdout is not actually readable. */ + while (select (nfds, &rfds, NULL, NULL, NULL) > 0 || errno == EINTR) + { + if (errno == EINTR) + continue; + if (FD_ISSET (fdin, &rfds)) /* input available or EOF; should now */ + return 0; /* be able to read() without blocking */ + if (FD_ISSET (fdout, &rfds)) /* POLLERR, POLLHUP (or POLLIN) */ + return IOPOLL_BROKEN_OUTPUT; /* output error or broken pipe */ + } + return IOPOLL_ERROR; /* select error */ +} + +#endif + + +/* Return true if fdin is relevant for iopoll(). + An fd is not relevant for iopoll() if it is always ready for reading, + which is the case for a regular file or block device. */ + +extern bool +iopoll_input_ok(int fdin) +{ + struct stat st; + bool always_ready = fstat (fdin, &st) == 0 && (S_ISREG (st.st_mode) || + S_ISBLK (st.st_mode)); + return !always_ready; +} + +/* Return true if fdout is suitable for iopoll(). + Namely, fdout refers to a pipe. */ + +extern bool +iopoll_output_ok(int fdout) +{ + return isapipe (fdout) > 0; +} + diff --git a/src/iopoll.h b/src/iopoll.h new file mode 100644 index 0000000..f57e3a3 --- /dev/null +++ b/src/iopoll.h @@ -0,0 +1,6 @@ +#define IOPOLL_BROKEN_OUTPUT -2 +#define IOPOLL_ERROR -3 + +int iopoll(int fdin, int fdout); +bool iopoll_input_ok(int fdin); +bool iopoll_output_ok(int fdout); diff --git a/src/local.mk b/src/local.mk index e1d15ce..6cc6f17 100644 --- a/src/local.mk +++ b/src/local.mk @@ -51,6 +51,7 @@ noinst_HEADERS = \ src/fs-is-local.h \ src/group-list.h \ src/ioblksize.h \ + src/iopoll.h \ src/longlong.h \ src/ls.h \ src/operand2sig.h \ -- 2.9.0