Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/poll/poll_backend_epoll.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Jakub Zelenka <bukka@php.net>                               |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#include "php_poll_internal.h"
16
17
#ifdef HAVE_EPOLL
18
19
#include <sys/epoll.h>
20
21
#ifdef HAVE_EPOLL_PWAIT2
22
/* Cleared when the running kernel returns ENOSYS */
23
static atomic_bool epoll_pwait2_available = true;
24
#endif
25
26
typedef struct epoll_backend_data {
27
  int epoll_fd;
28
  struct epoll_event *events;
29
  int events_capacity;
30
  int fd_count;
31
} epoll_backend_data_t;
32
33
static uint32_t epoll_events_to_native(uint32_t events)
34
0
{
35
0
  uint32_t native = 0;
36
0
  if (events & PHP_POLL_READ) {
37
0
    native |= EPOLLIN;
38
0
  }
39
0
  if (events & PHP_POLL_WRITE) {
40
0
    native |= EPOLLOUT;
41
0
  }
42
0
  if (events & PHP_POLL_ERROR) {
43
0
    native |= EPOLLERR;
44
0
  }
45
0
  if (events & PHP_POLL_HUP) {
46
0
    native |= EPOLLHUP;
47
0
  }
48
0
  if (events & PHP_POLL_RDHUP) {
49
0
    native |= EPOLLRDHUP;
50
0
  }
51
0
  if (events & PHP_POLL_ONESHOT) {
52
0
    native |= EPOLLONESHOT;
53
0
  }
54
0
  if (events & PHP_POLL_ET) {
55
0
    native |= EPOLLET;
56
0
  }
57
0
  return native;
58
0
}
59
60
static uint32_t epoll_events_from_native(uint32_t native)
61
0
{
62
0
  uint32_t events = 0;
63
0
  if (native & EPOLLIN) {
64
0
    events |= PHP_POLL_READ;
65
0
  }
66
0
  if (native & EPOLLOUT) {
67
0
    events |= PHP_POLL_WRITE;
68
0
  }
69
0
  if (native & EPOLLERR) {
70
0
    events |= PHP_POLL_ERROR;
71
0
  }
72
0
  if (native & EPOLLHUP) {
73
0
    events |= PHP_POLL_HUP;
74
0
  }
75
0
  if (native & EPOLLRDHUP) {
76
0
    events |= PHP_POLL_RDHUP;
77
0
  }
78
0
  return events;
79
0
}
80
81
static zend_result epoll_backend_init(php_poll_ctx *ctx)
82
0
{
83
0
  epoll_backend_data_t *data = php_poll_calloc(1, sizeof(epoll_backend_data_t), ctx->persistent);
84
0
  if (!data) {
85
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
86
0
    return FAILURE;
87
0
  }
88
89
0
  data->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
90
0
  if (data->epoll_fd == -1) {
91
0
    pefree(data, ctx->persistent);
92
0
    php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM);
93
0
    return FAILURE;
94
0
  }
95
96
  /* Use hint for initial allocation if provided, otherwise start with reasonable default */
97
0
  int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64;
98
0
  data->events = php_poll_calloc(initial_capacity, sizeof(struct epoll_event), ctx->persistent);
99
0
  if (!data->events) {
100
0
    close(data->epoll_fd);
101
0
    pefree(data, ctx->persistent);
102
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
103
0
    return FAILURE;
104
0
  }
105
0
  data->events_capacity = initial_capacity;
106
107
0
  ctx->backend_data = data;
108
0
  return SUCCESS;
109
0
}
110
111
static void epoll_backend_cleanup(php_poll_ctx *ctx)
112
0
{
113
0
  epoll_backend_data_t *data = (epoll_backend_data_t *) ctx->backend_data;
114
0
  if (data) {
115
0
    if (data->epoll_fd >= 0) {
116
0
      close(data->epoll_fd);
117
0
    }
118
0
    pefree(data->events, ctx->persistent);
119
0
    pefree(data, ctx->persistent);
120
0
    ctx->backend_data = NULL;
121
0
  }
122
0
}
123
124
static zend_result epoll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data)
125
0
{
126
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
127
128
0
  struct epoll_event ev = { 0 };
129
0
  ev.events = epoll_events_to_native(events);
130
0
  ev.data.ptr = data;
131
132
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
133
    /* EPERM means the target file does not implement polling, which is the case
134
     * for regular files and for character devices such as /dev/null. */
135
0
    if (errno == EPERM) {
136
0
      php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT);
137
0
    } else {
138
0
      php_poll_set_current_errno_error(ctx);
139
0
    }
140
0
    return FAILURE;
