Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/io_poll.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
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
   | Author: Jakub Zelenka <bukka@php.net>                                |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#include "php.h"
16
#include "zend_enum.h"
17
#include "zend_exceptions.h"
18
#include "php_network.h"
19
#include "php_poll.h"
20
#include "io_poll.h"
21
#include "io_poll_arginfo.h"
22
#include "io_poll_decl.h"
23
#include "ext/date/time_duration.h"
24
25
/* Class entries */
26
static zend_class_entry *php_io_poll_backend_class_entry;
27
static zend_class_entry *php_io_poll_event_class_entry;
28
static zend_class_entry *php_io_poll_context_class_entry;
29
static zend_class_entry *php_io_poll_watcher_class_entry;
30
static zend_class_entry *php_io_poll_handle_class_entry;
31
static zend_class_entry *php_io_exception_class_entry;
32
static zend_class_entry *php_io_poll_exception_class_entry;
33
static zend_class_entry *php_io_poll_failed_backend_unavailable_class_entry;
34
static zend_class_entry *php_io_poll_failed_operation_class_entry;
35
static zend_class_entry *php_io_poll_failed_context_init_class_entry;
36
static zend_class_entry *php_io_poll_failed_handle_add_class_entry;
37
static zend_class_entry *php_io_poll_failed_watcher_mod_class_entry;
38
static zend_class_entry *php_io_poll_failed_wait_class_entry;
39
static zend_class_entry *php_io_poll_inactive_watcher_class_entry;
40
static zend_class_entry *php_io_poll_handle_already_watched_class_entry;
41
static zend_class_entry *php_io_poll_invalid_handle_class_entry;
42
static zend_class_entry *php_stream_poll_handle_class_entry;
43
44
/* Object handlers */
45
static zend_object_handlers php_io_poll_context_object_handlers;
46
static zend_object_handlers php_io_poll_watcher_object_handlers;
47
static zend_object_handlers php_io_poll_handle_object_handlers;
48
49
typedef struct php_io_poll_context_object php_io_poll_context_object;
50
51
/* Watcher object structure */
52
typedef struct php_io_poll_watcher_object {
53
  php_poll_handle_object *handle;
54
  uint32_t watched_events;
55
  uint32_t triggered_events;
56
  zval data;
57
  bool active;
58
  bool closed; /* Deactivated because its stream was closed */
59
  php_io_poll_context_object *context; /* Back reference to Context object */
60
  php_socket_t fd; /* Registered fd, SOCK_ERR when inactive */
61
  php_stream *stream; /* Watched stream, NULL when not registered in its watcher list */
62
  zend_object std;
63
} php_io_poll_watcher_object;
64
65
/* Context object structure */
66
struct php_io_poll_context_object {
67
  php_poll_ctx *ctx;
68
  HashTable *watchers; /* Maps fd -> watcher object */
69
  zend_object std;
70
};
71
72
/* Stream poll handle specific data */
73
typedef struct php_stream_poll_handle_data {
74
  zend_resource *res;
75
} php_stream_poll_handle_data;
76
77
/* Accessor macros */
78
0
#define PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(_obj) ZEND_CONTAINER_OF(_obj, php_io_poll_context_object, std)
79
80
0
#define PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(_obj) ZEND_CONTAINER_OF(_obj, php_io_poll_watcher_object, std)
81
82
0
#define PHP_POLL_WATCHER_OBJ_FROM_ZV(_zv) PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv))
83
0
#define PHP_POLL_CONTEXT_OBJ_FROM_ZV(_zv) PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv))
84
85
/* Helper to throw failed operation exceptions with error code */
86
static inline void php_io_poll_throw_failed_operation(
87
    zend_class_entry *ce, const char *message, php_poll_error error)
88
0
{
89
0
  zend_throw_exception(ce, message, (zend_long) error);
90
0
}
91
92
/* Event enum to bit mask mapping */
93
static uint32_t php_io_poll_event_enum_to_bit(zend_object *event_enum)
94
0
{
95
0
  return 1 << (zend_enum_fetch_case_id(event_enum) - 1);
96
0
}
97
98
static uint32_t php_io_poll_event_enums_to_events(zval *event_enums)
99
0
{
100
0
  HashTable *ht;
101
0
  uint32_t events = 0;
102
103
0
  if (Z_TYPE_P(event_enums) != IS_ARRAY) {
104
0
    return 0;
105
0
  }
106
107
0
  ht = Z_ARRVAL_P(event_enums);
108
109
0
  ZEND_HASH_FOREACH_VAL(ht, zval *entry) {
110
0
    if (Z_TYPE_P(entry) != IS_OBJECT
111
0
        || !instanceof_function(Z_OBJCE_P(entry), php_io_poll_event_class_entry)) {
112
0
      return 0;
113
0
    }
114
0
    events |= php_io_poll_event_enum_to_bit(Z_OBJ_P(entry));
115
0
  }
116
0
  ZEND_HASH_FOREACH_END();
117
118
0
  return events;
119
0
}
120
121
static zend_result php_io_poll_events_to_event_enums(uint32_t events, zval *event_enums)
122
0
{
123
0
  zval enum_case;
124
125
0
  array_init(event_enums);
126
127
0
  if (events & PHP_POLL_READ) {
128
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Read));
129
0
    add_next_index_zval(event_enums, &enum_case);
130
0
  }
