Coverage Report

Created: 2025-08-29 06:44

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