/src/libusb/libusb/os/events_posix.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * libusb event abstraction on POSIX platforms |
3 | | * |
4 | | * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> |
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, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | | */ |
20 | | |
21 | | #include "libusbi.h" |
22 | | |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #ifdef HAVE_EVENTFD |
26 | | #include <sys/eventfd.h> |
27 | | #endif |
28 | | #ifdef HAVE_TIMERFD |
29 | | #include <sys/timerfd.h> |
30 | | #endif |
31 | | |
32 | | #ifdef __EMSCRIPTEN__ |
33 | | /* On Emscripten `pipe` does not conform to the spec and does not block |
34 | | * until events are available, which makes it unusable for event system |
35 | | * and often results in deadlocks when `pipe` is in a loop like it is |
36 | | * in libusb. |
37 | | * |
38 | | * Therefore use a custom event system based on browser event emitters. */ |
39 | | #include <emscripten.h> |
40 | | #include <emscripten/atomic.h> |
41 | | #include <emscripten/threading.h> |
42 | | |
43 | | EM_ASYNC_JS(void, em_libusb_wait_async, (const _Atomic int* ptr, int expected_value, int timeout), { |
44 | | await Atomics.waitAsync(HEAP32, ptr >> 2, expected_value, timeout).value; |
45 | | }); |
46 | | |
47 | | static void em_libusb_wait(const _Atomic int *ptr, int expected_value, int timeout) |
48 | | { |
49 | | if (emscripten_is_main_runtime_thread()) { |
50 | | em_libusb_wait_async(ptr, expected_value, timeout); |
51 | | } else { |
52 | | emscripten_atomic_wait_u32((int*)ptr, expected_value, 1000000LL * timeout); |
53 | | } |
54 | | } |
55 | | #endif |
56 | | #include <unistd.h> |
57 | | |
58 | | #ifdef HAVE_EVENTFD |
59 | 0 | #define EVENT_READ_FD(e) ((e)->eventfd) |
60 | 0 | #define EVENT_WRITE_FD(e) ((e)->eventfd) |
61 | | #else |
62 | | #define EVENT_READ_FD(e) ((e)->pipefd[0]) |
63 | | #define EVENT_WRITE_FD(e) ((e)->pipefd[1]) |
64 | | #endif |
65 | | |
66 | | #ifdef HAVE_NFDS_T |
67 | | typedef nfds_t usbi_nfds_t; |
68 | | #else |
69 | | typedef unsigned int usbi_nfds_t; |
70 | | #endif |
71 | | |
72 | | int usbi_create_event(usbi_event_t *event) |
73 | 0 | { |
74 | 0 | #ifdef HAVE_EVENTFD |
75 | 0 | event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); |
76 | 0 | if (event->eventfd == -1) { |
77 | 0 | usbi_err(NULL, "failed to create eventfd, errno=%d", errno); |
78 | 0 | return LIBUSB_ERROR_OTHER; |
79 | 0 | } |
80 | | |
81 | 0 | return 0; |
82 | | #else |
83 | | #if defined(HAVE_PIPE2) |
84 | | int ret = pipe2(event->pipefd, O_CLOEXEC); |
85 | | #else |
86 | | int ret = pipe(event->pipefd); |
87 | | #endif |
88 | | |
89 | | if (ret != 0) { |
90 | | usbi_err(NULL, "failed to create pipe, errno=%d", errno); |
91 | | return LIBUSB_ERROR_OTHER; |
92 | | } |
93 | | |
94 | | #if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC) |
95 | | ret = fcntl(event->pipefd[0], F_GETFD); |
96 | | if (ret == -1) { |
97 | | usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); |
98 | | goto err_close_pipe; |
99 | | } |
100 | | ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC); |
101 | | if (ret == -1) { |
102 | | usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); |
103 | | goto err_close_pipe; |
104 | | } |
105 | | |
106 | | ret = fcntl(event->pipefd[1], F_GETFD); |
107 | | if (ret == -1) { |
108 | | usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); |
109 | | goto err_close_pipe; |
110 | | } |
111 | | ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC); |
112 | | if (ret == -1) { |
113 | | usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); |
114 | | goto err_close_pipe; |
115 | | } |
116 | | #endif |
117 | | |
118 | | ret = fcntl(event->pipefd[1], F_GETFL); |
119 | | if (ret == -1) { |
120 | | usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno); |
121 | | goto err_close_pipe; |
122 | | } |
123 | | ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK); |
124 | | if (ret == -1) { |
125 | | usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno); |
126 | | goto err_close_pipe; |
127 | | } |
128 | | |
129 | | return 0; |
130 | | |
131 | | err_close_pipe: |
132 | | close(event->pipefd[1]); |
133 | | close(event->pipefd[0]); |
134 | | return LIBUSB_ERROR_OTHER; |
135 | | #endif |
136 | 0 | } |
137 | | |
138 | | void usbi_destroy_event(usbi_event_t *event) |
139 | 0 | { |
140 | 0 | #ifdef HAVE_EVENTFD |
141 | 0 | if (close(event->eventfd) == -1) |
142 | 0 | usbi_warn(NULL, "failed to close eventfd, errno=%d", errno); |
143 | | #else |
144 | | if (close(event->pipefd[1]) == -1) |
145 | | usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno); |
146 | | if (close(event->pipefd[0]) == -1) |
147 | | usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno); |
148 | | #endif |
149 | 0 | } |
150 | | |
151 | | void usbi_signal_event(usbi_event_t *event) |
152 | 0 | { |
153 | 0 | uint64_t dummy = 1; |
154 | 0 | ssize_t r; |
155 | |
|
156 | 0 | r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy)); |
157 | 0 | if (r != sizeof(dummy)) |
158 | 0 | usbi_warn(NULL, "event write failed"); |
159 | | #ifdef __EMSCRIPTEN__ |
160 | | event->has_event = 1; |
161 | | emscripten_atomic_notify(&event->has_event, EMSCRIPTEN_NOTIFY_ALL_WAITERS); |
162 | | #endif |
163 | 0 | } |
164 | | |
165 | | void usbi_clear_event(usbi_event_t *event) |
166 | 0 | { |
167 | 0 | uint64_t dummy; |
168 | 0 | ssize_t r; |
169 | |
|
170 | 0 | r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy)); |
171 | 0 | if (r != sizeof(dummy)) |
172 | 0 | usbi_warn(NULL, "event read failed"); |
173 | | #ifdef __EMSCRIPTEN__ |
174 | | event->has_event = 0; |
175 | | #endif |
176 | 0 | } |
177 | | |
178 | | #ifdef HAVE_TIMERFD |
179 | | int usbi_create_timer(usbi_timer_t *timer) |
180 | 0 | { |
181 | 0 | timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); |
182 | 0 | if (timer->timerfd == -1) { |
183 | 0 | usbi_warn(NULL, "failed to create timerfd, errno=%d", errno); |
184 | 0 | return LIBUSB_ERROR_OTHER; |
185 | 0 | } |
186 | | |
187 | 0 | return 0; |
188 | 0 | } |
189 | | |
190 | | void usbi_destroy_timer(usbi_timer_t *timer) |
191 | 0 | { |
192 | 0 | if (close(timer->timerfd) == -1) |
193 | 0 | usbi_warn(NULL, "failed to close timerfd, errno=%d", errno); |
194 | 0 | } |
195 | | |
196 | | int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout) |
197 | 0 | { |
198 | 0 | const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } }; |
199 | |
|
200 | 0 | if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) { |
201 | 0 | usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno); |
202 | 0 | return LIBUSB_ERROR_OTHER; |
203 | 0 | } |
204 | | |
205 | 0 | return 0; |
206 | 0 | } |
207 | | |
208 | | int usbi_disarm_timer(usbi_timer_t *timer) |
209 | 0 | { |
210 | 0 | const struct itimerspec it = { { 0, 0 }, { 0, 0 } }; |
211 | |
|
212 | 0 | if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) { |
213 | 0 | usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno); |
214 | 0 | return LIBUSB_ERROR_OTHER; |
215 | 0 | } |
216 | | |
217 | 0 | return 0; |
218 | 0 | } |
219 | | #endif |
220 | | |
221 | | int usbi_alloc_event_data(struct libusb_context *ctx) |
222 | 0 | { |
223 | 0 | struct usbi_event_source *ievent_source; |
224 | 0 | struct pollfd *fds; |
225 | 0 | size_t i = 0; |
226 | |
|
227 | 0 | if (ctx->event_data) { |
228 | 0 | free(ctx->event_data); |
229 | 0 | ctx->event_data = NULL; |
230 | 0 | } |
231 | |
|
232 | 0 | ctx->event_data_cnt = 0; |
233 | 0 | for_each_event_source(ctx, ievent_source) |
234 | 0 | ctx->event_data_cnt++; |
235 | |
|
236 | 0 | fds = calloc(ctx->event_data_cnt, sizeof(*fds)); |
237 | 0 | if (!fds) |
238 | 0 | return LIBUSB_ERROR_NO_MEM; |
239 | | |
240 | 0 | for_each_event_source(ctx, ievent_source) { |
241 | 0 | fds[i].fd = ievent_source->data.os_handle; |
242 | 0 | fds[i].events = ievent_source->data.poll_events; |
243 | 0 | i++; |
244 | 0 | } |
245 | |
|
246 | 0 | ctx->event_data = fds; |
247 | 0 | return 0; |
248 | 0 | } |
249 | | |
250 | | int usbi_wait_for_events(struct libusb_context *ctx, |
251 | | struct usbi_reported_events *reported_events, int timeout_ms) |
252 | 0 | { |
253 | 0 | struct pollfd *fds = ctx->event_data; |
254 | 0 | usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt; |
255 | 0 | int internal_fds, num_ready; |
256 | |
|
257 | 0 | usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); |
258 | | #ifdef __EMSCRIPTEN__ |
259 | | /* Emscripten's poll doesn't actually block, so we need to use an |
260 | | * out-of-band waiting signal. */ |
261 | | em_libusb_wait(&ctx->event.has_event, 0, timeout_ms); |
262 | | /* Emscripten ignores timeout_ms, but set it to 0 for future-proofing |
263 | | * in case they ever implement real poll. */ |
264 | | timeout_ms = 0; |
265 | | #endif |
266 | 0 | num_ready = poll(fds, nfds, timeout_ms); |
267 | 0 | usbi_dbg(ctx, "poll() returned %d", num_ready); |
268 | 0 | if (num_ready == 0) { |
269 | 0 | if (usbi_using_timer(ctx)) |
270 | 0 | goto done; |
271 | 0 | return LIBUSB_ERROR_TIMEOUT; |
272 | 0 | } else if (num_ready == -1) { |
273 | 0 | if (errno == EINTR) |
274 | 0 | return LIBUSB_ERROR_INTERRUPTED; |
275 | 0 | usbi_err(ctx, "poll() failed, errno=%d", errno); |
276 | 0 | return LIBUSB_ERROR_IO; |
277 | 0 | } |
278 | | |
279 | | /* fds[0] is always the internal signalling event */ |
280 | 0 | if (fds[0].revents) { |
281 | 0 | reported_events->event_triggered = 1; |
282 | 0 | num_ready--; |
283 | 0 | } else { |
284 | 0 | reported_events->event_triggered = 0; |
285 | 0 | } |
286 | |
|
287 | 0 | #ifdef HAVE_OS_TIMER |
288 | | /* on timer configurations, fds[1] is the timer */ |
289 | 0 | if (usbi_using_timer(ctx) && fds[1].revents) { |
290 | 0 | reported_events->timer_triggered = 1; |
291 | 0 | num_ready--; |
292 | 0 | } else { |
293 | 0 | reported_events->timer_triggered = 0; |
294 | 0 | } |
295 | 0 | #endif |
296 | |
|
297 | 0 | if (!num_ready) |
298 | 0 | goto done; |
299 | | |
300 | | /* the backend will never need to attempt to handle events on the |
301 | | * library's internal file descriptors, so we determine how many are |
302 | | * in use internally for this context and skip these when passing any |
303 | | * remaining pollfds to the backend. */ |
304 | 0 | internal_fds = usbi_using_timer(ctx) ? 2 : 1; |
305 | 0 | fds += internal_fds; |
306 | 0 | nfds -= internal_fds; |
307 | |
|
308 | 0 | usbi_mutex_lock(&ctx->event_data_lock); |
309 | 0 | if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { |
310 | 0 | struct usbi_event_source *ievent_source; |
311 | |
|
312 | 0 | for_each_removed_event_source(ctx, ievent_source) { |
313 | 0 | usbi_nfds_t n; |
314 | |
|
315 | 0 | for (n = 0; n < nfds; n++) { |
316 | 0 | if (ievent_source->data.os_handle != fds[n].fd) |
317 | 0 | continue; |
318 | 0 | if (!fds[n].revents) |
319 | 0 | continue; |
320 | | /* pollfd was removed between the creation of the fds array and |
321 | | * here. remove triggered revent as it is no longer relevant. */ |
322 | 0 | usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd); |
323 | 0 | fds[n].revents = 0; |
324 | 0 | num_ready--; |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | } |
328 | 0 | } |
329 | 0 | usbi_mutex_unlock(&ctx->event_data_lock); |
330 | |
|
331 | 0 | if (num_ready) { |
332 | 0 | assert(num_ready > 0); |
333 | 0 | reported_events->event_data = fds; |
334 | 0 | reported_events->event_data_count = (unsigned int)nfds; |
335 | 0 | } |
336 | | |
337 | 0 | done: |
338 | 0 | reported_events->num_ready = num_ready; |
339 | 0 | return LIBUSB_SUCCESS; |
340 | 0 | } |