131
0
  if (events & PHP_POLL_WRITE) {
132
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Write));
133
0
    add_next_index_zval(event_enums, &enum_case);
134
0
  }
135
0
  if (events & PHP_POLL_ERROR) {
136
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_Error));
137
0
    add_next_index_zval(event_enums, &enum_case);
138
0
  }
139
0
  if (events & PHP_POLL_HUP) {
140
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_HangUp));
141
0
    add_next_index_zval(event_enums, &enum_case);
142
0
  }
143
0
  if (events & PHP_POLL_RDHUP) {
144
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_ReadHangUp));
145
0
    add_next_index_zval(event_enums, &enum_case);
146
0
  }
147
0
  if (events & PHP_POLL_ONESHOT) {
148
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_OneShot));
149
0
    add_next_index_zval(event_enums, &enum_case);
150
0
  }
151
0
  if (events & PHP_POLL_ET) {
152
0
    ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_by_id(php_io_poll_event_class_entry, ZEND_ENUM_Io_Poll_Event_EdgeTriggered));
153
0
    add_next_index_zval(event_enums, &enum_case);
154
0
  }
155
156
0
  return SUCCESS;
157
0
}
158
159
/* Backend enum name to backend type mapping */
160
static php_poll_backend_type php_io_poll_backend_enum_to_type(zend_object *backend_enum)
161
0
{
162
0
  return zend_enum_fetch_case_id(backend_enum) - 2;
163
0
}
164
165
static const char *php_io_poll_backend_type_to_name(php_poll_backend_type type)
166
0
{
167
0
  switch (type) {
168
0
    case PHP_POLL_BACKEND_POLL:
169
0
      return "Poll";
170
0
    case PHP_POLL_BACKEND_EPOLL:
171
0
      return "Epoll";
172
0
    case PHP_POLL_BACKEND_KQUEUE:
173
0
      return "Kqueue";
174
0
    case PHP_POLL_BACKEND_EVENTPORT:
175
0
      return "EventPorts";
176
0
    case PHP_POLL_BACKEND_WSAPOLL:
177
0
      return "WSAPoll";
178
0
    case PHP_POLL_BACKEND_AUTO:
179
0
    default:
180
0
      return "Auto";
181
0
  }
182
0
}
183
184
/* Stream Poll Handle Implementation */
185
186
/* The stream is resolved from the resource on every use: fclose() frees the stream
187
 * while the resource, which the handle holds a reference to, stays as a closed one. */
188
static php_stream *php_stream_poll_handle_get_stream(php_poll_handle_object *handle)
189
0
{
190
0
  php_stream_poll_handle_data *data = handle->handle_data;
191
192
0
  if (!data) {
193
0
    return NULL;
194
0
  }
195
196
0
  return zend_fetch_resource2(data->res, NULL, php_file_le_stream(), php_file_le_pstream());
197
0
}
198
199
static php_socket_t php_stream_poll_handle_get_fd(php_poll_handle_object *handle)
200
0
{
201
0
  php_stream *stream = php_stream_poll_handle_get_stream(handle);
202
0
  php_socket_t fd;
203
204
0
  if (!stream) {
205
0
    return SOCK_ERR;
206
0
  }
207
208
0
  if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_POLL | PHP_STREAM_CAST_INTERNAL,
209
0
        (void *) &fd, 1)
210
0
          != SUCCESS
211
0
      || fd == -1) {
212
0
    return SOCK_ERR;
213
0
  }
214
215
0
  return fd;
