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 |
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 (size: 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 (epfd: efd, EPOLL_CTL_ADD, fd: fd, event: &ev) == 0; |
90 | |
91 | close (fd: 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 |
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 | |