/* 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;
}
}