216
0
}
217
218
static int php_stream_poll_handle_is_valid(php_poll_handle_object *handle)
219
0
{
220
0
  php_stream *stream = php_stream_poll_handle_get_stream(handle);
221
0
  return stream && !php_stream_eof(stream);
222
0
}
223
224
static void php_stream_poll_handle_cleanup(php_poll_handle_object *handle)
225
0
{
226
0
  php_stream_poll_handle_data *data = handle->handle_data;
227
0
  if (data) {
228
0
    if (data->res) {
229
0
      zend_list_delete(data->res);
230
0
    }
231
0
    efree(data);
232
0
    handle->handle_data = NULL;
233
0
  }
234
0
}
235
236
static php_poll_handle_ops php_stream_poll_handle_ops = {
237
  .get_fd = php_stream_poll_handle_get_fd,
238
  .is_valid = php_stream_poll_handle_is_valid,
239
  .cleanup = php_stream_poll_handle_cleanup
240
};
241
242
/* Handle interface internal only */
243
static void php_stream_poll_handle_implement_interface(zend_class_entry *interface, zend_class_entry *implementor)
244
16
{
245
16
  if (implementor->type == ZEND_USER_CLASS) {
246
0
    zend_error_noreturn(E_ERROR, "Io\\Poll\\Handle cannot be implemented by user classes");
247
0
  }
248
16
}
249
250
/* Object Creation Functions */
251
252
static zend_object *php_stream_poll_handle_create_object(zend_class_entry *ce)
253
0
{
254
0
  php_poll_handle_object *intern = php_poll_handle_object_create(
255
0
      sizeof(php_poll_handle_object), ce, &php_stream_poll_handle_ops);
256
0
  return &intern->std;
257
0
}
258
259
static zend_object *php_io_poll_watcher_create_object(zend_class_entry *ce)
260
0
{
261
0
  php_io_poll_watcher_object *intern = zend_object_alloc(sizeof(php_io_poll_watcher_object), ce);
262
263
0
  zend_object_std_init(&intern->std, ce);
264
0
  object_properties_init(&intern->std, ce);
265
266
0
  intern->handle = NULL;
267
0
  intern->watched_events = 0;
268
0
  intern->triggered_events = 0;
269
0
  intern->active = false;
270
0
  intern->closed = false;
271
0
  intern->context = NULL;
272
0
  intern->fd = SOCK_ERR;
273
0
  intern->stream = NULL;
274
0
  ZVAL_NULL(&intern->data);
275
276
0
  return &intern->std;
277
0
}
278
279
static zend_object *php_io_poll_context_create_object(zend_class_entry *ce)
280
0
{
281
0
  php_io_poll_context_object *intern = zend_object_alloc(sizeof(php_io_poll_context_object), ce);
282
283
0
  zend_object_std_init(&intern->std, ce);
284
0
  object_properties_init(&intern->std, ce);
285
286
0
  intern->ctx = NULL;
287
0
  intern->watchers = NULL;
288
289
0
  return &intern->std;
290
0
}
291
292
/* Watcher registration helpers */
293
294
static zend_always_inline zend_ulong php_io_poll_compute_ptr_key(void *ptr)
295
0
{
296
0
  zend_ulong key = (zend_ulong) (uintptr_t) ptr;
297
0
  return (key >> 3) | (key << ((sizeof(key) * 8) - 3));
298
0
}
299
300
static zend_always_inline void php_io_poll_watcher_deactivate(php_io_poll_watcher_object *watcher)
301
0
{
302
0
  watcher->active = false;
303
0
  watcher->context = NULL;
304
0
  watcher->fd = SOCK_ERR;
305
0
}
306
307
static void php_io_poll_stream_watch(php_stream *stream, php_io_poll_watcher_object *watcher)
308
0
{
309
0
  if (!stream->poll_watchers) {
310
0
    stream->poll_watchers = pemalloc(sizeof(HashTable), stream->is_persistent);
311
0
    zend_hash_init(stream->poll_watchers, 4, NULL, NULL, stream->is_persistent);
312
0
  }
313
314
0
  zval zv;
315
0
  ZVAL_PTR(&zv, watcher);
316
0
  zend_hash_index_add_new(stream->poll_watchers, php_io_poll_compute_ptr_key(watcher), &zv);
317
0
  watcher->stream = stream;
318
0
}
319
320
static void php_io_poll_stream_unwatch(php_io_poll_watcher_object *watcher)
321
0
{
322
0
  php_stream *stream = watcher->stream;
323
324
0
  if (!stream) {
325
0
    return;
326
0
  }
327
0
  watcher->stream = NULL;
328
329
0
  zend_hash_index_del(stream->poll_watchers, php_io_poll_compute_ptr_key(watcher));
330
0
  if (zend_hash_num_elements(stream->poll_watchers) == 0) {
331
0
    zend_hash_destroy(stream->poll_watchers);
332
0
    pefree(stream->poll_watchers, stream->is_persistent);
333
0
    stream->poll_watchers = NULL;
334
0
  }
335
0
}
336
337
static void php_io_poll_context_retire_watcher(
338
    php_io_poll_context_object *context, php_io_poll_watcher_object *watcher)
339
0
{
340
0
  php_socket_t fd = watcher->fd;
341
342
0
  php_poll_remove(context->ctx, (int) fd);
343
0
  php_io_poll_stream_unwatch(watcher);
344
0
  php_io_poll_watcher_deactivate(watcher);
345
0
  zend_hash_index_del(context->watchers, (zend_ulong) fd);
346
0
}
347
348
/* Called from php_stream_free() while the fd is still open */
349
PHPAPI void php_io_poll_stream_notify_close(php_stream *stream)
350
0
{
351
0
  HashTable *watchers = stream->poll_watchers;
352
0
  stream->poll_watchers = NULL;
353
354
0
  ZEND_HASH_FOREACH_VAL(watchers, zval *zv) {
355
0
    php_io_poll_watcher_object *watcher = Z_PTR_P(zv);
356
0
    watcher->stream = NULL;
357
0
    watcher->closed = true;
358
0
    if (watcher->context) {
359
0
      php_io_poll_context_retire_watcher(watcher->context, watcher);
360
0
    }
361
0
  } ZEND_HASH_FOREACH_END();
362
363
0
  zend_hash_destroy(watchers);
364
0
  pefree(watchers, stream->is_persistent);
365
0
}
366
367
/* Object Destruction Functions */
368
369
static void php_io_poll_watcher_free_object(zend_object *obj)
370
0
{
371
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(obj);
372
373
0
  php_io_poll_stream_unwatch(intern);
374
0
  zval_ptr_dtor(&intern->data);
375
376
0
  if (intern->handle) {
377
0
    OBJ_RELEASE(&intern->handle->std);
378
0
  }
379
380
0
  zend_object_std_dtor(&intern->std);
381
0
}
382
383
static void php_io_poll_context_free_object(zend_object *obj)
384
0
{
385
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(obj);
386
387
0
  if (intern->watchers) {
388
0
    ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) {
389
0
      php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(zv));
390
0
      php_io_poll_stream_unwatch(watcher);
391
0
      php_io_poll_watcher_deactivate(watcher);
392
0
    } ZEND_HASH_FOREACH_END();
