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