141
0
  }
142
0
  backend_data->fd_count++;
143
144
0
  return SUCCESS;
145
0
}
146
147
static zend_result epoll_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data)
148
0
{
149
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
150
151
0
  struct epoll_event ev = { 0 };
152
0
  ev.events = epoll_events_to_native(events);
153
0
  ev.data.ptr = data;
154
155
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == -1) {
156
0
    php_poll_set_current_errno_error(ctx);
157
0
    return FAILURE;
158
0
  }
159
160
0
  return SUCCESS;
161
0
}
162
163
static zend_result epoll_backend_remove(php_poll_ctx *ctx, int fd)
164
0
{
165
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
166
167
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
168
0
    php_poll_set_current_errno_error(ctx);
169
0
    return FAILURE;
170
0
  }
171
0
  backend_data->fd_count--;
172
173
0
  return SUCCESS;
174
0
}
175
176
static int epoll_backend_wait(
177
    php_poll_ctx *ctx, php_poll_event *events, int max_events,
178
    const struct timespec *timeout)
179
0
{
180
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
181
182
  /* Ensure we have enough space for the requested events */
183
0
  if (max_events > backend_data->events_capacity) {
184
0
    struct epoll_event *new_events = php_poll_realloc(
185
0
        backend_data->events, max_events * sizeof(struct epoll_event), ctx->persistent);
186
0
    if (!new_events) {
187
0
      php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
188
0
      return -1;
189
0
    }
190
0
    backend_data->events = new_events;
191
0
    backend_data->events_capacity = max_events;
192
0
  }
193
194
0
  int nfds = 0;
195
0
#ifdef HAVE_EPOLL_PWAIT2
196
0
  if (EXPECTED(atomic_load(&epoll_pwait2_available))) {
197
0
    nfds = epoll_pwait2(
198
0
        backend_data->epoll_fd, backend_data->events, max_events, timeout, NULL);
199
0
    if (UNEXPECTED(nfds < 0 && (errno == ENOSYS || errno == ENOTSUP))) {
200
0
      atomic_store(&epoll_pwait2_available, false);
201
0
    }
202
0
  }
203
0
  if (UNEXPECTED(!atomic_load(&epoll_pwait2_available)))
204
0
#endif
205
0
  {
206
0
    int timeout_ms = php_poll_timespec_to_ms(timeout);
207
0
    nfds = epoll_wait(backend_data->epoll_fd, backend_data->events, max_events, timeout_ms);
208
0
  }
209
210
0
  if (nfds > 0) {
211
0
    for (int i = 0; i < nfds; i++) {
212
0
      events[i].fd = backend_data->events[i].data.fd;
213
0
      events[i].events = 0; /* Not used in results */
214
0
      events[i].revents = epoll_events_from_native(backend_data->events[i].events);
215
0
      events[i].data = backend_data->events[i].data.ptr;
216
0
    }
217
0
  } else if (nfds < 0) {
218
0
    php_poll_set_current_errno_error(ctx);
219
0
  }
220
221
0
  return nfds;
222
0
}
223
224
static int epoll_backend_get_suitable_max_events(php_poll_ctx *ctx)
225
0
{
226
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
227
228
0
  if (!backend_data) {
229
0
    return -1;
230
0
  }
231
232
  /* For epoll, we now track exactly how many FDs are registered */
233
0
  int active_fds = backend_data->fd_count;
234
235
0
  if (active_fds == 0) {
236
0
    return 1;
237
0
  }
238
239
  /* Epoll can return exactly one event per registered FD,
240
   * so the suitable max_events is exactly the number of registered FDs */
241
0
  return active_fds;
242
0
}
243
244
static bool epoll_backend_is_available(void)
245
16
{
246
16
  int fd = epoll_create1(EPOLL_CLOEXEC);
247
16
  if (fd >= 0) {
248
16
    close(fd);
249
16
    return true;
250
16
  }
251
0
  return false;
252
16
}
253
254
const php_poll_backend_ops php_poll_backend_epoll_ops = {
255
  .type = PHP_POLL_BACKEND_EPOLL,
256
  .name = "epoll",
257
  .init = epoll_backend_init,
258
  .cleanup = epoll_backend_cleanup,
259
  .add = epoll_backend_add,
260
  .modify = epoll_backend_modify,
261
  .remove = epoll_backend_remove,
262
  .wait = epoll_backend_wait,
263
  .is_available = epoll_backend_is_available,
264
  .get_suitable_max_events = epoll_backend_get_suitable_max_events,
265
  .supports_et = true,
266
};
267
268
#endif /* HAVE_EPOLL */