393
0
  }
394
395
0
  if (intern->ctx) {
396
0
    php_poll_destroy(intern->ctx);
397
0
  }
398
399
0
  if (intern->watchers) {
400
0
    zend_hash_destroy(intern->watchers);
401
0
    efree(intern->watchers);
402
0
  }
403
404
0
  zend_object_std_dtor(&intern->std);
405
0
}
406
407
static HashTable *php_io_poll_watcher_get_gc(zend_object *obj, zval **table, int *n)
408
0
{
409
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(obj);
410
0
  zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
411
412
0
  zend_get_gc_buffer_add_zval(gc_buffer, &intern->data);
413
0
  if (intern->handle) {
414
0
    zend_get_gc_buffer_add_obj(gc_buffer, &intern->handle->std);
415
0
  }
416
417
0
  zend_get_gc_buffer_use(gc_buffer, table, n);
418
0
  return NULL;
419
0
}
420
421
static HashTable *php_io_poll_context_get_gc(zend_object *obj, zval **table, int *n)
422
0
{
423
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(obj);
424
0
  zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
425
426
0
  if (intern->watchers) {
427
0
    ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) {
428
0
      zend_get_gc_buffer_add_zval(gc_buffer, zv);
429
0
    } ZEND_HASH_FOREACH_END();
430
0
  }
431
432
0
  zend_get_gc_buffer_use(gc_buffer, table, n);
433
0
  return NULL;
434
0
}
435
436
/* Utility functions */
437
438
static zend_result php_io_poll_watcher_modify_events(
439
    php_io_poll_watcher_object *watcher, uint32_t events)
440
0
{
441
0
  if (!watcher->active || !watcher->context) {
442
0
    zend_throw_exception(
443
0
        php_io_poll_inactive_watcher_class_entry, "Cannot modify inactive watcher", 0);
444
0
    return FAILURE;
445
0
  }
446
447
  /* Re-add if the backend dropped a fired one-shot registration */
448
0
  php_poll_ctx *poll_ctx = watcher->context->ctx;
449
0
  int fd = (int) watcher->fd;
450
0
  if (php_poll_modify(poll_ctx, fd, events, watcher) != SUCCESS
451
0
      && (php_poll_get_error(poll_ctx) != PHP_POLL_ERR_NOTFOUND
452
0
          || php_poll_add(poll_ctx, fd, events, watcher) != SUCCESS)) {
453
0
    php_poll_error err = php_poll_get_error(poll_ctx);
454
0
    php_io_poll_throw_failed_operation(php_io_poll_failed_watcher_mod_class_entry,
455
0
        "Failed to modify watcher in polling system", err);
456
0
    return FAILURE;
457
0
  }
458
459
  /* Update watcher state */
460
0
  watcher->watched_events = events;
461
462
0
  return SUCCESS;
463
0
}
464
465
static zend_result php_io_poll_watcher_modify_data(php_io_poll_watcher_object *watcher, zval *data)
466
0
{
467
0
  if (!watcher->active) {
468
0
    zend_throw_exception(
469
0
        php_io_poll_inactive_watcher_class_entry, "Cannot modify inactive watcher", 0);
470
0
    return FAILURE;
471
0
  }
472
473
  /* Update user data */
474
0
  zval_ptr_dtor(&watcher->data);
475
0
  ZVAL_COPY(&watcher->data, data);
476
477
0
  return SUCCESS;
478
0
}
479
480
/* PHP Method Implementations */
481
482
PHP_METHOD(Io_Poll_Backend, getAvailableBackends)
483
0
{
484
0
  ZEND_PARSE_PARAMETERS_NONE();
485
486
0
  array_init(return_value);
487
488
  /* Check each backend type for availability */
489
0
  php_poll_backend_type backends[] = {PHP_POLL_BACKEND_POLL, PHP_POLL_BACKEND_EPOLL,
490
0
      PHP_POLL_BACKEND_KQUEUE, PHP_POLL_BACKEND_EVENTPORT, PHP_POLL_BACKEND_WSAPOLL};
491
492
0
  for (size_t i = 0; i < sizeof(backends) / sizeof(backends[0]); i++) {
493
0
    if (php_poll_is_backend_available(backends[i])) {
494
0
      const char *name = php_io_poll_backend_type_to_name(backends[i]);
495
0
      zval enum_case;
496
0
      ZVAL_OBJ_COPY(&enum_case, zend_enum_get_case_cstr(php_io_poll_backend_class_entry, name));
497
0
      add_next_index_zval(return_value, &enum_case);
498
0
    }
499
0
  }
500
0
}
501
502
PHP_METHOD(Io_Poll_Backend, isAvailable)
503
0
{
504
0
  ZEND_PARSE_PARAMETERS_NONE();
505
506
0
  zend_object *enum_obj = Z_OBJ_P(ZEND_THIS);
507
0
  php_poll_backend_type type = php_io_poll_backend_enum_to_type(enum_obj);
508
509
0
  RETURN_BOOL(php_poll_is_backend_available(type));
510
0
}
511
512
PHP_METHOD(Io_Poll_Backend, supportsEdgeTriggering)
513
0
{
514
0
  ZEND_PARSE_PARAMETERS_NONE();
515
516
0
  zend_object *enum_obj = Z_OBJ_P(ZEND_THIS);
517
0
  php_poll_backend_type type = php_io_poll_backend_enum_to_type(enum_obj);
518
519
0
  RETURN_BOOL(php_poll_backend_supports_edge_triggering(type));
520
0
}
521
522
PHP_METHOD(StreamPollHandle, __construct)
523
0
{
524
0
  php_stream *stream;
525
526
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
527
0
    PHP_Z_PARAM_STREAM(stream)
528
0
  ZEND_PARSE_PARAMETERS_END();
529
530
0
  php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis());
