Coverage Report

Created: 2026-07-25 06:39

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