Coverage Report

Created: 2026-09-14 06:25

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