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_poll.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
#ifndef PHP_WIN32
18
19
typedef struct poll_backend_data {
20
  php_poll_fd_table *fd_table;
21
  struct pollfd *temp_fds;
22
  int temp_fds_capacity;
23
} poll_backend_data_t;
24
25
static uint32_t poll_events_to_native(uint32_t events)
26
0
{
27
0
  uint32_t native = 0;
28
0
  if (events & PHP_POLL_READ) {
29
0
    native |= POLLIN;
30
0
  }
31
0
  if (events & PHP_POLL_WRITE) {
32
0
    native |= POLLOUT;
33
0
  }
34
0
  if (events & PHP_POLL_ERROR) {
35
0
    native |= POLLERR;
36
0
  }
37
0
  if (events & PHP_POLL_HUP) {
38
0
    native |= POLLHUP;
39
0
  }
40
0
  return native;
41
0
}
42
43
static uint32_t poll_events_from_native(uint32_t native)
44
0
{
45
0
  uint32_t events = 0;
46
0
  if (native & POLLIN) {
47
0
    events |= PHP_POLL_READ;
48
0
  }
49
0
  if (native & POLLOUT) {
50
0
    events |= PHP_POLL_WRITE;
51
0
  }
52
0
  if (native & POLLERR) {
53
0
    events |= PHP_POLL_ERROR;
54
0
  }
55
0
  if (native & POLLHUP) {
56
0
    events |= PHP_POLL_HUP;
57
0
  }
58
0
  if (native & POLLNVAL) {
59
0
    events |= PHP_POLL_ERROR; /* Map invalid FD to error */
60
0
  }
61
0
  return events;
62
0
}
63
64
static zend_result poll_backend_init(php_poll_ctx *ctx)
65
0
{
66
0
  poll_backend_data_t *data = php_poll_calloc(1, sizeof(poll_backend_data_t), ctx->persistent);
67
0
  if (!data) {
68
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
69
0
    return FAILURE;
70
0
  }
71
72
0
  int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64;
73
74
0
  data->fd_table = php_poll_fd_table_init(initial_capacity, ctx->persistent);
75
0
  if (!data->fd_table) {
76
0
    pefree(data, ctx->persistent);
77
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
78
0
    return FAILURE;
79
0
  }
80
81
0
  data->temp_fds = php_poll_calloc(initial_capacity, sizeof(struct pollfd), ctx->persistent);
82
0
  if (!data->temp_fds) {
83
0
    php_poll_fd_table_cleanup(data->fd_table);
84
0
    pefree(data, ctx->persistent);
85
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
86
0
    return FAILURE;
87
0
  }
88
0
  data->temp_fds_capacity = initial_capacity;
89
90
0
  ctx->backend_data = data;
91
0
  return SUCCESS;
92
0
}
93
94
static void poll_backend_cleanup(php_poll_ctx *ctx)
95
0
{
96
0
  poll_backend_data_t *data = (poll_backend_data_t *) ctx->backend_data;
97
0
  if (data) {
98
0
    php_poll_fd_table_cleanup(data->fd_table);
99
0
    pefree(data->temp_fds, ctx->persistent);
100
0
    pefree(data, ctx->persistent);
101
0
    ctx->backend_data = NULL;
102
0
  }
103
0
}
104
105
static zend_result poll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *user_data)
106
0
{
107
0
  poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data;
108
109
0
  if (events & PHP_POLL_ET) {
110
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT);
111
0
    return FAILURE;
112
0
  }
113
114
0
  if (php_poll_fd_table_find(backend_data->fd_table, fd)) {
115
0
    php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS);
116
0
    return FAILURE;
117
0
  }
118
119
0
  php_poll_fd_entry *entry = php_poll_fd_table_get_new(backend_data->fd_table, fd);
120
0
  if (!entry) {
121
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
122
0
    return FAILURE;
123
0
  }
124
125
0
  entry->events = events;
126
0
  entry->data = user_data;
127
128
0
  return SUCCESS;
129
0
}
130
131
static zend_result poll_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *user_data)
132
0
{
133
0
  poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data;
134
135
0
  if (events & PHP_POLL_ET) {
136
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT);
137
0
    return FAILURE;
138
0
  }
139
140
0
  php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd);
141
0
  if (!entry) {
142
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND);
143
0
    return FAILURE;
144
0
  }
145
146
0
  entry->events = events;
147
0
  entry->data = user_data;
148
149
0
  return SUCCESS;
150
0
}
151
152
static zend_result poll_backend_remove(php_poll_ctx *ctx, int fd)
153
0
{
154
0
  poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data;
155
156
0
  if (!php_poll_fd_table_remove(backend_data->fd_table, fd)) {
157
0
    php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND);
158
0
    return FAILURE;
159
0
  }
160
161
0
  return SUCCESS;
