summaryrefslogtreecommitdiff
path: root/src/iopoll.c
blob: ca27595e3ad884436f686cd5c87f7d97bbecfa50 (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
/* 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 <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"


/* Wait for FDIN to become ready for reading or FDOUT to become a broken pipe.
   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, otherwise IOPOLL_ERROR if there is a poll()
   or select() error.  */

extern int
iopoll (int fdin, int fdout, bool block)
{
#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 ret = 0;

  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)            /* POLLERR, POLLHUP (or POLLNVAL) */
        return IOPOLL_BROKEN_OUTPUT;  /* output error or broken pipe    */
    }

#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 rfds;
      FD_ZERO (&rfds);
      if (0 <= fdin)
        FD_SET (fdin, &rfds);
      if (0 <= fdout)
        FD_SET (fdout, &rfds);

      struct timeval delay = { .tv_sec = 0, .tv_usec = 0 };
      ret = select (nfds, &rfds, NULL, NULL, block ? NULL : &delay);

      if (ret < 0)
        continue;
      if (ret == 0 && ! block)
        return 0;
      assert (0 < ret);
      if (0 <= fdin && FD_ISSET (fdin, &rfds))   /* input available or EOF; */
        return 0;          /* should now be able to read() without blocking */
      if (0 <= fdout && FD_ISSET (fdout, &rfds)) /* equiv to POLLERR        */
        return IOPOLL_BROKEN_OUTPUT;      /* output error or broken pipe    */
    }

#endif
  return IOPOLL_ERROR;
}



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