summaryrefslogtreecommitdiff
path: root/src/iopoll.c
blob: 321a1245e21e8c903ff0328d8ebbca0aebae308f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/* 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 <https://www.gnu.org/licenses/>.

   Written by Carl Edquist in collaboration with Arsen Arsenović.  */

#include <config.h>

#include <assert.h>

/* 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 <poll.h>
#else
# include <sys/select.h>
#endif

#include "system.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 writeable.
   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)
{
  assert (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;
      assert (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 : NULL,
                    broken_output ? NULL : &fds,
                    NULL, block ? NULL : &delay);

      if (ret < 0)
        continue;
      if (ret == 0 && ! block)
        return 0;
      assert (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;
      assert (size >= 0);
      if (size <= 0)  /* everything written */
        return true;

      if (! fwait_for_nonblocking_write (f))
        return false;

      buf += written;
    }
}