531
532
0
  if (intern->handle_data) {
533
0
    zend_throw_error(NULL, "StreamPollHandle object is already constructed");
534
0
    RETURN_THROWS();
535
0
  }
536
537
  /* Set up stream-specific data */
538
0
  php_stream_poll_handle_data *data = emalloc(sizeof(php_stream_poll_handle_data));
539
0
  data->res = stream->res;
540
0
  intern->handle_data = data;
541
542
  /* Add reference to stream */
543
0
  GC_ADDREF(data->res);
544
0
}
545
546
PHP_METHOD(StreamPollHandle, getStream)
547
0
{
548
0
  ZEND_PARSE_PARAMETERS_NONE();
549
550
0
  php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis());
551
0
  php_stream_poll_handle_data *data = intern->handle_data;
552
553
0
  if (!data || !data->res) {
554
0
    RETURN_NULL();
555
0
  }
556
557
0
  GC_ADDREF(data->res);
558
0
  ZVAL_RES(return_value, data->res);
559
0
}
560
561
PHP_METHOD(StreamPollHandle, isValid)
562
0
{
563
0
  ZEND_PARSE_PARAMETERS_NONE();
564
565
0
  php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis());
566
0
  RETURN_BOOL(intern->ops->is_valid(intern));
567
0
}
568
569
PHP_METHOD(Io_Poll_Watcher, __construct)
570
0
{
571
0
  zend_throw_error(NULL, "Cannot directly construct Watcher, use Context::add");
572
0
}
573
574
PHP_METHOD(Io_Poll_Watcher, getHandle)
575
0
{
576
0
  ZEND_PARSE_PARAMETERS_NONE();
577
578
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
579
0
  if (!intern->handle) {
580
0
    RETURN_NULL();
581
0
  }
582
583
0
  RETURN_OBJ_COPY(&intern->handle->std);
584
0
}
585
586
PHP_METHOD(Io_Poll_Watcher, getWatchedEvents)
587
0
{
588
0
  ZEND_PARSE_PARAMETERS_NONE();
589
590
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
591
0
  php_io_poll_events_to_event_enums(intern->watched_events, return_value);
592
0
}
593
594
PHP_METHOD(Io_Poll_Watcher, getTriggeredEvents)
595
0
{
596
0
  ZEND_PARSE_PARAMETERS_NONE();
597
598
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
599
0
  php_io_poll_events_to_event_enums(intern->triggered_events, return_value);
600
0
}
601
602
PHP_METHOD(Io_Poll_Watcher, getData)
603
0
{
604
0
  ZEND_PARSE_PARAMETERS_NONE();
605
606
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
607
0
  ZVAL_COPY(return_value, &intern->data);
608
0
}
609
610
PHP_METHOD(Io_Poll_Watcher, hasTriggered)
611
0
{
612
0
  zval *event_enum;
613
614
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
615
0
    Z_PARAM_OBJECT_OF_CLASS(event_enum, php_io_poll_event_class_entry)
616
0
  ZEND_PARSE_PARAMETERS_END();
617
618
0
  uint32_t event_bit = php_io_poll_event_enum_to_bit(Z_OBJ_P(event_enum));
619
620
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
621
0
  RETURN_BOOL((intern->triggered_events & event_bit) != 0);
622
0
}
623
624
PHP_METHOD(Io_Poll_Watcher, isActive)
625
0
{
626
0
  ZEND_PARSE_PARAMETERS_NONE();
627
628
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
629
0
  RETURN_BOOL(intern->active);
630
0
}
631
632
PHP_METHOD(Io_Poll_Watcher, modify)
633
0
{
634
0
  zval *event_enums;
635
0
  zval *data = NULL;
636
637
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
638
0
    Z_PARAM_ARRAY(event_enums)
639
0
    Z_PARAM_OPTIONAL
640
0
    Z_PARAM_ZVAL(data)
641
0
  ZEND_PARSE_PARAMETERS_END();
642
643
0
  uint32_t events = php_io_poll_event_enums_to_events(event_enums);
644
0
  if (!events) {
645
0
    zend_argument_type_error(1, "must be array of Event enums");
646
0
    RETURN_THROWS();
647
0
  }
648
649
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
650
651
  /* Modify events first */
652
0
  if (php_io_poll_watcher_modify_events(intern, events) != SUCCESS) {
653
0
    RETURN_THROWS();
654
0
  }
655
656
  /* Then modify data if provided */
657
0
  if (data) {
658
0
    if (php_io_poll_watcher_modify_data(intern, data) != SUCCESS) {
659
0
      RETURN_THROWS();
660
0
    }
661
0
  }
662
0
}
663
664
PHP_METHOD(Io_Poll_Watcher, modifyEvents)
665
0
{
666
0
  zval *event_enums;
667
668
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
669
0
    Z_PARAM_ARRAY(event_enums)
670
0
  ZEND_PARSE_PARAMETERS_END();
671
672
0
  uint32_t events = php_io_poll_event_enums_to_events(event_enums);
673
0
  if (!events) {
674
0
    zend_argument_type_error(1, "must be array of Event enums");
675
0
    RETURN_THROWS();
676
0
  }
677
678
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
679
680
0
  if (php_io_poll_watcher_modify_events(intern, events) != SUCCESS) {
681
0
    RETURN_THROWS();
682
0
  }
683
0
}
684
685
PHP_METHOD(Io_Poll_Watcher, modifyData)
686
0
{
687
0
  zval *data;
688
689
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
690
0
    Z_PARAM_ZVAL(data)
691
0
  ZEND_PARSE_PARAMETERS_END();
692
693
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
694
695
0
  if (php_io_poll_watcher_modify_data(intern, data) != SUCCESS) {
696
0
    RETURN_THROWS();
697
0
  }
698
0
}
699
700
PHP_METHOD(Io_Poll_Watcher, remove)
701
0
{
702
0
  ZEND_PARSE_PARAMETERS_NONE();
703
704
0
  php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis());