162
0
}
163
164
/* Context for building struct pollfd array */
165
typedef struct poll_build_context {
166
  struct pollfd *fds;
167
  int index;
168
} poll_build_context;
169
170
/* Callback to build struct pollfd array from fd_table */
171
static bool poll_build_fds_callback(int fd, php_poll_fd_entry *entry, void *user_data)
172
0
{
173
0
  poll_build_context *ctx = (poll_build_context *) user_data;
174
175
0
  ctx->fds[ctx->index].fd = fd;
176
0
  ctx->fds[ctx->index].events
177
0
      = poll_events_to_native(entry->events & ~(PHP_POLL_ET | PHP_POLL_ONESHOT));
178
0
  ctx->fds[ctx->index].revents = 0;
179
0
  ctx->index++;
180
181
0
  return true;
182
0
}
183
184
static int poll_backend_wait(
185
    php_poll_ctx *ctx, php_poll_event *events, int max_events,
186
    const struct timespec *timeout)
187
0
{
188
0
  poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data;
189
190
0
  int fd_count = php_poll_fd_table_count(backend_data->fd_table);
191
0
  if (fd_count == 0) {
192
0
    if (timeout != NULL && (timeout->tv_sec > 0 || timeout->tv_nsec > 0)) {
193
0
      nanosleep(timeout, NULL);
194
0
    }
195
0
    return 0;
196
0
  }
197
198
  /* Ensure temp_fds array is large enough */
199
0
  if (fd_count > backend_data->temp_fds_capacity) {
200
0
    struct pollfd *new_fds = php_poll_realloc(
201
0
        backend_data->temp_fds, fd_count * sizeof(struct pollfd), ctx->persistent);
202
0
    if (!new_fds) {
203
0
      php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM);
204
0
      return -1;
205
0
    }
206
0
    backend_data->temp_fds = new_fds;
207
0
    backend_data->temp_fds_capacity = fd_count;
208
0
  }
209
210
  /* Build struct pollfd array from fd_table */
211
0
  poll_build_context build_ctx = { .fds = backend_data->temp_fds, .index = 0 };
212
0
  php_poll_fd_table_foreach(backend_data->fd_table, poll_build_fds_callback, &build_ctx);
213
214
  /* Convert timespec to milliseconds (poll() only supports ms resolution) */
215
0
  int timeout_ms = php_poll_timespec_to_ms(timeout);
216
0
  int nfds = poll(backend_data->temp_fds, fd_count, timeout_ms);
217
218
0
  if (nfds < 0) {
219
0
    php_poll_set_current_errno_error(ctx);
220
0
    return -1;
221
0
  } else if (nfds == 0) {
222
0
    return 0; /* timeout */
223
0
  }
224
225
  /* Process results - iterate through struct pollfd array directly */
226
0
  int event_count = 0;
227
0
  for (int i = 0; i < fd_count && event_count < max_events; i++) {
228
0
    struct pollfd *pfd = &backend_data->temp_fds[i];
229
230
0
    if (pfd->revents != 0) {
231
0
      php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, pfd->fd);
232
0
      if (entry) {
233
        /* Handle POLLNVAL by automatically removing the invalid FD */
234
0
        if (pfd->revents & POLLNVAL) {
235
0
          php_poll_fd_table_remove(backend_data->fd_table, pfd->fd);
236
0
          continue; /* Don't report this event */
237
0
        }
238
239
0
        events[event_count].fd = pfd->fd;
240
0
        events[event_count].events = entry->events;
241
0
        events[event_count].revents = poll_events_from_native(pfd->revents);
242
0
        events[event_count].data = entry->data;
243
0
        event_count++;
244
0
      }
245
0
    }
246
0
  }
247
248
  /* Handle oneshot removals */
249
0
  for (int i = 0; i < event_count; i++) {
250
0
    php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, events[i].fd);
251
0
    if (entry && (entry->events & PHP_POLL_ONESHOT) && events[i].revents != 0) {
252
0
      php_poll_fd_table_remove(backend_data->fd_table, events[i].fd);
253
0
    }
254
0
  }
255
256
0
  return event_count;
257
0
}
258
259
static bool poll_backend_is_available(void)
260
0
{
261
0
  return true;
262
0
}
263
264
static int poll_backend_get_suitable_max_events(php_poll_ctx *ctx)
265
0
{
266
0
  poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data;
267
268
0
  if (UNEXPECTED(!backend_data || !backend_data->fd_table)) {
269
0
    return -1;
270
0
  }
271
272
0
  int active_fds = php_poll_fd_table_count(backend_data->fd_table);
273
0
  return active_fds == 0 ? 1 : active_fds;
274
0
}
275
276
const php_poll_backend_ops php_poll_backend_poll_ops = {
277
  .type = PHP_POLL_BACKEND_POLL,
278
  .name = "poll",
279
  .init = poll_backend_init,
280
  .cleanup = poll_backend_cleanup,
281
  .add = poll_backend_add,
282
  .modify = poll_backend_modify,
283
  .remove = poll_backend_remove,
284
  .wait = poll_backend_wait,
285
  .is_available = poll_backend_is_available,
286
  .get_suitable_max_events = poll_backend_get_suitable_max_events,
287
  .supports_et = false,
288
};
289
290
#endif