Coverage Report

Created: 2026-07-25 06:39

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
typedef struct epoll_backend_data {
22
  int epoll_fd;
23
  struct epoll_event *events;
24
  int events_capacity;
25
  int fd_count;
26
} epoll_backend_data_t;
27
28
static uint32_t epoll_events_to_native(uint32_t events)
29
0
{
30
0
  uint32_t native = 0;
31
0
  if (events & PHP_POLL_READ) {
32
0
    native |= EPOLLIN;
33
0
  }
34
0
  if (events & PHP_POLL_WRITE) {
35
0
    native |= EPOLLOUT;
36
0
  }
37
0
  if (events & PHP_POLL_ERROR) {
38
0
    native |= EPOLLERR;
39
0
  }
40
0
  if (events & PHP_POLL_HUP) {
41
0
    native |= EPOLLHUP;
42
0
  }
43
0
  if (events & PHP_POLL_RDHUP) {
44
0
    native |= EPOLLRDHUP;
45
0
  }
46
0
  if (events & PHP_POLL_ONESHOT) {
47
0
    native |= EPOLLONESHOT;
48
0
  }
49
0
  if (events & PHP_POLL_ET) {
50
0
    native |= EPOLLET;
51
0
  }
52
0
  return native;
53
0
}
54
55
static uint32_t epoll_events_from_native(uint32_t native)
56
0
{
57
0
  uint32_t events = 0;
58
0
  if (native & EPOLLIN) {
59
0
    events |= PHP_POLL_READ;
60
0
  }
61
0
  if (native & EPOLLOUT) {
62
0
    events |= PHP_POLL_WRITE;
63
0
  }
64
0
  if (native & EPOLLERR) {
65
0
    events |= PHP_POLL_ERROR;
66
0
  }
67
0
  if (native & EPOLLHUP) {
68
0
    events |= PHP_POLL_HUP;
69
0
  }
70
0
  if (native & EPOLLRDHUP) {
71
0
    events |= PHP_POLL_RDHUP;
72
0
  }
73
0
  return events;
74
0
}
75
76
static zend_result epoll_backend_init(php_poll_ctx *ctx)
77
5
{
78
5
  epoll_backend_data_t *data = php_poll_calloc(1, sizeof(epoll_backend_data_t), ctx->persistent);
79
5
  if (!data) {
80
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
81
0
    return FAILURE;
82
0
  }
83
84
5
  data->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
85
5
  if (data->epoll_fd == -1) {
86
0
    pefree(data, ctx->persistent);
87
0
    php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM);
88
0
    return FAILURE;
89
0
  }
90
91
  /* Use hint for initial allocation if provided, otherwise start with reasonable default */
92
5
  int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64;
93
5
  data->events = php_poll_calloc(initial_capacity, sizeof(struct epoll_event), ctx->persistent);
94
5
  if (!data->events) {
95
0
    close(data->epoll_fd);
96
0
    pefree(data, ctx->persistent);
97
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
98
0
    return FAILURE;
99
0
  }
100
5
  data->events_capacity = initial_capacity;
101
102
5
  ctx->backend_data = data;
103
5
  return SUCCESS;
104
5
}
105
106
static void epoll_backend_cleanup(php_poll_ctx *ctx)
107
5
{
108
5
  epoll_backend_data_t *data = (epoll_backend_data_t *) ctx->backend_data;
109
5
  if (data) {
110
5
    if (data->epoll_fd >= 0) {
111
5
      close(data->epoll_fd);
112
5
    }
113
5
    pefree(data->events, ctx->persistent);
114
5
    pefree(data, ctx->persistent);
115
5
    ctx->backend_data = NULL;
116
5
  }
117
5
}
118
119
static zend_result epoll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data)
120
0
{
121
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
122
123
0
  struct epoll_event ev = { 0 };
124
0
  ev.events = epoll_events_to_native(events);
125
0
  ev.data.ptr = data;
126
127
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
128
0
    php_poll_set_error(ctx, (errno == EEXIST) ? PHP_POLL_ERR_EXISTS : PHP_POLL_ERR_SYSTEM);
129
0
    return FAILURE;
130
0
  }
131
0
  backend_data->fd_count++;
132
133
0
  return SUCCESS;