705
706
0
  if (!intern->active || !intern->context) {
707
    /* Closing the stream already removed it, so this is just the expected cleanup */
708
0
    if (intern->closed) {
709
0
      return;
710
0
    }
711
0
    zend_throw_exception(
712
0
        php_io_poll_inactive_watcher_class_entry, "Cannot remove inactive watcher", 0);
713
0
    RETURN_THROWS();
714
0
  }
715
716
0
  php_io_poll_context_retire_watcher(intern->context, intern);
717
0
}
718
719
PHP_METHOD(Io_Poll_Context, __construct)
720
0
{
721
0
  zval *backend_obj = NULL;
722
723
0
  ZEND_PARSE_PARAMETERS_START(0, 1)
724
0
    Z_PARAM_OPTIONAL
725
0
    Z_PARAM_OBJECT_OF_CLASS(backend_obj, php_io_poll_backend_class_entry)
726
0
  ZEND_PARSE_PARAMETERS_END();
727
728
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());
729
730
0
  if (intern->ctx) {
731
0
    zend_throw_error(NULL, "Io\\Poll\\Context object is already constructed");
732
0
    RETURN_THROWS();
733
0
  }
734
735
0
  php_poll_backend_type backend_type = PHP_POLL_BACKEND_AUTO;
736
0
  if (backend_obj != NULL) {
737
0
    backend_type = php_io_poll_backend_enum_to_type(Z_OBJ_P(backend_obj));
738
0
  }
739
740
0
  intern->ctx = php_poll_create(backend_type, 0);
741
742
0
  if (!intern->ctx) {
743
0
    zend_throw_exception_ex(
744
0
        php_io_poll_failed_backend_unavailable_class_entry, 0, "Backend %s not available",
745
0
        php_io_poll_backend_type_to_name(backend_type));
746
0
    RETURN_THROWS();
747
0
  }
748
749
0
  if (php_poll_init(intern->ctx) != SUCCESS) {
750
0
    php_poll_error err = php_poll_get_error(intern->ctx);
751
0
    php_poll_destroy(intern->ctx);
752
0
    intern->ctx = NULL;
753
0
    php_io_poll_throw_failed_operation(php_io_poll_failed_context_init_class_entry,
754
0
        "Failed to initialize polling context", err);
755
0
    RETURN_THROWS();
756
0
  }
757
758
0
  intern->watchers = emalloc(sizeof(HashTable));
759
0
  zend_hash_init(intern->watchers, 8, NULL, ZVAL_PTR_DTOR, 0);
760
0
}
761
762
PHP_METHOD(Io_Poll_Context, add)
763
0
{
764
0
  zval *handle_obj, *event_enums;
765
0
  uint32_t events;
766
0
  zval *data = NULL;
767
768
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
769
0
    Z_PARAM_OBJECT_OF_CLASS(handle_obj, php_io_poll_handle_class_entry)
770
0
    Z_PARAM_ARRAY(event_enums)
771
0
    Z_PARAM_OPTIONAL
772
0
    Z_PARAM_ZVAL(data)
773
0
  ZEND_PARSE_PARAMETERS_END();
774
775
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());
776
0
  php_poll_handle_object *handle = PHP_POLL_HANDLE_OBJ_FROM_ZV(handle_obj);
777
778
0
  events = php_io_poll_event_enums_to_events(event_enums);
779
0
  if (!events) {
780
0
    zend_argument_type_error(2, "must be array of Event enums");
781
0
    RETURN_THROWS();
782
0
  }
783
784
  /* Get file descriptor */
785
0
  php_socket_t fd = php_poll_handle_get_fd(handle);
786
0
  if (fd == SOCK_ERR) {
787
0
    zend_throw_exception(
788
0
        php_io_poll_invalid_handle_class_entry, "Invalid handle for polling", 0);
789
0
    RETURN_THROWS();
790
0
  }
791
792
0
  zval *existing_zv = zend_hash_index_find(intern->watchers, (zend_ulong) fd);
793
0
  if (existing_zv) {
794
0
    php_io_poll_watcher_object *existing = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(existing_zv));
795
0
    if (php_poll_handle_get_fd(existing->handle) == fd) {
796
0
      zend_throw_exception(
797
0
          php_io_poll_handle_already_watched_class_entry, "Handle already added", 0);
798
0
      RETURN_THROWS();
799
0
    }
