diff --git a/ext/curl/curl_private.h b/ext/curl/curl_private.h index 7058e9df9241..13942c384799 100644 --- a/ext/curl/curl_private.h +++ b/ext/curl/curl_private.h @@ -114,6 +114,9 @@ typedef struct { zval private_data; /* CurlShareHandle object set using CURLOPT_SHARE. */ struct _php_curlsh *share; + /* Socket/timer state during curl_exec (NULL outside exec) */ + HashTable *io_sockets; /* curl_socket_t -> int events */ + long io_timer_ms; /* timer value from TIMERFUNCTION, -1 = disabled */ zend_object std; } php_curl; diff --git a/ext/curl/curl_socket_handle.stub.php b/ext/curl/curl_socket_handle.stub.php new file mode 100644 index 000000000000..68469010cfd2 --- /dev/null +++ b/ext/curl/curl_socket_handle.stub.php @@ -0,0 +1,14 @@ +create_object = php_curl_socket_handle_create_object; + memcpy(&php_curl_socket_handle_object_handlers, &std_object_handlers, + sizeof(zend_object_handlers)); + php_curl_socket_handle_object_handlers.offset = XtOffsetOf(php_poll_handle_object, std); + php_curl_socket_handle_object_handlers.free_obj = php_poll_handle_object_free; + php_curl_socket_handle_ce->default_object_handlers = &php_curl_socket_handle_object_handlers; + return SUCCESS; } /* }}} */ @@ -1058,6 +1080,8 @@ void init_curl_handle(php_curl *ch) zend_hash_init(&ch->to_free->slist, 4, NULL, curl_free_slist, 0); ZVAL_UNDEF(&ch->postfields); + ch->io_sockets = NULL; + ch->io_timer_ms = -1; } /* }}} */ @@ -2309,6 +2333,291 @@ PHP_FUNCTION(curl_setopt_array) } /* }}} */ +/* Io\Curl\SocketHandle: wraps a curl_socket_t as an Io\Poll\Handle */ + +typedef struct { + curl_socket_t socket; +} php_curl_socket_handle_data; + +static php_socket_t php_curl_socket_handle_get_fd(php_poll_handle_object *handle) +{ + return (php_socket_t)((php_curl_socket_handle_data *)handle->handle_data)->socket; +} + +static int php_curl_socket_handle_is_valid(php_poll_handle_object *handle) +{ + return handle->handle_data != NULL && + ((php_curl_socket_handle_data *)handle->handle_data)->socket != CURL_SOCKET_BAD; +} + +static void php_curl_socket_handle_cleanup(php_poll_handle_object *handle) +{ + efree(handle->handle_data); + handle->handle_data = NULL; +} + +static php_poll_handle_ops php_curl_socket_handle_ops = { + .get_fd = php_curl_socket_handle_get_fd, + .is_valid = php_curl_socket_handle_is_valid, + .cleanup = php_curl_socket_handle_cleanup, +}; + +static zend_object *php_curl_socket_handle_create_object(zend_class_entry *ce) +{ + php_poll_handle_object *intern = php_poll_handle_object_create( + sizeof(php_poll_handle_object), ce, &php_curl_socket_handle_ops); + intern->std.handlers = &php_curl_socket_handle_object_handlers; + return &intern->std; +} + +static void php_curl_socket_handle_from_fd(zval *dest, curl_socket_t s) +{ + object_init_ex(dest, php_curl_socket_handle_ce); + php_poll_handle_object *h = PHP_POLL_HANDLE_OBJ_FROM_ZV(dest); + php_curl_socket_handle_data *data = emalloc(sizeof(php_curl_socket_handle_data)); + data->socket = s; + h->handle_data = data; +} + +/* CURLMOPT_SOCKETFUNCTION: log socket+events in ch->io_sockets */ +static int php_curl_socket_callback(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) +{ + php_curl *ch = (php_curl *)userp; + + if (what == CURL_POLL_REMOVE) { + if (ch->io_sockets) { + zend_hash_index_del(ch->io_sockets, (zend_ulong)s); + } + } else { + if (!ch->io_sockets) { + ch->io_sockets = emalloc(sizeof(HashTable)); + zend_hash_init(ch->io_sockets, 4, NULL, NULL, 0); + } + zval zv; + ZVAL_LONG(&zv, what); + zend_hash_index_update(ch->io_sockets, (zend_ulong)s, &zv); + } + return 0; +} + +/* CURLMOPT_TIMERFUNCTION: record the requested timeout */ +static int php_curl_timer_callback(CURLM *multi, long timeout_ms, void *userp) +{ + php_curl *ch = (php_curl *)userp; + ch->io_timer_ms = timeout_ms; + return 0; +} + +/* Translate CURL_POLL_* to Io\Poll\Event[] array */ +static void php_curl_poll_events_to_zval(int what, zval *dest) +{ + array_init(dest); + if (what & CURL_POLL_IN) { + zval zv; + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Read")); + zend_hash_next_index_insert(Z_ARRVAL_P(dest), &zv); + } + if (what & CURL_POLL_OUT) { + zval zv; + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Write")); + zend_hash_next_index_insert(Z_ARRVAL_P(dest), &zv); + } +} + +/* Translate PollResult::$events back to CURL_CSELECT_* flags */ +static int php_curl_poll_result_to_curl_events(zval *events_arr) +{ + int curl_events = 0; + zval *event; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(events_arr), event) { + if (Z_TYPE_P(event) != IS_OBJECT) continue; + zend_string *name = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(event))); + if (zend_string_equals_literal(name, "Read")) curl_events |= CURL_CSELECT_IN; + if (zend_string_equals_literal(name, "Write")) curl_events |= CURL_CSELECT_OUT; + if (zend_string_equals_literal(name, "Error")) curl_events |= CURL_CSELECT_ERR; + } ZEND_HASH_FOREACH_END(); + return curl_events; +} + +/* Main multi-based exec loop */ +static CURLcode php_curl_exec_multi(php_curl *ch) +{ + CURLcode result = CURLE_OK; + CURLM *multi = curl_multi_init(); + curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, php_curl_socket_callback); + curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, ch); + curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, php_curl_timer_callback); + curl_multi_setopt(multi, CURLMOPT_TIMERDATA, ch); + + curl_multi_add_handle(multi, ch->cp); + + int still_running; + curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &still_running); + + while (still_running) { + /* If timer is zero, fire immediately again without waiting */ + if (ch->io_timer_ms == 0) { + curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &still_running); + continue; + } + + if (PHP_HAS_IO_POLL_HOOK()) { + /* Count active sockets */ + uint32_t n = ch->io_sockets ? zend_hash_num_elements(ch->io_sockets) : 0; + + /* Build argv: [timeout_ms_or_null, PollInfo...] */ + zval *params = safe_emalloc(n, sizeof(zval), sizeof(zval)); + + if (ch->io_timer_ms < 0) { + ZVAL_NULL(¶ms[0]); + } else { + ZVAL_LONG(¶ms[0], ch->io_timer_ms); + } + + uint32_t i = 1; + if (n > 0) { + zend_ulong sock_ulong; + zval *events_zv; + ZEND_HASH_FOREACH_NUM_KEY_VAL(ch->io_sockets, sock_ulong, events_zv) { + zval *poll_info = ¶ms[i++]; + object_init_ex(poll_info, php_io_hooks_poll_info_ce); + + zval handle; + php_curl_socket_handle_from_fd(&handle, (curl_socket_t)sock_ulong); + zend_update_property(php_io_hooks_poll_info_ce, Z_OBJ_P(poll_info), + "handle", sizeof("handle") - 1, &handle); + zval_ptr_dtor(&handle); + + zval evts; + php_curl_poll_events_to_zval((int)Z_LVAL_P(events_zv), &evts); + zend_update_property(php_io_hooks_poll_info_ce, Z_OBJ_P(poll_info), + "events", sizeof("events") - 1, &evts); + zval_ptr_dtor(&evts); + + /* timeout_ms per PollInfo: -1 (we use global timeout via params[0]) */ + zend_update_property_long(php_io_hooks_poll_info_ce, Z_OBJ_P(poll_info), + "timeout_ms", sizeof("timeout_ms") - 1, -1); + } ZEND_HASH_FOREACH_END(); + } + + zval retval; + ZVAL_UNDEF(&retval); + zend_call_known_fcc(&FG(io_hooks_poll_multi_fcc), &retval, 1 + n, params, NULL); + + for (uint32_t j = 0; j < 1 + n; j++) { + zval_ptr_dtor(¶ms[j]); + } + efree(params); + + if (EG(exception)) { + zval_ptr_dtor(&retval); + break; + } + + if (zend_hash_num_elements(Z_ARRVAL(retval)) > 0) { + /* Drive ready sockets */ + zval *result; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL(retval), result) { + if (Z_TYPE_P(result) != IS_OBJECT || + Z_OBJCE_P(result) != php_io_hooks_poll_result_ce) continue; + + zval rv; + zval *handle_prop = zend_read_property(php_io_hooks_poll_result_ce, + Z_OBJ_P(result), "handle", sizeof("handle") - 1, 1, &rv); + if (Z_TYPE_P(handle_prop) != IS_OBJECT) continue; + + php_poll_handle_object *hobj = + PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(Z_OBJ_P(handle_prop)); + php_socket_t fd = php_poll_handle_get_fd(hobj); + + zval *events_prop = zend_read_property(php_io_hooks_poll_result_ce, + Z_OBJ_P(result), "events", sizeof("events") - 1, 1, &rv); + int curl_events = (Z_TYPE_P(events_prop) == IS_ARRAY) + ? php_curl_poll_result_to_curl_events(events_prop) : 0; + + curl_multi_socket_action(multi, (curl_socket_t)fd, curl_events, &still_running); + } ZEND_HASH_FOREACH_END(); + } else { + /* Empty array = timeout */ + curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &still_running); + } + zval_ptr_dtor(&retval); + } else { + /* No hook: select() on the sockets logged by the socket callback */ + fd_set rfds, wfds, efds; + FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); + php_socket_t max_fd = 0; + bool have_fds = false; + + if (ch->io_sockets) { + zend_ulong sock_ulong; + zval *ev; + ZEND_HASH_FOREACH_NUM_KEY_VAL(ch->io_sockets, sock_ulong, ev) { + curl_socket_t s = (curl_socket_t)sock_ulong; + int what = (int)Z_LVAL_P(ev); + if (what & CURL_POLL_IN) FD_SET(s, &rfds); + if (what & CURL_POLL_OUT) FD_SET(s, &wfds); + FD_SET(s, &efds); + if ((php_socket_t)s > max_fd) max_fd = (php_socket_t)s; + have_fds = true; + } ZEND_HASH_FOREACH_END(); + } + + long wait_ms = ch->io_timer_ms >= 0 ? ch->io_timer_ms : 1000; + struct timeval tv = { + .tv_sec = wait_ms / 1000, + .tv_usec = (wait_ms % 1000) * 1000, + }; + + int n = have_fds ? select((int)max_fd + 1, &rfds, &wfds, &efds, &tv) : 0; + + if (n > 0 && ch->io_sockets) { + /* Collect ready sockets before calling socket_action (avoids hash re-entry) */ + curl_socket_t ready_s[16]; + int ready_ev[16]; + int ready_n = 0; + zend_ulong sock_ulong; + zval *ev; + ZEND_HASH_FOREACH_NUM_KEY_VAL(ch->io_sockets, sock_ulong, ev) { + curl_socket_t s = (curl_socket_t)sock_ulong; + int ce = 0; + if (FD_ISSET(s, &rfds)) ce |= CURL_CSELECT_IN; + if (FD_ISSET(s, &wfds)) ce |= CURL_CSELECT_OUT; + if (FD_ISSET(s, &efds)) ce |= CURL_CSELECT_ERR; + if (ce && ready_n < 16) { ready_s[ready_n] = s; ready_ev[ready_n++] = ce; } + } ZEND_HASH_FOREACH_END(); + for (int k = 0; k < ready_n; k++) { + curl_multi_socket_action(multi, ready_s[k], ready_ev[k], &still_running); + } + } else { + curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &still_running); + } + } + + /* Check for completed messages */ + CURLMsg *msg; + int msgs_in_queue; + while ((msg = curl_multi_info_read(multi, &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE && msg->easy_handle == ch->cp) { + result = msg->data.result; + still_running = 0; + } + } + } + + curl_multi_remove_handle(multi, ch->cp); + curl_multi_cleanup(multi); + + if (ch->io_sockets) { + zend_hash_destroy(ch->io_sockets); + efree(ch->io_sockets); + ch->io_sockets = NULL; + } + ch->io_timer_ms = -1; + + return result; +} + /* {{{ _php_curl_cleanup_handle(ch) Cleanup an execution phase */ void _php_curl_cleanup_handle(php_curl *ch) @@ -2341,7 +2650,7 @@ PHP_FUNCTION(curl_exec) _php_curl_cleanup_handle(ch); - error = curl_easy_perform(ch->cp); + error = php_curl_exec_multi(ch); SAVE_CURL_ERROR(ch, error); if (error != CURLE_OK) { diff --git a/ext/curl/tests/curl_exec_io_hook.phpt b/ext/curl/tests/curl_exec_io_hook.phpt new file mode 100644 index 000000000000..6bf6f4466f05 --- /dev/null +++ b/ext/curl/tests/curl_exec_io_hook.phpt @@ -0,0 +1,51 @@ +--TEST-- +curl_exec() integrates with io_hooks scheduler +--EXTENSIONS-- +curl +--FILE-- +go($fn); +} + +$scheduler->run(function () { + $server = stream_socket_server('tcp://127.0.0.1:0'); + $addr = stream_socket_get_name($server, false); + + go(function () use ($server) { + $conn = stream_socket_accept($server, 5); + $request = ''; + while (!str_ends_with($request, "\r\n\r\n")) { + $chunk = fread($conn, 1024); + if ($chunk === false || $chunk === '') break; + $request .= $chunk; + } + fwrite($conn, "HTTP/1.0 200 OK\r\nContent-Length: 12\r\n\r\nHello, curl!"); + fclose($conn); + }); + + go(function () use ($addr) { + $ch = curl_init("http://$addr/"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + $body = curl_exec($ch); + $errno = curl_errno($ch); + if ($errno === 0) { + echo "OK: $body\n"; + } else { + echo "curl error $errno: " . curl_error($ch) . "\n"; + } + }); +}); + +?> +--EXPECT-- +OK: Hello, curl! diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index eea758da4713..cd6113e2919e 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -1873,7 +1873,7 @@ static int php_openssl_enable_crypto(php_stream *stream, if (has_timeout) { left_time = php_openssl_subtract_timeval(*timeout, elapsed_time); } - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + php_pollstream_for(stream, sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); } } else { @@ -2048,10 +2048,10 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si */ if (retry) { if (read) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + php_pollstream_for(stream, sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); } else { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + php_pollstream_for(stream, sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); } } @@ -2067,10 +2067,10 @@ static ssize_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, si /* Otherwise, we need to wait again (up to time_left or we get an error) */ if (began_blocked) { if (read) { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? + php_pollstream_for(stream, sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ? (POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL); } else { - php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? + php_pollstream_for(stream, sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL); } } @@ -2136,7 +2136,6 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle) /* {{{ { php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; #ifdef PHP_WIN32 - int n; #endif unsigned i; @@ -2164,6 +2163,7 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle) /* {{{ #endif if (sslsock->s.socket != SOCK_ERR) { #ifdef PHP_WIN32 + php_pollstream_result res; /* prevent more data from coming in */ shutdown(sslsock->s.socket, SHUT_RD); @@ -2174,8 +2174,8 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle) /* {{{ * but at the same time avoid hanging indefinitely. * */ do { - n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); - } while (n == -1 && php_socket_errno() == EINTR); + res = php_pollstream_for_ms(stream, sslsock->s.socket, POLLOUT, 500); + } while (res == PHP_POLLSTREAM_ERROR && php_socket_errno() == EINTR); #endif closesocket(sslsock->s.socket); sslsock->s.socket = SOCK_ERR; @@ -2240,7 +2240,8 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_ } } - php_socket_t clisock = php_network_accept_incoming_ex(sock->s.socket, + php_socket_t clisock = php_network_accept_incoming_ex(stream, + sock->s.socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL, @@ -2388,7 +2389,7 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val !(stream->flags & PHP_STREAM_FLAG_NO_IO) && ((MSG_DONTWAIT != 0) || !sslsock->s.is_blocked) ) || - php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0 + php_pollstream_for(stream, sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) == PHP_POLLSTREAM_READY ) { /* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */ /* additionally, we don't use this optimization if SSL is active because in that case, we're not using MSG_DONTWAIT */ @@ -2466,7 +2467,7 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val if (retry) { /* Now, how much time until we time out? */ left_time = php_openssl_subtract_timeval(*timeout, elapsed_time); - if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI|POLLOUT, has_timeout ? &left_time : NULL) <= 0) { + if (php_pollstream_for(stream, sslsock->s.socket, PHP_POLLREADABLE|POLLPRI|POLLOUT, has_timeout ? &left_time : NULL) < PHP_POLLSTREAM_READY) { retry = 0; alive = 0; }; diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index d63f8cbbfe64..98241d368f30 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -337,6 +337,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ BASIC_MINIT_SUBMODULE(exec) BASIC_MINIT_SUBMODULE(user_streams) + BASIC_MINIT_SUBMODULE(io_hooks) php_register_url_stream_wrapper("php", &php_stream_php_wrapper); php_register_url_stream_wrapper("file", &php_plain_files_wrapper); @@ -423,6 +424,10 @@ PHP_RINIT_FUNCTION(basic) /* {{{ */ /* Default to global filters only */ FG(stream_filters) = NULL; + ZVAL_UNDEF(&FG(io_hooks)); + FG(io_hooks_poll_fcc) = empty_fcall_info_cache; + FG(io_hooks_poll_multi_fcc) = empty_fcall_info_cache; + return SUCCESS; } /* }}} */ @@ -483,6 +488,14 @@ PHP_RSHUTDOWN_FUNCTION(basic) /* {{{ */ BG(page_uid) = -1; BG(page_gid) = -1; + + if (!Z_ISUNDEF(FG(io_hooks))) { + zval_ptr_dtor(&FG(io_hooks)); + ZVAL_UNDEF(&FG(io_hooks)); + zend_fcc_dtor(&FG(io_hooks_poll_fcc)); + zend_fcc_dtor(&FG(io_hooks_poll_multi_fcc)); + } + return SUCCESS; } /* }}} */ diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index 4d5a4c02aec0..83b8e4d62ca3 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -43,6 +43,7 @@ PHP_MINFO_FUNCTION(basic); ZEND_API void php_get_highlight_struct(zend_syntax_highlighter_ini *syntax_highlighter_ini); PHP_MINIT_FUNCTION(poll); +PHP_MINIT_FUNCTION(io_hooks); PHP_MINIT_FUNCTION(user_filters); PHP_RSHUTDOWN_FUNCTION(user_filters); PHP_RSHUTDOWN_FUNCTION(browscap); diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index 67c36b93ba34..2b6cb062d5cb 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -418,6 +418,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([ image.c incomplete_class.c info.c + io_hooks.c io_poll.c iptc.c levenshtein.c diff --git a/ext/standard/file.h b/ext/standard/file.h index 3c6160fd4bb1..4d7d03fc1a83 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -15,7 +15,9 @@ #ifndef FILE_H #define FILE_H -#include "php_network.h" +#ifdef HAVE_GETHOSTBYNAME_R +# include +#endif PHP_MINIT_FUNCTION(file); PHP_MSHUTDOWN_FUNCTION(file); @@ -100,6 +102,9 @@ typedef struct { HashTable *stream_filters; /* per-request copy of stream_filters_hash */ HashTable *wrapper_errors; /* key: wrapper address; value: linked list of char* */ int pclose_wait; + zval io_hooks; + zend_fcall_info_cache io_hooks_poll_fcc; + zend_fcall_info_cache io_hooks_poll_multi_fcc; #ifdef HAVE_GETHOSTBYNAME_R struct hostent tmp_host_info; char *tmp_host_buf; @@ -115,5 +120,6 @@ extern PHPAPI int file_globals_id; extern PHPAPI php_file_globals file_globals; #endif +#include "php_network.h" #endif /* FILE_H */ diff --git a/ext/standard/io_hooks.c b/ext/standard/io_hooks.c new file mode 100644 index 000000000000..d2942cffe25d --- /dev/null +++ b/ext/standard/io_hooks.c @@ -0,0 +1,174 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "zend_enum.h" +#include "ext/standard/file.h" +#include "ext/standard/io_poll.h" +#include "ext/standard/io_hooks.h" +#include "io_hooks_arginfo.h" + +#ifdef HAVE_POLL_H +#include +#elif HAVE_SYS_POLL_H +#include +#endif + +PHPAPI zend_class_entry *php_io_hooks_poll_info_ce; +PHPAPI zend_class_entry *php_io_hooks_poll_result_ce; +static zend_class_entry *php_io_hooks_hooks_ce; + +static void php_pollfd_events_to_io_poll_events(zend_array *dest, int events) +{ + zval zv; + + if (events & POLLIN) { + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Read")); + zend_hash_next_index_insert(dest, &zv); + } + if (events & POLLOUT) { + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Write")); + zend_hash_next_index_insert(dest, &zv); + } + if (events & POLLERR) { + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "Error")); + zend_hash_next_index_insert(dest, &zv); + } + if (events & POLLHUP) { + ZVAL_OBJ_COPY(&zv, zend_enum_get_case_cstr(php_io_poll_event_class_entry, "HangUp")); + zend_hash_next_index_insert(dest, &zv); + } +} + +PHPAPI zend_object *php_io_hooks_poll_stream(php_stream *stream, int events, const struct timeval *timeout) +{ + uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; + + // TODO: optimize poll_info object creation+init. Maybe reuse it too (e.g. if RC=1) + zval poll_info; + object_init_ex(&poll_info, php_io_hooks_poll_info_ce); + + zval handle; + php_stream_poll_handle_from_stream(&handle, stream); + zend_update_property(php_io_hooks_poll_info_ce, Z_OBJ(poll_info), + "handle", sizeof("handle") - 1, &handle); + zval_ptr_dtor(&handle); + + zval events_arr; + array_init(&events_arr); + php_pollfd_events_to_io_poll_events(Z_ARRVAL(events_arr), events); + zend_update_property(php_io_hooks_poll_info_ce, Z_OBJ(poll_info), + "events", sizeof("events") - 1, &events_arr); + zval_ptr_dtor(&events_arr); + + zend_long timeout_ms; + if (timeout == NULL) { + timeout_ms = -1; + } else { + timeout_ms = (zend_long)timeout->tv_sec * 1000 + (zend_long)timeout->tv_usec / 1000; + } + zend_update_property_long(php_io_hooks_poll_info_ce, Z_OBJ(poll_info), + "timeout_ms", sizeof("timeout_ms") - 1, timeout_ms); + + zval retval; + ZVAL_UNDEF(&retval); + zend_call_known_fcc(&FG(io_hooks_poll_fcc), &retval, 1, &poll_info, NULL); + zval_ptr_dtor(&poll_info); + + if (EG(exception)) { + goto return_error; + } + + if (UNEXPECTED(Z_TYPE(retval) != IS_OBJECT || Z_OBJCE(retval) != php_io_hooks_poll_result_ce)) { + zend_type_error("%s::poll() must return %s, %s returned", + ZSTR_VAL(php_io_hooks_hooks_ce->name), + ZSTR_VAL(php_io_hooks_poll_result_ce->name), + zend_zval_type_name(&retval)); + goto return_error; + } + + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + + return Z_OBJ(retval); + +return_error: + ZEND_ASSERT(EG(exception)); + zval_ptr_dtor(&retval); + + stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; + stream->flags |= orig_no_fclose; + + return NULL; +} + +PHP_FUNCTION(Io_Hooks_set_hooks) +{ + zval *hooks_obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(hooks_obj, php_io_hooks_hooks_ce) + ZEND_PARSE_PARAMETERS_END(); + + if (!Z_ISUNDEF(FG(io_hooks))) { + ZVAL_COPY(return_value, &FG(io_hooks)); + zval_ptr_dtor(&FG(io_hooks)); + zend_fcc_dtor(&FG(io_hooks_poll_fcc)); + } + + if (hooks_obj == NULL || Z_TYPE_P(hooks_obj) == IS_NULL) { + ZVAL_UNDEF(&FG(io_hooks)); + return; + } + + ZVAL_COPY(&FG(io_hooks), hooks_obj); + + zend_object *obj = Z_OBJ_P(hooks_obj); + + zend_string *poll_name = zend_string_init("poll", sizeof("poll") - 1, false); + zend_function *poll_fn = obj->handlers->get_method(&obj, poll_name, NULL); + zend_string_release(poll_name); + ZEND_ASSERT(poll_fn != NULL); + + FG(io_hooks_poll_fcc) = (zend_fcall_info_cache){ + .function_handler = poll_fn, + .object = obj, + .called_scope = obj->ce, + }; + GC_ADDREF(obj); + + zend_string *poll_multi_name = zend_string_init("poll_multi", sizeof("poll_multi") - 1, false); + zend_function *poll_multi_fn = obj->handlers->get_method(&obj, poll_multi_name, NULL); + zend_string_release(poll_multi_name); + ZEND_ASSERT(poll_multi_fn != NULL); + + FG(io_hooks_poll_multi_fcc) = (zend_fcall_info_cache){ + .function_handler = poll_multi_fn, + .object = obj, + .called_scope = obj->ce, + }; + GC_ADDREF(obj); +} + +PHP_MINIT_FUNCTION(io_hooks) +{ + php_io_hooks_poll_info_ce = register_class_Io_Hooks_PollInfo(); + php_io_hooks_poll_result_ce = register_class_Io_Hooks_PollResult(); + php_io_hooks_hooks_ce = register_class_Io_Hooks_Hooks(); + + zend_register_functions(NULL, ext_functions, NULL, type); + + return SUCCESS; +} diff --git a/ext/standard/io_hooks.h b/ext/standard/io_hooks.h new file mode 100644 index 000000000000..04f2f23396b3 --- /dev/null +++ b/ext/standard/io_hooks.h @@ -0,0 +1,31 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_IO_HOOKS_H +#define PHP_IO_HOOKS_H + +#include "main/php.h" +#include "Zend/zend_types.h" +#include "ext/standard/file.h" + +PHPAPI extern zend_class_entry *php_io_hooks_poll_info_ce; +PHPAPI extern zend_class_entry *php_io_hooks_poll_result_ce; + +#define PHP_HAS_IO_POLL_HOOK() ZEND_FCC_INITIALIZED(FG(io_hooks_poll_fcc)) + +PHPAPI zend_object *php_io_hooks_poll_stream(php_stream *stream, int events, const struct timeval *timeout); + +PHP_MINIT_FUNCTION(io_hooks); + +#endif /* PHP_IO_HOOKS_H */ diff --git a/ext/standard/io_hooks.stub.php b/ext/standard/io_hooks.stub.php new file mode 100644 index 000000000000..baddbc18bc41 --- /dev/null +++ b/ext/standard/io_hooks.stub.php @@ -0,0 +1,30 @@ +stream = stream; + intern->handle_data = data; + + GC_ADDREF(stream->res); +} + /* Handle interface internal only */ static int php_stream_poll_handle_implement_interface(zend_class_entry *interface, zend_class_entry *implementor) { diff --git a/ext/standard/io_poll.h b/ext/standard/io_poll.h new file mode 100644 index 000000000000..1d9e19d918ec --- /dev/null +++ b/ext/standard/io_poll.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_IO_POLL_H +#define PHP_IO_POLL_H + +#include "Zend/zend_types.h" +#include "main/php.h" + +PHPAPI extern zend_class_entry *php_io_poll_event_class_entry; +PHPAPI extern zend_class_entry *php_io_poll_handle_class_entry; +PHPAPI extern zend_class_entry *php_stream_poll_handle_class_entry; + +PHPAPI zend_result php_io_poll_events_to_event_enums(uint32_t events, zval *event_enums); + +PHPAPI void php_stream_poll_handle_from_stream(zval *dest, php_stream *stream); + +#endif /* PHP_IO_POLL_H */ diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 68f79783c6f8..0b708578442c 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -1703,3 +1703,4 @@ PHP_FUNCTION(stream_socket_shutdown) } /* }}} */ #endif + diff --git a/ext/standard/tests/streams/hooks/hook-close-fgets.phpt b/ext/standard/tests/streams/hooks/hook-close-fgets.phpt new file mode 100644 index 000000000000..913cf2be8430 --- /dev/null +++ b/ext/standard/tests/streams/hooks/hook-close-fgets.phpt @@ -0,0 +1,36 @@ +--TEST-- +Stream hook: closed during read hook +--FILE-- +handle->getStream()); + Io\Hooks\set_hooks(null); + $result = new PollResult(); + $result->handle = $info->handle; + $result->events = $info->events; + $result->timeout = false; + return $result; + } + public function poll_multi(?int $timeout_ms, PollInfo ...$info): array { throw new \Exception("poll_multi not implemented"); } +} + +Io\Hooks\set_hooks(new CloseOnceHooks()); +var_dump(fgets($client)); +?> +--EXPECTF-- +Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d +string(6) " spl_object_id(Handle) (for poll() handles only) + private array $fiberWatchers = []; // fiberId -> [[Watcher, fdKey|null], ...] + private array $fiberDeadlines = []; // fiberId -> [deadline_ns, fiber, PollInfo|null] + private array $fiberIsMulti = []; // fiberId -> true (for poll_multi fibers) + + public function __construct() + { + $this->pollContext = new Context(); + } + + public function run(callable $main): void + { + $this->ready[] = [new Fiber($main), null]; + + while ($this->ready !== [] || $this->fiberWatchers !== [] || $this->fiberDeadlines !== []) { + $this->runReadyFibers(); + $this->pollFds(); + } + } + + public function runReadyFibers(): void + { + while ($this->ready !== []) { + [$fiber, $resumeValue] = array_shift($this->ready); + $fiber->isSuspended() ? $fiber->resume($resumeValue) : $fiber->start(); + } + } + + public function pollFds(): void + { + if ($this->fiberWatchers === [] && $this->fiberDeadlines === []) { + return; + } + + // Compute wait() timeout from the nearest deadline + $timeoutSec = null; + $timeoutUsec = 0; + $now = hrtime(true); + foreach ($this->fiberDeadlines as [$deadline]) { + $remaining = $deadline - $now; + if ($remaining <= 0) { + $timeoutSec = 0; + $timeoutUsec = 0; + break; + } + $remainingUsec = intdiv($remaining, 1_000); + if ($timeoutSec === null || $remainingUsec < $timeoutSec * 1_000_000 + $timeoutUsec) { + $timeoutSec = intdiv($remainingUsec, 1_000_000); + $timeoutUsec = $remainingUsec % 1_000_000; + } + } + + $watchers = $this->pollContext->wait($timeoutSec, $timeoutUsec); + + // Group ready PollResults by fiber before resuming anyone + $fiberResults = []; + foreach ($watchers as $watcher) { + [$fiber] = $watcher->getData(); + $fiberId = spl_object_id($fiber); + if (!isset($this->fiberWatchers[$fiberId])) { + continue; + } + $result = new PollResult(); + $result->handle = $watcher->getHandle(); + $result->events = $watcher->getTriggeredEvents(); + $result->timeout = false; + $fiberResults[$fiberId] ??= [$fiber, []]; + $fiberResults[$fiberId][1][] = $result; + } + + foreach ($fiberResults as $fiberId => [$fiber, $results]) { + if (!isset($this->fiberWatchers[$fiberId])) { + continue; + } + $this->removeAllFiberWatchers($fiberId); + unset($this->fiberDeadlines[$fiberId]); + + if ($this->fiberIsMulti[$fiberId] ?? false) { + unset($this->fiberIsMulti[$fiberId]); + $this->ready[] = [$fiber, $results]; // array of PollResult + } else { + $this->ready[] = [$fiber, $results[0]]; // single PollResult + } + } + + // Handle expired deadlines + $now = hrtime(true); + foreach ($this->fiberDeadlines as $fiberId => [$deadline, $fiber, $info]) { + if ($deadline > $now) { + continue; + } + $this->removeAllFiberWatchers($fiberId); + unset($this->fiberDeadlines[$fiberId]); + + if ($this->fiberIsMulti[$fiberId] ?? false) { + unset($this->fiberIsMulti[$fiberId]); + $this->ready[] = [$fiber, []]; // empty array = timeout for poll_multi + } else { + $result = new PollResult(); + $result->handle = $info->handle; + $result->events = $info->events; + $result->timeout = true; + $this->ready[] = [$fiber, $result]; + } + } + } + + private function removeAllFiberWatchers(int $fiberId): void + { + foreach ($this->fiberWatchers[$fiberId] as [$w, $fdKey]) { + if ($fdKey !== null) { + unset($this->handles[$fdKey]); + } + $w->remove(); + } + unset($this->fiberWatchers[$fiberId]); + } + + public function poll(PollInfo $info): PollResult + { + $fiber = Fiber::getCurrent(); + $fiberId = spl_object_id($fiber); + + if ($info->timeout_ms >= 0) { + $deadline = hrtime(true) + $info->timeout_ms * 1_000_000; + $this->fiberDeadlines[$fiberId] = [$deadline, $fiber, $info]; + } + + $id = spl_object_id($info->handle); + if (isset($this->handles[$id])) { + throw new \Exception("Handle already registered"); + } + $this->handles[$id] = $id; + $this->fiberWatchers[$fiberId] = [ + [$this->pollContext->add($info->handle, $info->events, [$fiber]), $id], + ]; + + /** @var PollResult $result */ + $result = Fiber::suspend(); + return $result; + } + + public function poll_multi(?int $timeout_ms, PollInfo ...$infos): array + { + $fiber = Fiber::getCurrent(); + $fiberId = spl_object_id($fiber); + $this->fiberWatchers[$fiberId] = []; + $this->fiberIsMulti[$fiberId] = true; + + if ($timeout_ms !== null && $timeout_ms >= 0) { + $deadline = hrtime(true) + $timeout_ms * 1_000_000; + $this->fiberDeadlines[$fiberId] = [$deadline, $fiber, null]; + } + + foreach ($infos as $info) { + $this->fiberWatchers[$fiberId][] = [ + $this->pollContext->add($info->handle, $info->events, [$fiber]), + null, + ]; + } + + /** @var PollResult[] $results */ + $results = Fiber::suspend(); + return $results ?? []; + } + + public function go(callable $fn): void + { + $this->ready[] = [new Fiber($fn), null]; + $this->ready[] = [Fiber::getCurrent(), null]; + Fiber::suspend(); + } +} diff --git a/ext/standard/tests/streams/hooks/use-case.phpt b/ext/standard/tests/streams/hooks/use-case.phpt new file mode 100644 index 000000000000..fabcc1a330b6 --- /dev/null +++ b/ext/standard/tests/streams/hooks/use-case.phpt @@ -0,0 +1,66 @@ +--TEST-- +Stream hook: use case +--FILE-- +go($fn); +} + +$scheduler->run(function () { + $server = stream_socket_server('tcp://localhost:0'); + $socket_name = stream_socket_get_name($server, false); + if (!preg_match('/:(\d+)$/', $socket_name, $m)) { + die("Could not extract port from '$socket_name'"); + } + $port = $m[1]; + + go(function () use ($server) { + $client = stream_socket_accept($server); + go(function () use ($client) { + $headers = []; + while (!feof($client)) { + $line = fgets($client); + if ($line === false) { + break; + } + if ($line === "\r\n") { + break; + } + $headers[] = $line; + } + foreach ($headers as $header) { + echo "> " . trim($header) . "\n"; + } + fwrite($client, "HTTP/1.0 200 OK\r\n"); + fwrite($client, "\r\n"); + fwrite($client, "Hello world!\n"); + fclose($client); + }); + }); + + go(function () use ($port) { + $fd = stream_socket_client("tcp://localhost:$port"); + fwrite($fd, "GET / HTTP/1.0\r\n"); + fwrite($fd, "Host: localhost\r\n"); + fwrite($fd, "\r\n"); + while (!feof($fd)) { + echo trim("< " . fgets($fd)) . "\n"; + } + }); +}); + +?> +--EXPECT-- +> GET / HTTP/1.0 +> Host: localhost +< HTTP/1.0 200 OK +< +< Hello world! diff --git a/main/network.c b/main/network.c index f652cf555ffb..6a2ae183c994 100644 --- a/main/network.c +++ b/main/network.c @@ -807,7 +807,8 @@ PHPAPI int php_network_get_sock_name(php_socket_t sock, * version of the address will be emalloc'd and returned. * */ -PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, +PHPAPI php_socket_t php_network_accept_incoming_ex(php_stream *stream, + php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen, @@ -818,16 +819,17 @@ PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, ) { php_socket_t clisock = -1; - int error = 0, n; + int error = 0; + php_pollstream_result result; php_sockaddr_storage sa; socklen_t sl; - n = php_pollfd_for(srvsock, PHP_POLLREADABLE, timeout); + result = php_pollstream_for(stream, srvsock, PHP_POLLREADABLE, timeout); - if (n == 0) { + if (result == PHP_POLLSTREAM_TIMEOUT) { error = PHP_TIMEOUT_ERROR_VALUE; - } else if (n == -1) { - error = php_socket_errno(); + } else if (result == PHP_POLLSTREAM_ERROR) { + error = php_socket_errno(); // TODO } else { sl = sizeof(sa); @@ -866,7 +868,8 @@ PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, return clisock; } -PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, +PHPAPI php_socket_t php_network_accept_incoming(php_stream *stream, + php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen, @@ -878,7 +881,7 @@ PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, { php_sockvals sockvals = { .mask = tcp_nodelay ? PHP_SOCKVAL_TCP_NODELAY : 0 }; - return php_network_accept_incoming_ex(srvsock, textaddr, addr, addrlen, timeout, error_string, + return php_network_accept_incoming_ex(stream, srvsock, textaddr, addr, addrlen, timeout, error_string, error_code, &sockvals); } diff --git a/main/php_network.h b/main/php_network.h index e6d3009a6c82..ce5c47330096 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -16,6 +16,8 @@ #define _PHP_NETWORK_H #include +#include "zend_enum.h" +#include "ext/standard/io_hooks.h" #ifndef PHP_WIN32 # undef closesocket @@ -212,6 +214,49 @@ static inline int php_pollfd_for_ms(php_socket_t fd, int events, int timeout) return n; } +// TODO: C23_ENUM() +typedef enum php_pollstream_result { + PHP_POLLSTREAM_ERROR = -1, + PHP_POLLSTREAM_TIMEOUT = 0, + PHP_POLLSTREAM_READY = 1, +} php_pollstream_result; + +static inline php_pollstream_result php_pollstream_for(php_stream *stream, php_socket_t fd, int events, struct timeval *timeouttv) +{ + if (!PHP_HAS_IO_POLL_HOOK()) { + int n = php_pollfd_for(fd, events, timeouttv); + if (n > 0) { + return PHP_POLLSTREAM_READY; + } + if (n == 0) { + return PHP_POLLSTREAM_TIMEOUT; + } + return PHP_POLLSTREAM_ERROR; + } + + zend_object *result = php_io_hooks_poll_stream(stream, events, timeouttv); + if (result == NULL) { + return PHP_POLLSTREAM_ERROR; + } + + zval rv; + zval *timeout_prop = zend_read_property(php_io_hooks_poll_result_ce, result, "timeout", sizeof("timeout") - 1, /* silent */ 1, &rv); + bool timed_out = zend_is_true(timeout_prop); + OBJ_RELEASE(result); + + return timed_out ? PHP_POLLSTREAM_TIMEOUT : PHP_POLLSTREAM_READY; +} + +static inline php_pollstream_result php_pollstream_for_ms(php_stream *stream, php_socket_t fd, int events, int timeout_ms) +{ + struct timeval timeouttv = { + .tv_sec = timeout_ms / 1000, + .tv_usec = (timeout_ms % 1000) * 1000, + }; + + return php_pollstream_for(stream, fd, events, &timeouttv); +} + /* emit warning and suggestion for unsafe select(2) usage */ PHPAPI void _php_emit_fd_setsize_warning(int max_fd); @@ -315,7 +360,8 @@ PHPAPI php_socket_t php_network_bind_socket_to_local_addr(const char *host, unsi int socktype, long sockopts, zend_string **error_string, int *error_code ); -PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, +PHPAPI php_socket_t php_network_accept_incoming_ex(php_stream *stream, + php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen, @@ -325,7 +371,8 @@ PHPAPI php_socket_t php_network_accept_incoming_ex(php_socket_t srvsock, php_sockvals *sockvals ); -PHPAPI php_socket_t php_network_accept_incoming(php_socket_t srvsock, +PHPAPI php_socket_t php_network_accept_incoming(php_stream *stream, + php_socket_t srvsock, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen, diff --git a/main/php_streams.h b/main/php_streams.h index d248de7a8168..15be5f9f235d 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -667,6 +667,7 @@ PHPAPI HashTable *_php_get_stream_filters_hash(void); PHPAPI HashTable *php_get_stream_filters_hash_global(void); extern const php_stream_wrapper_ops *php_stream_user_wrapper_ops; + static inline bool php_is_stream_path(const char *filename) { const char *p; diff --git a/main/streams/streams.c b/main/streams/streams.c index 31d1eda16790..074a512a879c 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2607,3 +2607,4 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in return -1; } /* }}} */ + diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 77977a557fc6..2afd42361333 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -91,14 +91,14 @@ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t coun sock->timeout_event = false; do { - retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); + retval = php_pollstream_for(stream, sock->socket, POLLOUT, ptimeout); - if (retval == 0) { + if (retval == PHP_POLLSTREAM_TIMEOUT) { sock->timeout_event = true; break; } - if (retval > 0) { + if (retval == PHP_POLLSTREAM_READY) { /* writable now; retry */ goto retry; } @@ -151,12 +151,12 @@ static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data } while(1) { - retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout); + retval = php_pollstream_for(stream, sock->socket, PHP_POLLREADABLE, ptimeout); - if (retval == 0) + if (retval == PHP_POLLSTREAM_TIMEOUT) sock->timeout_event = true; - if (retval >= 0) + if (retval == PHP_POLLSTREAM_READY) break; if (php_socket_errno() != EINTR) @@ -373,7 +373,7 @@ static int php_sockop_set_option(php_stream *stream, int option, int value, void !(stream->flags & PHP_STREAM_FLAG_NO_IO) && ((MSG_DONTWAIT != 0) || !sock->is_blocked) ) || - php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0 + php_pollstream_for(stream, sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) == PHP_POLLSTREAM_READY ) { /* the poll() call was skipped if the socket is non-blocking (or MSG_DONTWAIT is available) and if the timeout is zero */ #ifdef PHP_WIN32 @@ -979,7 +979,7 @@ static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t } } - php_socket_t clisock = php_network_accept_incoming_ex(sock->socket, + php_socket_t clisock = php_network_accept_incoming_ex(stream, sock->socket, xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, xparam->want_addr ? &xparam->outputs.addr : NULL, xparam->want_addr ? &xparam->outputs.addrlen : NULL,