134
0
}
135
136
static zend_result epoll_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data)
137
0
{
138
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
139
140
0
  struct epoll_event ev = { 0 };
141
0
  ev.events = epoll_events_to_native(events);
142
0
  ev.data.ptr = data;
143
144
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == -1) {
145
0
    php_poll_set_error(ctx, (errno == ENOENT) ? PHP_POLL_ERR_NOTFOUND : PHP_POLL_ERR_SYSTEM);
146
0
    return FAILURE;
147
0
  }
148
149
0
  return SUCCESS;
150
0
}
151
152
static zend_result epoll_backend_remove(php_poll_ctx *ctx, int fd)
153
0
{
154
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
155
156
0
  if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
157
0
    php_poll_set_error(ctx, (errno == ENOENT) ? PHP_POLL_ERR_NOTFOUND : PHP_POLL_ERR_SYSTEM);
158
0
    return FAILURE;
159
0
  }
160
0
  backend_data->fd_count--;
161
162
0
  return SUCCESS;
163
0
}
164
165
static int epoll_backend_wait(
166
    php_poll_ctx *ctx, php_poll_event *events, int max_events,
167
    const struct timespec *timeout)
168
0
{
169
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
170
171
  /* Ensure we have enough space for the requested events */
172
0
  if (max_events > backend_data->events_capacity) {
173
0
    struct epoll_event *new_events = php_poll_realloc(
174
0
        backend_data->events, max_events * sizeof(struct epoll_event), ctx->persistent);
175
0
    if (!new_events) {
176
0
      php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
177
0
      return -1;
178
0
    }
179
0
    backend_data->events = new_events;
180
0
    backend_data->events_capacity = max_events;
181
0
  }
182
183
0
  int nfds;
184
#ifdef HAVE_EPOLL_PWAIT2
185
  nfds = epoll_pwait2(backend_data->epoll_fd, backend_data->events, max_events, timeout, NULL);
186
#else
187
0
  int timeout_ms = php_poll_timespec_to_ms(timeout);
188
0
  nfds = epoll_wait(backend_data->epoll_fd, backend_data->events, max_events, timeout_ms);
189
0
#endif
190
191
0
  if (nfds > 0) {
192
0
    for (int i = 0; i < nfds; i++) {
193
0
      events[i].fd = backend_data->events[i].data.fd;
194
0
      events[i].events = 0; /* Not used in results */
195
0
      events[i].revents = epoll_events_from_native(backend_data->events[i].events);
196
0
      events[i].data = backend_data->events[i].data.ptr;
197
0
    }
198
0
  } else if (nfds < 0) {
199
0
    php_poll_set_current_errno_error(ctx);
200
0
  }
201
202
0
  return nfds;
203
0
}
204
205
static int epoll_backend_get_suitable_max_events(php_poll_ctx *ctx)
206
0
{
207
0
  epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data;
208
209
0
  if (!backend_data) {
210
0
    return -1;
211
0
  }
212
213
  /* For epoll, we now track exactly how many FDs are registered */
214
0
  int active_fds = backend_data->fd_count;
215
216
0
  if (active_fds == 0) {
217
0
    return 1;
218
0
  }
219
220
  /* Epoll can return exactly one event per registered FD,
221
   * so the suitable max_events is exactly the number of registered FDs */
222
0
  return active_fds;
223
0
}
224
225
static bool epoll_backend_is_available(void)
226
16
{
227
16
  int fd = epoll_create1(EPOLL_CLOEXEC);
228
16
  if (fd >= 0) {
229
16
    close(fd);
230
16
    return true;
231
16
  }
232
0
  return false;
233
16
}
234
235
const php_poll_backend_ops php_poll_backend_epoll_ops = {
236
  .type = PHP_POLL_BACKEND_EPOLL,
237
  .name = "epoll",
238
  .init = epoll_backend_init,
239
  .cleanup = epoll_backend_cleanup,
240
  .add = epoll_backend_add,
241
  .modify = epoll_backend_modify,
242
  .remove = epoll_backend_remove,
243
  .wait = epoll_backend_wait,
244
  .is_available = epoll_backend_is_available,
245
  .get_suitable_max_events = epoll_backend_get_suitable_max_events,
246
  .supports_et = true,
247
};
248
249
#endif /* HAVE_EPOLL */