• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2021 Ole André Vadla Ravnås
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <errno.h>
21 #include <unistd.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #if defined (HAVE_EPOLL_CREATE)
25 #include <sys/epoll.h>
26 #elif defined (HAVE_KQUEUE)
27 #include <sys/event.h>
28 #include <sys/time.h>
29 #endif
30 
31 #include "giounix-private.h"
32 
33 #define G_TEMP_FAILURE_RETRY(expression)      \
34   ({                                          \
35     gssize __result;                          \
36                                               \
37     do                                        \
38       __result = (gssize) (expression);       \
39     while (__result == -1 && errno == EINTR); \
40                                               \
41     __result;                                 \
42   })
43 
44 static gboolean g_fd_is_regular_file (int fd) G_GNUC_UNUSED;
45 
46 gboolean
_g_fd_is_pollable(int fd)47 _g_fd_is_pollable (int fd)
48 {
49   /*
50    * Determining whether a file-descriptor (FD) is pollable turns out to be
51    * quite hard.
52    *
53    * We used to detect this by attempting to lseek() and check if it failed with
54    * ESPIPE, and if so we'd consider the FD pollable. But this turned out to not
55    * work on e.g. PTYs and other devices that are pollable.
56    *
57    * Another approach that was considered was to call fstat() and if it failed
58    * we'd assume that the FD is pollable, and if it succeeded we'd consider it
59    * pollable as long as it's not a regular file. This seemed to work alright
60    * except for FDs backed by simple devices, such as /dev/null.
61    *
62    * There are however OS-specific methods that allow us to figure this out with
63    * absolute certainty:
64    */
65 
66 #if defined (HAVE_EPOLL_CREATE)
67   /*
68    * Linux
69    *
70    * The answer we seek is provided by the kernel's file_can_poll():
71    * https://github.com/torvalds/linux/blob/2ab38c17aac10bf55ab3efde4c4db3893d8691d2/include/linux/poll.h#L81-L84
72    * But we cannot probe that by using poll() as the returned events for
73    * non-pollable FDs are always IN | OUT.
74    *
75    * The best option then seems to be using epoll, as it will refuse to add FDs
76    * where file_can_poll() returns FALSE.
77    */
78 
79   int efd;
80   struct epoll_event ev = { 0, };
81   gboolean add_succeeded;
82 
83   efd = epoll_create (1);
84   if (efd == -1)
85     g_error ("epoll_create () failed: %s", g_strerror (errno));
86 
87   ev.events = EPOLLIN;
88 
89   add_succeeded = epoll_ctl (efd, EPOLL_CTL_ADD, fd, &ev) == 0;
90 
91   close (efd);
92 
93   return add_succeeded;
94 #elif defined (HAVE_KQUEUE)
95   /*
96    * Apple OSes and BSDs
97    *
98    * Like on Linux, we cannot use poll() to do the probing, but kqueue does
99    * the trick as it will refuse to add non-pollable FDs. (Except for regular
100    * files, which we need to special-case. Even though kqueue does support them,
101    * poll() does not.)
102    */
103 
104   int kfd;
105   struct kevent ev;
106   gboolean add_succeeded;
107 
108   if (g_fd_is_regular_file (fd))
109     return FALSE;
110 
111   kfd = kqueue ();
112   if (kfd == -1)
113     g_error ("kqueue () failed: %s", g_strerror (errno));
114 
115   EV_SET (&ev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
116 
117   add_succeeded =
118       G_TEMP_FAILURE_RETRY (kevent (kfd, &ev, 1, NULL, 0, NULL)) == 0;
119 
120   close (kfd);
121 
122   return add_succeeded;
123 #else
124   /*
125    * Other UNIXes (AIX, QNX, Solaris, etc.)
126    *
127    * We can rule out regular files, but devices such as /dev/null will be
128    * reported as pollable even though they're not. This is hopefully good
129    * enough for most use-cases, but easy to expand on later if needed.
130    */
131 
132   return !g_fd_is_regular_file (fd);
133 #endif
134 }
135 
136 static gboolean
g_fd_is_regular_file(int fd)137 g_fd_is_regular_file (int fd)
138 {
139   struct stat st;
140 
141   if (G_TEMP_FAILURE_RETRY (fstat (fd, &st)) == -1)
142     return FALSE;
143 
144   return S_ISREG (st.st_mode);
145 }
146