From e1908ae95dd4c9d19ee4dfabfc8bf8a7f85943fe Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 18:58:41 +0200 Subject: Adding upstream version 9.4. Signed-off-by: Daniel Baumann --- src/iopoll.c | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 src/iopoll.c (limited to 'src/iopoll.c') diff --git a/src/iopoll.c b/src/iopoll.c new file mode 100644 index 0000000..e60e019 --- /dev/null +++ b/src/iopoll.c @@ -0,0 +1,239 @@ +/* iopoll.c -- broken pipe detection / non blocking output handling + Copyright (C) 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). */ + +#if defined _AIX || defined __sun || defined __APPLE__ || \ + defined __linux__ || defined __ANDROID__ +# define IOPOLL_USES_POLL 1 + /* Check we've not enabled gnulib's poll module + as that will emulate poll() in a way not + currently compatible with our usage. */ +# if defined HAVE_POLL +# error "gnulib's poll() replacement is currently incompatible" +# endif +#endif + +#if IOPOLL_USES_POLL +# include +#else +# include +#endif + +#include "system.h" +#include "assure.h" +#include "iopoll.h" +#include "isapipe.h" + + +/* BROKEN_OUTPUT selects the mode of operation of this function. + If BROKEN_OUTPUT, wait for FDIN to become ready for reading + or FDOUT to become a broken pipe. + If !BROKEN_OUTPUT, wait for FDIN or FDOUT to become ready for writing. + If either of those are -1, then they're not checked. Set BLOCK to true + to wait for an event, otherwise return the status immediately. + Return 0 if not BLOCKing and there is no event on the requested descriptors. + Return 0 if FDIN can be read() without blocking, or IOPOLL_BROKEN_OUTPUT if + FDOUT becomes a broken pipe. If !BROKEN_OUTPUT return 0 if FDOUT writable. + Otherwise return IOPOLL_ERROR if there is a poll() or select() error. */ + +static int +iopoll_internal (int fdin, int fdout, bool block, bool broken_output) +{ + affirm (fdin != -1 || fdout != -1); + +#if IOPOLL_USES_POLL + struct pollfd pfds[2] = { /* POLLRDBAND needed for illumos, macOS. */ + { .fd = fdin, .events = POLLIN | POLLRDBAND, .revents = 0 }, + { .fd = fdout, .events = POLLRDBAND, .revents = 0 }, + }; + int check_out_events = POLLERR | POLLHUP | POLLNVAL; + int ret = 0; + + if (! broken_output) + { + pfds[0].events = pfds[1].events = POLLOUT; + check_out_events = POLLOUT; + } + + while (0 <= ret || errno == EINTR) + { + ret = poll (pfds, 2, block ? -1 : 0); + + if (ret < 0) + continue; + if (ret == 0 && ! block) + return 0; + affirm (0 < ret); + 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 & check_out_events) + return broken_output ? IOPOLL_BROKEN_OUTPUT : 0; + } + +#else /* fall back to select()-based implementation */ + + int nfds = (fdin > fdout ? fdin : fdout) + 1; + int ret = 0; + + if (FD_SETSIZE < nfds) + { + errno = EINVAL; + ret = -1; + } + + /* 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 (0 <= ret || errno == EINTR) + { + fd_set fds; + FD_ZERO (&fds); + if (0 <= fdin) + FD_SET (fdin, &fds); + if (0 <= fdout) + FD_SET (fdout, &fds); + + struct timeval delay = { .tv_sec = 0, .tv_usec = 0 }; + ret = select (nfds, + broken_output ? &fds : nullptr, + broken_output ? nullptr : &fds, + nullptr, block ? nullptr : &delay); + + if (ret < 0) + continue; + if (ret == 0 && ! block) + return 0; + affirm (0 < ret); + if (0 <= fdin && FD_ISSET (fdin, &fds)) /* input available or EOF; */ + return 0; /* should now be able to read() without blocking */ + if (0 <= fdout && FD_ISSET (fdout, &fds)) /* equiv to POLLERR */ + return broken_output ? IOPOLL_BROKEN_OUTPUT : 0; + } + +#endif + return IOPOLL_ERROR; +} + +extern int +iopoll (int fdin, int fdout, bool block) +{ + return iopoll_internal (fdin, fdout, block, true); +} + + + +/* 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; +} + +#ifdef EWOULDBLOCK +# define IS_EAGAIN(errcode) ((errcode) == EAGAIN || (errcode) == EWOULDBLOCK) +#else +# define IS_EAGAIN(errcode) ((errcode) == EAGAIN) +#endif + +/* Inspect the errno of the previous syscall. + On EAGAIN, wait for the underlying file descriptor to become writable. + Return true, if EAGAIN has been successfully handled. */ + +static bool +fwait_for_nonblocking_write (FILE *f) +{ + if (! IS_EAGAIN (errno)) + /* non-recoverable write error */ + return false; + + int fd = fileno (f); + if (fd == -1) + goto fail; + + /* wait for the file descriptor to become writable */ + if (iopoll_internal (-1, fd, true, false) != 0) + goto fail; + + /* successfully waited for the descriptor to become writable */ + clearerr (f); + return true; + +fail: + errno = EAGAIN; + return false; +} + + +/* wrapper for fclose() that also waits for F if non blocking. */ + +extern bool +fclose_wait (FILE *f) +{ + for (;;) + { + if (fflush (f) == 0) + break; + + if (! fwait_for_nonblocking_write (f)) + break; + } + + return fclose (f) == 0; +} + + +/* wrapper for fwrite() that also waits for F if non blocking. */ + +extern bool +fwrite_wait (char const *buf, ssize_t size, FILE *f) +{ + for (;;) + { + const size_t written = fwrite (buf, 1, size, f); + size -= written; + affirm (size >= 0); + if (size <= 0) /* everything written */ + return true; + + if (! fwait_for_nonblocking_write (f)) + return false; + + buf += written; + } +} -- cgit v1.2.3