800
0
    php_io_poll_context_retire_watcher(intern, existing);
801
0
  }
802
803
  /* Create watcher object */
804
0
  object_init_ex(return_value, php_io_poll_watcher_class_entry);
805
0
  php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZV(return_value);
806
807
0
  watcher->handle = handle;
808
0
  watcher->watched_events = events;
809
0
  watcher->triggered_events = 0;
810
811
0
  GC_ADDREF(&handle->std);
812
813
0
  if (data) {
814
0
    ZVAL_COPY(&watcher->data, data);
815
0
  } else {
816
0
    ZVAL_NULL(&watcher->data);
817
0
  }
818
819
  /* Add to poll context */
820
0
  if (php_poll_add(intern->ctx, (int) fd, events, watcher) != SUCCESS) {
821
0
    php_poll_error err = php_poll_get_error(intern->ctx);
822
0
    if (err == PHP_POLL_ERR_EXISTS) {
823
0
      zend_throw_exception(
824
0
          php_io_poll_handle_already_watched_class_entry, "Handle already added", 0);
825
0
    } else {
826
0
      php_io_poll_throw_failed_operation(
827
0
          php_io_poll_failed_handle_add_class_entry, "Failed to add handle", err);
828
0
    }
829
0
    RETURN_THROWS();
830
0
  }
831
832
  /* Store in our watchers map */
833
0
  zval watcher_zv;
834
0
  ZVAL_OBJ_COPY(&watcher_zv, &watcher->std);
835
0
  zend_hash_index_add_new(intern->watchers, (zend_ulong) fd, &watcher_zv);
836
837
0
  watcher->active = true;
838
0
  watcher->context = intern;
839
0
  watcher->fd = fd;
840
841
0
  if (handle->ops == &php_stream_poll_handle_ops) {
842
0
    php_stream *stream = php_stream_poll_handle_get_stream(handle);
843
0
    if (stream) {
844
0
      php_io_poll_stream_watch(stream, watcher);
845
0
    }
846
0
  }
847
0
}
848
849
PHP_METHOD(Io_Poll_Context, wait)
850
0
{
851
0
  php_date_time_duration *timeout = NULL;
852
0
  zend_long max_events = 0;
853
0
  bool max_events_is_null = true;
854
855
0
  ZEND_PARSE_PARAMETERS_START(0, 2)
856
0
    Z_PARAM_OPTIONAL
857
0
    Z_PARAM_DATE_TIME_DURATION_OR_NULL(timeout)
858
0
    Z_PARAM_LONG_OR_NULL(max_events, max_events_is_null)
859
0
  ZEND_PARSE_PARAMETERS_END();
860
861
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());
862
863
  /* Build timespec from php_date_time_duration, or NULL for indefinite */
864
0
  struct timespec timeout_ts;
865
0
  if (timeout) {
866
0
    if (timeout->duration.negative) {
867
0
      zend_argument_value_error(1, "must not be negative");
868
0
      RETURN_THROWS();
869
0
    }
870
871
0
    timeout_ts.tv_sec = timeout->duration.seconds;
872
0
    timeout_ts.tv_nsec = timeout->duration.nanoseconds;
873
0
  }
874
875
0
  if (max_events_is_null) {
876
0
    max_events = php_poll_get_suitable_max_events(intern->ctx);
877
0
    if (max_events <= 0) {
878
0
      max_events = 64;
879
0
    }
880
0
  } else if (UNEXPECTED(max_events <= 0)) {
881
0
    zend_argument_value_error(2, "must be greater than 0");
882
0
    RETURN_THROWS();
883
0
  } else if (ZEND_LONG_INT_OVFL(max_events)) {
884
0
    zend_argument_value_error(2, "must be less than or equal to %d", INT_MAX);
885
0
    RETURN_THROWS();
886
0
  }
887
888
0
  php_poll_event *events = safe_emalloc((size_t) max_events, sizeof(*events), 0);
889
0
  int num_events = php_poll_wait(intern->ctx, events, (int) max_events, timeout ? &timeout_ts : NULL);
890
891
0
  if (num_events < 0) {
892
0
    php_poll_error err = php_poll_get_error(intern->ctx);
893
0
    efree(events);
894
0
    php_io_poll_throw_failed_operation(
895
0
        php_io_poll_failed_wait_class_entry, "Poll wait failed", err);
896
0
    RETURN_THROWS();
897
0
  }
898
899
0
  array_init(return_value);
900
901
0
  for (int i = 0; i < num_events; i++) {
902
0
    php_io_poll_watcher_object *watcher = (php_io_poll_watcher_object *) events[i].data;
903
0
    if (watcher) {
904
0
      watcher->triggered_events = events[i].revents;
905
906
0
      add_next_index_object(return_value, zend_object_copy(&watcher->std));
907
0
    }
908
0
  }
909
910
0
  efree(events);
911
0
}
912
913
PHP_METHOD(Io_Poll_Context, getBackend)
914
0
{
915
0
  ZEND_PARSE_PARAMETERS_NONE();
916
917
0
  php_io_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis());
918
0
  php_poll_backend_type backend_type = php_poll_get_backend_type(intern->ctx);
919
0
  const char *backend_name = php_io_poll_backend_type_to_name(backend_type);
920
921
0
  RETURN_OBJ_COPY(zend_enum_get_case_cstr(php_io_poll_backend_class_entry, backend_name));
922
0
}
923
924
/* Initialize the stream poll classes - add to PHP_MINIT_FUNCTION */
925
PHP_MINIT_FUNCTION(poll)
926
16
{
927
  /* Register backend enum */
928
16
  php_io_poll_backend_class_entry = register_class_Io_Poll_Backend();
929
930
  /* Register event enum */
931
16
  php_io_poll_event_class_entry = register_class_Io_Poll_Event();
932
933
  /* Register Handle interface */
934
16
  php_io_poll_handle_class_entry = register_class_Io_Poll_Handle();
935
16
  php_io_poll_handle_class_entry->interface_gets_implemented = php_stream_poll_handle_implement_interface;
936
937
  /* Register StreamPollHandle class */
938
16
  php_stream_poll_handle_class_entry
939
16
      = register_class_StreamPollHandle(php_io_poll_handle_class_entry);
940
16
  php_stream_poll_handle_class_entry->create_object = php_stream_poll_handle_create_object;
941
942
16
  memcpy(&php_io_poll_handle_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
943
16
  php_io_poll_handle_object_handlers.offset = offsetof(php_poll_handle_object, std);
944
16
  php_io_poll_handle_object_handlers.free_obj = php_poll_handle_object_free;
945
16
  php_io_poll_handle_object_handlers.clone_obj = NULL;
946
16
  php_stream_poll_handle_class_entry->default_object_handlers = &php_io_poll_handle_object_handlers;
947
948
  /* Register Watcher class */
949
16
  php_io_poll_watcher_class_entry = register_class_Io_Poll_Watcher();
950
16
  php_io_poll_watcher_class_entry->create_object = php_io_poll_watcher_create_object;
951
952
16
  memcpy(&php_io_poll_watcher_object_handlers, &std_object_handlers,
953
16
      sizeof(zend_object_handlers));
954
16
  php_io_poll_watcher_object_handlers.offset = offsetof(php_io_poll_watcher_object, std);
955
16
  php_io_poll_watcher_object_handlers.free_obj = php_io_poll_watcher_free_object;
956
16
  php_io_poll_watcher_object_handlers.get_gc = php_io_poll_watcher_get_gc;
957
16
  php_io_poll_watcher_object_handlers.clone_obj = NULL;
958
16
  php_io_poll_watcher_class_entry->default_object_handlers = &php_io_poll_watcher_object_handlers;
959
960
  /* Register Context class */
961
16
  php_io_poll_context_class_entry = register_class_Io_Poll_Context();
962
16
  php_io_poll_context_class_entry->create_object = php_io_poll_context_create_object;
963
964
16
  memcpy(&php_io_poll_context_object_handlers, &std_object_handlers,
965
16
      sizeof(zend_object_handlers));
966
16
  php_io_poll_context_object_handlers.offset = offsetof(php_io_poll_context_object, std);
967
16
  php_io_poll_context_object_handlers.free_obj = php_io_poll_context_free_object;
968
16
  php_io_poll_context_object_handlers.get_gc = php_io_poll_context_get_gc;
969
16
  php_io_poll_context_object_handlers.clone_obj = NULL;
970
16
  php_io_poll_context_class_entry->default_object_handlers = &php_io_poll_context_object_handlers;
971
972
  /* Register exception hierarchy */
973
16
  php_io_exception_class_entry = register_class_Io_IoException(zend_ce_exception);
974
975
16
  php_io_poll_exception_class_entry
976
16
      = register_class_Io_Poll_PollException(php_io_exception_class_entry);
977
978
16
  php_io_poll_failed_operation_class_entry = register_class_Io_Poll_FailedPollOperationException(
979
16
      php_io_poll_exception_class_entry);
980
981
16
  php_io_poll_failed_context_init_class_entry
982
16
      = register_class_Io_Poll_FailedContextInitializationException(
983
16
          php_io_poll_failed_operation_class_entry);
984
985
16
  php_io_poll_failed_handle_add_class_entry = register_class_Io_Poll_FailedHandleAddException(
986
16
      php_io_poll_failed_operation_class_entry);
987
988
16
  php_io_poll_failed_watcher_mod_class_entry
989
16
      = register_class_Io_Poll_FailedWatcherModificationException(
990
16
          php_io_poll_failed_operation_class_entry);
991
992
16
  php_io_poll_failed_wait_class_entry = register_class_Io_Poll_FailedPollWaitException(
993
16
      php_io_poll_failed_operation_class_entry);
994
995
16
  php_io_poll_failed_backend_unavailable_class_entry = register_class_Io_Poll_BackendUnavailableException(
996
16
    php_io_poll_exception_class_entry);
997
998
16
  php_io_poll_inactive_watcher_class_entry = register_class_Io_Poll_InactiveWatcherException(
999
16
      php_io_poll_exception_class_entry);
1000
1001
16
  php_io_poll_handle_already_watched_class_entry
1002
16
      = register_class_Io_Poll_HandleAlreadyWatchedException(
1003
16
          php_io_poll_exception_class_entry);
1004
1005
16
  php_io_poll_invalid_handle_class_entry
1006
16
      = register_class_Io_Poll_InvalidHandleException(php_io_poll_exception_class_entry);
1007
1008
  /* Initialize polling backends */
1009
16
  php_poll_register_backends();
1010
1011
16
  return SUCCESS;
1012
16
}