From dd0f8c953ffd024963376c433dc227a805f49590 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 30 Sep 2025 20:29:14 +0200 Subject: [PATCH 01/61] stream: add initial stream errors API --- configure.ac | 1 + ext/standard/basic_functions.c | 1 + ext/standard/file.h | 4 +- main/php_streams.h | 3 + main/streams/php_stream_errors.h | 167 ++++++++ main/streams/stream_errors.c | 553 +++++++++++++++++++++++++++ main/streams/stream_errors.stub.php | 16 + main/streams/stream_errors_arginfo.h | 39 ++ main/streams/streams.c | 149 +------- win32/build/config.w32 | 2 +- 10 files changed, 794 insertions(+), 141 deletions(-) create mode 100644 main/streams/php_stream_errors.h create mode 100644 main/streams/stream_errors.c create mode 100644 main/streams/stream_errors.stub.php create mode 100644 main/streams/stream_errors_arginfo.h diff --git a/configure.ac b/configure.ac index 77fc8c89cdf40..a515c34f2394b 100644 --- a/configure.ac +++ b/configure.ac @@ -1695,6 +1695,7 @@ PHP_ADD_SOURCES([main/streams], m4_normalize([ memory.c mmap.c plain_wrapper.c + stream_errors.c streams.c transports.c userspace.c diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 8b72ffffc8379..7812d4f4f6658 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -336,6 +336,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ #endif BASIC_MINIT_SUBMODULE(exec) + BASIC_MINIT_SUBMODULE(stream_errors) BASIC_MINIT_SUBMODULE(user_streams) php_register_url_stream_wrapper("php", &php_stream_php_wrapper); diff --git a/ext/standard/file.h b/ext/standard/file.h index f8faebd028293..a3c84791cc1ee 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -34,6 +34,7 @@ PHPAPI PHP_FUNCTION(ftell); PHPAPI PHP_FUNCTION(fseek); PHPAPI PHP_FUNCTION(fpassthru); +PHP_MINIT_FUNCTION(stream_errors); PHP_MINIT_FUNCTION(user_streams); PHPAPI zend_result php_copy_file(const char *src, const char *dest); @@ -100,7 +101,8 @@ typedef struct { php_stream_context *default_context; HashTable *stream_wrappers; /* per-request copy of url_stream_wrappers_hash */ HashTable *stream_filters; /* per-request copy of stream_filters_hash */ - HashTable *wrapper_errors; /* key: wrapper address; value: linked list of char* */ + HashTable *wrapper_logged_errors; /* key: wrapper address; value: linked list of error entries */ + HashTable *wrapper_stored_errors; /* key: wrapper address; value: linked list of error entries */ int pclose_wait; #ifdef HAVE_GETHOSTBYNAME_R struct hostent tmp_host_info; diff --git a/main/php_streams.h b/main/php_streams.h index 1c52539cfcaee..33af6c3c91c30 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -248,6 +248,8 @@ struct _php_stream { #endif struct _php_stream *enclosing_stream; /* this is a private stream owned by enclosing_stream */ + + zend_llist *error_list; }; /* php_stream */ #define PHP_STREAM_CONTEXT(stream) \ @@ -539,6 +541,7 @@ PHPAPI ssize_t _php_stream_passthru(php_stream * src STREAMS_DC); #define php_stream_passthru(stream) _php_stream_passthru((stream) STREAMS_CC) END_EXTERN_C() +#include "streams/php_stream_errors.h" #include "streams/php_stream_transport.h" #include "streams/php_stream_plain_wrapper.h" #include "streams/php_stream_glob_wrapper.h" diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h new file mode 100644 index 0000000000000..08998a87111cf --- /dev/null +++ b/main/streams/php_stream_errors.h @@ -0,0 +1,167 @@ +#ifndef PHP_STREAM_ERRORS_H +#define PHP_STREAM_ERRORS_H + +#include "php.h" +#include "php_streams.h" + +BEGIN_EXTERN_C() + +/* Error mode context options */ +#define PHP_STREAM_ERROR_MODE_ERROR 0 +#define PHP_STREAM_ERROR_MODE_EXCEPTION 1 +#define PHP_STREAM_ERROR_MODE_SILENT 2 + +/* Error store context options */ +#define PHP_STREAM_ERROR_STORE_AUTO 0 +#define PHP_STREAM_ERROR_STORE_NONE 1 +#define PHP_STREAM_ERROR_STORE_NON_TERM 2 +#define PHP_STREAM_ERROR_STORE_TERMINAL 3 +#define PHP_STREAM_ERROR_STORE_ALL 4 + +/* Error code definition for registration */ +typedef struct { + int code; + const char *name; +} php_stream_error_code_def; + +/* Stored error entry */ +typedef struct { + zend_string *message; + int code; + const char *wrapper_name; /* Points to wrapper->wops->label, no need to duplicate */ + const char *param; /* Points to passed string, caller manages lifetime for storage */ + int severity; + bool terminal; +} php_stream_error_entry; + +/* Sentinel for error code array termination */ +#define PHP_STREAM_ERROR_CODE_END {0, NULL} + +/* Error code registration */ +PHPAPI void php_stream_wrapper_register_error_codes( + php_stream_wrapper *wrapper, + const php_stream_error_code_def *codes +); + +PHPAPI const char *php_stream_wrapper_get_error_name( + php_stream_wrapper *wrapper, + int code +); + +/* Main error reporting functions */ +PHPAPI void php_stream_wrapper_error( + php_stream_wrapper *wrapper, + php_stream_context *context, + int options, + int severity, + bool terminal, + int code, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); + +PHPAPI void php_stream_wrapper_error_param( + php_stream_wrapper *wrapper, + php_stream_context *context, + int options, + int severity, + bool terminal, + int code, + const char *param, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); + +PHPAPI void php_stream_wrapper_error_param2( + php_stream_wrapper *wrapper, + php_stream_context *context, + int options, + int severity, + bool terminal, + int code, + const char *param1, + const char *param2, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); + +PHPAPI void php_stream_error( + php_stream *stream, + int severity, + bool terminal, + int code, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 5, 6); + +/* Legacy wrapper error log - updated API */ +PHPAPI void php_stream_wrapper_log_error( + const php_stream_wrapper *wrapper, + int options, + int severity, + bool terminal, + int code, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); + +PHPAPI void php_stream_wrapper_log_error_param( + const php_stream_wrapper *wrapper, + int options, + int severity, + bool terminal, + int code, + const char *param, + const char *fmt, + ... +) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); + +PHPAPI void php_stream_display_wrapper_errors( + php_stream_wrapper *wrapper, + const char *path, + const char *caption +); + +PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); + +void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption); +void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); + +/* Convenience macros */ +#define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ + php_stream_wrapper_error(wrapper, context, options, E_WARNING, true, code, __VA_ARGS__) + +#define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \ + php_stream_wrapper_error(wrapper, context, options, E_WARNING, false, code, __VA_ARGS__) + +#define php_stream_wrapper_notice(wrapper, context, options, code, ...) \ + php_stream_wrapper_error(wrapper, context, options, E_NOTICE, false, code, __VA_ARGS__) + +#define php_stream_wrapper_warn_param(wrapper, context, options, code, param, ...) \ + php_stream_wrapper_error_param(wrapper, context, options, E_WARNING, true, code, param, __VA_ARGS__) + +#define php_stream_warn(stream, code, ...) \ + php_stream_error(stream, E_WARNING, true, code, __VA_ARGS__) + +#define php_stream_warn_nt(stream, code, ...) \ + php_stream_error(stream, E_WARNING, false, code, __VA_ARGS__) + +#define php_stream_notice(stream, code, ...) \ + php_stream_error(stream, E_NOTICE, false, code, __VA_ARGS__) + +#define php_stream_fatal(stream, code, ...) \ + php_stream_error(stream, E_ERROR, true, code, __VA_ARGS__) + +/* Legacy log variants */ +#define php_stream_wrapper_log_warn(wrapper, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, options, E_WARNING, true, code, __VA_ARGS__) + +#define php_stream_wrapper_log_warn_nt(wrapper, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, options, E_WARNING, false, code, __VA_ARGS__) + +#define php_stream_wrapper_log_notice(wrapper, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, options, E_NOTICE, false, code, __VA_ARGS__) + +END_EXTERN_C() + +#endif /* PHP_STREAM_ERRORS_H */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c new file mode 100644 index 0000000000000..b308521ca33c3 --- /dev/null +++ b/main/streams/stream_errors.c @@ -0,0 +1,553 @@ +#include "php.h" +#include "php_globals.h" +#include "php_streams.h" +#include "php_stream_errors.h" +#include "stream_errors_arginfo.h" +#include "zend_exceptions.h" +#include "ext/standard/file.h" + +/* StreamException class entry */ +static zend_class_entry *php_ce_stream_exception; + +/* Error code registry */ +static HashTable *php_stream_wrapper_error_codes = NULL; + +static void php_stream_error_entry_dtor(void *error) +{ + php_stream_error_entry *entry = *(php_stream_error_entry **) error; + zend_string_release(entry->message); + efree(entry); +} + +static void php_stream_error_list_dtor(zval *item) +{ + zend_llist *list = (zend_llist *) Z_PTR_P(item); + zend_llist_destroy(list); + efree(list); +} + +/* Error code registration */ + +PHPAPI void php_stream_wrapper_register_error_codes( + php_stream_wrapper *wrapper, const php_stream_error_code_def *codes) +{ + if (!php_stream_wrapper_error_codes) { + php_stream_wrapper_error_codes = zend_new_array(8); + } + + zval code_table_zv, *code_table; + code_table = zend_hash_index_find(php_stream_wrapper_error_codes, (zend_ulong) wrapper); + + if (!code_table) { + code_table = &code_table_zv; + ZVAL_ARR(code_table, zend_new_array(8)); + zend_hash_index_update(php_stream_wrapper_error_codes, (zend_ulong) wrapper, code_table); + } + + for (const php_stream_error_code_def *def = codes; def->name != NULL; def++) { + zend_string *name = zend_string_init(def->name, strlen(def->name), 1); + zval zv; + ZVAL_STR(&zv, name); + zend_hash_index_update(code_table, def->code, &zv); + } +} + +PHPAPI const char *php_stream_wrapper_get_error_name(php_stream_wrapper *wrapper, int code) +{ + if (!php_stream_wrapper_error_codes) { + return NULL; + } + + zval *code_table = zend_hash_index_find(php_stream_wrapper_error_codes, (zend_ulong) wrapper); + + if (!code_table) { + return NULL; + } + + zval *error_name = zend_hash_index_find(Z_ARR_P(code_table), code); + return error_name ? Z_STRVAL_P(error_name) : NULL; +} + +/* Context option helpers */ + +static int php_stream_auto_decide_error_store_mode(int error_mode) +{ + switch (error_mode) { + case PHP_STREAM_ERROR_MODE_ERROR: + return PHP_STREAM_ERROR_STORE_NONE; + case PHP_STREAM_ERROR_MODE_EXCEPTION: + return PHP_STREAM_ERROR_STORE_NON_TERM; + case PHP_STREAM_ERROR_MODE_SILENT: + return PHP_STREAM_ERROR_STORE_ALL; + default: + return PHP_STREAM_ERROR_STORE_NONE; + } +} + +static int php_stream_get_error_mode(php_stream_context *context) +{ + if (!context) { + return PHP_STREAM_ERROR_MODE_ERROR; + } + + zval *option = php_stream_context_get_option(context, "stream", "error_mode"); + if (option && Z_TYPE_P(option) == IS_LONG) { + return Z_LVAL_P(option); + } + + return PHP_STREAM_ERROR_MODE_ERROR; +} + +static int php_stream_get_error_store_mode(php_stream_context *context, int error_mode) +{ + if (!context) { + return php_stream_auto_decide_error_store_mode(error_mode); + } + + zval *option = php_stream_context_get_option(context, "stream", "error_store"); + if (option && Z_TYPE_P(option) == IS_LONG) { + int store_mode = Z_LVAL_P(option); + + if (store_mode == PHP_STREAM_ERROR_STORE_AUTO) { + return php_stream_auto_decide_error_store_mode(error_mode); + } + + return store_mode; + } + + return php_stream_auto_decide_error_store_mode(error_mode); +} + +static bool php_stream_should_store_error(int store_mode, bool terminal) +{ + switch (store_mode) { + case PHP_STREAM_ERROR_STORE_NONE: + return false; + case PHP_STREAM_ERROR_STORE_NON_TERM: + return !terminal; + case PHP_STREAM_ERROR_STORE_TERMINAL: + return terminal; + case PHP_STREAM_ERROR_STORE_ALL: + return true; + default: + return false; + } +} + +/* StreamException methods */ + +static void php_stream_throw_exception( + const char *message, int code, const char *wrapper_name, const char *param) +{ + zval ex; + + object_init_ex(&ex, php_ce_stream_exception); + + zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"), message); + zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"), code); + zend_update_property_string( + php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("wrapperName"), wrapper_name); + + if (param) { + zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("param"), param); + } + + zend_throw_exception_object(&ex); +} + +/* Core error processing */ + +static void php_stream_process_error(php_stream_context *context, const char *wrapper_name, + php_stream *stream, int code, const char *message, const char *param, int severity, + bool terminal) +{ + int error_mode = php_stream_get_error_mode(context); + + /* Handle error based on error_mode */ + switch (error_mode) { + case PHP_STREAM_ERROR_MODE_ERROR: + if (param) { + php_error_docref1(NULL, param, severity, "%s", message); + } else { + php_error_docref(NULL, severity, "%s", message); + } + break; + + case PHP_STREAM_ERROR_MODE_EXCEPTION: + if (terminal) { + php_stream_throw_exception(message, code, wrapper_name, param); + } + break; + + case PHP_STREAM_ERROR_MODE_SILENT: + break; + } + + /* Call user error handler if set */ + if (context) { + zval *handler = php_stream_context_get_option(context, "stream", "error_handler"); + if (handler && Z_TYPE_P(handler) == IS_CALLABLE) { + zval retval; + zval args[5]; + + /* Arg 0: wrapper name */ + ZVAL_STRING(&args[0], wrapper_name); + + /* Arg 1: stream resource or null */ + if (stream && stream->res) { + ZVAL_RES(&args[1], stream->res); + GC_ADDREF(stream->res); + } else { + ZVAL_NULL(&args[1]); + } + + /* Arg 2: error code */ + ZVAL_LONG(&args[2], code); + + /* Arg 3: message */ + ZVAL_STRING(&args[3], message); + + /* Arg 4: param (or null) */ + if (param) { + ZVAL_STRING(&args[4], param); + } else { + ZVAL_NULL(&args[4]); + } + + call_user_function(NULL, NULL, handler, &retval, 5, args); + + zval_ptr_dtor(&retval); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[1]); + zval_ptr_dtor(&args[3]); + zval_ptr_dtor(&args[4]); + } + } +} + +/* Helper to create error entry */ +static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, + const char *wrapper_name, const char *param, int severity, bool terminal) +{ + php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + entry->message = message; /* Takes ownership */ + entry->code = code; + entry->wrapper_name = wrapper_name; + entry->param = param; + entry->severity = severity; + entry->terminal = terminal; + return entry; +} + +/* Common storage function*/ +static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, + php_stream_wrapper *wrapper, zend_string *message, int code, const char *wrapper_name, + const char *param, int severity, bool terminal) +{ + int error_mode = php_stream_get_error_mode(context); + int store_mode = php_stream_get_error_store_mode(context, error_mode); + + if (!php_stream_should_store_error(store_mode, terminal)) { + return; + } + + php_stream_error_entry *entry + = php_stream_create_error_entry(message, code, wrapper_name, param, severity, terminal); + zend_string_addref(message); /* Storage keeps a reference */ + + zend_llist *list; + + if (stream) { + /* Store in stream's error list */ + if (!stream->error_list) { + stream->error_list = emalloc(sizeof(zend_llist)); + zend_llist_init(stream->error_list, sizeof(php_stream_error_entry *), + php_stream_error_entry_dtor, 0); + } + list = stream->error_list; + } else { + /* Store in FG(stream_errors) for wrapper errors */ + if (!FG(wrapper_stored_errors)) { + ALLOC_HASHTABLE(FG(wrapper_stored_errors)); + zend_hash_init(FG(wrapper_stored_errors), 8, NULL, php_stream_error_list_dtor, 0); + list = NULL; + } else { + list = zend_hash_str_find_ptr( + FG(wrapper_stored_errors), (const char *) &wrapper, sizeof(wrapper)); + } + + if (!list) { + zend_llist new_list; + zend_llist_init( + &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); + list = zend_hash_str_update_mem(FG(wrapper_stored_errors), (const char *) &wrapper, + sizeof(wrapper), &new_list, sizeof(new_list)); + } + } + + zend_llist_add_element(list, &entry); +} + +/* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ + +static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *param, const char *fmt, va_list args) +{ + zend_string *message = vstrpprintf(0, fmt, args); + const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + + if (options & REPORT_ERRORS) { + php_stream_process_error( + context, wrapper_name, NULL, code, ZSTR_VAL(message), param, severity, terminal); + } + + php_stream_store_error_common( + context, NULL, wrapper, message, code, wrapper_name, param, severity, terminal); + + zend_string_release(message); +} + +PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, + int options, int severity, bool terminal, int code, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal( + wrapper, context, options, severity, terminal, code, NULL, fmt, args); + va_end(args); +} + +PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, + int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal( + wrapper, context, options, severity, terminal, code, param, fmt, args); + va_end(args); +} + +PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *param1, const char *param2, const char *fmt, ...) +{ + char *combined_param; + spprintf(&combined_param, 0, "%s,%s", param1, param2); + + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal( + wrapper, context, options, severity, terminal, code, combined_param, fmt, args); + va_end(args); + + efree(combined_param); +} + +/* Wrapper error logging - stores in FG(wrapper_logged_errors) */ + +static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper, + zend_string *message, int code, const char *wrapper_name, const char *param, int severity, + bool terminal) +{ + php_stream_error_entry *entry + = php_stream_create_error_entry(message, code, wrapper_name, param, severity, terminal); + zend_string_addref(message); + + if (!FG(wrapper_logged_errors)) { + ALLOC_HASHTABLE(FG(wrapper_logged_errors)); + zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); + } + + zend_llist *list = zend_hash_str_find_ptr( + FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); + + if (!list) { + zend_llist new_list; + zend_llist_init( + &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); + list = zend_hash_str_update_mem(FG(wrapper_logged_errors), (const char *) &wrapper, + sizeof(wrapper), &new_list, sizeof(new_list)); + } + + zend_llist_add_element(list, &entry); +} + +static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, int options, + int severity, bool terminal, int code, const char *param, va_list args) +{ + zend_string *message = vstrpprintf(0, args, args); + const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + + if (options & REPORT_ERRORS) { + /* Report immediately using standard error functions */ + if (param) { + php_error_docref1(NULL, param, severity, "%s", ZSTR_VAL(message)); + } else { + php_error_docref(NULL, severity, "%s", ZSTR_VAL(message)); + } + zend_string_release(message); + } else { + /* Store for later display in FG(wrapper_logged_errors) */ + php_stream_wrapper_log_store_error( + wrapper, message, code, wrapper_name, param, severity, terminal); + zend_string_release(message); + } +} + +PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, + int severity, bool terminal, int code, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + php_stream_wrapper_log_error_internal(wrapper, options, severity, terminal, code, NULL, args); + va_end(args); +} + +PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, int options, + int severity, bool terminal, int code, const char *param, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + php_stream_wrapper_log_error_internal(wrapper, options, severity, terminal, code, param, args); + va_end(args); +} + +static zend_llist *php_stream_get_wrapper_errors_list(php_stream_wrapper *wrapper) +{ + if (!FG(wrapper_logged_errors)) { + return NULL; + } else { + return (zend_llist*) zend_hash_str_find_ptr(FG(wrapper_logged_errors), (const char*)&wrapper, sizeof(wrapper)); + } +} + +/* {{{ wrapper error reporting */ +static void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption) +{ + char *tmp; + char *msg; + char errstr[256]; + int free_msg = 0; + + if (EG(exception)) { + /* Don't emit additional warnings if an exception has already been thrown. */ + return; + } + + tmp = estrdup(path); + if (wrapper) { + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper); + if (err_list) { + size_t l = 0; + int brlen; + int i; + int count = (int) zend_llist_count(err_list); + const char *br; + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + if (PG(html_errors)) { + brlen = 7; + br = "
\n"; + } else { + brlen = 1; + br = "\n"; + } + + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + l += brlen; + } + } + msg = emalloc(l + 1); + msg[0] = '\0'; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + strcat(msg, br); + } + } + + free_msg = 1; + } else { + if (wrapper == &php_plain_files_wrapper) { + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); + } else { + msg = "operation failed"; + } + } + } else { + msg = "no suitable wrapper could be found"; + } + + php_strip_url_passwd(tmp); + php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg); + efree(tmp); + if (free_msg) { + efree(msg); + } +} + +void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +{ + if (wrapper && FG(wrapper_logged_errors)) { + zend_hash_str_del(FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); + } +} + +/* Stream error reporting */ + +PHPAPI void php_stream_error( + php_stream *stream, int severity, bool terminal, int code, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + php_stream_wrapper *wrapper = stream->wrapper; + const char *wrapper_name = wrapper ? wrapper->wops->label : "stream"; + + php_stream_context *context = PHP_STREAM_CONTEXT(stream); + + /* Process the error - always report for stream errors */ + php_stream_process_error( + context, wrapper_name, stream, code, ZSTR_VAL(message), NULL, severity, terminal); + + /* Store error */ + php_stream_store_error_common( + context, stream, wrapper, message, code, wrapper_name, NULL, severity, terminal); + + zend_string_release(message); +} + +/* StreamException class registration */ + +PHP_MINIT_FUNCTION(stream_errors) +{ + php_ce_stream_exception = register_class_StreamException(zend_ce_exception); + + return SUCCESS; +} + +PHP_METHOD(StreamException, getParam) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zval *param = zend_read_property( + php_ce_stream_exception, Z_OBJ_P(getThis()), ZEND_STRL("param"), 1, NULL); + RETURN_COPY(param); +} + +PHP_METHOD(StreamException, getWrapperName) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zval *wrapper_name = zend_read_property( + php_ce_stream_exception, Z_OBJ_P(getThis()), ZEND_STRL("wrapperName"), 1, NULL); + RETURN_COPY(wrapper_name); +} diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php new file mode 100644 index 0000000000000..387c2c692703f --- /dev/null +++ b/main/streams/stream_errors.stub.php @@ -0,0 +1,16 @@ +\n"; - } else { - brlen = 1; - br = "\n"; - } - - for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0; - err_buf_p; - err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) { - l += strlen(*err_buf_p); - if (i < count - 1) { - l += brlen; - } - } - msg = emalloc(l + 1); - msg[0] = '\0'; - for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0; - err_buf_p; - err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, *err_buf_p); - if (i < count - 1) { - strcat(msg, br); - } - } - - free_msg = 1; - } else { - if (wrapper == &php_plain_files_wrapper) { - msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); - } else { - msg = "operation failed"; - } - } - } else { - msg = "no suitable wrapper could be found"; - } - - php_strip_url_passwd(tmp); - php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg); - efree(tmp); - if (free_msg) { - efree(msg); - } -} - -static void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) -{ - if (wrapper && FG(wrapper_errors)) { - zend_hash_str_del(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper)); - } -} - -static void wrapper_error_dtor(void *error) -{ - efree(*(char**)error); -} - -static void wrapper_list_dtor(zval *item) { - zend_llist *list = (zend_llist*)Z_PTR_P(item); - zend_llist_destroy(list); - efree(list); -} - -PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, const char *fmt, ...) -{ - va_list args; - char *buffer = NULL; - - va_start(args, fmt); - vspprintf(&buffer, 0, fmt, args); - va_end(args); - - if ((options & REPORT_ERRORS) || wrapper == NULL) { - php_error_docref(NULL, E_WARNING, "%s", buffer); - efree(buffer); - } else { - zend_llist *list = NULL; - if (!FG(wrapper_errors)) { - ALLOC_HASHTABLE(FG(wrapper_errors)); - zend_hash_init(FG(wrapper_errors), 8, NULL, wrapper_list_dtor, 0); - } else { - list = zend_hash_str_find_ptr(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper)); - } - - if (!list) { - zend_llist new_list; - zend_llist_init(&new_list, sizeof(buffer), wrapper_error_dtor, 0); - list = zend_hash_str_update_mem(FG(wrapper_errors), (const char*)&wrapper, - sizeof(wrapper), &new_list, sizeof(new_list)); - } - - /* append to linked list */ - zend_llist_add_element(list, &buffer); - } -} - - /* }}} */ /* allocate a new stream for a particular ops */ @@ -1877,10 +1742,16 @@ void php_shutdown_stream_hashes(void) FG(stream_filters) = NULL; } - if (FG(wrapper_errors)) { - zend_hash_destroy(FG(wrapper_errors)); - efree(FG(wrapper_errors)); - FG(wrapper_errors) = NULL; + if (FG(wrapper_logged_errors)) { + zend_hash_destroy(FG(wrapper_logged_errors)); + efree(FG(wrapper_logged_errors)); + FG(wrapper_logged_errors) = NULL; + } + + if (FG(wrapper_stored_errors)) { + zend_hash_destroy(FG(wrapper_stored_errors)); + efree(FG(wrapper_stored_errors)); + FG(wrapper_stored_errors) = NULL; } } diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 403f0aa6efbfe..6e02ae5c07341 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -298,7 +298,7 @@ AC_DEFINE('HAVE_STRNLEN', 1); AC_DEFINE('ZEND_CHECK_STACK_LIMIT', 1) -ADD_SOURCES("main/streams", "streams.c cast.c memory.c filter.c plain_wrapper.c \ +ADD_SOURCES("main/streams", "streams.c stream_errors.c cast.c memory.c filter.c plain_wrapper.c \ userspace.c transports.c xp_socket.c mmap.c glob_wrapper.c"); ADD_FLAG("CFLAGS_BD_MAIN_STREAMS", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); From c2c811427d0a4bb8382f5f5ed52a917e59eb3cc2 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 15:53:30 +0100 Subject: [PATCH 02/61] stream: add error codes and handler check --- main/php_streams.h | 4 - main/streams/php_stream_errors.h | 105 ++++++++-- main/streams/stream_errors.c | 76 +++---- main/streams/stream_errors.stub.php | 294 ++++++++++++++++++++++++++- main/streams/stream_errors_arginfo.h | 71 ++++++- 5 files changed, 472 insertions(+), 78 deletions(-) diff --git a/main/php_streams.h b/main/php_streams.h index 33af6c3c91c30..601223bd9a5fd 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -645,10 +645,6 @@ PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf); #define php_stream_open_wrapper(path, mode, options, opened) _php_stream_open_wrapper_ex((path), (mode), (options), (opened), NULL STREAMS_CC) #define php_stream_open_wrapper_ex(path, mode, options, opened, context) _php_stream_open_wrapper_ex((path), (mode), (options), (opened), (context) STREAMS_CC) - -/* pushes an error message onto the stack for a wrapper instance */ -PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, const char *fmt, ...) PHP_ATTRIBUTE_FORMAT(printf, 3, 4); - typedef enum { PHP_STREAM_UNCHANGED = 0, /* orig stream was seekable anyway */ PHP_STREAM_RELEASED = 1, /* newstream should be used; origstream is no longer valid */ diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 08998a87111cf..5331000dfe149 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ + */ + #ifndef PHP_STREAM_ERRORS_H #define PHP_STREAM_ERRORS_H @@ -18,11 +34,76 @@ BEGIN_EXTERN_C() #define PHP_STREAM_ERROR_STORE_TERMINAL 3 #define PHP_STREAM_ERROR_STORE_ALL 4 -/* Error code definition for registration */ -typedef struct { - int code; - const char *name; -} php_stream_error_code_def; +/* Stream Error Codes*/ +/* No error */ +#define STREAM_ERROR_CODE_NONE 0 +/* Generic unspecified error */ +#define STREAM_ERROR_CODE_GENERIC 1 +/* Stream I/O operations */ +#define STREAM_ERROR_CODE_READ_FAILED 10 +#define STREAM_ERROR_CODE_WRITE_FAILED 11 +#define STREAM_ERROR_CODE_SEEK_FAILED 12 +#define STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED 13 +#define STREAM_ERROR_CODE_FLUSH_FAILED 14 +#define STREAM_ERROR_CODE_TRUNCATE_FAILED 15 +#define STREAM_ERROR_CODE_NOT_WRITABLE 16 +#define STREAM_ERROR_CODE_NOT_READABLE 17 +/* File system operations */ +#define STREAM_ERROR_CODE_NOT_FOUND 30 +#define STREAM_ERROR_CODE_PERMISSION_DENIED 31 +#define STREAM_ERROR_CODE_ALREADY_EXISTS 32 +#define STREAM_ERROR_CODE_INVALID_PATH 33 +#define STREAM_ERROR_CODE_PATH_TOO_LONG 34 +#define STREAM_ERROR_CODE_UNLINK_FAILED 35 +#define STREAM_ERROR_CODE_RENAME_FAILED 36 +#define STREAM_ERROR_CODE_MKDIR_FAILED 37 +#define STREAM_ERROR_CODE_RMDIR_FAILED 38 +#define STREAM_ERROR_CODE_STAT_FAILED 39 +#define STREAM_ERROR_CODE_CHMOD_FAILED 40 +#define STREAM_ERROR_CODE_CHOWN_FAILED 41 +#define STREAM_ERROR_CODE_TOUCH_FAILED 42 +#define STREAM_ERROR_CODE_INVALID_MODE 43 +#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 44 +#define STREAM_ERROR_CODE_READONLY 45 +#define STREAM_ERROR_CODE_RECURSION_DETECTED 46 +/* Wrapper/protocol operations */ +#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 60 +#define STREAM_ERROR_CODE_WRAPPER_DISABLED 61 +#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 62 +#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 63 +#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 64 +#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 65 +/* Filter operations */ +#define STREAM_ERROR_CODE_FILTER_NOT_FOUND 80 +#define STREAM_ERROR_CODE_FILTER_FAILED 81 +/* Cast/conversion operations */ +#define STREAM_ERROR_CODE_CAST_FAILED 90 +#define STREAM_ERROR_CODE_CAST_NOT_SUPPORTED 91 +#define STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED 92 +#define STREAM_ERROR_CODE_BUFFERED_DATA_LOST 93 +/* Network/socket operations */ +#define STREAM_ERROR_CODE_NETWORK_SEND_FAILED 100 +#define STREAM_ERROR_CODE_NETWORK_RECV_FAILED 101 +#define STREAM_ERROR_CODE_SSL_NOT_SUPPORTED 102 +#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 103 +#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 104 +#define STREAM_ERROR_CODE_PROTOCOL_ERROR 105 +#define STREAM_ERROR_CODE_INVALID_URL 106 +#define STREAM_ERROR_CODE_INVALID_RESPONSE 107 +#define STREAM_ERROR_CODE_REDIRECT_LIMIT 108 +#define STREAM_ERROR_CODE_AUTH_FAILED 109 +/* Encoding/decoding operations */ +#define STREAM_ERROR_CODE_ENCODING_FAILED 120 +#define STREAM_ERROR_CODE_INVALID_FORMAT 121 +/* Resource/allocation operations */ +#define STREAM_ERROR_CODE_ALLOCATION_FAILED 130 +#define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 131 +/* Locking operations */ +#define STREAM_ERROR_CODE_LOCK_FAILED 140 +#define STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED 141 +/* Userspace stream operations */ +#define STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED 150 +#define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 151 /* Stored error entry */ typedef struct { @@ -34,20 +115,6 @@ typedef struct { bool terminal; } php_stream_error_entry; -/* Sentinel for error code array termination */ -#define PHP_STREAM_ERROR_CODE_END {0, NULL} - -/* Error code registration */ -PHPAPI void php_stream_wrapper_register_error_codes( - php_stream_wrapper *wrapper, - const php_stream_error_code_def *codes -); - -PHPAPI const char *php_stream_wrapper_get_error_name( - php_stream_wrapper *wrapper, - int code -); - /* Main error reporting functions */ PHPAPI void php_stream_wrapper_error( php_stream_wrapper *wrapper, diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index b308521ca33c3..52428e9065f34 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ + */ + #include "php.h" #include "php_globals.h" #include "php_streams.h" @@ -26,48 +42,6 @@ static void php_stream_error_list_dtor(zval *item) efree(list); } -/* Error code registration */ - -PHPAPI void php_stream_wrapper_register_error_codes( - php_stream_wrapper *wrapper, const php_stream_error_code_def *codes) -{ - if (!php_stream_wrapper_error_codes) { - php_stream_wrapper_error_codes = zend_new_array(8); - } - - zval code_table_zv, *code_table; - code_table = zend_hash_index_find(php_stream_wrapper_error_codes, (zend_ulong) wrapper); - - if (!code_table) { - code_table = &code_table_zv; - ZVAL_ARR(code_table, zend_new_array(8)); - zend_hash_index_update(php_stream_wrapper_error_codes, (zend_ulong) wrapper, code_table); - } - - for (const php_stream_error_code_def *def = codes; def->name != NULL; def++) { - zend_string *name = zend_string_init(def->name, strlen(def->name), 1); - zval zv; - ZVAL_STR(&zv, name); - zend_hash_index_update(code_table, def->code, &zv); - } -} - -PHPAPI const char *php_stream_wrapper_get_error_name(php_stream_wrapper *wrapper, int code) -{ - if (!php_stream_wrapper_error_codes) { - return NULL; - } - - zval *code_table = zend_hash_index_find(php_stream_wrapper_error_codes, (zend_ulong) wrapper); - - if (!code_table) { - return NULL; - } - - zval *error_name = zend_hash_index_find(Z_ARR_P(code_table), code); - return error_name ? Z_STRVAL_P(error_name) : NULL; -} - /* Context option helpers */ static int php_stream_auto_decide_error_store_mode(int error_mode) @@ -186,10 +160,22 @@ static void php_stream_process_error(php_stream_context *context, const char *wr /* Call user error handler if set */ if (context) { zval *handler = php_stream_context_get_option(context, "stream", "error_handler"); - if (handler && Z_TYPE_P(handler) == IS_CALLABLE) { + if (handler) { + zend_fcall_info_cache fcc; + char *is_callable_error = NULL; zval retval; zval args[5]; + if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) { + if (is_callable_error) { + zend_type_error("stream error handler must be a valid callback, %s", is_callable_error); + efree(is_callable_error); + } else { + zend_type_error("stream error must be a valid callback"); + } + return; + } + /* Arg 0: wrapper name */ ZVAL_STRING(&args[0], wrapper_name); @@ -525,10 +511,12 @@ PHPAPI void php_stream_error( zend_string_release(message); } -/* StreamException class registration */ +/* StreamException class and error constants registration */ PHP_MINIT_FUNCTION(stream_errors) { + register_stream_errors_symbols(module_number); + php_ce_stream_exception = register_class_StreamException(zend_ce_exception); return SUCCESS; diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 387c2c692703f..fb660ad6e56c5 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -1,16 +1,298 @@ Date: Sun, 16 Nov 2025 16:46:12 +0100 Subject: [PATCH 03/61] stream: convert phar errors --- ext/phar/dirstream.c | 92 ++++++++++++++++++++++--------- ext/phar/stream.c | 128 +++++++++++++++++++++++++++++-------------- 2 files changed, 152 insertions(+), 68 deletions(-) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index f37599e7db117..7faeddba25d7d 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -254,25 +254,30 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, phar_archive_data *phar; if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) { - php_stream_wrapper_log_error(wrapper, options, "phar url \"%s\" is unknown", path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar url \"%s\" is unknown", path); return NULL; } /* we must have at the very least phar://alias.phar/ */ if (!resource->scheme || !resource->host || !resource->path) { if (resource->host && !resource->path) { - php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", path, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PATH, + "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", + path, ZSTR_VAL(resource->host)); php_url_free(resource); return NULL; } php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\", must have at least phar://%s/", path, path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url \"%s\", must have at least phar://%s/", path, path); return NULL; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar url \"%s\"", path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: not a phar url \"%s\"", path); return NULL; } @@ -280,10 +285,11 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { if (error) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); efree(error); } else { - php_stream_wrapper_log_error(wrapper, options, "phar file \"%s\" is unknown", ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar file \"%s\" is unknown", ZSTR_VAL(resource->host)); } php_url_free(resource); return NULL; @@ -353,7 +359,8 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo /* pre-readonly check, we need to know if this is a data phar */ if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, NULL, NULL, 2, 2)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: cannot create directory \"%s\", no phar archive specified", url_from); return 0; } @@ -364,7 +371,8 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo efree(arch); if (PHAR_G(readonly) && (!phar || !phar->is_data)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + "phar error: cannot create directory \"%s\", write operations disabled", url_from); return 0; } @@ -375,18 +383,22 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url_from); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url \"%s\"", url_from); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url_from); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: not a phar stream url \"%s\"", url_from); return 0; } if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", + ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error); efree(error); php_url_free(resource); return 0; @@ -398,13 +410,17 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo zend_string_efree(e->filename); efree(e); } - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); php_url_free(resource); return 0; } if (error) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "phar error: cannot create directory \"%s\" in phar \"%s\", %s", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); php_url_free(resource); return 0; @@ -412,13 +428,17 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo if (phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 0, &error, true)) { /* entry exists as a file */ - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); php_url_free(resource); return 0; } if (error) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "phar error: cannot create directory \"%s\" in phar \"%s\", %s", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); php_url_free(resource); return 0; @@ -447,7 +467,9 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo entry.old_flags = PHAR_ENT_PERM_DEF_DIR; if (NULL == zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info))) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", ZSTR_VAL(entry.filename), phar->fname); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", + ZSTR_VAL(entry.filename), phar->fname); zend_string_efree(entry.filename); return 0; } @@ -455,7 +477,9 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo phar_flush(phar, &error); if (error) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry.filename), phar->fname, error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "phar error: cannot create directory \"%s\" in phar \"%s\", %s", + ZSTR_VAL(entry.filename), phar->fname, error); zend_hash_del(&phar->manifest, entry.filename); efree(error); return 0; @@ -479,7 +503,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options /* pre-readonly check, we need to know if this is a data phar */ if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, NULL, NULL, 2, 2)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url); return 0; } @@ -490,7 +515,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options efree(arch); if (PHAR_G(readonly) && (!phar || !phar->is_data)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + "phar error: cannot rmdir directory \"%s\", write operations disabled", url); return 0; } @@ -501,18 +527,22 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url \"%s\"", url); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: not a phar stream url \"%s\"", url); return 0; } if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); php_url_free(resource); return 0; @@ -522,10 +552,14 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options if (!(entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, true))) { if (error) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); } else { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", + ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); } php_url_free(resource); return 0; @@ -539,7 +573,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len) && IS_SLASH(ZSTR_VAL(str_key)[path_len]) ) { - php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "phar error: Directory not empty"); if (entry->is_temp_dir) { zend_string_efree(entry->filename); efree(entry); @@ -555,7 +590,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len) && IS_SLASH(ZSTR_VAL(str_key)[path_len]) ) { - php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "phar error: Directory not empty"); if (entry->is_temp_dir) { zend_string_efree(entry->filename); efree(entry); @@ -576,7 +612,9 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options phar_flush(phar, &error); if (error) { - php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry->filename), phar->fname, error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", + ZSTR_VAL(entry->filename), phar->fname, error); php_url_free(resource); efree(error); return 0; diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 4bd1e85666b85..8e7a06faa2a9a 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -68,17 +68,21 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (mode[0] == 'a') { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: open mode append not supported"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + "phar error: open mode append not supported"); } return NULL; } if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { if (arch && !entry) { - php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PATH, + "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", + filename, arch); arch = NULL; } else { - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url or non-existent phar \"%s\"", filename); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url or non-existent phar \"%s\"", filename); } } return NULL; @@ -111,7 +115,8 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + "phar error: write operations disabled by the php.ini setting phar.readonly"); } php_url_free(resource); return NULL; @@ -120,7 +125,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); } efree(error); } @@ -131,7 +136,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (error) { spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", ZSTR_VAL(resource->host)); if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); } efree(error); } @@ -143,7 +148,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); } efree(error); } @@ -176,13 +181,15 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url \"%s\"", path); return NULL; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: not a phar stream url \"%s\"", path); return NULL; } @@ -193,10 +200,11 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) { if (NULL == (idata = phar_get_or_create_entry_data(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), mode, 0, &error, true, time(NULL)))) { if (error) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); efree(error); } else { - php_stream_wrapper_log_error(wrapper, options, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); } efree(internal_file); php_url_free(resource); @@ -236,7 +244,8 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (!*internal_file && (options & STREAM_OPEN_FOR_INCLUDE)) { /* retrieve the stub */ if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, NULL)) { - php_stream_wrapper_log_error(wrapper, options, "file %s is not a valid phar archive", ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_FORMAT, + "file %s is not a valid phar archive", ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); return NULL; @@ -256,7 +265,8 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (stream == NULL) { stream = phar_open_archive_fp(phar); if (UNEXPECTED(!stream)) { - php_stream_wrapper_log_error(wrapper, options, "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); return NULL; @@ -293,10 +303,11 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if ((FAILURE == phar_get_entry_data(&idata, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), "r", 0, &error, false)) || !idata) { idata_error: if (error) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); efree(error); } else { - php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); } efree(internal_file); php_url_free(resource); @@ -314,7 +325,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha /* check length, crc32 */ if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); efree(error); phar_entry_delref(idata); efree(internal_file); @@ -445,7 +456,9 @@ static ssize_t phar_stream_write(php_stream *stream, const char *buf, size_t cou php_stream_seek(data->fp, data->position + data->zero, SEEK_SET); if (count != php_stream_write(data->fp, buf, count)) { - php_stream_wrapper_log_error(stream->wrapper, stream->flags, "phar error: Could not write %zu characters to \"%s\" in phar \"%s\"", count, ZSTR_VAL(data->internal_file->filename), data->phar->fname); + php_stream_wrapper_log_warn(stream->wrapper, stream->flags, STREAM_ERROR_CODE_WRITE_FAILED, + "phar error: Could not write %zu characters to \"%s\" in phar \"%s\"", + count, ZSTR_VAL(data->internal_file->filename), data->phar->fname); return -1; } data->position = php_stream_tell(data->fp) - data->zero; @@ -472,7 +485,7 @@ static int phar_stream_flush(php_stream *stream) /* {{{ */ data->internal_file->timestamp = time(0); ret = phar_flush(data->phar, &error); if (error) { - php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS, "%s", error); + php_stream_wrapper_log_warn(stream->wrapper, REPORT_ERRORS, STREAM_ERROR_CODE_FLUSH_FAILED, "%s", error); efree(error); } return ret; @@ -663,20 +676,23 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int phar_archive_data *pphar; if ((resource = phar_parse_url(wrapper, url, "rb", options)) == NULL) { - php_stream_wrapper_log_error(wrapper, options, "phar error: unlink failed"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + "phar error: unlink failed"); return 0; } /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: invalid url \"%s\"", url); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: not a phar stream url \"%s\"", url); return 0; } @@ -685,7 +701,8 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), resource->host); if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { php_url_free(resource); - php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + "phar error: write operations disabled by the php.ini setting phar.readonly"); return 0; } @@ -695,10 +712,12 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int if (FAILURE == phar_get_entry_data(&idata, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, internal_file_len, "r", 0, &error, true)) { /* constraints of fp refcount were not met */ if (error) { - php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed: %s", url, error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + "unlink of \"%s\" failed: %s", url, error); efree(error); } else { - php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed, file does not exist", url); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + "unlink of \"%s\" failed, file does not exist", url); } efree(internal_file); php_url_free(resource); @@ -709,7 +728,9 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int } if (idata->internal_file->fp_refcount > 1) { /* more than just our fp resource is open for this file */ - php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, ZSTR_VAL(resource->host)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", + internal_file, ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); phar_entry_delref(idata); @@ -719,7 +740,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int efree(internal_file); phar_entry_remove(idata, &error); if (error) { - php_stream_wrapper_log_error(wrapper, options, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, "%s", error); efree(error); } return 1; @@ -738,7 +759,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from error = NULL; if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", + url_from, url_to, url_from); return 0; } if (SUCCESS != phar_get_archive(&pfrom, ZSTR_VAL(resource_from->host), ZSTR_LEN(resource_from->host), NULL, 0, &error)) { @@ -749,13 +772,16 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from } if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) { php_url_free(resource_from); - php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly"); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, + "phar error: Write operations disabled by the php.ini setting phar.readonly"); return 0; } if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { php_url_free(resource_from); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", + url_from, url_to, url_to); return 0; } if (SUCCESS != phar_get_archive(&pto, ZSTR_VAL(resource_to->host), ZSTR_LEN(resource_to->host), NULL, 0, &error)) { @@ -767,14 +793,17 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (PHAR_G(readonly) && (!pto || !pto->is_data)) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly"); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, + "phar error: Write operations disabled by the php.ini setting phar.readonly"); return 0; } if (!zend_string_equals(resource_from->host, resource_to->host)) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", + url_from, url_to); return 0; } @@ -782,35 +811,44 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (!resource_from->scheme || !resource_from->host || !resource_from->path) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", + url_from, url_to, url_from); return 0; } if (!resource_to->scheme || !resource_to->host || !resource_to->path) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, + "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", + url_from, url_to, url_to); return 0; } if (!zend_string_equals_literal_ci(resource_from->scheme, "phar")) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", + url_from, url_to, url_from); return 0; } if (!zend_string_equals_literal_ci(resource_to->scheme, "phar")) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", + url_from, url_to, url_to); return 0; } if (SUCCESS != phar_get_archive(&phar, ZSTR_VAL(resource_from->host), ZSTR_LEN(resource_from->host), NULL, 0, &error)) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); efree(error); return 0; } @@ -818,7 +856,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", + url_from, url_to); return 0; } @@ -829,7 +869,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (entry->is_deleted) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", + url_from, url_to); return 0; } /* transfer all data over to the new entry */ @@ -849,7 +891,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (FAILURE == phar_copy_entry_fp(source, entry, &error)) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); efree(error); zend_hash_del(&phar->manifest, entry->filename); return 0; @@ -863,7 +906,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from /* file does not exist */ php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, + "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", + url_from, url_to); return 0; } @@ -942,7 +987,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from if (error) { php_url_free(resource_from); php_url_free(resource_to); - php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error); efree(error); return 0; } From 440296fa152ce5cf8dea0e672423e9ecd1d1b0bc Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 16:55:22 +0100 Subject: [PATCH 04/61] stream: convert ftp wrapper errors --- ext/standard/ftp_fopen_wrapper.c | 78 +++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index d1bb3aeeccd68..9c95214ab3b0f 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -109,7 +109,9 @@ static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream * /* For write modes close data stream first to signal EOF to server */ result = GET_FTP_RESULT(controlstream); if (result != 226 && result != 250) { - php_error_docref(NULL, E_WARNING, "FTP server error %d:%s", result, tmp_line); + php_stream_wrapper_warn(wrapper, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_PROTOCOL_ERROR, + "FTP server error %d:%s", result, tmp_line); ret = EOF; } } @@ -187,7 +189,8 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char /* get the response */ result = GET_FTP_RESULT(stream); if (result != 334) { - php_stream_wrapper_log_error(wrapper, options, "Server doesn't support FTPS."); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "Server doesn't support FTPS."); goto connect_errexit; } else { /* we must reuse the old SSL session id */ @@ -206,7 +209,8 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(stream, 1) < 0) { - php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "Unable to activate SSL mode"); php_stream_close(stream); stream = NULL; goto connect_errexit; @@ -237,7 +241,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len; \ while (s < e) { \ if (iscntrl(*s)) { \ - php_stream_wrapper_log_error(wrapper, options, err_msg, val); \ + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_AUTH_FAILED, err_msg, val); \ goto connect_errexit; \ } \ s++; \ @@ -434,7 +438,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa } if (strpbrk(mode, "wa+")) { if (read_write) { - php_stream_wrapper_log_error(wrapper, options, "FTP does not support simultaneous read/write connections"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + "FTP does not support simultaneous read/write connections"); return NULL; } if (strchr(mode, 'a')) { @@ -445,7 +450,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa } if (!read_write) { /* No mode specified? */ - php_stream_wrapper_log_error(wrapper, options, "Unknown file open mode"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_MODE, + "Unknown file open mode"); return NULL; } @@ -456,7 +462,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC); } else { /* ftp proxy is read-only */ - php_stream_wrapper_log_error(wrapper, options, "FTP proxy may only be used in read mode"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + "FTP proxy may only be used in read mode"); return NULL; } } @@ -508,7 +515,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa goto errexit; } } else { - php_stream_wrapper_log_error(wrapper, options, "Remote file already exists and overwrite context option not specified"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + "Remote file already exists and overwrite context option not specified"); errno = EEXIST; goto errexit; } @@ -532,7 +540,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval)); result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { - php_stream_wrapper_log_error(wrapper, options, "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval)); goto errexit; } } @@ -577,7 +586,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { - php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; tmp_line[0]='\0'; @@ -599,10 +609,12 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa php_stream_close(stream); } if (tmp_line[0] != '\0') - php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "FTP server reports %s", tmp_line); if (error_message) { - php_stream_wrapper_log_error(wrapper, options, "Failed to set up data channel: %s", ZSTR_VAL(error_message)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NETWORK_SEND_FAILED, + "Failed to set up data channel: %s", ZSTR_VAL(error_message)); zend_string_release(error_message); } return NULL; @@ -747,7 +759,8 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { - php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; goto opendir_errexit; @@ -771,7 +784,8 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch php_stream_close(stream); } if (tmp_line[0] != '\0') { - php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "FTP server reports %s", tmp_line); } return NULL; } @@ -910,14 +924,16 @@ static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, i stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_AUTH_FAILED, + "Unable to connect to %s", url); } goto unlink_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PATH, + "Invalid path provided in %s", url); } goto unlink_errexit; } @@ -928,7 +944,8 @@ static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, i result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Error Deleting file: %s", tmp_line); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, + "Error Deleting file: %s", tmp_line); } goto unlink_errexit; } @@ -992,7 +1009,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, context, NULL, NULL, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Unable to connect to %s", ZSTR_VAL(resource_from->host)); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_AUTH_FAILED, + "Unable to connect to %s", ZSTR_VAL(resource_from->host)); } goto rename_errexit; } @@ -1003,7 +1021,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "Error Renaming file: %s", tmp_line); } goto rename_errexit; } @@ -1014,7 +1033,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RENAME_FAILED, + "Error Renaming file: %s", tmp_line); } goto rename_errexit; } @@ -1047,14 +1067,16 @@ static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, in stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_AUTH_FAILED, + "Unable to connect to %s", url); } goto mkdir_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PATH, + "Invalid path provided in %s", url); } goto mkdir_errexit; } @@ -1095,7 +1117,8 @@ static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, in result = GET_FTP_RESULT(stream); if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", tmp_line); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, + "%s", tmp_line); } break; } @@ -1139,14 +1162,16 @@ static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, in stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL); if (!stream) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_AUTH_FAILED, + "Unable to connect to %s", url); } goto rmdir_errexit; } if (resource->path == NULL) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PATH, + "Invalid path provided in %s", url); } goto rmdir_errexit; } @@ -1156,7 +1181,8 @@ static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, in if (result < 200 || result > 299) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", tmp_line); + php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, + "%s", tmp_line); } goto rmdir_errexit; } From 0a92d9485b40b59cc967807f9b4cdc06ba50092a Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 17:44:50 +0100 Subject: [PATCH 05/61] stream: add few more errors to replace generic errors in phar and ftp --- ext/phar/stream.c | 12 +++---- ext/standard/ftp_fopen_wrapper.c | 2 +- main/streams/php_stream_errors.h | 48 +++++++++++++++------------- main/streams/stream_errors.stub.php | 20 ++++++++++++ main/streams/stream_errors_arginfo.h | 6 +++- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 8e7a06faa2a9a..eed488c1c68a7 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -125,7 +125,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); } efree(error); } @@ -136,7 +136,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (error) { spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", ZSTR_VAL(resource->host)); if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); } efree(error); } @@ -200,10 +200,10 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) { if (NULL == (idata = phar_get_or_create_entry_data(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), mode, 0, &error, true, time(NULL)))) { if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_CREATE_FAILED, "%s", error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_CREATE_FAILED, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); } efree(internal_file); @@ -265,7 +265,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (stream == NULL) { stream = phar_open_archive_fp(phar); if (UNEXPECTED(!stream)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); @@ -325,7 +325,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha /* check length, crc32 */ if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, "%s", error); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ARCHIVING_FAILED, "%s", error); efree(error); phar_entry_delref(idata); efree(internal_file); diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 9c95214ab3b0f..6f6550b6ac8f7 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -540,7 +540,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval)); result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_GENERIC, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RESUMPTION_FAILED, "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval)); goto errexit; } diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 5331000dfe149..f36ece142bc06 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -54,18 +54,20 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_ALREADY_EXISTS 32 #define STREAM_ERROR_CODE_INVALID_PATH 33 #define STREAM_ERROR_CODE_PATH_TOO_LONG 34 -#define STREAM_ERROR_CODE_UNLINK_FAILED 35 -#define STREAM_ERROR_CODE_RENAME_FAILED 36 -#define STREAM_ERROR_CODE_MKDIR_FAILED 37 -#define STREAM_ERROR_CODE_RMDIR_FAILED 38 -#define STREAM_ERROR_CODE_STAT_FAILED 39 -#define STREAM_ERROR_CODE_CHMOD_FAILED 40 -#define STREAM_ERROR_CODE_CHOWN_FAILED 41 -#define STREAM_ERROR_CODE_TOUCH_FAILED 42 -#define STREAM_ERROR_CODE_INVALID_MODE 43 -#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 44 -#define STREAM_ERROR_CODE_READONLY 45 -#define STREAM_ERROR_CODE_RECURSION_DETECTED 46 +#define STREAM_ERROR_CODE_OPEN_FAILED 35 +#define STREAM_ERROR_CODE_CREATE_FAILED 36 +#define STREAM_ERROR_CODE_UNLINK_FAILED 37 +#define STREAM_ERROR_CODE_RENAME_FAILED 38 +#define STREAM_ERROR_CODE_MKDIR_FAILED 39 +#define STREAM_ERROR_CODE_RMDIR_FAILED 40 +#define STREAM_ERROR_CODE_STAT_FAILED 41 +#define STREAM_ERROR_CODE_CHMOD_FAILED 42 +#define STREAM_ERROR_CODE_CHOWN_FAILED 43 +#define STREAM_ERROR_CODE_TOUCH_FAILED 44 +#define STREAM_ERROR_CODE_INVALID_MODE 45 +#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 46 +#define STREAM_ERROR_CODE_READONLY 47 +#define STREAM_ERROR_CODE_RECURSION_DETECTED 48 /* Wrapper/protocol operations */ #define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 60 #define STREAM_ERROR_CODE_WRAPPER_DISABLED 61 @@ -85,16 +87,18 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_NETWORK_SEND_FAILED 100 #define STREAM_ERROR_CODE_NETWORK_RECV_FAILED 101 #define STREAM_ERROR_CODE_SSL_NOT_SUPPORTED 102 -#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 103 -#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 104 -#define STREAM_ERROR_CODE_PROTOCOL_ERROR 105 -#define STREAM_ERROR_CODE_INVALID_URL 106 -#define STREAM_ERROR_CODE_INVALID_RESPONSE 107 -#define STREAM_ERROR_CODE_REDIRECT_LIMIT 108 -#define STREAM_ERROR_CODE_AUTH_FAILED 109 -/* Encoding/decoding operations */ -#define STREAM_ERROR_CODE_ENCODING_FAILED 120 -#define STREAM_ERROR_CODE_INVALID_FORMAT 121 +#define STREAM_ERROR_CODE_RESUMPTION_FAILED 103 +#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 104 +#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 105 +#define STREAM_ERROR_CODE_PROTOCOL_ERROR 106 +#define STREAM_ERROR_CODE_INVALID_URL 107 +#define STREAM_ERROR_CODE_INVALID_RESPONSE 108 +#define STREAM_ERROR_CODE_REDIRECT_LIMIT 109 +#define STREAM_ERROR_CODE_AUTH_FAILED 110 +/* Encoding/decoding/archiving operations */ +#define STREAM_ERROR_CODE_ARCHIVING_FAILED 120 +#define STREAM_ERROR_CODE_ENCODING_FAILED 121 +#define STREAM_ERROR_CODE_INVALID_FORMAT 122 /* Resource/allocation operations */ #define STREAM_ERROR_CODE_ALLOCATION_FAILED 130 #define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 131 diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index fb660ad6e56c5..a9d090858aff6 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -86,6 +86,16 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_PATH_TOO_LONG */ const STREAM_ERROR_CODE_PATH_TOO_LONG = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_CREATE_FAILED + */ +const STREAM_ERROR_CODE_CREATE_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_OPEN_FAILED + */ +const STREAM_ERROR_CODE_OPEN_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_UNLINK_FAILED @@ -221,6 +231,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_SSL_NOT_SUPPORTED */ const STREAM_ERROR_CODE_SSL_NOT_SUPPORTED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_RESUMPTION_FAILED + */ +const STREAM_ERROR_CODE_RESUMPTION_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG @@ -256,6 +271,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_AUTH_FAILED */ const STREAM_ERROR_CODE_AUTH_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_ARCHIVING_FAILED + */ +const STREAM_ERROR_CODE_ARCHIVING_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_ENCODING_FAILED diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index fde8805efd8e8..ff8da9dab548e 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 33e2f6f12083668388d80dbd77d97fd3f1efbe39 */ + * Stub hash: e3d25328018715348be75f6cb31ae6684afe0a56 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -33,6 +33,8 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALREADY_EXISTS", STREAM_ERROR_CODE_ALREADY_EXISTS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_PATH", STREAM_ERROR_CODE_INVALID_PATH, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PATH_TOO_LONG", STREAM_ERROR_CODE_PATH_TOO_LONG, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CREATE_FAILED", STREAM_ERROR_CODE_CREATE_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_OPEN_FAILED", STREAM_ERROR_CODE_OPEN_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_UNLINK_FAILED", STREAM_ERROR_CODE_UNLINK_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RENAME_FAILED", STREAM_ERROR_CODE_RENAME_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MKDIR_FAILED", STREAM_ERROR_CODE_MKDIR_FAILED, CONST_PERSISTENT); @@ -60,6 +62,7 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NETWORK_SEND_FAILED", STREAM_ERROR_CODE_NETWORK_SEND_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NETWORK_RECV_FAILED", STREAM_ERROR_CODE_NETWORK_RECV_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SSL_NOT_SUPPORTED", STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RESUMPTION_FAILED", STREAM_ERROR_CODE_RESUMPTION_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG", STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_OOB_NOT_SUPPORTED", STREAM_ERROR_CODE_OOB_NOT_SUPPORTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PROTOCOL_ERROR", STREAM_ERROR_CODE_PROTOCOL_ERROR, CONST_PERSISTENT); @@ -67,6 +70,7 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_RESPONSE", STREAM_ERROR_CODE_INVALID_RESPONSE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_REDIRECT_LIMIT", STREAM_ERROR_CODE_REDIRECT_LIMIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_AUTH_FAILED", STREAM_ERROR_CODE_AUTH_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ARCHIVING_FAILED", STREAM_ERROR_CODE_ARCHIVING_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ENCODING_FAILED", STREAM_ERROR_CODE_ENCODING_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_FORMAT", STREAM_ERROR_CODE_INVALID_FORMAT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALLOCATION_FAILED", STREAM_ERROR_CODE_ALLOCATION_FAILED, CONST_PERSISTENT); From 6dbdab23f5cf18efa2acb52ca354955cbee8d104 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 18:24:22 +0100 Subject: [PATCH 06/61] stream: convert http wrapper errors --- ext/standard/http_fopen_wrapper.c | 49 ++++++++++++++++++---------- main/streams/php_stream_errors.h | 6 ++-- main/streams/stream_errors.stub.php | 10 ++++++ main/streams/stream_errors_arginfo.h | 4 ++- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index da150381f43f3..9f206a02c272d 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -246,7 +246,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w while (last_header_name < last_header_value) { if (*last_header_name == ' ' || *last_header_name == '\t') { header_info->error = true; - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (space in header name)!"); zend_string_efree(last_header_line_str); return NULL; @@ -264,7 +264,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w } else { /* There is no colon which means invalid response so error. */ header_info->error = true; - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (no colon in header line)!"); zend_string_efree(last_header_line_str); return NULL; @@ -288,7 +288,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w size_t last_header_value_len = strlen(last_header_value); if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) { header_info->error = true; - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP Location header size is over the limit of %d bytes", HTTP_HEADER_MAX_LOCATION_SIZE); zend_string_efree(last_header_line_str); @@ -389,7 +389,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, tmp_line[0] = '\0'; if (redirect_max < 1) { - php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_REDIRECT_LIMIT, + "Redirection limit reached, aborting"); return NULL; } @@ -422,7 +423,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* Normal http request (possibly with proxy) */ if (strpbrk(mode, "awx+")) { - php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + "HTTP wrapper does not support writeable connections"); php_uri_struct_free(resource); return NULL; } @@ -451,7 +453,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) { - php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "HTTP wrapper full URI path does not allow CR or LF characters"); php_uri_struct_free(resource); zend_string_release(transport_string); return NULL; @@ -466,7 +469,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, #endif if (d > timeoutmax) { - php_stream_wrapper_log_error(wrapper, options, "timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PARAM, + "timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax); zend_string_release(transport_string); php_uri_struct_free(resource); return NULL; @@ -496,7 +500,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } if (errstr) { - php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr)); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "%s", ZSTR_VAL(errstr)); zend_string_release_ex(errstr, 0); errstr = NULL; } @@ -547,7 +552,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) { - php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "Cannot connect to HTTPS server through proxy"); php_stream_close(stream); stream = NULL; } @@ -570,7 +576,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (stream) { if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(stream, 1) < 0) { - php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "Cannot connect to HTTPS server through proxy"); php_stream_close(stream); stream = NULL; } @@ -827,7 +834,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, ua[ua_len] = 0; smart_str_appendl(&req_buf, ua, ua_len); } else { - php_error_docref(NULL, E_WARNING, "Cannot construct User-agent header"); + php_stream_wrapper_warn_nt(wrapper, context, options, STREAM_ERROR_CODE_INVALID_HEADER, + "Cannot construct User-agent header"); } efree(ua); } @@ -866,7 +874,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } if (!(have_header & HTTP_HEADER_TYPE)) { smart_str_appends(&req_buf, "Content-Type: application/x-www-form-urlencoded\r\n"); - php_error_docref(NULL, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded"); + php_stream_wrapper_notice(wrapper, context, options, STREAM_ERROR_CODE_INVALID_HEADER, + "Content-type not specified assuming application/x-www-form-urlencoded"); } smart_str_appends(&req_buf, "\r\n"); smart_str_appendl(&req_buf, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); @@ -953,7 +962,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } else { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_error(wrapper, options, "HTTP request failed!"); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "HTTP request failed!"); goto out; } } @@ -971,7 +981,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (http_header_line[1] != '\n') { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid header name (cannot start with CR character)!"); goto out; } @@ -1002,7 +1012,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (*http_header_line == ' ' || *http_header_line == '\t') { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (folding header at the start)!"); goto out; } @@ -1098,7 +1108,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, php_uri_struct_free(resource); /* check for invalid redirection URLs */ if ((resource = php_uri_parse_to_struct(uri_parser, new_path, strlen(new_path), PHP_URI_COMPONENT_READ_MODE_RAW, true)) == NULL) { - php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + "Invalid redirect URL! %s", new_path); efree(new_path); goto out; } @@ -1110,7 +1121,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, s = (unsigned char*)ZSTR_VAL(val); e = s + ZSTR_LEN(val); \ while (s < e) { \ if (iscntrl(*s)) { \ - php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \ + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, \ + "Invalid redirect URL! %s", new_path); \ efree(new_path); \ goto out; \ } \ @@ -1136,7 +1148,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, --redirect_max, new_flags, response_header STREAMS_CC); efree(new_path); } else { - php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); + php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + "HTTP request failed! %s", tmp_line); } } out: diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index f36ece142bc06..2d77c17864db5 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -93,8 +93,10 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_PROTOCOL_ERROR 106 #define STREAM_ERROR_CODE_INVALID_URL 107 #define STREAM_ERROR_CODE_INVALID_RESPONSE 108 -#define STREAM_ERROR_CODE_REDIRECT_LIMIT 109 -#define STREAM_ERROR_CODE_AUTH_FAILED 110 +#define STREAM_ERROR_CODE_INVALID_HEADER 109 +#define STREAM_ERROR_CODE_INVALID_PARAM 110 +#define STREAM_ERROR_CODE_REDIRECT_LIMIT 111 +#define STREAM_ERROR_CODE_AUTH_FAILED 112 /* Encoding/decoding/archiving operations */ #define STREAM_ERROR_CODE_ARCHIVING_FAILED 120 #define STREAM_ERROR_CODE_ENCODING_FAILED 121 diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index a9d090858aff6..7e76865b02a62 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -261,6 +261,16 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_INVALID_RESPONSE */ const STREAM_ERROR_CODE_INVALID_RESPONSE = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_INVALID_HEADER + */ +const STREAM_ERROR_CODE_INVALID_HEADER = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_INVALID_PARAM + */ +const STREAM_ERROR_CODE_INVALID_PARAM = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_REDIRECT_LIMIT diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index ff8da9dab548e..dd2051c0e10df 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e3d25328018715348be75f6cb31ae6684afe0a56 */ + * Stub hash: c40a28ed8224ab9fc3673ca4dec0d5376f9acd7e */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -68,6 +68,8 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PROTOCOL_ERROR", STREAM_ERROR_CODE_PROTOCOL_ERROR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_URL", STREAM_ERROR_CODE_INVALID_URL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_RESPONSE", STREAM_ERROR_CODE_INVALID_RESPONSE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_HEADER", STREAM_ERROR_CODE_INVALID_HEADER, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_PARAM", STREAM_ERROR_CODE_INVALID_PARAM, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_REDIRECT_LIMIT", STREAM_ERROR_CODE_REDIRECT_LIMIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_AUTH_FAILED", STREAM_ERROR_CODE_AUTH_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ARCHIVING_FAILED", STREAM_ERROR_CODE_ARCHIVING_FAILED, CONST_PERSISTENT); From 311b4008c091b7a7181384d49ea8b66785df6274 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 18:46:22 +0100 Subject: [PATCH 07/61] stream: add context param to php_stream_wrapper_log_* variants --- ext/phar/dirstream.c | 54 +++++++++++++++---------------- ext/phar/stream.c | 48 +++++++++++++-------------- ext/standard/ftp_fopen_wrapper.c | 26 +++++++-------- ext/standard/http_fopen_wrapper.c | 20 ++++++------ main/streams/php_stream_errors.h | 18 ++++++----- main/streams/stream_errors.c | 22 ++++++------- 6 files changed, 93 insertions(+), 95 deletions(-) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index 7faeddba25d7d..6ea3f7ba2fb06 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -254,7 +254,7 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, phar_archive_data *phar; if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar url \"%s\" is unknown", path); return NULL; } @@ -262,21 +262,21 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, /* we must have at the very least phar://alias.phar/ */ if (!resource->scheme || !resource->host || !resource->path) { if (resource->host && !resource->path) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PATH, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PATH, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", path, ZSTR_VAL(resource->host)); php_url_free(resource); return NULL; } php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url \"%s\", must have at least phar://%s/", path, path); return NULL; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, "phar error: not a phar url \"%s\"", path); return NULL; } @@ -285,10 +285,10 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "phar file \"%s\" is unknown", ZSTR_VAL(resource->host)); } php_url_free(resource); @@ -359,7 +359,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo /* pre-readonly check, we need to know if this is a data phar */ if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, NULL, NULL, 2, 2)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "phar error: cannot create directory \"%s\", no phar archive specified", url_from); return 0; } @@ -371,7 +371,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo efree(arch); if (PHAR_G(readonly) && (!phar || !phar->is_data)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, "phar error: cannot create directory \"%s\", write operations disabled", url_from); return 0; } @@ -383,20 +383,20 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url \"%s\"", url_from); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, "phar error: not a phar stream url \"%s\"", url_from); return 0; } if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error); efree(error); @@ -410,7 +410,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo zend_string_efree(e->filename); efree(e); } - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_ALREADY_EXISTS, "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); php_url_free(resource); @@ -418,7 +418,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo } if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); @@ -428,7 +428,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo if (phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 0, &error, true)) { /* entry exists as a file */ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_ALREADY_EXISTS, "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); php_url_free(resource); @@ -436,7 +436,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo } if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); @@ -467,7 +467,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo entry.old_flags = PHAR_ENT_PERM_DEF_DIR; if (NULL == zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info))) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", ZSTR_VAL(entry.filename), phar->fname); zend_string_efree(entry.filename); @@ -477,7 +477,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo phar_flush(phar, &error); if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MKDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MKDIR_FAILED, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry.filename), phar->fname, error); zend_hash_del(&phar->manifest, entry.filename); @@ -503,7 +503,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options /* pre-readonly check, we need to know if this is a data phar */ if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, NULL, NULL, 2, 2)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url); return 0; } @@ -515,7 +515,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options efree(arch); if (PHAR_G(readonly) && (!phar || !phar->is_data)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, "phar error: cannot rmdir directory \"%s\", write operations disabled", url); return 0; } @@ -527,20 +527,20 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url \"%s\"", url); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, "phar error: not a phar stream url \"%s\"", url); return 0; } if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); @@ -552,12 +552,12 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options if (!(entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, true))) { if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host)); } @@ -573,7 +573,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len) && IS_SLASH(ZSTR_VAL(str_key)[path_len]) ) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, "phar error: Directory not empty"); if (entry->is_temp_dir) { zend_string_efree(entry->filename); @@ -590,7 +590,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len) && IS_SLASH(ZSTR_VAL(str_key)[path_len]) ) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, "phar error: Directory not empty"); if (entry->is_temp_dir) { zend_string_efree(entry->filename); @@ -612,7 +612,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options phar_flush(phar, &error); if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RMDIR_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RMDIR_FAILED, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry->filename), phar->fname, error); php_url_free(resource); diff --git a/ext/phar/stream.c b/ext/phar/stream.c index eed488c1c68a7..ee583707e16a7 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -68,7 +68,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (mode[0] == 'a') { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, "phar error: open mode append not supported"); } return NULL; @@ -76,12 +76,12 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { if (arch && !entry) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PATH, + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_INVALID_PATH, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch); arch = NULL; } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url or non-existent phar \"%s\"", filename); } } @@ -115,7 +115,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_READONLY, "phar error: write operations disabled by the php.ini setting phar.readonly"); } php_url_free(resource); @@ -125,7 +125,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); } efree(error); } @@ -136,7 +136,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (error) { spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", ZSTR_VAL(resource->host)); if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); } efree(error); } @@ -148,7 +148,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); + php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); } efree(error); } @@ -181,14 +181,14 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url \"%s\"", path); return NULL; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, "phar error: not a phar stream url \"%s\"", path); return NULL; } @@ -200,10 +200,10 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) { if (NULL == (idata = phar_get_or_create_entry_data(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), mode, 0, &error, true, time(NULL)))) { if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_CREATE_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_CREATE_FAILED, "%s", error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_CREATE_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_CREATE_FAILED, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); } efree(internal_file); @@ -244,7 +244,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (!*internal_file && (options & STREAM_OPEN_FOR_INCLUDE)) { /* retrieve the stub */ if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, NULL)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_FORMAT, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_FORMAT, "file %s is not a valid phar archive", ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); @@ -265,7 +265,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if (stream == NULL) { stream = phar_open_archive_fp(phar); if (UNEXPECTED(!stream)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_OPEN_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_OPEN_FAILED, "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host)); efree(internal_file); php_url_free(resource); @@ -303,10 +303,10 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha if ((FAILURE == phar_get_entry_data(&idata, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), "r", 0, &error, false)) || !idata) { idata_error: if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host)); } efree(internal_file); @@ -325,7 +325,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha /* check length, crc32 */ if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ARCHIVING_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_ARCHIVING_FAILED, "%s", error); efree(error); phar_entry_delref(idata); efree(internal_file); @@ -676,7 +676,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int phar_archive_data *pphar; if ((resource = phar_parse_url(wrapper, url, "rb", options)) == NULL) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, "phar error: unlink failed"); return 0; } @@ -684,14 +684,14 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int /* we must have at the very least phar://alias.phar/internalfile.php */ if (!resource->scheme || !resource->host || !resource->path) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url \"%s\"", url); return 0; } if (!zend_string_equals_literal_ci(resource->scheme, "phar")) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, "phar error: not a phar stream url \"%s\"", url); return 0; } @@ -701,7 +701,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), resource->host); if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { php_url_free(resource); - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_READONLY, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, "phar error: write operations disabled by the php.ini setting phar.readonly"); return 0; } @@ -712,11 +712,11 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int if (FAILURE == phar_get_entry_data(&idata, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, internal_file_len, "r", 0, &error, true)) { /* constraints of fp refcount were not met */ if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, "unlink of \"%s\" failed: %s", url, error); efree(error); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NOT_FOUND, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "unlink of \"%s\" failed, file does not exist", url); } efree(internal_file); @@ -728,7 +728,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int } if (idata->internal_file->fp_refcount > 1) { /* more than just our fp resource is open for this file */ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, ZSTR_VAL(resource->host)); efree(internal_file); @@ -740,7 +740,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int efree(internal_file); phar_entry_remove(idata, &error); if (error) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_UNLINK_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, "%s", error); efree(error); } return 1; diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 6f6550b6ac8f7..3e3b74bb2a2e7 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -189,7 +189,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char /* get the response */ result = GET_FTP_RESULT(stream); if (result != 334) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, "Server doesn't support FTPS."); goto connect_errexit; } else { @@ -209,7 +209,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(stream, 1) < 0) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, "Unable to activate SSL mode"); php_stream_close(stream); stream = NULL; @@ -241,7 +241,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len; \ while (s < e) { \ if (iscntrl(*s)) { \ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_AUTH_FAILED, err_msg, val); \ + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_AUTH_FAILED, err_msg, val); \ goto connect_errexit; \ } \ s++; \ @@ -438,7 +438,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa } if (strpbrk(mode, "wa+")) { if (read_write) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, "FTP does not support simultaneous read/write connections"); return NULL; } @@ -450,7 +450,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa } if (!read_write) { /* No mode specified? */ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_MODE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_MODE, "Unknown file open mode"); return NULL; } @@ -462,7 +462,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC); } else { /* ftp proxy is read-only */ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, "FTP proxy may only be used in read mode"); return NULL; } @@ -515,7 +515,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa goto errexit; } } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_ALREADY_EXISTS, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_ALREADY_EXISTS, "Remote file already exists and overwrite context option not specified"); errno = EEXIST; goto errexit; @@ -540,7 +540,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval)); result = GET_FTP_RESULT(stream); if (result < 300 || result > 399) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_RESUMPTION_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_RESUMPTION_FAILED, "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval)); goto errexit; } @@ -586,7 +586,7 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; @@ -609,11 +609,11 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa php_stream_close(stream); } if (tmp_line[0] != '\0') - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "FTP server reports %s", tmp_line); if (error_message) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_NETWORK_SEND_FAILED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NETWORK_SEND_FAILED, "Failed to set up data channel: %s", ZSTR_VAL(error_message)); zend_string_release(error_message); } @@ -759,7 +759,7 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(datastream, 1) < 0)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, "Unable to activate SSL mode"); php_stream_close(datastream); datastream = NULL; @@ -784,7 +784,7 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch php_stream_close(stream); } if (tmp_line[0] != '\0') { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "FTP server reports %s", tmp_line); } return NULL; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 9f206a02c272d..b772fb0364a6f 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -246,7 +246,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w while (last_header_name < last_header_value) { if (*last_header_name == ' ' || *last_header_name == '\t') { header_info->error = true; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (space in header name)!"); zend_string_efree(last_header_line_str); return NULL; @@ -264,7 +264,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w } else { /* There is no colon which means invalid response so error. */ header_info->error = true; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (no colon in header line)!"); zend_string_efree(last_header_line_str); return NULL; @@ -389,7 +389,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, tmp_line[0] = '\0'; if (redirect_max < 1) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_REDIRECT_LIMIT, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_REDIRECT_LIMIT, "Redirection limit reached, aborting"); return NULL; } @@ -423,7 +423,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* Normal http request (possibly with proxy) */ if (strpbrk(mode, "awx+")) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, "HTTP wrapper does not support writeable connections"); php_uri_struct_free(resource); return NULL; @@ -453,7 +453,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "HTTP wrapper full URI path does not allow CR or LF characters"); php_uri_struct_free(resource); zend_string_release(transport_string); @@ -552,7 +552,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "Cannot connect to HTTPS server through proxy"); php_stream_close(stream); stream = NULL; @@ -576,7 +576,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (stream) { if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || php_stream_xport_crypto_enable(stream, 1) < 0) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, "Cannot connect to HTTPS server through proxy"); php_stream_close(stream); stream = NULL; @@ -962,7 +962,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } else { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "HTTP request failed!"); goto out; } @@ -981,7 +981,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (http_header_line[1] != '\n') { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid header name (cannot start with CR character)!"); goto out; } @@ -1012,7 +1012,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (*http_header_line == ' ' || *http_header_line == '\t') { php_stream_close(stream); stream = NULL; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP invalid response format (folding header at the start)!"); goto out; } diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 2d77c17864db5..1ed4257bf47e8 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -170,16 +170,18 @@ PHPAPI void php_stream_error( /* Legacy wrapper error log - updated API */ PHPAPI void php_stream_wrapper_log_error( const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); +) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); PHPAPI void php_stream_wrapper_log_error_param( const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, @@ -187,7 +189,7 @@ PHPAPI void php_stream_wrapper_log_error_param( const char *param, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); +) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_display_wrapper_errors( php_stream_wrapper *wrapper, @@ -226,14 +228,14 @@ void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); php_stream_error(stream, E_ERROR, true, code, __VA_ARGS__) /* Legacy log variants */ -#define php_stream_wrapper_log_warn(wrapper, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, options, E_WARNING, true, code, __VA_ARGS__) +#define php_stream_wrapper_log_warn(wrapper, context, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, true, code, __VA_ARGS__) -#define php_stream_wrapper_log_warn_nt(wrapper, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, options, E_WARNING, false, code, __VA_ARGS__) +#define php_stream_wrapper_log_warn_nt(wrapper, context, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, false, code, __VA_ARGS__) -#define php_stream_wrapper_log_notice(wrapper, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, options, E_NOTICE, false, code, __VA_ARGS__) +#define php_stream_wrapper_log_notice(wrapper, context, options, code, ...) \ + php_stream_wrapper_log_error(wrapper, context, options, E_NOTICE, false, code, __VA_ARGS__) END_EXTERN_C() diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 52428e9065f34..74e3af9a7a1c0 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -359,19 +359,15 @@ static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper zend_llist_add_element(list, &entry); } -static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, int options, - int severity, bool terminal, int code, const char *param, va_list args) +static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, php_stream_context *context, + int options, int severity, bool terminal, int code, const char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, args, args); const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; if (options & REPORT_ERRORS) { /* Report immediately using standard error functions */ - if (param) { - php_error_docref1(NULL, param, severity, "%s", ZSTR_VAL(message)); - } else { - php_error_docref(NULL, severity, "%s", ZSTR_VAL(message)); - } + php_stream_wrapper_error_internal(wrapper, context, options, severity, terminal, code, param, fmt, args); zend_string_release(message); } else { /* Store for later display in FG(wrapper_logged_errors) */ @@ -381,21 +377,21 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap } } -PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, - int severity, bool terminal, int code, const char *fmt, ...) +PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, php_stream_context *context, + int options, int severity, bool terminal, int code, const char *fmt, ...) { va_list args; va_start(args, fmt); - php_stream_wrapper_log_error_internal(wrapper, options, severity, terminal, code, NULL, args); + php_stream_wrapper_log_error_internal(wrapper, context, options, severity, terminal, code, NULL, fmt, args); va_end(args); } -PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, int options, - int severity, bool terminal, int code, const char *param, const char *fmt, ...) +PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, php_stream_context *context, + int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) { va_list args; va_start(args, fmt); - php_stream_wrapper_log_error_internal(wrapper, options, severity, terminal, code, param, args); + php_stream_wrapper_log_error_internal(wrapper, context, options, severity, terminal, code, param, fmt, args); va_end(args); } From c4d6d048887117cfd72398f86f1898223496b883 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 19:06:55 +0100 Subject: [PATCH 08/61] stream: convert php fopen wrapper --- ext/standard/php_fopen_wrapper.c | 37 +++++++--- main/streams/php_stream_errors.h | 106 ++++++++++++++------------- main/streams/stream_errors.stub.php | 10 +++ main/streams/stream_errors_arginfo.h | 4 +- 4 files changed, 94 insertions(+), 63 deletions(-) diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c index ea33ba4904346..b13f18e08bf78 100644 --- a/ext/standard/php_fopen_wrapper.c +++ b/ext/standard/php_fopen_wrapper.c @@ -158,14 +158,18 @@ static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, i if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { php_stream_filter_append(&stream->readfilters, temp_filter); } else { - php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p); + php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_CREATE_FAILED, + "Unable to create filter (%s)", p); } } if (write_chain) { if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) { php_stream_filter_append(&stream->writefilters, temp_filter); } else { - php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p); + php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_CREATE_FAILED, + "Unable to create filter (%s)", p); } } p = php_strtok_r(NULL, "|", &token); @@ -219,7 +223,9 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_DISABLED, + "URL file-access is disabled in the server configuration"); } return NULL; } @@ -238,7 +244,9 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c if (!strcasecmp(path, "stdin")) { if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_DISABLED, + "URL file-access is disabled in the server configuration"); } return NULL; } @@ -297,14 +305,18 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c if (strcmp(sapi_module.name, "cli")) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Direct access to file descriptors is only available from command-line PHP"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_DISABLED, + "Direct access to file descriptors is only available from command-line PHP"); } return NULL; } if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_DISABLED, + "URL file-access is disabled in the server configuration"); } return NULL; } @@ -312,7 +324,8 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c start = &path[3]; fildes_ori = ZEND_STRTOL(start, &end, 10); if (end == start || *end != '\0') { - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, "php://fd/ stream must be specified in the form php://fd/"); return NULL; } @@ -324,14 +337,16 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c #endif if (fildes_ori < 0 || fildes_ori >= dtablesize) { - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_PARAM, "The file descriptors must be non-negative numbers smaller than %d", dtablesize); return NULL; } fd = dup((int)fildes_ori); if (fd == -1) { - php_stream_wrapper_log_error(wrapper, options, + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_DUP_FAILED, "Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: " "[%d]: %s", fildes_ori, errno, strerror(errno)); return NULL; @@ -380,7 +395,9 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c return stream; } else { /* invalid php://thingy */ - php_error_docref(NULL, E_WARNING, "Invalid php:// URL specified"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, + "Invalid php:// URL specified"); return NULL; } diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 1ed4257bf47e8..1841ad5ceaf7a 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -49,67 +49,69 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_NOT_WRITABLE 16 #define STREAM_ERROR_CODE_NOT_READABLE 17 /* File system operations */ -#define STREAM_ERROR_CODE_NOT_FOUND 30 -#define STREAM_ERROR_CODE_PERMISSION_DENIED 31 -#define STREAM_ERROR_CODE_ALREADY_EXISTS 32 -#define STREAM_ERROR_CODE_INVALID_PATH 33 -#define STREAM_ERROR_CODE_PATH_TOO_LONG 34 -#define STREAM_ERROR_CODE_OPEN_FAILED 35 -#define STREAM_ERROR_CODE_CREATE_FAILED 36 -#define STREAM_ERROR_CODE_UNLINK_FAILED 37 -#define STREAM_ERROR_CODE_RENAME_FAILED 38 -#define STREAM_ERROR_CODE_MKDIR_FAILED 39 -#define STREAM_ERROR_CODE_RMDIR_FAILED 40 -#define STREAM_ERROR_CODE_STAT_FAILED 41 -#define STREAM_ERROR_CODE_CHMOD_FAILED 42 -#define STREAM_ERROR_CODE_CHOWN_FAILED 43 -#define STREAM_ERROR_CODE_TOUCH_FAILED 44 -#define STREAM_ERROR_CODE_INVALID_MODE 45 -#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 46 -#define STREAM_ERROR_CODE_READONLY 47 -#define STREAM_ERROR_CODE_RECURSION_DETECTED 48 +#define STREAM_ERROR_CODE_DISABLED 30 +#define STREAM_ERROR_CODE_NOT_FOUND 31 +#define STREAM_ERROR_CODE_PERMISSION_DENIED 32 +#define STREAM_ERROR_CODE_ALREADY_EXISTS 33 +#define STREAM_ERROR_CODE_INVALID_PATH 34 +#define STREAM_ERROR_CODE_PATH_TOO_LONG 35 +#define STREAM_ERROR_CODE_OPEN_FAILED 36 +#define STREAM_ERROR_CODE_CREATE_FAILED 37 +#define STREAM_ERROR_CODE_DUP_FAILED 38 +#define STREAM_ERROR_CODE_UNLINK_FAILED 39 +#define STREAM_ERROR_CODE_RENAME_FAILED 40 +#define STREAM_ERROR_CODE_MKDIR_FAILED 41 +#define STREAM_ERROR_CODE_RMDIR_FAILED 42 +#define STREAM_ERROR_CODE_STAT_FAILED 43 +#define STREAM_ERROR_CODE_CHMOD_FAILED 44 +#define STREAM_ERROR_CODE_CHOWN_FAILED 45 +#define STREAM_ERROR_CODE_TOUCH_FAILED 46 +#define STREAM_ERROR_CODE_INVALID_MODE 47 +#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 48 +#define STREAM_ERROR_CODE_READONLY 49 +#define STREAM_ERROR_CODE_RECURSION_DETECTED 50 /* Wrapper/protocol operations */ -#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 60 -#define STREAM_ERROR_CODE_WRAPPER_DISABLED 61 -#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 62 -#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 63 -#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 64 -#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 65 +#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 70 +#define STREAM_ERROR_CODE_WRAPPER_DISABLED 71 +#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 72 +#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 73 +#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 74 +#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 75 /* Filter operations */ -#define STREAM_ERROR_CODE_FILTER_NOT_FOUND 80 -#define STREAM_ERROR_CODE_FILTER_FAILED 81 +#define STREAM_ERROR_CODE_FILTER_NOT_FOUND 90 +#define STREAM_ERROR_CODE_FILTER_FAILED 91 /* Cast/conversion operations */ -#define STREAM_ERROR_CODE_CAST_FAILED 90 -#define STREAM_ERROR_CODE_CAST_NOT_SUPPORTED 91 -#define STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED 92 -#define STREAM_ERROR_CODE_BUFFERED_DATA_LOST 93 +#define STREAM_ERROR_CODE_CAST_FAILED 100 +#define STREAM_ERROR_CODE_CAST_NOT_SUPPORTED 101 +#define STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED 102 +#define STREAM_ERROR_CODE_BUFFERED_DATA_LOST 103 /* Network/socket operations */ -#define STREAM_ERROR_CODE_NETWORK_SEND_FAILED 100 -#define STREAM_ERROR_CODE_NETWORK_RECV_FAILED 101 -#define STREAM_ERROR_CODE_SSL_NOT_SUPPORTED 102 -#define STREAM_ERROR_CODE_RESUMPTION_FAILED 103 -#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 104 -#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 105 -#define STREAM_ERROR_CODE_PROTOCOL_ERROR 106 -#define STREAM_ERROR_CODE_INVALID_URL 107 -#define STREAM_ERROR_CODE_INVALID_RESPONSE 108 -#define STREAM_ERROR_CODE_INVALID_HEADER 109 +#define STREAM_ERROR_CODE_NETWORK_SEND_FAILED 110 +#define STREAM_ERROR_CODE_NETWORK_RECV_FAILED 111 +#define STREAM_ERROR_CODE_SSL_NOT_SUPPORTED 112 +#define STREAM_ERROR_CODE_RESUMPTION_FAILED 113 +#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 114 +#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 115 +#define STREAM_ERROR_CODE_PROTOCOL_ERROR 116 +#define STREAM_ERROR_CODE_INVALID_URL 117 +#define STREAM_ERROR_CODE_INVALID_RESPONSE 118 +#define STREAM_ERROR_CODE_INVALID_HEADER 119 #define STREAM_ERROR_CODE_INVALID_PARAM 110 -#define STREAM_ERROR_CODE_REDIRECT_LIMIT 111 -#define STREAM_ERROR_CODE_AUTH_FAILED 112 +#define STREAM_ERROR_CODE_REDIRECT_LIMIT 121 +#define STREAM_ERROR_CODE_AUTH_FAILED 122 /* Encoding/decoding/archiving operations */ -#define STREAM_ERROR_CODE_ARCHIVING_FAILED 120 -#define STREAM_ERROR_CODE_ENCODING_FAILED 121 -#define STREAM_ERROR_CODE_INVALID_FORMAT 122 +#define STREAM_ERROR_CODE_ARCHIVING_FAILED 130 +#define STREAM_ERROR_CODE_ENCODING_FAILED 131 +#define STREAM_ERROR_CODE_INVALID_FORMAT 132 /* Resource/allocation operations */ -#define STREAM_ERROR_CODE_ALLOCATION_FAILED 130 -#define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 131 +#define STREAM_ERROR_CODE_ALLOCATION_FAILED 140 +#define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 141 /* Locking operations */ -#define STREAM_ERROR_CODE_LOCK_FAILED 140 -#define STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED 141 +#define STREAM_ERROR_CODE_LOCK_FAILED 150 +#define STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED 151 /* Userspace stream operations */ -#define STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED 150 -#define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 151 +#define STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED 160 +#define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 161 /* Stored error entry */ typedef struct { diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 7e76865b02a62..7bec3e68db235 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -61,6 +61,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_NOT_READABLE */ const STREAM_ERROR_CODE_NOT_READABLE = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_DISABLED + */ +const STREAM_ERROR_CODE_DISABLED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_NOT_FOUND @@ -91,6 +96,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_CREATE_FAILED */ const STREAM_ERROR_CODE_CREATE_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_DUP_FAILED + */ +const STREAM_ERROR_CODE_DUP_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_OPEN_FAILED diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index dd2051c0e10df..dca72e8b8375c 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c40a28ed8224ab9fc3673ca4dec0d5376f9acd7e */ + * Stub hash: ea73ead3bd777bd305d626af87d75dfc21140f97 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -28,12 +28,14 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TRUNCATE_FAILED", STREAM_ERROR_CODE_TRUNCATE_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_WRITABLE", STREAM_ERROR_CODE_NOT_WRITABLE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_READABLE", STREAM_ERROR_CODE_NOT_READABLE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DISABLED", STREAM_ERROR_CODE_DISABLED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_FOUND", STREAM_ERROR_CODE_NOT_FOUND, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PERMISSION_DENIED", STREAM_ERROR_CODE_PERMISSION_DENIED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALREADY_EXISTS", STREAM_ERROR_CODE_ALREADY_EXISTS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_PATH", STREAM_ERROR_CODE_INVALID_PATH, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PATH_TOO_LONG", STREAM_ERROR_CODE_PATH_TOO_LONG, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CREATE_FAILED", STREAM_ERROR_CODE_CREATE_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DUP_FAILED", STREAM_ERROR_CODE_DUP_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_OPEN_FAILED", STREAM_ERROR_CODE_OPEN_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_UNLINK_FAILED", STREAM_ERROR_CODE_UNLINK_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RENAME_FAILED", STREAM_ERROR_CODE_RENAME_FAILED, CONST_PERSISTENT); From caaff5c7e9a7413e3815e567af3a5f8f2b9ac23b Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 19:22:05 +0100 Subject: [PATCH 09/61] stream: convert memory wrappers errors --- main/streams/memory.c | 25 +++++++++++++++++-------- main/streams/php_stream_errors.h | 3 ++- main/streams/stream_errors.stub.php | 5 +++++ main/streams/stream_errors_arginfo.h | 3 ++- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/main/streams/memory.c b/main/streams/memory.c index 785109db6582c..1b0bad3410857 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -362,7 +362,9 @@ static ssize_t php_stream_temp_write(php_stream *stream, const char *buf, size_t zend_string *membuf = php_stream_memory_get_buffer(ts->innerstream); php_stream *file = php_stream_fopen_temporary_file(ts->tmpdir, "php", NULL); if (file == NULL) { - php_error_docref(NULL, E_WARNING, "Unable to create temporary file, Check permissions in temporary files directory."); + php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_PERMISSION_DENIED, + "Unable to create temporary file, Check permissions in temporary files directory."); return 0; } php_stream_write(file, ZSTR_VAL(membuf), ZSTR_LEN(membuf)); @@ -489,7 +491,8 @@ static int php_stream_temp_cast(php_stream *stream, int castas, void **ret) file = php_stream_fopen_tmpfile(); if (file == NULL) { - php_error_docref(NULL, E_WARNING, "Unable to create temporary file."); + php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_PERMISSION_DENIED, "Unable to create temporary file."); return FAILURE; } @@ -638,7 +641,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con } if ((comma = memchr(path, ',', dlen)) == NULL) { - php_stream_wrapper_log_error(wrapper, options, "rfc2397: no comma in URL"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, "rfc2397: no comma in URL"); return NULL; } @@ -650,7 +654,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con sep = memchr(path, '/', mlen); if (!semi && !sep) { - php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, "rfc2397: illegal media type"); return NULL; } @@ -665,7 +670,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con path += plen; } else if (semi != path || mlen != sizeof(";base64")-1 || memcmp(path, ";base64", sizeof(";base64")-1)) { /* must be error since parameters are only allowed after mediatype */ zval_ptr_dtor(&meta); - php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, "rfc2397: illegal media type"); return NULL; } /* get parameters and potentially ';base64' */ @@ -678,7 +684,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con if (mlen != sizeof("base64")-1 || memcmp(path, "base64", sizeof("base64")-1)) { /* must be error since parameters are only allowed after mediatype and we have no '=' sign */ zval_ptr_dtor(&meta); - php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal parameter"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_PARAM, "rfc2397: illegal parameter"); return NULL; } base64 = 1; @@ -698,7 +705,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con } if (mlen) { zval_ptr_dtor(&meta); - php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal URL"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_URL, "rfc2397: illegal URL"); return NULL; } } else { @@ -714,7 +722,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con base64_comma = php_base64_decode_ex((const unsigned char *)comma, dlen, 1); if (!base64_comma) { zval_ptr_dtor(&meta); - php_stream_wrapper_log_error(wrapper, options, "rfc2397: unable to decode"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_DECODING_FAILED, "rfc2397: unable to decode"); return NULL; } comma = ZSTR_VAL(base64_comma); diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 1841ad5ceaf7a..c88f4b5113002 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -102,7 +102,8 @@ BEGIN_EXTERN_C() /* Encoding/decoding/archiving operations */ #define STREAM_ERROR_CODE_ARCHIVING_FAILED 130 #define STREAM_ERROR_CODE_ENCODING_FAILED 131 -#define STREAM_ERROR_CODE_INVALID_FORMAT 132 +#define STREAM_ERROR_CODE_DECODING_FAILED 132 +#define STREAM_ERROR_CODE_INVALID_FORMAT 133 /* Resource/allocation operations */ #define STREAM_ERROR_CODE_ALLOCATION_FAILED 140 #define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 141 diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 7bec3e68db235..c55a4abfefa56 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -301,6 +301,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_ENCODING_FAILED */ const STREAM_ERROR_CODE_ENCODING_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_DECODING_FAILED + */ +const STREAM_ERROR_CODE_DECODING_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_INVALID_FORMAT diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index dca72e8b8375c..c51b1080c998a 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ea73ead3bd777bd305d626af87d75dfc21140f97 */ + * Stub hash: e19e8ac3e877b2f3a8a2b2ec0033c57c356daffa */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -76,6 +76,7 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_AUTH_FAILED", STREAM_ERROR_CODE_AUTH_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ARCHIVING_FAILED", STREAM_ERROR_CODE_ARCHIVING_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ENCODING_FAILED", STREAM_ERROR_CODE_ENCODING_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DECODING_FAILED", STREAM_ERROR_CODE_DECODING_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_FORMAT", STREAM_ERROR_CODE_INVALID_FORMAT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALLOCATION_FAILED", STREAM_ERROR_CODE_ALLOCATION_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED", STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED, CONST_PERSISTENT); From ad1fe81c8ca8adf0a1dec1c97c276f838073c2c9 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 22:10:26 +0100 Subject: [PATCH 10/61] stream: convert plain wrappers errors --- main/streams/php_stream_errors.h | 25 +++++--- main/streams/plain_wrapper.c | 93 +++++++++++++++++++--------- main/streams/stream_errors.stub.php | 10 +++ main/streams/stream_errors_arginfo.h | 4 +- 4 files changed, 94 insertions(+), 38 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index c88f4b5113002..7f57a684b8250 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -63,13 +63,15 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_MKDIR_FAILED 41 #define STREAM_ERROR_CODE_RMDIR_FAILED 42 #define STREAM_ERROR_CODE_STAT_FAILED 43 -#define STREAM_ERROR_CODE_CHMOD_FAILED 44 -#define STREAM_ERROR_CODE_CHOWN_FAILED 45 -#define STREAM_ERROR_CODE_TOUCH_FAILED 46 -#define STREAM_ERROR_CODE_INVALID_MODE 47 -#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 48 -#define STREAM_ERROR_CODE_READONLY 49 -#define STREAM_ERROR_CODE_RECURSION_DETECTED 50 +#define STREAM_ERROR_CODE_META_FAILED 44 +#define STREAM_ERROR_CODE_CHMOD_FAILED 45 +#define STREAM_ERROR_CODE_CHOWN_FAILED 46 +#define STREAM_ERROR_CODE_COPY_FAILED 47 +#define STREAM_ERROR_CODE_TOUCH_FAILED 48 +#define STREAM_ERROR_CODE_INVALID_MODE 49 +#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 50 +#define STREAM_ERROR_CODE_READONLY 51 +#define STREAM_ERROR_CODE_RECURSION_DETECTED 52 /* Wrapper/protocol operations */ #define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 70 #define STREAM_ERROR_CODE_WRAPPER_DISABLED 71 @@ -218,6 +220,15 @@ void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); #define php_stream_wrapper_warn_param(wrapper, context, options, code, param, ...) \ php_stream_wrapper_error_param(wrapper, context, options, E_WARNING, true, code, param, __VA_ARGS__) +#define php_stream_wrapper_warn_param_nt(wrapper, context, options, code, param, ...) \ + php_stream_wrapper_error_param(wrapper, context, options, E_WARNING, false, code, param, __VA_ARGS__) + +#define php_stream_wrapper_warn_param2(wrapper, context, options, code, param1, param2, ...) \ + php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, true, code, param1, param2, __VA_ARGS__) + +#define php_stream_wrapper_warn_param2_nt(wrapper, context, options, code, param1, param2, ...) \ + php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, false, code, param1, param2, __VA_ARGS__) + #define php_stream_warn(stream, code, ...) \ php_stream_error(stream, E_WARNING, true, code, __VA_ARGS__) diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 688d271db8147..8572f6f0275bf 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -394,7 +394,8 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun } if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { char errstr[256]; - php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", + php_stream_notice(stream, STREAM_ERROR_CODE_WRITE_FAILED, + "Write of %zu bytes failed with errno=%d %s", count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr))); } } @@ -472,7 +473,8 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) } else { if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { char errstr[256]; - php_error_docref(NULL, E_NOTICE, "Read of %zu bytes failed with errno=%d %s", + php_stream_notice(stream, STREAM_ERROR_CODE_READ_FAILED, + "Read of %zu bytes failed with errno=%d %s", count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr))); } @@ -621,7 +623,8 @@ static int php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, ze assert(data != NULL); if (!data->is_seekable) { - php_error_docref(NULL, E_WARNING, "Cannot seek on this stream"); + php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS, + STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED, "Cannot seek on this stream"); return -1; } @@ -1158,7 +1161,8 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen char *persistent_id = NULL; if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) { - php_stream_wrapper_log_error(&php_plain_files_wrapper, options, "`%s' is not a valid mode for fopen", mode); + php_stream_wrapper_log_warn(&php_plain_files_wrapper, NULL, options, + STREAM_ERROR_CODE_INVALID_MODE, "`%s' is not a valid mode for fopen", mode); return NULL; } @@ -1311,8 +1315,9 @@ static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, if (ret == -1) { if (options & REPORT_ERRORS) { char errstr[256]; - php_error_docref1(NULL, url, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param(wrapper, context, options, + STREAM_ERROR_CODE_UNLINK_FAILED, url, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1379,20 +1384,22 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f * access to the file in the meantime. */ if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); if (errno != EPERM) { success = 0; } + php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, + !success, STREAM_ERROR_CODE_CHOWN_FAILED, url_from, url_to, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } if (success) { if (VCWD_CHMOD(url_to, sb.st_mode)) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); if (errno != EPERM) { success = 0; } + php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, + !success, STREAM_ERROR_CODE_CHOWN_FAILED, url_from, url_to, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } } # endif @@ -1400,12 +1407,14 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f VCWD_UNLINK(url_from); } } else { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param2_nt(wrapper, context, options, + STREAM_ERROR_CODE_STAT_FAILED, url_from, url_to, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } } else { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param2_nt(wrapper, context, options, + STREAM_ERROR_CODE_COPY_FAILED, url_from, url_to, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } # if !defined(ZTS) && !defined(TSRM_WIN32) umask(oldmask); @@ -1418,8 +1427,9 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f #ifdef PHP_WIN32 php_win32_docref2_from_error(GetLastError(), url_from, url_to); #else - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param2(wrapper, context, options, + STREAM_ERROR_CODE_RENAME_FAILED, url_from, url_to, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); #endif return 0; } @@ -1443,7 +1453,8 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i int ret = VCWD_MKDIR(dir, (mode_t)mode); if (ret < 0 && (options & REPORT_ERRORS)) { - php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_MKDIR_FAILED, "%s", strerror(errno)); return 0; } @@ -1452,7 +1463,8 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i char buf[MAXPATHLEN]; if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) { - php_error_docref(NULL, E_WARNING, "Invalid path"); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_INVALID_PATH, "Invalid path"); return 0; } @@ -1504,7 +1516,9 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i int ret = VCWD_MKDIR(buf, (mode_t) mode); if (ret < 0 && errno != EEXIST) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_MKDIR_FAILED, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1524,7 +1538,9 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i /* issue a warning to client when the last directory was created failed */ if (ret < 0) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_MKDIR_FAILED, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1546,13 +1562,17 @@ static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, i char errstr[256]; #ifdef PHP_WIN32 if (!php_win32_check_trailing_space(url, strlen(url))) { - php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param(wrapper, context, options, + STREAM_ERROR_CODE_NOT_FOUND, url, + "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); return 0; } #endif if (VCWD_RMDIR(url) < 0) { - php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param(wrapper, context, options, + STREAM_ERROR_CODE_RMDIR_FAILED, url, + "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } @@ -1575,7 +1595,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url #ifdef PHP_WIN32 if (!php_win32_check_trailing_space(url, strlen(url))) { - php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_NOT_FOUND, url, + "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); return 0; } #endif @@ -1594,7 +1616,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url if (VCWD_ACCESS(url, F_OK) != 0) { FILE *file = VCWD_FOPEN(url, "w"); if (file == NULL) { - php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_PERMISSION_DENIED, url, + "Unable to create file %s because %s", url, php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } @@ -1608,7 +1632,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url case PHP_STREAM_META_OWNER: if(option == PHP_STREAM_META_OWNER_NAME) { if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) { - php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value); + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_META_FAILED, url, + "Unable to find uid for %s", (char *)value); return 0; } } else { @@ -1620,7 +1646,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url case PHP_STREAM_META_GROUP_NAME: if(option == PHP_STREAM_META_GROUP_NAME) { if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) { - php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value); + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_META_FAILED, url, + "Unable to find gid for %s", (char *)value); return 0; } } else { @@ -1638,8 +1666,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url return 0; } if (ret == -1) { - php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", - php_socket_strerror_s(errno, errstr, sizeof(errstr))); + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_META_FAILED, url, + "Operation failed: %s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } php_clear_stat_cache(0, NULL, 0); @@ -1732,7 +1761,9 @@ PHPAPI php_stream *_php_stream_fopen_with_path(const char *filename, const char *(cwd+3) = '\0'; if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) { - php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN); + php_stream_wrapper_notice(NULL, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_PATH_TOO_LONG, + "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN); } efree(cwd); @@ -1787,7 +1818,9 @@ PHPAPI php_stream *_php_stream_fopen_with_path(const char *filename, const char goto stream_skip; } if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) { - php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN); + php_stream_wrapper_notice(NULL, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_PATH_TOO_LONG, + "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN); } if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) { diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index c55a4abfefa56..b600a7faa796d 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -131,6 +131,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_STAT_FAILED */ const STREAM_ERROR_CODE_STAT_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_META_FAILED + */ +const STREAM_ERROR_CODE_META_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_CHMOD_FAILED @@ -141,6 +146,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_CHOWN_FAILED */ const STREAM_ERROR_CODE_CHOWN_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_COPY_FAILED + */ +const STREAM_ERROR_CODE_COPY_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_TOUCH_FAILED diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index c51b1080c998a..5859f825c4ee4 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e19e8ac3e877b2f3a8a2b2ec0033c57c356daffa */ + * Stub hash: c14298e97a4960fe35b7b1b464a5b0b94099897f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -42,8 +42,10 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MKDIR_FAILED", STREAM_ERROR_CODE_MKDIR_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RMDIR_FAILED", STREAM_ERROR_CODE_RMDIR_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_STAT_FAILED", STREAM_ERROR_CODE_STAT_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_META_FAILED", STREAM_ERROR_CODE_META_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CHMOD_FAILED", STREAM_ERROR_CODE_CHMOD_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CHOWN_FAILED", STREAM_ERROR_CODE_CHOWN_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_COPY_FAILED", STREAM_ERROR_CODE_COPY_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TOUCH_FAILED", STREAM_ERROR_CODE_TOUCH_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_MODE", STREAM_ERROR_CODE_INVALID_MODE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MODE_NOT_SUPPORTED", STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, CONST_PERSISTENT); From 119d887f44cda162cb538d0d12ee168412f310f2 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 22:17:32 +0100 Subject: [PATCH 11/61] stream: convert cast errors --- main/streams/cast.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/main/streams/cast.c b/main/streams/cast.c index 4b7183024571b..b7edfe0568825 100644 --- a/main/streams/cast.c +++ b/main/streams/cast.c @@ -189,7 +189,6 @@ void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *resul result[res_curs] = '\0'; } /* }}} */ - /* {{{ php_stream_cast */ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err) { @@ -259,7 +258,7 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, b) no memory -> lets bail */ - php_error_docref(NULL, E_ERROR, "fopencookie failed"); + php_stream_fatal(stream, STREAM_ERROR_CODE_CAST_FAILED, "fopencookie failed"); return FAILURE; #endif @@ -299,7 +298,8 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, if (php_stream_is_filtered(stream)) { if (show_err) { - php_error_docref(NULL, E_WARNING, "Cannot cast a filtered stream on this system"); + php_stream_warn(stream, STREAM_ERROR_CODE_CAST_NOT_SUPPORTED, + "Cannot cast a filtered stream on this system"); } return FAILURE; } else if (stream->ops->cast && stream->ops->cast(stream, castas, ret) == SUCCESS) { @@ -315,7 +315,8 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, "select()able descriptor" }; - php_error_docref(NULL, E_WARNING, "Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]); + php_stream_warn(stream, STREAM_ERROR_CODE_CAST_NOT_SUPPORTED, + "Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]); } return FAILURE; @@ -330,7 +331,9 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, * will be accessing the stream. Emit a warning so that the end-user will * know that they should try something else */ - php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!", (zend_long)(stream->writepos - stream->readpos)); + php_stream_warn_nt(stream, STREAM_ERROR_CODE_BUFFERED_DATA_LOST, + ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!", + (zend_long)(stream->writepos - stream->readpos)); } if (castas == PHP_STREAM_AS_STDIO && ret) { From 7f0e7683e14538692803f1bc04850b176d466d93 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 22:23:41 +0100 Subject: [PATCH 12/61] stream: convert filter errors --- main/streams/filter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/streams/filter.c b/main/streams/filter.c index b63d789190792..55f1c7ef5ea76 100644 --- a/main/streams/filter.c +++ b/main/streams/filter.c @@ -343,7 +343,8 @@ PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, p php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } - php_error_docref(NULL, E_WARNING, "Filter failed to process pre-buffered data"); + php_stream_warn(stream, STREAM_ERROR_CODE_FILTER_FAILED, + "Filter failed to process pre-buffered data"); return FAILURE; case PSFS_FEED_ME: /* We don't actually need data yet, From 380dbf8f7a0c163b7822f03fc266a6b30cc6442c Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 16 Nov 2025 22:52:22 +0100 Subject: [PATCH 13/61] stream: convert core streams errors --- main/streams/php_stream_errors.h | 14 +++++++----- main/streams/stream_errors.stub.php | 10 +++++++++ main/streams/streams.c | 35 ++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 7f57a684b8250..34894d2512706 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -73,12 +73,14 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_READONLY 51 #define STREAM_ERROR_CODE_RECURSION_DETECTED 52 /* Wrapper/protocol operations */ -#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 70 -#define STREAM_ERROR_CODE_WRAPPER_DISABLED 71 -#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 72 -#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 73 -#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 74 -#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 75 +#define STREAM_ERROR_CODE_NO_OPENER 70 +#define STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED 71 +#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 72 +#define STREAM_ERROR_CODE_WRAPPER_DISABLED 73 +#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 74 +#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 75 +#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 76 +#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 77 /* Filter operations */ #define STREAM_ERROR_CODE_FILTER_NOT_FOUND 90 #define STREAM_ERROR_CODE_FILTER_FAILED 91 diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index b600a7faa796d..802a90e00fb8d 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -176,6 +176,16 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_RECURSION_DETECTED */ const STREAM_ERROR_CODE_RECURSION_DETECTED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_NO_OPENER + */ +const STREAM_ERROR_CODE_NO_OPENER = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED + */ +const STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_WRAPPER_NOT_FOUND diff --git a/main/streams/streams.c b/main/streams/streams.c index a929c532bdc8a..fb9901d7c6aff 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1184,7 +1184,7 @@ PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t cou ZEND_ASSERT(buf != NULL); if (stream->ops->write == NULL) { - php_error_docref(NULL, E_NOTICE, "Stream is not writable"); + php_stream_notice(stream, STREAM_ERROR_CODE_NOT_WRITABLE, "Stream is not writable"); return (ssize_t) -1; } @@ -1318,7 +1318,8 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence) return 0; } - php_error_docref(NULL, E_WARNING, "Stream does not support seeking"); + php_stream_warn(stream, STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED, + "Stream does not support seeking"); return -1; } @@ -1919,7 +1920,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const if (!localhost && path[n+3] != '\0' && path[n+3] != '/') { #endif if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "Remote host file access not supported, %s", path); + php_stream_wrapper_warn(plain_files_wrapper, NULL, options, + STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "Remote host file access not supported, %s", path); } return NULL; } @@ -1958,7 +1961,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const } if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "file:// wrapper is disabled in the server configuration"); + php_stream_wrapper_warn(plain_files_wrapper, NULL, options, + STREAM_ERROR_CODE_DISABLED, + "file:// wrapper is disabled in the server configuration"); } return NULL; } @@ -2056,7 +2061,8 @@ PHPAPI php_stream *_php_stream_opendir(const char *path, int options, stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR; } } else if (wrapper) { - php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS, "not implemented"); + php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS, + STREAM_ERROR_CODE_NO_OPENER, "not implemented"); } if (stream == NULL && (options & REPORT_ERRORS)) { php_stream_display_wrapper_errors(wrapper, path, "Failed to open directory"); @@ -2126,7 +2132,13 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options); if ((options & STREAM_USE_URL) && (!wrapper || !wrapper->is_url)) { - php_error_docref(NULL, E_WARNING, "This function may only be used against URLs"); + if (wrapper) { + php_stream_wrapper_warn(wrapper, context, options, + STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, + "This function may only be used against URLs"); + } else { + php_error_docref(NULL, E_WARNING, "This function may only be used against URLs"); + } if (resolved_path) { zend_string_release_ex(resolved_path, 0); } @@ -2135,7 +2147,8 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod if (wrapper) { if (!wrapper->wops->stream_opener) { - php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS, + php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS, + STREAM_ERROR_CODE_NO_OPENER, "wrapper does not support stream open"); } else { stream = wrapper->wops->stream_opener(wrapper, @@ -2146,7 +2159,8 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod /* if the caller asked for a persistent stream but the wrapper did not * return one, force an error here */ if (stream && persistent && !stream->is_persistent) { - php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS, + php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS, + STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED, "wrapper does not support persistent streams"); php_stream_close(stream); stream = NULL; @@ -2198,8 +2212,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod if (options & REPORT_ERRORS) { char *tmp = estrdup(path); php_strip_url_passwd(tmp); - php_error_docref1(NULL, tmp, E_WARNING, "could not make seekable - %s", - tmp); + php_stream_wrapper_warn_param(wrapper, context, options, + STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED, tmp, + "could not make seekable - %s", tmp); efree(tmp); options &= ~REPORT_ERRORS; From c51addb06ef368b980bb0abc9eb9c4bc28f45ea0 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 12:50:58 +0100 Subject: [PATCH 14/61] stream: add docref param to error functions --- main/streams/php_stream_errors.h | 42 +++++++----- main/streams/plain_wrapper.c | 4 +- main/streams/stream_errors.c | 108 +++++++++++++++++++------------ 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 34894d2512706..ffd0969eef15f 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -122,8 +122,9 @@ BEGIN_EXTERN_C() typedef struct { zend_string *message; int code; - const char *wrapper_name; /* Points to wrapper->wops->label, no need to duplicate */ - const char *param; /* Points to passed string, caller manages lifetime for storage */ + const char *wrapper_name; /* Points to wrapper->wops->label, no need to duplicate */ + const char *docref; + const char *param; int severity; bool terminal; } php_stream_error_entry; @@ -132,17 +133,19 @@ typedef struct { PHPAPI void php_stream_wrapper_error( php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminal, int code, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); +) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_wrapper_error_param( php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminal, @@ -150,11 +153,12 @@ PHPAPI void php_stream_wrapper_error_param( const char *param, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); +) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); PHPAPI void php_stream_wrapper_error_param2( php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminal, @@ -163,16 +167,17 @@ PHPAPI void php_stream_wrapper_error_param2( const char *param2, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); +) ZEND_ATTRIBUTE_FORMAT(printf, 10, 11); PHPAPI void php_stream_error( php_stream *stream, + const char *docref, int severity, bool terminal, int code, const char *fmt, ... -) ZEND_ATTRIBUTE_FORMAT(printf, 5, 6); +) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); /* Legacy wrapper error log - updated API */ PHPAPI void php_stream_wrapper_log_error( @@ -211,37 +216,40 @@ void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); /* Convenience macros */ #define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, options, E_WARNING, true, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) #define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, options, E_WARNING, false, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, code, __VA_ARGS__) #define php_stream_wrapper_notice(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, options, E_NOTICE, false, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_NOTICE, false, code, __VA_ARGS__) #define php_stream_wrapper_warn_param(wrapper, context, options, code, param, ...) \ - php_stream_wrapper_error_param(wrapper, context, options, E_WARNING, true, code, param, __VA_ARGS__) + php_stream_wrapper_error_param(wrapper, context, NULL, options, E_WARNING, true, code, param, __VA_ARGS__) #define php_stream_wrapper_warn_param_nt(wrapper, context, options, code, param, ...) \ - php_stream_wrapper_error_param(wrapper, context, options, E_WARNING, false, code, param, __VA_ARGS__) + php_stream_wrapper_error_param(wrapper, context, NULL, options, E_WARNING, false, code, param, __VA_ARGS__) #define php_stream_wrapper_warn_param2(wrapper, context, options, code, param1, param2, ...) \ - php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, true, code, param1, param2, __VA_ARGS__) + php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, true, code, param1, param2, __VA_ARGS__) #define php_stream_wrapper_warn_param2_nt(wrapper, context, options, code, param1, param2, ...) \ - php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, false, code, param1, param2, __VA_ARGS__) + php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, false, code, param1, param2, __VA_ARGS__) #define php_stream_warn(stream, code, ...) \ - php_stream_error(stream, E_WARNING, true, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_WARNING, true, code, __VA_ARGS__) #define php_stream_warn_nt(stream, code, ...) \ - php_stream_error(stream, E_WARNING, false, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_WARNING, false, code, __VA_ARGS__) + +#define php_stream_warn_docref(stream, docref, code, ...) \ + php_stream_error(stream, docref, E_WARNING, true, code, __VA_ARGS__) #define php_stream_notice(stream, code, ...) \ - php_stream_error(stream, E_NOTICE, false, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_NOTICE, false, code, __VA_ARGS__) #define php_stream_fatal(stream, code, ...) \ - php_stream_error(stream, E_ERROR, true, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_ERROR, true, code, __VA_ARGS__) /* Legacy log variants */ #define php_stream_wrapper_log_warn(wrapper, context, options, code, ...) \ diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 8572f6f0275bf..22375713a2a93 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -1387,7 +1387,7 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f if (errno != EPERM) { success = 0; } - php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, + php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, !success, STREAM_ERROR_CODE_CHOWN_FAILED, url_from, url_to, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } @@ -1397,7 +1397,7 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f if (errno != EPERM) { success = 0; } - php_stream_wrapper_error_param2(wrapper, context, options, E_WARNING, + php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, !success, STREAM_ERROR_CODE_CHOWN_FAILED, url_from, url_to, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 74e3af9a7a1c0..be4faabd1162a 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -32,6 +32,8 @@ static void php_stream_error_entry_dtor(void *error) { php_stream_error_entry *entry = *(php_stream_error_entry **) error; zend_string_release(entry->message); + efree(entry->docref); + efree(entry->param); efree(entry); } @@ -132,8 +134,8 @@ static void php_stream_throw_exception( /* Core error processing */ static void php_stream_process_error(php_stream_context *context, const char *wrapper_name, - php_stream *stream, int code, const char *message, const char *param, int severity, - bool terminal) + php_stream *stream, const char *docref, int code, const char *message, const char *param, + int severity, bool terminal) { int error_mode = php_stream_get_error_mode(context); @@ -141,9 +143,9 @@ static void php_stream_process_error(php_stream_context *context, const char *wr switch (error_mode) { case PHP_STREAM_ERROR_MODE_ERROR: if (param) { - php_error_docref1(NULL, param, severity, "%s", message); + php_error_docref1(docref, param, severity, "%s", message); } else { - php_error_docref(NULL, severity, "%s", message); + php_error_docref(docref, severity, "%s", message); } break; @@ -168,7 +170,8 @@ static void php_stream_process_error(php_stream_context *context, const char *wr if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) { if (is_callable_error) { - zend_type_error("stream error handler must be a valid callback, %s", is_callable_error); + zend_type_error( + "stream error handler must be a valid callback, %s", is_callable_error); efree(is_callable_error); } else { zend_type_error("stream error must be a valid callback"); @@ -213,7 +216,8 @@ static void php_stream_process_error(php_stream_context *context, const char *wr /* Helper to create error entry */ static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, - const char *wrapper_name, const char *param, int severity, bool terminal) + const char *wrapper_name, const char *docref, const char *param, int severity, + bool terminal) { php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); entry->message = message; /* Takes ownership */ @@ -227,18 +231,22 @@ static php_stream_error_entry *php_stream_create_error_entry(zend_string *messag /* Common storage function*/ static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, - php_stream_wrapper *wrapper, zend_string *message, int code, const char *wrapper_name, - const char *param, int severity, bool terminal) + php_stream_wrapper *wrapper, zend_string *message, const char *docref, int code, + const char *wrapper_name, const char *param, int severity, bool terminal) { int error_mode = php_stream_get_error_mode(context); int store_mode = php_stream_get_error_store_mode(context, error_mode); if (!php_stream_should_store_error(store_mode, terminal)) { + efree(param); return; } - php_stream_error_entry *entry - = php_stream_create_error_entry(message, code, wrapper_name, param, severity, terminal); + if (docref != NULL) { + docref = estrdup(docref); + } + php_stream_error_entry *entry = php_stream_create_error_entry( + message, code, wrapper_name, docref, param, severity, terminal); zend_string_addref(message); /* Storage keeps a reference */ zend_llist *list; @@ -277,46 +285,51 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea /* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, - const char *param, const char *fmt, va_list args) + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, fmt, args); const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; if (options & REPORT_ERRORS) { - php_stream_process_error( - context, wrapper_name, NULL, code, ZSTR_VAL(message), param, severity, terminal); + php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), + param, severity, terminal); } php_stream_store_error_common( - context, NULL, wrapper, message, code, wrapper_name, param, severity, terminal); + context, NULL, wrapper, message, docref, code, wrapper_name, param, severity, terminal); zend_string_release(message); } PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, - int options, int severity, bool terminal, int code, const char *fmt, ...) + const char *docref, int options, int severity, bool terminal, int code, const char *fmt, + ...) { va_list args; va_start(args, fmt); php_stream_wrapper_error_internal( - wrapper, context, options, severity, terminal, code, NULL, fmt, args); + wrapper, context, docref, options, severity, terminal, code, NULL, fmt, args); va_end(args); } PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, - int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) + const char *docref, int options, int severity, bool terminal, int code, const char *param, + const char *fmt, ...) { va_list args; va_start(args, fmt); + if (param = NULL) { + param = estrdup(param); + } php_stream_wrapper_error_internal( - wrapper, context, options, severity, terminal, code, param, fmt, args); + wrapper, context, docref, options, severity, terminal, code, param, fmt, args); va_end(args); } PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, - const char *param1, const char *param2, const char *fmt, ...) + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *param1, const char *param2, const char *fmt, ...) { char *combined_param; spprintf(&combined_param, 0, "%s,%s", param1, param2); @@ -324,10 +337,8 @@ PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, va_list args; va_start(args, fmt); php_stream_wrapper_error_internal( - wrapper, context, options, severity, terminal, code, combined_param, fmt, args); + wrapper, context, docref, options, severity, terminal, code, combined_param, fmt, args); va_end(args); - - efree(combined_param); } /* Wrapper error logging - stores in FG(wrapper_logged_errors) */ @@ -336,8 +347,11 @@ static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper zend_string *message, int code, const char *wrapper_name, const char *param, int severity, bool terminal) { - php_stream_error_entry *entry - = php_stream_create_error_entry(message, code, wrapper_name, param, severity, terminal); + if (param != NULL) { + param = estrdup(param); + } + php_stream_error_entry *entry = php_stream_create_error_entry( + message, code, wrapper_name, NULL, param, severity, terminal); zend_string_addref(message); if (!FG(wrapper_logged_errors)) { @@ -359,15 +373,17 @@ static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper zend_llist_add_element(list, &entry); } -static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, php_stream_context *context, - int options, int severity, bool terminal, int code, const char *param, const char *fmt, va_list args) +static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, args, args); const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; if (options & REPORT_ERRORS) { /* Report immediately using standard error functions */ - php_stream_wrapper_error_internal(wrapper, context, options, severity, terminal, code, param, fmt, args); + php_stream_wrapper_error_internal( + wrapper, context, options, severity, terminal, code, param, fmt, args); zend_string_release(message); } else { /* Store for later display in FG(wrapper_logged_errors) */ @@ -377,21 +393,25 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap } } -PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, php_stream_context *context, - int options, int severity, bool terminal, int code, const char *fmt, ...) +PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *fmt, ...) { va_list args; va_start(args, fmt); - php_stream_wrapper_log_error_internal(wrapper, context, options, severity, terminal, code, NULL, fmt, args); + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminal, code, NULL, fmt, args); va_end(args); } -PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, php_stream_context *context, - int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) +PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *param, const char *fmt, ...) { va_list args; va_start(args, fmt); - php_stream_wrapper_log_error_internal(wrapper, context, options, severity, terminal, code, param, fmt, args); + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminal, code, param, fmt, args); va_end(args); } @@ -400,12 +420,14 @@ static zend_llist *php_stream_get_wrapper_errors_list(php_stream_wrapper *wrappe if (!FG(wrapper_logged_errors)) { return NULL; } else { - return (zend_llist*) zend_hash_str_find_ptr(FG(wrapper_logged_errors), (const char*)&wrapper, sizeof(wrapper)); + return (zend_llist *) zend_hash_str_find_ptr( + FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); } } /* {{{ wrapper error reporting */ -static void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption) +static void php_stream_display_wrapper_errors( + php_stream_wrapper *wrapper, const char *path, const char *caption) { char *tmp; char *msg; @@ -483,8 +505,8 @@ void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) /* Stream error reporting */ -PHPAPI void php_stream_error( - php_stream *stream, int severity, bool terminal, int code, const char *fmt, ...) +PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminal, + int code, const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -497,12 +519,12 @@ PHPAPI void php_stream_error( php_stream_context *context = PHP_STREAM_CONTEXT(stream); /* Process the error - always report for stream errors */ - php_stream_process_error( - context, wrapper_name, stream, code, ZSTR_VAL(message), NULL, severity, terminal); + php_stream_process_error(context, wrapper_name, stream, docref, code, ZSTR_VAL(message), NULL, + severity, terminal); /* Store error */ - php_stream_store_error_common( - context, stream, wrapper, message, code, wrapper_name, NULL, severity, terminal); + php_stream_store_error_common(context, stream, wrapper, message, docref, code, wrapper_name, + NULL, severity, terminal); zend_string_release(message); } From 06b794799fbded5158f8aa6af1690e267d4569b3 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 13:15:00 +0100 Subject: [PATCH 15/61] stream: convert transport errors --- main/streams/php_stream_errors.h | 7 +++++-- main/streams/transports.c | 32 +++++++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index ffd0969eef15f..cb1c69556355b 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -46,8 +46,11 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED 13 #define STREAM_ERROR_CODE_FLUSH_FAILED 14 #define STREAM_ERROR_CODE_TRUNCATE_FAILED 15 -#define STREAM_ERROR_CODE_NOT_WRITABLE 16 -#define STREAM_ERROR_CODE_NOT_READABLE 17 +#define STREAM_ERROR_CODE_CONNECT_FAILED 16 +#define STREAM_ERROR_CODE_BIND_FAILED 17 +#define STREAM_ERROR_CODE_LISTEN_FAILED 18 +#define STREAM_ERROR_CODE_NOT_WRITABLE 19 +#define STREAM_ERROR_CODE_NOT_READABLE 20 /* File system operations */ #define STREAM_ERROR_CODE_DISABLED 30 #define STREAM_ERROR_CODE_NOT_FOUND 31 diff --git a/main/streams/transports.c b/main/streams/transports.c index 83297d9a06ceb..ceab9a083de82 100644 --- a/main/streams/transports.c +++ b/main/streams/transports.c @@ -39,13 +39,13 @@ PHPAPI int php_stream_xport_unregister(const char *protocol) return zend_hash_str_del(&xport_hash, protocol, strlen(protocol)); } -#define ERR_REPORT(out_err, fmt, arg) \ +#define ERR_REPORT(code, out_err, fmt, arg) \ if (out_err) { *out_err = strpprintf(0, fmt, arg); } \ - else { php_error_docref(NULL, E_WARNING, fmt, arg); } + else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, arg); } -#define ERR_RETURN(out_err, local_err, fmt) \ +#define ERR_RETURN(code, out_err, local_err, fmt) \ if (out_err) { *out_err = local_err; } \ - else { php_error_docref(NULL, E_WARNING, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \ + else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \ if (local_err) { zend_string_release_ex(local_err, 0); local_err = NULL; } \ } @@ -116,7 +116,8 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in n = sizeof(wrapper_name) - 1; PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n); - ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?", + ERR_REPORT(STREAM_ERROR_CODE_WRAPPER_NOT_FOUND, error_string, + "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?", wrapper_name); return NULL; @@ -125,7 +126,8 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in if (factory == NULL) { /* should never happen */ - php_error_docref(NULL, E_WARNING, "Could not find a factory !?"); + php_stream_wrapper_warn(NULL, context, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_NOT_FOUND, "Could not find a factory !?"); return NULL; } @@ -146,7 +148,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0, timeout, &error_text, error_code)) { - ERR_RETURN(error_string, error_text, "connect() failed: %s"); + ERR_RETURN(STREAM_ERROR_CODE_CONNECT_FAILED, error_string, error_text, "connect() failed: %s"); failed = true; } @@ -156,7 +158,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in /* server */ if (flags & STREAM_XPORT_BIND) { if (0 != php_stream_xport_bind(stream, name, namelen, &error_text)) { - ERR_RETURN(error_string, error_text, "bind() failed: %s"); + ERR_RETURN(STREAM_ERROR_CODE_BIND_FAILED, error_string, error_text, "bind() failed: %s"); failed = true; } else if (flags & STREAM_XPORT_LISTEN) { zval *zbacklog = NULL; @@ -167,7 +169,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in } if (0 != php_stream_xport_listen(stream, backlog, &error_text)) { - ERR_RETURN(error_string, error_text, "listen() failed: %s"); + ERR_RETURN(STREAM_ERROR_CODE_LISTEN_FAILED, error_string, error_text, "listen() failed: %s"); failed = true; } } @@ -370,7 +372,8 @@ PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_cr return param.outputs.returncode; } - php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto"); + php_stream_warn_docref(stream, "streams.crypto", STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "This stream does not support SSL/crypto"); return ret; } @@ -390,7 +393,8 @@ PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate) return param.outputs.returncode; } - php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto"); + php_stream_warn_docref(stream, "streams.crypto", STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, + "This stream does not support SSL/crypto"); return ret; } @@ -412,7 +416,8 @@ PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t bufle } if (stream->readfilters.head) { - php_error_docref(NULL, E_WARNING, "Cannot peek or fetch OOB data from a filtered stream"); + php_stream_warn(stream, STREAM_ERROR_CODE_FILTER_FAILED, + "Cannot peek or fetch OOB data from a filtered stream"); return -1; } @@ -482,7 +487,8 @@ PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t b oob = (flags & STREAM_OOB) == STREAM_OOB; if ((oob || addr) && stream->writefilters.head) { - php_error_docref(NULL, E_WARNING, "Cannot write OOB data, or data to a targeted address on a filtered stream"); + php_stream_warn(stream, STREAM_ERROR_CODE_FILTER_FAILED, + "Cannot write OOB data, or data to a targeted address on a filtered stream"); return -1; } From e1d57f8321cb6d973759ef5aad631b0ba8e51c79 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 15:53:46 +0100 Subject: [PATCH 16/61] stream: store wrapper erros by name instead of ptr --- main/streams/stream_errors.c | 71 ++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index be4faabd1162a..0879a02c68954 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -32,6 +32,7 @@ static void php_stream_error_entry_dtor(void *error) { php_stream_error_entry *entry = *(php_stream_error_entry **) error; zend_string_release(entry->message); + efree(entry->wrapper_name); efree(entry->docref); efree(entry->param); efree(entry); @@ -220,18 +221,22 @@ static php_stream_error_entry *php_stream_create_error_entry(zend_string *messag bool terminal) { php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); - entry->message = message; /* Takes ownership */ + entry->message = message; entry->code = code; - entry->wrapper_name = wrapper_name; + entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; + entry->docref = docref ? estrdup(docref) : NULL; entry->param = param; entry->severity = severity; entry->terminal = terminal; + + zend_string_addref(message); + return entry; } /* Common storage function*/ static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, - php_stream_wrapper *wrapper, zend_string *message, const char *docref, int code, + zend_string *message, const char *docref, int code, const char *wrapper_name, const char *param, int severity, bool terminal) { int error_mode = php_stream_get_error_mode(context); @@ -241,13 +246,8 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea efree(param); return; } - - if (docref != NULL) { - docref = estrdup(docref); - } php_stream_error_entry *entry = php_stream_create_error_entry( message, code, wrapper_name, docref, param, severity, terminal); - zend_string_addref(message); /* Storage keeps a reference */ zend_llist *list; @@ -267,15 +267,15 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea list = NULL; } else { list = zend_hash_str_find_ptr( - FG(wrapper_stored_errors), (const char *) &wrapper, sizeof(wrapper)); + FG(wrapper_stored_errors), wrapper_name, strlen(wrapper_name)); } if (!list) { zend_llist new_list; zend_llist_init( &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); - list = zend_hash_str_update_mem(FG(wrapper_stored_errors), (const char *) &wrapper, - sizeof(wrapper), &new_list, sizeof(new_list)); + list = zend_hash_str_update_mem(FG(wrapper_stored_errors), wrapper_name, + strlen(wrapper_name), &new_list, sizeof(new_list)); } } @@ -283,13 +283,11 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea } /* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ - -static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, +static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, fmt, args); - const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; if (options & REPORT_ERRORS) { php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), @@ -297,11 +295,32 @@ static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, } php_stream_store_error_common( - context, NULL, wrapper, message, docref, code, wrapper_name, param, severity, terminal); + context, NULL, message, docref, code, wrapper_name, param, severity, terminal); zend_string_release(message); } +static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *param, const char *fmt, va_list args) +{ + const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + + php_stream_wrapper_error_internal_with_name(wrapper_name, context, docref, options, severity, + terminal, code, param, fmt, args); +} + +PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal_with_name( + wrapper_name, context, docref, options, severity, terminal, code, NULL, fmt, args); + va_end(args); +} + PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *fmt, ...) @@ -343,16 +362,14 @@ PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, /* Wrapper error logging - stores in FG(wrapper_logged_errors) */ -static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper, - zend_string *message, int code, const char *wrapper_name, const char *param, int severity, - bool terminal) +static void php_stream_wrapper_log_store_error(zend_string *message, int code, + const char *wrapper_name, const char *param, int severity, bool terminal) { if (param != NULL) { param = estrdup(param); } php_stream_error_entry *entry = php_stream_create_error_entry( message, code, wrapper_name, NULL, param, severity, terminal); - zend_string_addref(message); if (!FG(wrapper_logged_errors)) { ALLOC_HASHTABLE(FG(wrapper_logged_errors)); @@ -360,14 +377,14 @@ static void php_stream_wrapper_log_store_error(const php_stream_wrapper *wrapper } zend_llist *list = zend_hash_str_find_ptr( - FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); + FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); if (!list) { zend_llist new_list; zend_llist_init( &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); - list = zend_hash_str_update_mem(FG(wrapper_logged_errors), (const char *) &wrapper, - sizeof(wrapper), &new_list, sizeof(new_list)); + list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name, + strlen(wrapper_name), &new_list, sizeof(new_list)); } zend_llist_add_element(list, &entry); @@ -382,13 +399,13 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap if (options & REPORT_ERRORS) { /* Report immediately using standard error functions */ - php_stream_wrapper_error_internal( - wrapper, context, options, severity, terminal, code, param, fmt, args); + php_stream_wrapper_error_internal_with_name( + wrapper_name, context, NULL, options, severity, terminal, code, param, fmt, args); zend_string_release(message); } else { /* Store for later display in FG(wrapper_logged_errors) */ php_stream_wrapper_log_store_error( - wrapper, message, code, wrapper_name, param, severity, terminal); + message, code, wrapper_name, param, severity, terminal); zend_string_release(message); } } @@ -523,8 +540,8 @@ PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severit severity, terminal); /* Store error */ - php_stream_store_error_common(context, stream, wrapper, message, docref, code, wrapper_name, - NULL, severity, terminal); + php_stream_store_error_common(context, stream, message, docref, code, wrapper_name, NULL, + severity, terminal); zend_string_release(message); } From 0ff94be0ec2839fecb03a1185635db5df1d5521e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 16:53:50 +0100 Subject: [PATCH 17/61] stream: convert user stream wrapper errors --- main/streams/php_stream_errors.h | 135 ++++++++-------------- main/streams/stream_errors.stub.php | 30 +++++ main/streams/stream_errors_arginfo.h | 10 +- main/streams/userspace.c | 162 +++++++++++++++++---------- 4 files changed, 186 insertions(+), 151 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index cb1c69556355b..f015a01e4994d 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -72,18 +72,20 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_COPY_FAILED 47 #define STREAM_ERROR_CODE_TOUCH_FAILED 48 #define STREAM_ERROR_CODE_INVALID_MODE 49 -#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 50 -#define STREAM_ERROR_CODE_READONLY 51 -#define STREAM_ERROR_CODE_RECURSION_DETECTED 52 +#define STREAM_ERROR_CODE_INVALID_META 50 +#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 51 +#define STREAM_ERROR_CODE_READONLY 52 +#define STREAM_ERROR_CODE_RECURSION_DETECTED 53 /* Wrapper/protocol operations */ -#define STREAM_ERROR_CODE_NO_OPENER 70 -#define STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED 71 -#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 72 -#define STREAM_ERROR_CODE_WRAPPER_DISABLED 73 -#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 74 -#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 75 -#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 76 -#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 77 +#define STREAM_ERROR_CODE_NOT_IMPLEMENTED 70 +#define STREAM_ERROR_CODE_NO_OPENER 71 +#define STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED 72 +#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 73 +#define STREAM_ERROR_CODE_WRAPPER_DISABLED 74 +#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 75 +#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 76 +#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 77 +#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 78 /* Filter operations */ #define STREAM_ERROR_CODE_FILTER_NOT_FOUND 90 #define STREAM_ERROR_CODE_FILTER_FAILED 91 @@ -120,12 +122,13 @@ BEGIN_EXTERN_C() /* Userspace stream operations */ #define STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED 160 #define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 161 +#define STREAM_ERROR_CODE_USERSPACE_CALL_FAILED 162 /* Stored error entry */ typedef struct { zend_string *message; int code; - const char *wrapper_name; /* Points to wrapper->wops->label, no need to duplicate */ + const char *wrapper_name; const char *docref; const char *param; int severity; @@ -133,84 +136,37 @@ typedef struct { } php_stream_error_entry; /* Main error reporting functions */ -PHPAPI void php_stream_wrapper_error( - php_stream_wrapper *wrapper, - php_stream_context *context, - const char *docref, - int options, - int severity, - bool terminal, - int code, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); - -PHPAPI void php_stream_wrapper_error_param( - php_stream_wrapper *wrapper, - php_stream_context *context, - const char *docref, - int options, - int severity, - bool terminal, - int code, - const char *param, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); - -PHPAPI void php_stream_wrapper_error_param2( - php_stream_wrapper *wrapper, - php_stream_context *context, - const char *docref, - int options, - int severity, - bool terminal, - int code, - const char *param1, - const char *param2, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 10, 11); - -PHPAPI void php_stream_error( - php_stream *stream, - const char *docref, - int severity, - bool terminal, - int code, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); +PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); + +PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminal, int code, const char *fmt, + ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); + +PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminal, int code, const char *param, + const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); + +PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, const char *param1, const char *param2, const char *fmt, ...) + ZEND_ATTRIBUTE_FORMAT(printf, 10, 11); + +PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminal, + int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); /* Legacy wrapper error log - updated API */ -PHPAPI void php_stream_wrapper_log_error( - const php_stream_wrapper *wrapper, - php_stream_context *context, - int options, - int severity, - bool terminal, - int code, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); - -PHPAPI void php_stream_wrapper_log_error_param( - const php_stream_wrapper *wrapper, - php_stream_context *context, - int options, - int severity, - bool terminal, - int code, - const char *param, - const char *fmt, - ... -) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); - -PHPAPI void php_stream_display_wrapper_errors( - php_stream_wrapper *wrapper, - const char *path, - const char *caption -); +PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); + +PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminal, int code, + const char *param, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); + +PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, + const char *path, const char *caption); PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); @@ -221,6 +177,9 @@ void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); #define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) +#define php_stream_wrapper_warn_name(wrapper_name, context, options, code, ...) \ + php_stream_wrapper_error_with_name(wrapper_name, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) + #define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \ php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, code, __VA_ARGS__) diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 802a90e00fb8d..8a27e0060fc3d 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -51,6 +51,21 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_TRUNCATE_FAILED */ const STREAM_ERROR_CODE_TRUNCATE_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_CONNECT_FAILED + */ +const STREAM_ERROR_CODE_CONNECT_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_BIND_FAILED + */ +const STREAM_ERROR_CODE_BIND_FAILED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_LISTEN_FAILED + */ +const STREAM_ERROR_CODE_LISTEN_FAILED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_NOT_WRITABLE @@ -161,6 +176,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_INVALID_MODE */ const STREAM_ERROR_CODE_INVALID_MODE = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_INVALID_META + */ +const STREAM_ERROR_CODE_INVALID_META = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_MODE_NOT_SUPPORTED @@ -176,6 +196,11 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_RECURSION_DETECTED */ const STREAM_ERROR_CODE_RECURSION_DETECTED = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_NOT_IMPLEMENTED + */ +const STREAM_ERROR_CODE_NOT_IMPLEMENTED = UNKNOWN; /** * @var int * @cvalue STREAM_ERROR_CODE_NO_OPENER @@ -361,3 +386,8 @@ public function getWrapperName(): string {} * @cvalue STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN */ const STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN = UNKNOWN; +/** + * @var int + * @cvalue STREAM_ERROR_CODE_USERSPACE_CALL_FAILED + */ +const STREAM_ERROR_CODE_USERSPACE_CALL_FAILED = UNKNOWN; diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 5859f825c4ee4..523cf64323a0e 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c14298e97a4960fe35b7b1b464a5b0b94099897f */ + * Stub hash: faa3f0e64b8388a495ac645fd3eda49c1ed243bd */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -26,6 +26,9 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED", STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_FLUSH_FAILED", STREAM_ERROR_CODE_FLUSH_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TRUNCATE_FAILED", STREAM_ERROR_CODE_TRUNCATE_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CONNECT_FAILED", STREAM_ERROR_CODE_CONNECT_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_BIND_FAILED", STREAM_ERROR_CODE_BIND_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_LISTEN_FAILED", STREAM_ERROR_CODE_LISTEN_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_WRITABLE", STREAM_ERROR_CODE_NOT_WRITABLE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_READABLE", STREAM_ERROR_CODE_NOT_READABLE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DISABLED", STREAM_ERROR_CODE_DISABLED, CONST_PERSISTENT); @@ -48,9 +51,13 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_COPY_FAILED", STREAM_ERROR_CODE_COPY_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TOUCH_FAILED", STREAM_ERROR_CODE_TOUCH_FAILED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_MODE", STREAM_ERROR_CODE_INVALID_MODE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_META", STREAM_ERROR_CODE_INVALID_META, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MODE_NOT_SUPPORTED", STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_READONLY", STREAM_ERROR_CODE_READONLY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RECURSION_DETECTED", STREAM_ERROR_CODE_RECURSION_DETECTED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_IMPLEMENTED", STREAM_ERROR_CODE_NOT_IMPLEMENTED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NO_OPENER", STREAM_ERROR_CODE_NO_OPENER, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED", STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_NOT_FOUND", STREAM_ERROR_CODE_WRAPPER_NOT_FOUND, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_DISABLED", STREAM_ERROR_CODE_WRAPPER_DISABLED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED", STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, CONST_PERSISTENT); @@ -86,6 +93,7 @@ static void register_stream_errors_symbols(int module_number) REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED", STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED", STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN", STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_CALL_FAILED", STREAM_ERROR_CODE_USERSPACE_CALL_FAILED, CONST_PERSISTENT); } static zend_class_entry *register_class_StreamException(zend_class_entry *class_entry_Exception) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index f5e25aa96c772..4683dd270ab39 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -293,7 +293,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * /* Try to catch bad usage without preventing flexibility */ if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) { - php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_RECURSION_DETECTED, "infinite recursion prevented"); return NULL; } FG(user_stream_current_filename) = filename; @@ -334,8 +335,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * zval_ptr_dtor(&args[0]); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" is not implemented", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_wrapper_log_warn(wrapper, context, options,STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "\"%s::" USERSTREAM_OPEN "\" is not implemented", ZSTR_VAL(us->wrapper->ce->name)); zval_ptr_dtor(&args[3]); goto end; } @@ -357,8 +358,9 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * /* set wrapper data to be a reference to our object */ ZVAL_COPY(&stream->wrapperdata, &us->object); } else { - php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_USERSPACE_CALL_FAILED, + "\"%s::" USERSTREAM_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name)); } zval_ptr_dtor(&zretval); @@ -394,7 +396,8 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char /* Try to catch bad usage without preventing flexibility */ if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) { - php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented"); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_RECURSION_DETECTED, "infinite recursion prevented"); return NULL; } FG(user_stream_current_filename) = filename; @@ -419,8 +422,9 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char zval_ptr_dtor(&args[0]); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented", + ZSTR_VAL(us->wrapper->ce->name)); goto end; } /* Exception occurred in call */ @@ -435,8 +439,9 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char /* set wrapper data to be a reference to our object */ ZVAL_COPY(&stream->wrapperdata, &us->object); } else { - php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_wrapper_log_warn(wrapper, context, options, + STREAM_ERROR_CODE_USERSPACE_CALL_FAILED, + "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name)); } zval_ptr_dtor(&zretval); @@ -479,10 +484,15 @@ PHP_FUNCTION(stream_wrapper_register) /* We failed. But why? */ if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol)) { - php_error_docref(NULL, E_WARNING, "Protocol %s:// is already defined.", ZSTR_VAL(protocol)); + php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED, + "Protocol %s:// is already defined.", ZSTR_VAL(protocol)); } else { /* Hash doesn't exist so it must have been an invalid protocol scheme */ - php_error_docref(NULL, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol)); + php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED, + "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", + ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol)); } zend_list_delete(rsrc); @@ -502,7 +512,9 @@ PHP_FUNCTION(stream_wrapper_unregister) php_stream_wrapper *wrapper = zend_hash_find_ptr(php_stream_get_url_stream_wrappers_hash(), protocol); if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) { /* We failed */ - php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol)); + php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED, + "Unable to unregister protocol %s://", ZSTR_VAL(protocol)); RETURN_FALSE; } @@ -530,13 +542,17 @@ PHP_FUNCTION(stream_wrapper_restore) global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global(); if ((wrapper = zend_hash_find_ptr(global_wrapper_hash, protocol)) == NULL) { - php_error_docref(NULL, E_WARNING, "%s:// never existed, nothing to restore", ZSTR_VAL(protocol)); + php_stream_wrapper_warn_name(user_stream_wops.label, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_NOT_FOUND, + "%s:// never existed, nothing to restore", ZSTR_VAL(protocol)); RETURN_FALSE; } wrapper_hash = php_stream_get_url_stream_wrappers_hash(); if (wrapper_hash == global_wrapper_hash || zend_hash_find_ptr(wrapper_hash, protocol) == wrapper) { - php_error_docref(NULL, E_NOTICE, "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol)); + php_stream_wrapper_notice(wrapper, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED, + "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol)); RETURN_TRUE; } @@ -544,7 +560,9 @@ PHP_FUNCTION(stream_wrapper_restore) php_unregister_url_stream_wrapper_volatile(protocol); if (php_register_url_stream_wrapper_volatile(protocol, wrapper) == FAILURE) { - php_error_docref(NULL, E_WARNING, "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol)); + php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS, + STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED, + "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol)); RETURN_FALSE; } @@ -572,8 +590,8 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ zval_ptr_dtor(&args[0]); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_WRITE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); } stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE; @@ -593,7 +611,9 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ /* don't allow strange buffer overruns due to bogus return */ if (didwrite > 0 && didwrite > count) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)", + php_stream_warn_nt(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" + ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)", ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didwrite - count), (zend_long)didwrite, (zend_long)count); didwrite = count; @@ -624,8 +644,8 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count } if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); goto err; } @@ -641,8 +661,12 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count didread = Z_STRLEN(retval); if (didread > 0) { if (didread > count) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " read, " ZEND_LONG_FMT " max) - excess data will be lost", - ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count), (zend_long)didread, (zend_long)count); + php_stream_warn_nt(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT + " bytes more data than requested (" ZEND_LONG_FMT " read, " + ZEND_LONG_FMT " max) - excess data will be lost", + ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count), + (zend_long)didread, (zend_long)count); didread = count; } memcpy(buf, Z_STRVAL(retval), didread); @@ -658,7 +682,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", ZSTR_VAL(us->wrapper->ce->name)); stream->eof = 1; @@ -774,7 +798,8 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when *newoffs = Z_LVAL(retval); ret = 0; } else if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); ret = -1; } else { ret = -1; @@ -838,8 +863,8 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb) zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_STAT " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return -1; } if (UNEXPECTED(Z_ISUNDEF(retval))) { @@ -857,7 +882,7 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb) return ret; } -static int user_stream_set_check_liveliness(const php_userstream_data_t *us) +static int user_stream_set_check_liveliness(php_stream *stream, const php_userstream_data_t *us) { zval retval; @@ -866,7 +891,8 @@ static int user_stream_set_check_liveliness(const php_userstream_data_t *us) zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, + php_stream_warn(stream, REPORT_ERRORS, + STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; @@ -877,15 +903,16 @@ static int user_stream_set_check_liveliness(const php_userstream_data_t *us) if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; } else { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_EOF " value must be of type bool, %s given", + php_stream_warn(stream, REPORT_ERRORS, + STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_EOF " value must be of type bool, %s given", ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); return PHP_STREAM_OPTION_RETURN_ERR; } } -static int user_stream_set_locking(const php_userstream_data_t *us, int value) +static int user_stream_set_locking(php_stream *stream, const php_userstream_data_t *us, int value) { zval retval; zval zlock; @@ -920,9 +947,8 @@ static int user_stream_set_locking(const php_userstream_data_t *us, int value) /* lock support test (TODO: more check) */ return PHP_STREAM_OPTION_RETURN_OK; } - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_LOCK " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_LOCK " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; } if (UNEXPECTED(Z_ISUNDEF(retval))) { @@ -934,14 +960,15 @@ static int user_stream_set_locking(const php_userstream_data_t *us, int value) } // TODO: ext/standard/tests/file/userstreams_004.phpt returns null implicitly for function // Should this warn or not? And should this be considered an error? - //php_error_docref(NULL, E_WARNING, - // "%s::" USERSTREAM_LOCK " value must be of type bool, %s given", + //php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + // "%s::" USERSTREAM_LOCK " value must be of type bool, %s given", // ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); return PHP_STREAM_OPTION_RETURN_NOTIMPL; } -static int user_stream_set_truncation(const php_userstream_data_t *us, int value, void *ptrparam) { +static int user_stream_set_truncation(php_stream *stream, const php_userstream_data_t *us, + int value, void *ptrparam) { zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_TRUNCATE, false); if (value == PHP_STREAM_TRUNCATE_SUPPORTED) { @@ -969,9 +996,8 @@ static int user_stream_set_truncation(const php_userstream_data_t *us, int value zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_TRUNCATE " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_TRUNCATE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; } if (UNEXPECTED(Z_ISUNDEF(retval))) { @@ -980,15 +1006,16 @@ static int user_stream_set_truncation(const php_userstream_data_t *us, int value if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; } else { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given", + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given", ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); return PHP_STREAM_OPTION_RETURN_ERR; } } -static int user_stream_set_option(const php_userstream_data_t *us, int option, int value, void *ptrparam) +static int user_stream_set_option(php_stream *stream, const php_userstream_data_t *us, int option, + int value, void *ptrparam) { zval args[3]; ZVAL_LONG(&args[0], option); @@ -1013,7 +1040,7 @@ static int user_stream_set_option(const php_userstream_data_t *us, int option, i zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_SET_OPTION " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; @@ -1038,19 +1065,19 @@ static int php_userstreamop_set_option(php_stream *stream, int option, int value switch (option) { case PHP_STREAM_OPTION_CHECK_LIVENESS: - return user_stream_set_check_liveliness(us); + return user_stream_set_check_liveliness(stream, us); case PHP_STREAM_OPTION_LOCKING: - return user_stream_set_locking(us, value); + return user_stream_set_locking(stream, us, value); case PHP_STREAM_OPTION_TRUNCATE_API: - return user_stream_set_truncation(us, value, ptrparam); + return user_stream_set_truncation(stream, us, value, ptrparam); case PHP_STREAM_OPTION_READ_BUFFER: case PHP_STREAM_OPTION_WRITE_BUFFER: case PHP_STREAM_OPTION_READ_TIMEOUT: case PHP_STREAM_OPTION_BLOCKING: - return user_stream_set_option(us, option, value, ptrparam); + return user_stream_set_option(stream, us, option, value, ptrparam); default: return PHP_STREAM_OPTION_RETURN_NOTIMPL; @@ -1083,7 +1110,8 @@ static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name)); } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { ret = Z_TYPE(zretval) == IS_TRUE; } @@ -1121,7 +1149,8 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name)); } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { ret = Z_TYPE(zretval) == IS_TRUE; } @@ -1159,7 +1188,8 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { ret = Z_TYPE(zretval) == IS_TRUE; } @@ -1196,7 +1226,8 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { ret = Z_TYPE(zretval) == IS_TRUE; } @@ -1235,7 +1266,9 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i ZVAL_STRING(&args[2], value); break; default: - php_error_docref(NULL, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, + STREAM_ERROR_CODE_INVALID_META, + "Unknown option %d for " USERSTREAM_METADATA, option); return ret; } @@ -1258,7 +1291,8 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name)); } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { ret = Z_TYPE(zretval) == IS_TRUE; } @@ -1296,8 +1330,8 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i zval_ptr_dtor(&object); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!", - ZSTR_VAL(uwrap->ce->name)); + php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_STATURL " is not implemented!", ZSTR_VAL(uwrap->ce->name)); return -1; } if (UNEXPECTED(Z_ISUNDEF(zretval))) { @@ -1332,8 +1366,9 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_DIR_READ " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); return -1; } if (UNEXPECTED(Z_ISUNDEF(retval))) { @@ -1418,7 +1453,8 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) if (UNEXPECTED(call_result == FAILURE)) { if (report_errors) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!", + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + "%s::" USERSTREAM_CAST " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); } goto out; @@ -1432,14 +1468,16 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) php_stream_from_zval_no_verify(intstream, &retval); if (!intstream) { if (report_errors) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource", + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_CAST " must return a stream resource", ZSTR_VAL(us->wrapper->ce->name)); } break; } if (intstream == stream) { if (report_errors) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself", + php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + "%s::" USERSTREAM_CAST " must not return itself", ZSTR_VAL(us->wrapper->ce->name)); } intstream = NULL; From e3243e2f18a26490f07fdb007656989d5df5e394 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 17:03:17 +0100 Subject: [PATCH 18/61] stream: convert xp_socket errors --- main/streams/xp_socket.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index db35a9b7952c8..4d4428442a867 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -106,9 +106,8 @@ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t coun if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { estr = php_socket_strerror(err, NULL, 0); - php_error_docref(NULL, E_NOTICE, - "Send of %zu bytes failed with errno=%d %s", - count, err, estr); + php_stream_warn(stream, STREAM_ERROR_CODE_NETWORK_SEND_FAILED, + "Send of %zu bytes failed with errno=%d %s", count, err, estr); efree(estr); } } @@ -444,8 +443,7 @@ static int php_sockop_set_option(php_stream *stream, int option, int value, void xparam->inputs.addrlen); if (xparam->outputs.returncode == -1) { char *err = php_socket_strerror(php_socket_errno(), NULL, 0); - php_error_docref(NULL, E_WARNING, - "%s\n", err); + php_stream_warn(stream, STREAM_ERROR_CODE_NETWORK_SEND_FAILED, "%s", err); efree(err); } return PHP_STREAM_OPTION_RETURN_OK; @@ -585,7 +583,8 @@ static const php_stream_ops php_stream_unixdg_socket_ops = { /* network socket operations */ #ifdef AF_UNIX -static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr) +static inline int parse_unix_address(php_stream *stream, php_stream_xport_param *xparam, + struct sockaddr_un *unix_addr) { memset(unix_addr, 0, sizeof(*unix_addr)); unix_addr->sun_family = AF_UNIX; @@ -604,9 +603,9 @@ static inline int parse_unix_address(php_stream_xport_param *xparam, struct sock * BUT, to get into this branch of code, the name is too long, * so we don't care. */ xparam->inputs.namelen = max_length; - php_error_docref(NULL, E_NOTICE, - "socket path exceeded the maximum allowed length of %lu bytes " - "and was truncated", max_length); + php_stream_notice(stream, STREAM_ERROR_CODE_INVALID_PATH, + "socket path exceeded the maximum allowed length of %lu bytes and was truncated", + max_length); } memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); @@ -686,7 +685,7 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t * return -1; } - parse_unix_address(xparam, &unix_addr); + parse_unix_address(stream, xparam, &unix_addr); return bind(sock->socket, (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen); @@ -775,7 +774,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_ return -1; } - parse_unix_address(xparam, &unix_addr); + parse_unix_address(stream, xparam, &unix_addr); ret = php_network_connect_socket(sock->socket, (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen, From 6ff98f24035ae127b5356da17801cb1a90629c75 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 17:41:03 +0100 Subject: [PATCH 19/61] stream: fix compilation issues in core stream errors --- main/streams/php_stream_errors.h | 6 +++--- main/streams/stream_errors.c | 33 +++++++++++++------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index f015a01e4994d..66d705c2bc3bc 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -128,9 +128,9 @@ BEGIN_EXTERN_C() typedef struct { zend_string *message; int code; - const char *wrapper_name; - const char *docref; - const char *param; + char *wrapper_name; + char *docref; + char *param; int severity; bool terminal; } php_stream_error_entry; diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 0879a02c68954..ec403acedfe5a 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -25,9 +25,6 @@ /* StreamException class entry */ static zend_class_entry *php_ce_stream_exception; -/* Error code registry */ -static HashTable *php_stream_wrapper_error_codes = NULL; - static void php_stream_error_entry_dtor(void *error) { php_stream_error_entry *entry = *(php_stream_error_entry **) error; @@ -217,7 +214,7 @@ static void php_stream_process_error(php_stream_context *context, const char *wr /* Helper to create error entry */ static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, - const char *wrapper_name, const char *docref, const char *param, int severity, + const char *wrapper_name, const char *docref, char *param, int severity, bool terminal) { php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); @@ -237,7 +234,7 @@ static php_stream_error_entry *php_stream_create_error_entry(zend_string *messag /* Common storage function*/ static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, zend_string *message, const char *docref, int code, - const char *wrapper_name, const char *param, int severity, bool terminal) + const char *wrapper_name, char *param, int severity, bool terminal) { int error_mode = php_stream_get_error_mode(context); int store_mode = php_stream_get_error_store_mode(context, error_mode); @@ -285,7 +282,7 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea /* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name, php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *param, const char *fmt, va_list args) + int code, char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, fmt, args); @@ -302,7 +299,7 @@ static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *param, const char *fmt, va_list args) + int code, char *param, const char *fmt, va_list args) { const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; @@ -338,11 +335,9 @@ PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stre { va_list args; va_start(args, fmt); - if (param = NULL) { - param = estrdup(param); - } + char *param_copy = param ? estrdup(param): NULL; php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, param, fmt, args); + wrapper, context, docref, options, severity, terminal, code, param_copy, fmt, args); va_end(args); } @@ -365,11 +360,9 @@ PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, static void php_stream_wrapper_log_store_error(zend_string *message, int code, const char *wrapper_name, const char *param, int severity, bool terminal) { - if (param != NULL) { - param = estrdup(param); - } + char *param_copy = param ? estrdup(param): NULL; php_stream_error_entry *entry = php_stream_create_error_entry( - message, code, wrapper_name, NULL, param, severity, terminal); + message, code, wrapper_name, NULL, param_copy, severity, terminal); if (!FG(wrapper_logged_errors)) { ALLOC_HASHTABLE(FG(wrapper_logged_errors)); @@ -392,9 +385,9 @@ static void php_stream_wrapper_log_store_error(zend_string *message, int code, static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, php_stream_context *context, int options, int severity, bool terminal, int code, - const char *param, const char *fmt, va_list args) + char *param, const char *fmt, va_list args) { - zend_string *message = vstrpprintf(0, args, args); + zend_string *message = vstrpprintf(0, fmt, args); const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; if (options & REPORT_ERRORS) { @@ -427,8 +420,9 @@ PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper { va_list args; va_start(args, fmt); + char *param_copy = param ? estrdup(param): NULL; php_stream_wrapper_log_error_internal( - wrapper, context, options, severity, terminal, code, param, fmt, args); + wrapper, context, options, severity, terminal, code, param_copy, fmt, args); va_end(args); } @@ -442,8 +436,7 @@ static zend_llist *php_stream_get_wrapper_errors_list(php_stream_wrapper *wrappe } } -/* {{{ wrapper error reporting */ -static void php_stream_display_wrapper_errors( +void php_stream_display_wrapper_errors( php_stream_wrapper *wrapper, const char *path, const char *caption) { char *tmp; From 0cccb45c99b025f8032b004cd23bbc8110ec7786 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 19:51:55 +0100 Subject: [PATCH 20/61] stream: extend and fix php_stream_display_wrapper_errors --- main/streams/php_stream_errors.h | 5 +---- main/streams/stream_errors.c | 9 +++++---- main/streams/streams.c | 6 ++++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 66d705c2bc3bc..9a926c952c520 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -166,13 +166,10 @@ PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper const char *param, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - const char *path, const char *caption); + php_stream_context *context, int code, const char *path, const char *caption); PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); -void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption); -void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); - /* Convenience macros */ #define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index ec403acedfe5a..4c69b4a72ddf2 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -436,8 +436,8 @@ static zend_llist *php_stream_get_wrapper_errors_list(php_stream_wrapper *wrappe } } -void php_stream_display_wrapper_errors( - php_stream_wrapper *wrapper, const char *path, const char *caption) +void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, + php_stream_context *context, int code, const char *path, const char *caption) { char *tmp; char *msg; @@ -471,7 +471,7 @@ void php_stream_display_wrapper_errors( for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + l += ZSTR_LEN((*err_entry_p)->message); if (i < count - 1) { l += brlen; } @@ -499,7 +499,8 @@ void php_stream_display_wrapper_errors( } php_strip_url_passwd(tmp); - php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg); + php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, code, tmp, + "%s: %s", caption, msg); efree(tmp); if (free_msg) { efree(msg); diff --git a/main/streams/streams.c b/main/streams/streams.c index fb9901d7c6aff..067c51c472619 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2065,7 +2065,8 @@ PHPAPI php_stream *_php_stream_opendir(const char *path, int options, STREAM_ERROR_CODE_NO_OPENER, "not implemented"); } if (stream == NULL && (options & REPORT_ERRORS)) { - php_stream_display_wrapper_errors(wrapper, path, "Failed to open directory"); + php_stream_display_wrapper_errors(wrapper, context, path, STREAM_ERROR_CODE_OPEN_FAILED, + "Failed to open directory"); } php_stream_tidy_wrapper_error_log(wrapper); @@ -2232,7 +2233,8 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod } if (stream == NULL && (options & REPORT_ERRORS)) { - php_stream_display_wrapper_errors(wrapper, path, "Failed to open stream"); + php_stream_display_wrapper_errors(wrapper, context, path, STREAM_ERROR_CODE_OPEN_FAILED, + "Failed to open stream"); if (opened_path && *opened_path) { zend_string_release_ex(*opened_path, 0); *opened_path = NULL; From 7e6ac6e6fe166217cb7ce322f459d29dc1ef91b8 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 20:13:33 +0100 Subject: [PATCH 21/61] stream: fix phar errors --- ext/phar/dirstream.c | 6 +++--- ext/phar/dirstream.h | 2 +- ext/phar/stream.c | 30 +++++++++++++++--------------- ext/phar/stream.h | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index 6ea3f7ba2fb06..e364300b0b5f3 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -253,7 +253,7 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, char *error; phar_archive_data *phar; - if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, path, mode, options)) == NULL) { php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar url \"%s\" is unknown", path); return NULL; @@ -376,7 +376,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo return 0; } - if ((resource = phar_parse_url(wrapper, url_from, "w", options)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, url_from, "w", options)) == NULL) { return 0; } @@ -520,7 +520,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options return 0; } - if ((resource = phar_parse_url(wrapper, url, "w", options)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, url, "w", options)) == NULL) { return 0; } diff --git a/ext/phar/dirstream.h b/ext/phar/dirstream.h index 4debfecde41a1..42d79c6fc21e9 100644 --- a/ext/phar/dirstream.h +++ b/ext/phar/dirstream.h @@ -24,7 +24,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options #ifdef PHAR_DIRSTREAM #include "ext/standard/url.h" -php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options); +php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context, const char *filename, const char *mode, int options); /* directory handlers */ static ssize_t phar_dir_write(php_stream *stream, const char *buf, size_t count); diff --git a/ext/phar/stream.c b/ext/phar/stream.c index ee583707e16a7..4d3e4f81922c0 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -57,7 +57,8 @@ const php_stream_wrapper php_stream_phar_wrapper = { /** * Open a phar file for streams API */ -php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options) /* {{{ */ +php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context, + const char *filename, const char *mode, int options) { php_url *resource; char *arch = NULL, *entry = NULL, *error; @@ -68,7 +69,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (mode[0] == 'a') { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, "phar error: open mode append not supported"); } return NULL; @@ -76,12 +77,12 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { if (arch && !entry) { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_INVALID_PATH, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PATH, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch); arch = NULL; } else { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: invalid url or non-existent phar \"%s\"", filename); } } @@ -115,7 +116,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_READONLY, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_READONLY, "phar error: write operations disabled by the php.ini setting phar.readonly"); } php_url_free(resource); @@ -136,7 +137,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const if (error) { spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", ZSTR_VAL(resource->host)); if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_OPEN_FAILED, "%s", error); } efree(error); } @@ -148,7 +149,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const { if (error) { if (!(options & PHP_STREAM_URL_STAT_QUIET)) { - php_stream_wrapper_log_warn(wrapper, NULL, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_NOT_FOUND, "%s", error); } efree(error); } @@ -158,7 +159,6 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const } return resource; } -/* }}} */ /** * used for fopen('phar://...') and company @@ -174,7 +174,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha php_stream *fpf; zval *pzoption, *metadata; - if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, path, mode, options)) == NULL) { return NULL; } @@ -456,7 +456,7 @@ static ssize_t phar_stream_write(php_stream *stream, const char *buf, size_t cou php_stream_seek(data->fp, data->position + data->zero, SEEK_SET); if (count != php_stream_write(data->fp, buf, count)) { - php_stream_wrapper_log_warn(stream->wrapper, stream->flags, STREAM_ERROR_CODE_WRITE_FAILED, + php_stream_warn(stream, STREAM_ERROR_CODE_WRITE_FAILED, "phar error: Could not write %zu characters to \"%s\" in phar \"%s\"", count, ZSTR_VAL(data->internal_file->filename), data->phar->fname); return -1; @@ -485,7 +485,7 @@ static int phar_stream_flush(php_stream *stream) /* {{{ */ data->internal_file->timestamp = time(0); ret = phar_flush(data->phar, &error); if (error) { - php_stream_wrapper_log_warn(stream->wrapper, REPORT_ERRORS, STREAM_ERROR_CODE_FLUSH_FAILED, "%s", error); + php_stream_warn(stream, STREAM_ERROR_CODE_FLUSH_FAILED, "%s", error); efree(error); } return ret; @@ -575,7 +575,7 @@ static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int f phar_entry_info *entry; size_t internal_file_len; - if ((resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, url, "r", flags|PHP_STREAM_URL_STAT_QUIET)) == NULL) { return FAILURE; } @@ -675,7 +675,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int phar_entry_data *idata; phar_archive_data *pphar; - if ((resource = phar_parse_url(wrapper, url, "rb", options)) == NULL) { + if ((resource = phar_parse_url(wrapper, context, url, "rb", options)) == NULL) { php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_UNLINK_FAILED, "phar error: unlink failed"); return 0; @@ -758,7 +758,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from error = NULL; - if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { + if ((resource_from = phar_parse_url(wrapper, context, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from); @@ -777,7 +777,7 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from return 0; } - if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { + if ((resource_to = phar_parse_url(wrapper, context, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET)) == NULL) { php_url_free(resource_from); php_stream_wrapper_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", diff --git a/ext/phar/stream.h b/ext/phar/stream.h index 83b395b4cfca3..e39ffb2c10baf 100644 --- a/ext/phar/stream.h +++ b/ext/phar/stream.h @@ -20,7 +20,7 @@ BEGIN_EXTERN_C() #include "ext/standard/url.h" -php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options); +php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context, const char *filename, const char *mode, int options); ZEND_ATTRIBUTE_NONNULL void phar_entry_remove(phar_entry_data *idata, char **error); static php_stream* phar_wrapper_open_url(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); From 0795310b1c4d0c7f4cd411e22ce56c1cc3c9ef25 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 21:52:41 +0100 Subject: [PATCH 22/61] stream: fix http wrapper errors --- ext/standard/http_fopen_wrapper.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index b772fb0364a6f..5852beff7a380 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -288,7 +288,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w size_t last_header_value_len = strlen(last_header_value); if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) { header_info->error = true; - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_RESPONSE, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_RESPONSE, "HTTP Location header size is over the limit of %d bytes", HTTP_HEADER_MAX_LOCATION_SIZE); zend_string_efree(last_header_line_str); @@ -469,7 +469,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, #endif if (d > timeoutmax) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_PARAM, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_PARAM, "timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax); zend_string_release(transport_string); php_uri_struct_free(resource); @@ -500,7 +500,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } if (errstr) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "%s", ZSTR_VAL(errstr)); zend_string_release_ex(errstr, 0); errstr = NULL; @@ -1108,7 +1108,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, php_uri_struct_free(resource); /* check for invalid redirection URLs */ if ((resource = php_uri_parse_to_struct(uri_parser, new_path, strlen(new_path), PHP_URI_COMPONENT_READ_MODE_RAW, true)) == NULL) { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, "Invalid redirect URL! %s", new_path); efree(new_path); goto out; @@ -1121,7 +1121,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, s = (unsigned char*)ZSTR_VAL(val); e = s + ZSTR_LEN(val); \ while (s < e) { \ if (iscntrl(*s)) { \ - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_INVALID_URL, \ + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_INVALID_URL, \ "Invalid redirect URL! %s", new_path); \ efree(new_path); \ goto out; \ @@ -1148,7 +1148,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, --redirect_max, new_flags, response_header STREAMS_CC); efree(new_path); } else { - php_stream_wrapper_log_warn(wrapper, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, + php_stream_wrapper_log_warn(wrapper, context, options, STREAM_ERROR_CODE_PROTOCOL_ERROR, "HTTP request failed! %s", tmp_line); } } From 680a50ef099542468c810baea1bdb46a214fd045 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 22:10:13 +0100 Subject: [PATCH 23/61] stream: fix user wrapper error calls --- main/streams/userspace.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 4683dd270ab39..4ba3bed83d3be 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -590,7 +590,7 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ zval_ptr_dtor(&args[0]); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_WRITE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); } @@ -611,7 +611,7 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ /* don't allow strange buffer overruns due to bogus return */ if (didwrite > 0 && didwrite > count) { - php_stream_warn_nt(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn_nt(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)", ZSTR_VAL(us->wrapper->ce->name), @@ -644,7 +644,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count } if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); goto err; } @@ -661,7 +661,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count didread = Z_STRLEN(retval); if (didread > 0) { if (didread > count) { - php_stream_warn_nt(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn_nt(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " read, " ZEND_LONG_FMT " max) - excess data will be lost", @@ -682,7 +682,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", ZSTR_VAL(us->wrapper->ce->name)); stream->eof = 1; @@ -798,7 +798,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when *newoffs = Z_LVAL(retval); ret = 0; } else if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); ret = -1; } else { @@ -863,7 +863,7 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb) zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_STAT " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return -1; } @@ -891,8 +891,7 @@ static int user_stream_set_check_liveliness(php_stream *stream, const php_userst zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, - STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; @@ -903,8 +902,7 @@ static int user_stream_set_check_liveliness(php_stream *stream, const php_userst if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; } else { - php_stream_warn(stream, REPORT_ERRORS, - STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_EOF " value must be of type bool, %s given", ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); @@ -947,7 +945,7 @@ static int user_stream_set_locking(php_stream *stream, const php_userstream_data /* lock support test (TODO: more check) */ return PHP_STREAM_OPTION_RETURN_OK; } - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_LOCK " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; } @@ -960,7 +958,7 @@ static int user_stream_set_locking(php_stream *stream, const php_userstream_data } // TODO: ext/standard/tests/file/userstreams_004.phpt returns null implicitly for function // Should this warn or not? And should this be considered an error? - //php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + //php_stream_warn(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, // "%s::" USERSTREAM_LOCK " value must be of type bool, %s given", // ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); @@ -996,7 +994,7 @@ static int user_stream_set_truncation(php_stream *stream, const php_userstream_d zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_TRUNCATE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; } @@ -1006,7 +1004,7 @@ static int user_stream_set_truncation(php_stream *stream, const php_userstream_d if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; } else { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given", ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); @@ -1040,7 +1038,7 @@ static int user_stream_set_option(php_stream *stream, const php_userstream_data_ zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_SET_OPTION " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return PHP_STREAM_OPTION_RETURN_ERR; @@ -1366,7 +1364,7 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co zend_string_release_ex(func_name, false); if (UNEXPECTED(call_result == FAILURE)) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_DIR_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return -1; @@ -1453,7 +1451,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) if (UNEXPECTED(call_result == FAILURE)) { if (report_errors) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_NOT_IMPLEMENTED, + php_stream_warn(stream, STREAM_ERROR_CODE_NOT_IMPLEMENTED, "%s::" USERSTREAM_CAST " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); } @@ -1468,7 +1466,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) php_stream_from_zval_no_verify(intstream, &retval); if (!intstream) { if (report_errors) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_CAST " must return a stream resource", ZSTR_VAL(us->wrapper->ce->name)); } @@ -1476,7 +1474,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) } if (intstream == stream) { if (report_errors) { - php_stream_warn(stream, REPORT_ERRORS, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, + php_stream_warn(stream, STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, "%s::" USERSTREAM_CAST " must not return itself", ZSTR_VAL(us->wrapper->ce->name)); } From 9ba734b678dd927d001279492c954daa5cc6ba7a Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 22:10:27 +0100 Subject: [PATCH 24/61] stream: fix php_stream_display_wrapper_errors usage --- main/streams/streams.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/streams/streams.c b/main/streams/streams.c index 067c51c472619..68a03e1499330 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2065,7 +2065,7 @@ PHPAPI php_stream *_php_stream_opendir(const char *path, int options, STREAM_ERROR_CODE_NO_OPENER, "not implemented"); } if (stream == NULL && (options & REPORT_ERRORS)) { - php_stream_display_wrapper_errors(wrapper, context, path, STREAM_ERROR_CODE_OPEN_FAILED, + php_stream_display_wrapper_errors(wrapper, context, STREAM_ERROR_CODE_OPEN_FAILED, path, "Failed to open directory"); } php_stream_tidy_wrapper_error_log(wrapper); @@ -2233,7 +2233,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod } if (stream == NULL && (options & REPORT_ERRORS)) { - php_stream_display_wrapper_errors(wrapper, context, path, STREAM_ERROR_CODE_OPEN_FAILED, + php_stream_display_wrapper_errors(wrapper, context, STREAM_ERROR_CODE_OPEN_FAILED, path, "Failed to open stream"); if (opened_path && *opened_path) { zend_string_release_ex(*opened_path, 0); From 8a3960f9093ab58da08fd9c8ef4a70ca0e2c0981 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 17 Nov 2025 22:28:41 +0100 Subject: [PATCH 25/61] stream: fix wrapper selection for displaying logged errors --- main/streams/stream_errors.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 4c69b4a72ddf2..97e5692a035e5 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -426,20 +426,19 @@ PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper va_end(args); } -static zend_llist *php_stream_get_wrapper_errors_list(php_stream_wrapper *wrapper) +static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) { if (!FG(wrapper_logged_errors)) { return NULL; } else { return (zend_llist *) zend_hash_str_find_ptr( - FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); + FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); } } void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, php_stream_context *context, int code, const char *path, const char *caption) { - char *tmp; char *msg; char errstr[256]; int free_msg = 0; @@ -449,9 +448,10 @@ void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, return; } - tmp = estrdup(path); + const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + char *tmp = estrdup(path); if (wrapper) { - zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper); + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); if (err_list) { size_t l = 0; int brlen; From 75716c91bcf85582750e0a2601ca7f3dc5c8c510 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 10:58:40 +0100 Subject: [PATCH 26/61] stream: fix wrapper key in php_stream_tidy_wrapper_error_log --- main/streams/stream_errors.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 97e5692a035e5..6fad781750d2a 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -510,7 +510,8 @@ void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) { if (wrapper && FG(wrapper_logged_errors)) { - zend_hash_str_del(FG(wrapper_logged_errors), (const char *) &wrapper, sizeof(wrapper)); + const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); } } From bb6d60b9177b6c9738c74f99af4ccf83026d6bd4 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 11:36:40 +0100 Subject: [PATCH 27/61] stream: stream remove not reported warning --- ext/standard/php_fopen_wrapper.c | 3 +-- ext/standard/tests/file/php_fd_wrapper_03.phpt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c index b13f18e08bf78..902b0f853f4e4 100644 --- a/ext/standard/php_fopen_wrapper.c +++ b/ext/standard/php_fopen_wrapper.c @@ -396,8 +396,7 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c } else { /* invalid php://thingy */ php_stream_wrapper_warn(wrapper, context, options, - STREAM_ERROR_CODE_INVALID_URL, - "Invalid php:// URL specified"); + STREAM_ERROR_CODE_INVALID_URL, "Invalid php:// URL specified"); return NULL; } diff --git a/ext/standard/tests/file/php_fd_wrapper_03.phpt b/ext/standard/tests/file/php_fd_wrapper_03.phpt index 991c497f5e193..a19d1f5acd94b 100644 --- a/ext/standard/tests/file/php_fd_wrapper_03.phpt +++ b/ext/standard/tests/file/php_fd_wrapper_03.phpt @@ -10,7 +10,6 @@ fopen("php://fd/1/", "w"); echo "\nDone.\n"; ?> --EXPECTF-- -Warning: fopen(): Invalid php:// URL specified in %s on line %d Warning: fopen(php://fd): Failed to open stream: operation failed in %s on line 2 From 2908af076f587102ab9a50b126e74290864c1f0f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 12:06:34 +0100 Subject: [PATCH 28/61] stream: fix reporting errors in rename --- ext/standard/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index bd6ee252875e5..b908f48645068 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1227,7 +1227,7 @@ PHP_FUNCTION(rename) context = php_stream_context_from_zval(zcontext, 0); - RETURN_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, 0, context)); + RETURN_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, REPORT_ERRORS, context)); } /* }}} */ From a7a82fec9aa8b06129b99a2eba4dd28391ba6d02 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 12:46:38 +0100 Subject: [PATCH 29/61] stream: do not store wrapper errors if no REPORT_ERRORS set --- main/streams/stream_errors.c | 65 +++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 6fad781750d2a..cdf3775ed277d 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -286,10 +286,8 @@ static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name { zend_string *message = vstrpprintf(0, fmt, args); - if (options & REPORT_ERRORS) { - php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), - param, severity, terminal); - } + php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), + param, severity, terminal); php_stream_store_error_common( context, NULL, message, docref, code, wrapper_name, param, severity, terminal); @@ -311,48 +309,56 @@ PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal_with_name( - wrapper_name, context, docref, options, severity, terminal, code, NULL, fmt, args); - va_end(args); + if (options & REPORT_ERRORS) { + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal_with_name( + wrapper_name, context, docref, options, severity, terminal, code, NULL, fmt, args); + va_end(args); + } } PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, NULL, fmt, args); - va_end(args); + if (options & REPORT_ERRORS) { + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal( + wrapper, context, docref, options, severity, terminal, code, NULL, fmt, args); + va_end(args); + } } PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - char *param_copy = param ? estrdup(param): NULL; - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, param_copy, fmt, args); - va_end(args); + if (options & REPORT_ERRORS) { + va_list args; + va_start(args, fmt); + char *param_copy = param ? estrdup(param): NULL; + php_stream_wrapper_error_internal( + wrapper, context, docref, options, severity, terminal, code, param_copy, fmt, args); + va_end(args); + } } PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, const char *param1, const char *param2, const char *fmt, ...) { - char *combined_param; - spprintf(&combined_param, 0, "%s,%s", param1, param2); - - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, combined_param, fmt, args); - va_end(args); + if (options & REPORT_ERRORS) { + char *combined_param; + spprintf(&combined_param, 0, "%s,%s", param1, param2); + + va_list args; + va_start(args, fmt); + php_stream_wrapper_error_internal( + wrapper, context, docref, options, severity, terminal, code, combined_param, fmt, args); + va_end(args); + } } /* Wrapper error logging - stores in FG(wrapper_logged_errors) */ @@ -394,13 +400,12 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap /* Report immediately using standard error functions */ php_stream_wrapper_error_internal_with_name( wrapper_name, context, NULL, options, severity, terminal, code, param, fmt, args); - zend_string_release(message); } else { /* Store for later display in FG(wrapper_logged_errors) */ php_stream_wrapper_log_store_error( message, code, wrapper_name, param, severity, terminal); - zend_string_release(message); } + zend_string_release(message); } PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, From 294bdf6ff62124f7f174ff3a14a790c98914915f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 14:41:06 +0100 Subject: [PATCH 30/61] stream: add stream_get_errors function --- ext/standard/basic_functions.stub.php | 6 ++ ext/standard/basic_functions_arginfo.h | 8 ++- ext/standard/streamsfuncs.c | 98 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index e27dca069c55b..83585f65ab0fd 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -3541,6 +3541,12 @@ function stream_resolve_include_path(string $filename): string|false {} */ function stream_get_wrappers(): array {} +/** + * @param resource|string|null $subject + * @return array + */ +function stream_get_errors($subject = null): array {} + /** * @return array * @refcount 1 diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 6f202c01463fd..c83fd6735e02c 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1a1667a5c59111f096a758d5bb4aa7cf3ec09cfe */ + * Stub hash: c16449293fe2fe5092af5911e770745d00cec522 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1986,6 +1986,10 @@ ZEND_END_ARG_INFO() #define arginfo_stream_get_wrappers arginfo_ob_list_handlers +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stream_get_errors, 0, 0, IS_ARRAY, 0) + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, subject, "null") +ZEND_END_ARG_INFO() + #define arginfo_stream_get_transports arginfo_ob_list_handlers #define arginfo_stream_is_local arginfo_rewind @@ -2828,6 +2832,7 @@ ZEND_FUNCTION(stream_get_meta_data); ZEND_FUNCTION(stream_get_line); ZEND_FUNCTION(stream_resolve_include_path); ZEND_FUNCTION(stream_get_wrappers); +ZEND_FUNCTION(stream_get_errors); ZEND_FUNCTION(stream_get_transports); ZEND_FUNCTION(stream_is_local); ZEND_FUNCTION(stream_isatty); @@ -3436,6 +3441,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(stream_get_line, arginfo_stream_get_line) ZEND_FE(stream_resolve_include_path, arginfo_stream_resolve_include_path) ZEND_FE(stream_get_wrappers, arginfo_stream_get_wrappers) + ZEND_FE(stream_get_errors, arginfo_stream_get_errors) ZEND_FE(stream_get_transports, arginfo_stream_get_transports) ZEND_FE(stream_is_local, arginfo_stream_is_local) ZEND_FE(stream_isatty, arginfo_stream_isatty) diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 506ce0dafed8b..6472bbc23dedc 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -594,6 +594,104 @@ PHP_FUNCTION(stream_get_wrappers) } /* }}} */ +/* Helper function to convert error list to array */ +static void php_stream_errors_list_to_array(zend_llist *list, zval *return_value) +{ + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + for (err_entry_p = zend_llist_get_first_ex(list, &pos); + err_entry_p; + err_entry_p = zend_llist_get_next_ex(list, &pos)) { + php_stream_error_entry *entry = *err_entry_p; + zval error_array; + array_init(&error_array); + + add_assoc_str(&error_array, "message", zend_string_copy(entry->message)); + add_assoc_long(&error_array, "code", entry->code); + add_assoc_long(&error_array, "severity", entry->severity); + add_assoc_bool(&error_array, "terminal", entry->terminal); + + if (entry->wrapper_name) { + add_assoc_string(&error_array, "wrapper", entry->wrapper_name); + } + if (entry->param) { + add_assoc_string(&error_array, "param", entry->param); + } + if (entry->docref) { + add_assoc_string(&error_array, "docref", entry->docref); + } + + add_next_index_zval(return_value, &error_array); + } +} + +/* Retrieves list of stored stream errors */ +PHP_FUNCTION(stream_get_errors) +{ + zval *subject = NULL; + php_stream *stream = NULL; + char *wrapper_name = NULL; + size_t wrapper_name_len = 0; + bool get_all_wrappers = false; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(subject) + ZEND_PARSE_PARAMETERS_END(); + + /* Determine what we're querying for */ + if (subject == NULL) { + /* No parameter - get all wrapper errors */ + get_all_wrappers = true; + } else if (Z_TYPE_P(subject) == IS_RESOURCE) { + /* Stream resource - get errors for this stream */ + php_stream_from_zval_no_verify(stream, subject); + if (stream == NULL) { + zend_argument_type_error(1, "must be a valid stream resource"); + RETURN_THROWS(); + } + } else if (Z_TYPE_P(subject) == IS_STRING) { + /* Wrapper name - get errors for this wrapper */ + wrapper_name = Z_STRVAL_P(subject); + wrapper_name_len = Z_STRLEN_P(subject); + } else { + zend_argument_type_error(1, "must be a stream resource, string, or null"); + RETURN_THROWS(); + } + + array_init(return_value); + + /* Handle stream errors */ + if (stream) { + if (stream->error_list) { + php_stream_errors_list_to_array(stream->error_list, return_value); + } + } else if (get_all_wrappers) { + /* Get errors from all wrappers */ + if (FG(wrapper_stored_errors)) { + zend_string *key; + zval *val; + ZEND_HASH_FOREACH_STR_KEY_VAL(FG(wrapper_stored_errors), key, val) { + if (key) { + zend_llist *list = (zend_llist *) Z_PTR_P(val); + php_stream_errors_list_to_array(list, return_value); + } + } ZEND_HASH_FOREACH_END(); + } + } else if (wrapper_name) { + /* Get errors for specific wrapper */ + if (FG(wrapper_stored_errors)) { + zend_llist *list = zend_hash_str_find_ptr( + FG(wrapper_stored_errors), wrapper_name, wrapper_name_len); + + if (list) { + php_stream_errors_list_to_array(list, return_value); + } + } + } +} + /* {{{ stream_select related functions */ static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, php_socket_t *max_fd) { From f71f3b45bed644beee1bdf95d34e3323b1731e90 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 18:31:18 +0100 Subject: [PATCH 31/61] stream: fix error storing for persistent streams --- main/streams/php_stream_errors.h | 1 + main/streams/stream_errors.c | 39 ++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 9a926c952c520..d53e9a0a5fe1e 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -133,6 +133,7 @@ typedef struct { char *param; int severity; bool terminal; + bool persistent; } php_stream_error_entry; /* Main error reporting functions */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index cdf3775ed277d..03e989a1395ca 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -29,10 +29,12 @@ static void php_stream_error_entry_dtor(void *error) { php_stream_error_entry *entry = *(php_stream_error_entry **) error; zend_string_release(entry->message); - efree(entry->wrapper_name); - efree(entry->docref); + pefree(entry->wrapper_name, entry->persistent); + pefree(entry->docref, entry->persistent); + // param is not currently supported for streams so cannot be persistent + ZEND_ASSERT(!entry->persistent || entry->param == NULL); efree(entry->param); - efree(entry); + pefree(entry, entry->persistent); } static void php_stream_error_list_dtor(zval *item) @@ -215,18 +217,23 @@ static void php_stream_process_error(php_stream_context *context, const char *wr /* Helper to create error entry */ static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, const char *wrapper_name, const char *docref, char *param, int severity, - bool terminal) + bool terminal, bool persistent) { - php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + if (persistent) { + message = zend_string_dup(message, true); + } else { + zend_string_addref(message); + } + + php_stream_error_entry *entry = pemalloc(sizeof(php_stream_error_entry), persistent); entry->message = message; entry->code = code; - entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; - entry->docref = docref ? estrdup(docref) : NULL; + entry->wrapper_name = wrapper_name ? pestrdup(wrapper_name, persistent) : NULL; + entry->docref = docref ? pestrdup(docref, persistent) : NULL; entry->param = param; entry->severity = severity; entry->terminal = terminal; - - zend_string_addref(message); + entry->persistent = persistent; return entry; } @@ -243,17 +250,16 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea efree(param); return; } - php_stream_error_entry *entry = php_stream_create_error_entry( - message, code, wrapper_name, docref, param, severity, terminal); zend_llist *list; - + bool persistent = false; if (stream) { + persistent = stream->is_persistent; /* Store in stream's error list */ if (!stream->error_list) { - stream->error_list = emalloc(sizeof(zend_llist)); + stream->error_list = pemalloc(sizeof(zend_llist), persistent); zend_llist_init(stream->error_list, sizeof(php_stream_error_entry *), - php_stream_error_entry_dtor, 0); + php_stream_error_entry_dtor, persistent); } list = stream->error_list; } else { @@ -276,6 +282,9 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea } } + php_stream_error_entry *entry = php_stream_create_error_entry( + message, code, wrapper_name, docref, param, severity, terminal, persistent); + zend_llist_add_element(list, &entry); } @@ -368,7 +377,7 @@ static void php_stream_wrapper_log_store_error(zend_string *message, int code, { char *param_copy = param ? estrdup(param): NULL; php_stream_error_entry *entry = php_stream_create_error_entry( - message, code, wrapper_name, NULL, param_copy, severity, terminal); + message, code, wrapper_name, NULL, param_copy, severity, terminal, false); if (!FG(wrapper_logged_errors)) { ALLOC_HASHTABLE(FG(wrapper_logged_errors)); From b5834a691695fc4ba992dc62b301ff323a9fa2ff Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 18:31:59 +0100 Subject: [PATCH 32/61] stream: add missing error mode and store constants --- main/streams/stream_errors.stub.php | 42 ++++++++++++++++++++++++++++ main/streams/stream_errors_arginfo.h | 10 ++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 8a27e0060fc3d..fb6c5584a5f48 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -11,6 +11,48 @@ public function getParam(): ?string {} public function getWrapperName(): string {} } +/** + * @var int + * @cvalue PHP_STREAM_ERROR_MODE_ERROR + */ +const STREAM_ERROR_MODE_ERROR = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_MODE_EXCEPTION + */ +const STREAM_ERROR_MODE_EXCEPTION = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_MODE_SILENT + */ +const STREAM_ERROR_MODE_SILENT = UNKNOWN; + +/** + * @var int + * @cvalue PHP_STREAM_ERROR_STORE_AUTO + */ +const STREAM_ERROR_STORE_AUTO = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_STORE_NONE + */ +const STREAM_ERROR_STORE_NONE = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_STORE_NON_TERM + */ +const STREAM_ERROR_STORE_NON_TERMINAL = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_STORE_TERMINAL + */ +const STREAM_ERROR_STORE_TERMINAL = UNKNOWN; +/** + * @var int + * @cvalue PHP_STREAM_ERROR_STORE_ALL + */ +const STREAM_ERROR_STORE_ALL = UNKNOWN; + /** * @var int * @cvalue STREAM_ERROR_CODE_NONE diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 523cf64323a0e..79d86059dbdb6 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: faa3f0e64b8388a495ac645fd3eda49c1ed243bd */ + * Stub hash: 97a63d009e4a3e951b0c3b213e9cb784d1af5634 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -18,6 +18,14 @@ static const zend_function_entry class_StreamException_methods[] = { static void register_stream_errors_symbols(int module_number) { + REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_ERROR", PHP_STREAM_ERROR_MODE_ERROR, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_EXCEPTION", PHP_STREAM_ERROR_MODE_EXCEPTION, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_SILENT", PHP_STREAM_ERROR_MODE_SILENT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_AUTO", PHP_STREAM_ERROR_STORE_AUTO, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_NONE", PHP_STREAM_ERROR_STORE_NONE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_NON_TERMINAL", PHP_STREAM_ERROR_STORE_NON_TERM, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_TERMINAL", PHP_STREAM_ERROR_STORE_TERMINAL, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_ALL", PHP_STREAM_ERROR_STORE_ALL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NONE", STREAM_ERROR_CODE_NONE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_GENERIC", STREAM_ERROR_CODE_GENERIC, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_READ_FAILED", STREAM_ERROR_CODE_READ_FAILED, CONST_PERSISTENT); From a7f8666e300c217fc18cb6d8661ff0b9ba803671 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 18:34:18 +0100 Subject: [PATCH 33/61] stream: set context for opened streams This is limited to non persistent streams to be safe. If it shows problematic for BC reasons, it should be limited to only some context (e.g. if error options set). --- main/streams/streams.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/streams/streams.c b/main/streams/streams.c index 68a03e1499330..07cbeee02fb50 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2185,6 +2185,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename; stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno; #endif + if (stream->ctx == NULL && context != NULL && !persistent) { + php_stream_context_set(stream, context); + } } if (stream != NULL && (options & STREAM_MUST_SEEK)) { From 65ca935284962dd73c72579ae5fa4adc9ea9d1f3 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 18:36:20 +0100 Subject: [PATCH 34/61] stream: fix leaked stream error list --- main/streams/streams.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main/streams/streams.c b/main/streams/streams.c index 07cbeee02fb50..3fb64cdb74127 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -377,6 +377,11 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remov ZVAL_UNDEF(&stream->wrapperdata); } + if (stream->error_list) { + zend_llist_destroy(stream->error_list); + pefree(stream->error_list, stream->is_persistent); + } + if (stream->readbuf) { pefree(stream->readbuf, stream->is_persistent); stream->readbuf = NULL; From 690759cf8905020da74ea7989f5d2ab74cad422e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 18:37:30 +0100 Subject: [PATCH 35/61] stream: add tests for new stream error handling --- ...stream_errors_exception_mode_terminal.phpt | 24 +++ .../stream_errors_mix_modes_storage.phpt | 194 ++++++++++++++++++ .../stream_errors_modes_with_auto_store.phpt | 49 +++++ .../stream_errors_silent_with_handler.phpt | 33 +++ .../stream_errors_silent_with_storage.phpt | 27 +++ .../stream_errors_silent_without_storage.phpt | 20 ++ .../streams/stream_errors_standard_error.phpt | 20 ++ 7 files changed, 367 insertions(+) create mode 100644 ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt create mode 100644 ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt create mode 100644 ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt create mode 100644 ext/standard/tests/streams/stream_errors_silent_with_handler.phpt create mode 100644 ext/standard/tests/streams/stream_errors_silent_with_storage.phpt create mode 100644 ext/standard/tests/streams/stream_errors_silent_without_storage.phpt create mode 100644 ext/standard/tests/streams/stream_errors_standard_error.phpt diff --git a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt new file mode 100644 index 0000000000000..016b989276fa0 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt @@ -0,0 +1,24 @@ +--TEST-- +Stream errors - exception mode for terminal errors +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, + ] +]); + +try { + $stream = fopen('php://nonexistent', 'r', false, $context); +} catch (StreamException $e) { + echo "Caught: " . $e->getMessage() . "\n"; + echo "Code: " . $e->getCode() . "\n"; + echo "Wrapper: " . $e->getWrapperName() . "\n"; +} + +?> +--EXPECTF-- +Caught: Failed to open stream: operation failed +Code: 36 +Wrapper: PHP diff --git a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt new file mode 100644 index 0000000000000..cb765de62fe07 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt @@ -0,0 +1,194 @@ +--TEST-- +Stream errors - silent mode with mixed error stores +--FILE-- +position += $count; + return str_repeat('x', $count + 10); + } + + public function stream_eof() { + return $this->position >= 100; + } + + public function stream_stat() { + return []; + } +} + +stream_wrapper_register('test', 'TestStream'); + +function stream_test_errors($title, $context) { + $stream = fopen('test://foo', 'r', false, $context); + try { + echo $title . "\n"; + $readin = fopen( 'php://stdin', 'r' ); + $data = fread( $stream, 10 ); + + $read = [$readin, $stream]; + $write = NULL; + $except = NULL; + stream_select($read, $write, $except, 0); + } catch (StreamException $e) { + echo 'EXCEPTION: ' . $e->getMessage() . "\n"; + } + + $errors = stream_get_errors($stream); + var_dump($errors); +} + +stream_test_errors('ALL', stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_store' => STREAM_ERROR_STORE_ALL, + ] +])); +stream_test_errors('NON TERMINAL', stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_store' => STREAM_ERROR_STORE_NON_TERMINAL, + ] +])); +stream_test_errors('TERMINAL', stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_store' => STREAM_ERROR_STORE_TERMINAL, + ] +])); +stream_test_errors('AUTO EXCEPTION', stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, + 'error_store' => STREAM_ERROR_STORE_AUTO, + ] +])); +stream_test_errors('AUTO ERROR', stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_ERROR, + 'error_store' => STREAM_ERROR_STORE_AUTO, + ] +])); + +?> +--EXPECTF-- +ALL +array(3) { + [0]=> + array(5) { + ["message"]=> + string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" + ["code"]=> + int(161) + ["severity"]=> + int(2) + ["terminal"]=> + bool(false) + ["wrapper"]=> + string(10) "user-space" + } + [1]=> + array(5) { + ["message"]=> + string(43) "TestStream::stream_cast is not implemented!" + ["code"]=> + int(70) + ["severity"]=> + int(2) + ["terminal"]=> + bool(true) + ["wrapper"]=> + string(10) "user-space" + } + [2]=> + array(5) { + ["message"]=> + string(73) "Cannot represent a stream of type user-space as a select()able descriptor" + ["code"]=> + int(101) + ["severity"]=> + int(2) + ["terminal"]=> + bool(true) + ["wrapper"]=> + string(10) "user-space" + } +} +NON TERMINAL +array(1) { + [0]=> + array(5) { + ["message"]=> + string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" + ["code"]=> + int(161) + ["severity"]=> + int(2) + ["terminal"]=> + bool(false) + ["wrapper"]=> + string(10) "user-space" + } +} +TERMINAL +array(2) { + [0]=> + array(5) { + ["message"]=> + string(43) "TestStream::stream_cast is not implemented!" + ["code"]=> + int(70) + ["severity"]=> + int(2) + ["terminal"]=> + bool(true) + ["wrapper"]=> + string(10) "user-space" + } + [1]=> + array(5) { + ["message"]=> + string(73) "Cannot represent a stream of type user-space as a select()able descriptor" + ["code"]=> + int(101) + ["severity"]=> + int(2) + ["terminal"]=> + bool(true) + ["wrapper"]=> + string(10) "user-space" + } +} +AUTO EXCEPTION +EXCEPTION: Cannot represent a stream of type user-space as a select()able descriptor +array(1) { + [0]=> + array(5) { + ["message"]=> + string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" + ["code"]=> + int(161) + ["severity"]=> + int(2) + ["terminal"]=> + bool(false) + ["wrapper"]=> + string(10) "user-space" + } +} +AUTO ERROR + +Warning: fread(): TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost in %s on line %d + +Warning: stream_select(): TestStream::stream_cast is not implemented! in %s on line %d + +Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d +array(0) { +} diff --git a/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt new file mode 100644 index 0000000000000..bda445eabe428 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt @@ -0,0 +1,49 @@ +--TEST-- +Stream errors - error_store AUTO mode behavior +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_ERROR, + 'error_store' => STREAM_ERROR_STORE_AUTO, + ] +]); + +@fopen('php://nonexistent', 'r', false, $context1); +$errors1 = stream_get_errors('PHP'); +echo "ERROR mode AUTO: " . count($errors1) . "\n"; + +// AUTO with EXCEPTION mode should store NON_TERM +$context2 = stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, + 'error_store' => STREAM_ERROR_STORE_AUTO, + ] +]); + +try { + fopen('php://nonexistent2', 'r', false, $context2); +} catch (StreamException $e) {} + +$errors2 = stream_get_errors('PHP'); +echo "EXCEPTION mode AUTO: " . count($errors2) . "\n"; + +// AUTO with SILENT mode should store ALL +$context3 = stream_context_create([ + 'stream' => [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_store' => STREAM_ERROR_STORE_AUTO, + ] +]); + +fopen('php://nonexistent3', 'r', false, $context3); +$errors3 = stream_get_errors('PHP'); +echo "SILENT mode AUTO: " . count($errors3) . "\n"; + +?> +--EXPECTF-- +ERROR mode AUTO: 0 +EXCEPTION mode AUTO: %d +SILENT mode AUTO: %d diff --git a/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt new file mode 100644 index 0000000000000..9ce86adb09658 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stream errors - custom error handler +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_handler' => function($wrapper, $stream, $code, $message, $param) use (&$handler_called) { + $handler_called = true; + echo "Handler called\n"; + echo "Wrapper: $wrapper\n"; + echo "Code: $code\n"; + echo "Message: $message\n"; + echo "Param: " . ($param ?? 'null') . "\n"; + } + ] +]); + +$stream = fopen('php://nonexistent', 'r', false, $context); + +var_dump($handler_called); + +?> +--EXPECT-- +Handler called +Wrapper: PHP +Code: 36 +Message: Failed to open stream: operation failed +Param: php://nonexistent +bool(true) diff --git a/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt new file mode 100644 index 0000000000000..563857d308fff --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt @@ -0,0 +1,27 @@ +--TEST-- +Stream errors - silent mode with error storage +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + ] +]); + +$stream = fopen('php://nonexistent', 'r', false, $context); +var_dump($stream); + +$errors = stream_get_errors(); +echo "Error count: " . count($errors) . "\n"; +if (count($errors) > 0) { + echo "First error code: " . $errors[0]['code'] . "\n"; + echo "First error wrapper: " . $errors[0]['wrapper'] . "\n"; +} + +?> +--EXPECT-- +bool(false) +Error count: 1 +First error code: 36 +First error wrapper: PHP diff --git a/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt new file mode 100644 index 0000000000000..6ed79378d8226 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt @@ -0,0 +1,20 @@ +--TEST-- +Stream errors - error_store NONE option +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_store' => STREAM_ERROR_STORE_NONE, + ] +]); + +$stream = fopen('php://nonexistent', 'r', false, $context); + +$errors = stream_get_errors(); +echo "Error count: " . count($errors) . "\n"; + +?> +--EXPECT-- +Error count: 0 diff --git a/ext/standard/tests/streams/stream_errors_standard_error.phpt b/ext/standard/tests/streams/stream_errors_standard_error.phpt new file mode 100644 index 0000000000000..a9725f0b89b26 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_standard_error.phpt @@ -0,0 +1,20 @@ +--TEST-- +Stream errors - error mode with standard error reporting +--FILE-- + [ + 'error_mode' => STREAM_ERROR_MODE_ERROR, + ] +]); + +// This will trigger a warning +$stream = fopen('php://nonexistent', 'r', false, $context); + +var_dump($stream); + +?> +--EXPECTF-- +Warning: fopen(php://nonexistent): Failed to open stream: %s in %s on line %d +bool(false) From 2299c9e320802e686f6d5ae5d98a45a060c6b391 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 18 Nov 2025 19:44:53 +0100 Subject: [PATCH 36/61] stream: update reflection class name test --- .../tests/ReflectionExtension_getClassNames_basic.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt index 47813255381e4..dc72254abc076 100644 --- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt @@ -16,5 +16,6 @@ AssertionError Directory RoundingMode StreamBucket +StreamException __PHP_Incomplete_Class php_user_filter From 3f0a0b6e7d16153da9f47fec05c1fa1e3a5a1865 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 19 Nov 2025 20:09:01 +0100 Subject: [PATCH 37/61] stream: fix possible use of wrapper after it is freed --- main/streams/php_stream_errors.h | 8 ++ main/streams/stream_errors.c | 163 ++++++++++++++++++------------- main/streams/streams.c | 9 +- 3 files changed, 108 insertions(+), 72 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index d53e9a0a5fe1e..b59083e234591 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -124,6 +124,9 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 161 #define STREAM_ERROR_CODE_USERSPACE_CALL_FAILED 162 +/* Wrapper name for PHP errors */ +#define PHP_STREAM_ERROR_WRAPPER_NAME(_wrapper) (_wrapper ? _wrapper->wops->label : "unknown") + /* Stored error entry */ typedef struct { zend_string *message; @@ -166,9 +169,14 @@ PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper php_stream_context *context, int options, int severity, bool terminal, int code, const char *param, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); +PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, + php_stream_context *context, int code, const char *path, const char *caption); + PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, php_stream_context *context, int code, const char *path, const char *caption); +PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name); + PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); /* Convenience macros */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 03e989a1395ca..3228b3c5c8ed8 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -31,7 +31,7 @@ static void php_stream_error_entry_dtor(void *error) zend_string_release(entry->message); pefree(entry->wrapper_name, entry->persistent); pefree(entry->docref, entry->persistent); - // param is not currently supported for streams so cannot be persistent + // param is not currently supported for streams so cannot be persistent ZEND_ASSERT(!entry->persistent || entry->param == NULL); efree(entry->param); pefree(entry, entry->persistent); @@ -216,8 +216,8 @@ static void php_stream_process_error(php_stream_context *context, const char *wr /* Helper to create error entry */ static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, - const char *wrapper_name, const char *docref, char *param, int severity, - bool terminal, bool persistent) + const char *wrapper_name, const char *docref, char *param, int severity, bool terminal, + bool persistent) { if (persistent) { message = zend_string_dup(message, true); @@ -240,8 +240,8 @@ static php_stream_error_entry *php_stream_create_error_entry(zend_string *messag /* Common storage function*/ static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, - zend_string *message, const char *docref, int code, - const char *wrapper_name, char *param, int severity, bool terminal) + zend_string *message, const char *docref, int code, const char *wrapper_name, char *param, + int severity, bool terminal) { int error_mode = php_stream_get_error_mode(context); int store_mode = php_stream_get_error_store_mode(context, error_mode); @@ -295,8 +295,8 @@ static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name { zend_string *message = vstrpprintf(0, fmt, args); - php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), - param, severity, terminal); + php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), param, + severity, terminal); php_stream_store_error_common( context, NULL, message, docref, code, wrapper_name, param, severity, terminal); @@ -304,14 +304,27 @@ static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name zend_string_release(message); } +static void php_stream_wrapper_error_internal_with_name_variadic(const char *wrapper_name, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, char *param, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + php_stream_wrapper_error_internal_with_name( + wrapper_name, context, docref, options, severity, terminal, code, param, fmt, args); + + va_end(args); +} + static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, php_stream_context *context, const char *docref, int options, int severity, bool terminal, int code, char *param, const char *fmt, va_list args) { - const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_wrapper_error_internal_with_name(wrapper_name, context, docref, options, severity, - terminal, code, param, fmt, args); + php_stream_wrapper_error_internal_with_name( + wrapper_name, context, docref, options, severity, terminal, code, param, fmt, args); } PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, @@ -347,7 +360,7 @@ PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stre if (options & REPORT_ERRORS) { va_list args; va_start(args, fmt); - char *param_copy = param ? estrdup(param): NULL; + char *param_copy = param ? estrdup(param) : NULL; php_stream_wrapper_error_internal( wrapper, context, docref, options, severity, terminal, code, param_copy, fmt, args); va_end(args); @@ -364,8 +377,8 @@ PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, va_list args; va_start(args, fmt); - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, combined_param, fmt, args); + php_stream_wrapper_error_internal(wrapper, context, docref, options, severity, terminal, + code, combined_param, fmt, args); va_end(args); } } @@ -375,7 +388,7 @@ PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, static void php_stream_wrapper_log_store_error(zend_string *message, int code, const char *wrapper_name, const char *param, int severity, bool terminal) { - char *param_copy = param ? estrdup(param): NULL; + char *param_copy = param ? estrdup(param) : NULL; php_stream_error_entry *entry = php_stream_create_error_entry( message, code, wrapper_name, NULL, param_copy, severity, terminal, false); @@ -384,8 +397,8 @@ static void php_stream_wrapper_log_store_error(zend_string *message, int code, zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); } - zend_llist *list = zend_hash_str_find_ptr( - FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + zend_llist *list + = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); if (!list) { zend_llist new_list; @@ -403,7 +416,7 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap char *param, const char *fmt, va_list args) { zend_string *message = vstrpprintf(0, fmt, args); - const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); if (options & REPORT_ERRORS) { /* Report immediately using standard error functions */ @@ -411,8 +424,7 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap wrapper_name, context, NULL, options, severity, terminal, code, param, fmt, args); } else { /* Store for later display in FG(wrapper_logged_errors) */ - php_stream_wrapper_log_store_error( - message, code, wrapper_name, param, severity, terminal); + php_stream_wrapper_log_store_error(message, code, wrapper_name, param, severity, terminal); } zend_string_release(message); } @@ -434,7 +446,7 @@ PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper { va_list args; va_start(args, fmt); - char *param_copy = param ? estrdup(param): NULL; + char *param_copy = param ? estrdup(param) : NULL; php_stream_wrapper_log_error_internal( wrapper, context, options, severity, terminal, code, param_copy, fmt, args); va_end(args); @@ -450,7 +462,7 @@ static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) } } -void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, +PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, php_stream_context *context, int code, const char *path, const char *caption) { char *msg; @@ -462,73 +474,84 @@ void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, return; } - const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; char *tmp = estrdup(path); - if (wrapper) { - zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); - if (err_list) { - size_t l = 0; - int brlen; - int i; - int count = (int) zend_llist_count(err_list); - const char *br; - php_stream_error_entry **err_entry_p; - zend_llist_position pos; - - if (PG(html_errors)) { - brlen = 7; - br = "
\n"; - } else { - brlen = 1; - br = "\n"; - } + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); + if (err_list) { + size_t l = 0; + int brlen; + int i; + int count = (int) zend_llist_count(err_list); + const char *br; + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + if (PG(html_errors)) { + brlen = 7; + br = "
\n"; + } else { + brlen = 1; + br = "\n"; + } - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - l += ZSTR_LEN((*err_entry_p)->message); - if (i < count - 1) { - l += brlen; - } + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + l += ZSTR_LEN((*err_entry_p)->message); + if (i < count - 1) { + l += brlen; } - msg = emalloc(l + 1); - msg[0] = '\0'; - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, ZSTR_VAL((*err_entry_p)->message)); - if (i < count - 1) { - strcat(msg, br); - } + } + msg = emalloc(l + 1); + msg[0] = '\0'; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + strcat(msg, br); } + } - free_msg = 1; + free_msg = 1; + } else { + if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); } else { - if (wrapper == &php_plain_files_wrapper) { - msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); - } else { - msg = "operation failed"; - } + msg = "operation failed"; } - } else { - msg = "no suitable wrapper could be found"; } php_strip_url_passwd(tmp); - php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS, code, tmp, - "%s: %s", caption, msg); - efree(tmp); + php_stream_wrapper_error_internal_with_name_variadic(wrapper_name, context, NULL, REPORT_ERRORS, + E_WARNING, true, code, tmp, "%s: %s", caption, msg); + if (free_msg) { efree(msg); } } -void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, + php_stream_context *context, int code, const char *path, const char *caption) { - if (wrapper && FG(wrapper_logged_errors)) { - const char *wrapper_name = wrapper ? wrapper->wops->label : "unknown"; + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_display_wrapper_errors(wrapper_name, context, code, path, caption); + } +} + +PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name) +{ + if (FG(wrapper_logged_errors)) { zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); } } +PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +{ + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_tidy_wrapper_name_error_log(wrapper_name); + } +} + /* Stream error reporting */ PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminal, @@ -549,8 +572,8 @@ PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severit severity, terminal); /* Store error */ - php_stream_store_error_common(context, stream, message, docref, code, wrapper_name, NULL, - severity, terminal); + php_stream_store_error_common( + context, stream, message, docref, code, wrapper_name, NULL, severity, terminal); zend_string_release(message); } diff --git a/main/streams/streams.c b/main/streams/streams.c index 3fb64cdb74127..446602b08bd74 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2151,6 +2151,8 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod return NULL; } + /* wrapper name needs to be stored as wrapper can be removed in opener (user stream) */ + char *wrapper_name = pestrdup(PHP_STREAM_ERROR_WRAPPER_NAME(wrapper), persistent); if (wrapper) { if (!wrapper->wops->stream_opener) { php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS, @@ -2205,6 +2207,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod if (resolved_path) { zend_string_release_ex(resolved_path, 0); } + pefree(wrapper_name, persistent); return stream; case PHP_STREAM_RELEASED: if (newstream->orig_path) { @@ -2214,6 +2217,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod if (resolved_path) { zend_string_release_ex(resolved_path, 0); } + pefree(wrapper_name, persistent); return newstream; default: php_stream_close(stream); @@ -2241,14 +2245,15 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod } if (stream == NULL && (options & REPORT_ERRORS)) { - php_stream_display_wrapper_errors(wrapper, context, STREAM_ERROR_CODE_OPEN_FAILED, path, + php_stream_display_wrapper_name_errors(wrapper_name, context, STREAM_ERROR_CODE_OPEN_FAILED, path, "Failed to open stream"); if (opened_path && *opened_path) { zend_string_release_ex(*opened_path, 0); *opened_path = NULL; } } - php_stream_tidy_wrapper_error_log(wrapper); + php_stream_tidy_wrapper_name_error_log(wrapper_name); + pefree(wrapper_name, persistent); if (resolved_path) { zend_string_release_ex(resolved_path, 0); } From 1b07139674c8a73af0d261eae744572355d02975 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 19 Nov 2025 23:07:35 +0100 Subject: [PATCH 38/61] stream: fix double error message creation --- main/streams/stream_errors.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 3228b3c5c8ed8..e4762f7f12869 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -289,17 +289,25 @@ static void php_stream_store_error_common(php_stream_context *context, php_strea } /* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ -static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name, +static void php_stream_wrapper_error_internal_with_name_and_message(const char *wrapper_name, php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, char *param, const char *fmt, va_list args) + int code, char *param, zend_string *message) { - zend_string *message = vstrpprintf(0, fmt, args); - php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), param, severity, terminal); php_stream_store_error_common( context, NULL, message, docref, code, wrapper_name, param, severity, terminal); +} + +static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name, + php_stream_context *context, const char *docref, int options, int severity, bool terminal, + int code, char *param, const char *fmt, va_list args) +{ + zend_string *message = vstrpprintf(0, fmt, args); + + php_stream_wrapper_error_internal_with_name_and_message( + wrapper_name, context, docref, options, severity, terminal, code, param, message); zend_string_release(message); } @@ -420,8 +428,8 @@ static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrap if (options & REPORT_ERRORS) { /* Report immediately using standard error functions */ - php_stream_wrapper_error_internal_with_name( - wrapper_name, context, NULL, options, severity, terminal, code, param, fmt, args); + php_stream_wrapper_error_internal_with_name_and_message( + wrapper_name, context, NULL, options, severity, terminal, code, param, message); } else { /* Store for later display in FG(wrapper_logged_errors) */ php_stream_wrapper_log_store_error(message, code, wrapper_name, param, severity, terminal); @@ -533,7 +541,7 @@ PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, { if (wrapper) { const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_display_wrapper_errors(wrapper_name, context, code, path, caption); + php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption); } } From be49c0320c616ef471be7a915648643693c8622f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 19 Nov 2025 23:31:31 +0100 Subject: [PATCH 39/61] stream: fix empty wrapper check in error displaying --- main/streams/php_stream_errors.h | 4 +- main/streams/stream_errors.c | 76 +++++++++++++++++--------------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index b59083e234591..a519149285e66 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -125,7 +125,9 @@ BEGIN_EXTERN_C() #define STREAM_ERROR_CODE_USERSPACE_CALL_FAILED 162 /* Wrapper name for PHP errors */ -#define PHP_STREAM_ERROR_WRAPPER_NAME(_wrapper) (_wrapper ? _wrapper->wops->label : "unknown") +#define PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME ":na" +#define PHP_STREAM_ERROR_WRAPPER_NAME(_wrapper) \ + (_wrapper ? _wrapper->wops->label : PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME) /* Stored error entry */ typedef struct { diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index e4762f7f12869..0a199589370ae 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -483,48 +483,52 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, } char *tmp = estrdup(path); - zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); - if (err_list) { - size_t l = 0; - int brlen; - int i; - int count = (int) zend_llist_count(err_list); - const char *br; - php_stream_error_entry **err_entry_p; - zend_llist_position pos; - - if (PG(html_errors)) { - brlen = 7; - br = "
\n"; - } else { - brlen = 1; - br = "\n"; - } + if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) { + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); + if (err_list) { + size_t l = 0; + int brlen; + int i; + int count = (int) zend_llist_count(err_list); + const char *br; + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + if (PG(html_errors)) { + brlen = 7; + br = "
\n"; + } else { + brlen = 1; + br = "\n"; + } - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - l += ZSTR_LEN((*err_entry_p)->message); - if (i < count - 1) { - l += brlen; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + l += ZSTR_LEN((*err_entry_p)->message); + if (i < count - 1) { + l += brlen; + } } - } - msg = emalloc(l + 1); - msg[0] = '\0'; - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, ZSTR_VAL((*err_entry_p)->message)); - if (i < count - 1) { - strcat(msg, br); + msg = emalloc(l + 1); + msg[0] = '\0'; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + strcat(msg, br); + } } - } - free_msg = 1; - } else { - if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { - msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); + free_msg = 1; } else { - msg = "operation failed"; + if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); + } else { + msg = "operation failed"; + } } + } else { + msg = "no suitable wrapper could be found"; } php_strip_url_passwd(tmp); From 9c67e72e4891ffcb11fbf97ace580113e5bf2665 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 20 Nov 2025 00:05:47 +0100 Subject: [PATCH 40/61] stream: generate optimizer info for stream_get_errors --- Zend/Optimizer/zend_func_infos.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index b7b118c710c53..16d254b3a38fd 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -596,6 +596,7 @@ static const func_info_t func_infos[] = { F1("stream_get_line", MAY_BE_STRING|MAY_BE_FALSE), F1("stream_resolve_include_path", MAY_BE_STRING|MAY_BE_FALSE), F1("stream_get_wrappers", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), + FN("stream_get_errors", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY), F1("stream_get_transports", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), #if defined(HAVE_GETTIMEOFDAY) F1("uniqid", MAY_BE_STRING), From 1a58fac4c09252639a79650b6816aeb28fc81e45 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 20 Nov 2025 00:10:27 +0100 Subject: [PATCH 41/61] stream: make exception wrapper optional --- main/streams/stream_errors.stub.php | 4 ++-- main/streams/stream_errors_arginfo.h | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index fb6c5584a5f48..61779037fdc6c 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -4,11 +4,11 @@ class StreamException extends Exception { - protected string $wrapperName; + protected ?string $wrapperName = null; protected ?string $param = null; public function getParam(): ?string {} - public function getWrapperName(): string {} + public function getWrapperName(): ?string {} } /** diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 79d86059dbdb6..5c21a632755a9 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,11 +1,10 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 97a63d009e4a3e951b0c3b213e9cb784d1af5634 */ + * Stub hash: afc1e2ff4a74033102ca8b69ea55ab9ce30986da */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getWrapperName, 0, 0, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_StreamException_getWrapperName arginfo_class_StreamException_getParam static ZEND_METHOD(StreamException, getParam); static ZEND_METHOD(StreamException, getWrapperName); @@ -112,9 +111,9 @@ static zend_class_entry *register_class_StreamException(zend_class_entry *class_ class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Exception, 0); zval property_wrapperName_default_value; - ZVAL_UNDEF(&property_wrapperName_default_value); + ZVAL_NULL(&property_wrapperName_default_value); zend_string *property_wrapperName_name = zend_string_init("wrapperName", sizeof("wrapperName") - 1, true); - zend_declare_typed_property(class_entry, property_wrapperName_name, &property_wrapperName_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_wrapperName_name, &property_wrapperName_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); zend_string_release_ex(property_wrapperName_name, true); zval property_param_default_value; From 6c7f860baa7c421f9b66b3fc3dc5274a21def90f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 28 Dec 2025 16:19:53 +0100 Subject: [PATCH 42/61] stream: restructure error api --- ext/standard/basic_functions.stub.php | 5 +- ext/standard/basic_functions_arginfo.h | 9 +- ext/standard/file.h | 2 +- ext/standard/streamsfuncs.c | 113 +- .../tests/streams/stream_errors_chaining.phpt | 41 + ...stream_errors_error_code_helpers copy.phpt | 28 + .../stream_errors_error_has_code copy.phpt | 30 + ...stream_errors_exception_mode_terminal.phpt | 10 +- .../streams/stream_errors_invalid_types.phpt | 33 + .../stream_errors_mix_modes_storage.phpt | 192 +-- .../stream_errors_modes_with_auto_store.phpt | 30 +- .../streams/stream_errors_multi_store.phpt | 23 + .../stream_errors_silent_with_handler.phpt | 16 +- .../stream_errors_silent_with_storage.phpt | 20 +- .../stream_errors_silent_without_storage.phpt | 10 +- .../streams/stream_errors_standard_error.phpt | 2 +- main/streams/php_stream_errors.h | 362 +++-- main/streams/stream_errors.c | 1260 +++++++++++------ main/streams/stream_errors.stub.php | 481 +------ main/streams/stream_errors_arginfo.h | 246 ++-- main/streams/streams.c | 6 +- 21 files changed, 1580 insertions(+), 1339 deletions(-) create mode 100644 ext/standard/tests/streams/stream_errors_chaining.phpt create mode 100644 ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt create mode 100644 ext/standard/tests/streams/stream_errors_error_has_code copy.phpt create mode 100644 ext/standard/tests/streams/stream_errors_invalid_types.phpt create mode 100644 ext/standard/tests/streams/stream_errors_multi_store.phpt diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 83585f65ab0fd..45da0bd80a425 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -3542,10 +3542,9 @@ function stream_resolve_include_path(string $filename): string|false {} function stream_get_wrappers(): array {} /** - * @param resource|string|null $subject - * @return array + * @refcount 1 */ -function stream_get_errors($subject = null): array {} +function stream_get_last_error(): ?StreamError {} /** * @return array diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index c83fd6735e02c..b2762f1818c6e 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c16449293fe2fe5092af5911e770745d00cec522 */ + * Stub hash: 869c393acff78247591c92190e2be8e09d66a772 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1986,8 +1986,7 @@ ZEND_END_ARG_INFO() #define arginfo_stream_get_wrappers arginfo_ob_list_handlers -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stream_get_errors, 0, 0, IS_ARRAY, 0) - ZEND_ARG_INFO_WITH_DEFAULT_VALUE(0, subject, "null") +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_stream_get_last_error, 0, 0, StreamError, 1) ZEND_END_ARG_INFO() #define arginfo_stream_get_transports arginfo_ob_list_handlers @@ -2832,7 +2831,7 @@ ZEND_FUNCTION(stream_get_meta_data); ZEND_FUNCTION(stream_get_line); ZEND_FUNCTION(stream_resolve_include_path); ZEND_FUNCTION(stream_get_wrappers); -ZEND_FUNCTION(stream_get_errors); +ZEND_FUNCTION(stream_get_last_error); ZEND_FUNCTION(stream_get_transports); ZEND_FUNCTION(stream_is_local); ZEND_FUNCTION(stream_isatty); @@ -3441,7 +3440,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(stream_get_line, arginfo_stream_get_line) ZEND_FE(stream_resolve_include_path, arginfo_stream_resolve_include_path) ZEND_FE(stream_get_wrappers, arginfo_stream_get_wrappers) - ZEND_FE(stream_get_errors, arginfo_stream_get_errors) + ZEND_FE(stream_get_last_error, arginfo_stream_get_last_error) ZEND_FE(stream_get_transports, arginfo_stream_get_transports) ZEND_FE(stream_is_local, arginfo_stream_is_local) ZEND_FE(stream_isatty, arginfo_stream_isatty) diff --git a/ext/standard/file.h b/ext/standard/file.h index a3c84791cc1ee..6f08dad366f88 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -102,7 +102,7 @@ typedef struct { HashTable *stream_wrappers; /* per-request copy of url_stream_wrappers_hash */ HashTable *stream_filters; /* per-request copy of stream_filters_hash */ HashTable *wrapper_logged_errors; /* key: wrapper address; value: linked list of error entries */ - HashTable *wrapper_stored_errors; /* key: wrapper address; value: linked list of error entries */ + php_stream_error_state stream_error_state; int pclose_wait; #ifdef HAVE_GETHOSTBYNAME_R struct hostent tmp_host_info; diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 6472bbc23dedc..e81e14c082175 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -594,102 +594,25 @@ PHP_FUNCTION(stream_get_wrappers) } /* }}} */ -/* Helper function to convert error list to array */ -static void php_stream_errors_list_to_array(zend_llist *list, zval *return_value) +/* Retrieves the last stored stream error */ +PHP_FUNCTION(stream_get_last_error) { - php_stream_error_entry **err_entry_p; - zend_llist_position pos; - - for (err_entry_p = zend_llist_get_first_ex(list, &pos); - err_entry_p; - err_entry_p = zend_llist_get_next_ex(list, &pos)) { - php_stream_error_entry *entry = *err_entry_p; - zval error_array; - array_init(&error_array); - - add_assoc_str(&error_array, "message", zend_string_copy(entry->message)); - add_assoc_long(&error_array, "code", entry->code); - add_assoc_long(&error_array, "severity", entry->severity); - add_assoc_bool(&error_array, "terminal", entry->terminal); - - if (entry->wrapper_name) { - add_assoc_string(&error_array, "wrapper", entry->wrapper_name); - } - if (entry->param) { - add_assoc_string(&error_array, "param", entry->param); - } - if (entry->docref) { - add_assoc_string(&error_array, "docref", entry->docref); - } - - add_next_index_zval(return_value, &error_array); - } -} - -/* Retrieves list of stored stream errors */ -PHP_FUNCTION(stream_get_errors) -{ - zval *subject = NULL; - php_stream *stream = NULL; - char *wrapper_name = NULL; - size_t wrapper_name_len = 0; - bool get_all_wrappers = false; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(subject) - ZEND_PARSE_PARAMETERS_END(); - - /* Determine what we're querying for */ - if (subject == NULL) { - /* No parameter - get all wrapper errors */ - get_all_wrappers = true; - } else if (Z_TYPE_P(subject) == IS_RESOURCE) { - /* Stream resource - get errors for this stream */ - php_stream_from_zval_no_verify(stream, subject); - if (stream == NULL) { - zend_argument_type_error(1, "must be a valid stream resource"); - RETURN_THROWS(); - } - } else if (Z_TYPE_P(subject) == IS_STRING) { - /* Wrapper name - get errors for this wrapper */ - wrapper_name = Z_STRVAL_P(subject); - wrapper_name_len = Z_STRLEN_P(subject); - } else { - zend_argument_type_error(1, "must be a stream resource, string, or null"); - RETURN_THROWS(); - } - - array_init(return_value); - - /* Handle stream errors */ - if (stream) { - if (stream->error_list) { - php_stream_errors_list_to_array(stream->error_list, return_value); - } - } else if (get_all_wrappers) { - /* Get errors from all wrappers */ - if (FG(wrapper_stored_errors)) { - zend_string *key; - zval *val; - ZEND_HASH_FOREACH_STR_KEY_VAL(FG(wrapper_stored_errors), key, val) { - if (key) { - zend_llist *list = (zend_llist *) Z_PTR_P(val); - php_stream_errors_list_to_array(list, return_value); - } - } ZEND_HASH_FOREACH_END(); - } - } else if (wrapper_name) { - /* Get errors for specific wrapper */ - if (FG(wrapper_stored_errors)) { - zend_llist *list = zend_hash_str_find_ptr( - FG(wrapper_stored_errors), wrapper_name, wrapper_name_len); - - if (list) { - php_stream_errors_list_to_array(list, return_value); - } - } - } + ZEND_PARSE_PARAMETERS_NONE(); + + /* Check if we have any stored errors */ + if (!FG(stream_error_state).stored_errors) { + RETURN_NULL(); + } + + /* Get the most recent stored error (head of list) */ + php_stream_stored_error *stored = FG(stream_error_state).stored_errors; + + if (!stored->first_error) { + RETURN_NULL(); + } + + /* Create StreamError object from the error chain */ + php_stream_error_create_object(return_value, stored->first_error); } /* {{{ stream_select related functions */ diff --git a/ext/standard/tests/streams/stream_errors_chaining.phpt b/ext/standard/tests/streams/stream_errors_chaining.phpt new file mode 100644 index 0000000000000..859e468d3f5ee --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_chaining.phpt @@ -0,0 +1,41 @@ +--TEST-- +Stream errors - error chaining +--FILE-- + [ + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::All, + ] +]); + +$stream = fopen('test://foo', 'r', false, $context); + +$error = stream_get_last_error(); +if ($error) { + echo "Error count: " . $error->count() . "\n"; + echo "First error: " . $error->message . "\n"; + + // Check if it has next error + if ($error->next) { + echo "Has chained error: yes\n"; + } +} + +?> +--EXPECTF-- +Error count: %d +First error: %s +%a diff --git a/ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt b/ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt new file mode 100644 index 0000000000000..c52bafb52c6f1 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt @@ -0,0 +1,28 @@ +--TEST-- +Stream errors - StreamErrorCode helper methods +--FILE-- + [ + 'error_mode' => StreamErrorMode::Silent, + ] +]); + +// Generate a network error +$stream = @fsockopen('invalid-host-12345.example.com', 80, $errno, $errstr, 1, $context); + +$error = stream_get_last_error(); +if ($error) { + echo "Is I/O error: " . ($error->code->isIoError() ? 'yes' : 'no') . "\n"; + echo "Is filesystem error: " . ($error->code->isFileSystemError() ? 'yes' : 'no') . "\n"; + echo "Is network error: " . ($error->code->isNetworkError() ? 'yes' : 'no') . "\n"; + echo "Is wrapper error: " . ($error->code->isWrapperError() ? 'yes' : 'no') . "\n"; +} + +?> +--EXPECTF-- +Is I/O error: %s +Is filesystem error: no +Is network error: %s +Is wrapper error: %s diff --git a/ext/standard/tests/streams/stream_errors_error_has_code copy.phpt b/ext/standard/tests/streams/stream_errors_error_has_code copy.phpt new file mode 100644 index 0000000000000..2bd9595f67876 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_error_has_code copy.phpt @@ -0,0 +1,30 @@ +--TEST-- +Stream errors - multiple operations stored +--FILE-- + [ + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::All, + ] +]); + +// First operation +$stream1 = fopen('php://nonexistent1', 'r', false, $context); +$error1 = stream_get_last_error(); + +// Second operation +$stream2 = fopen('php://nonexistent2', 'r', false, $context); +$error2 = stream_get_last_error(); + +// Should get the most recent error (second operation) +if ($error2) { + echo "Got most recent error\n"; + echo "Param contains 'nonexistent2': " . (strpos($error2->param ?? '', 'nonexistent2') !== false ? 'yes' : 'no') . "\n"; +} + +?> +--EXPECT-- +Got most recent error +Param contains 'nonexistent2': yes diff --git a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt index 016b989276fa0..d66e6d654f1df 100644 --- a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt +++ b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt @@ -5,7 +5,7 @@ Stream errors - exception mode for terminal errors $context = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, + 'error_mode' => StreamErrorMode::Exception, ] ]); @@ -14,7 +14,12 @@ try { } catch (StreamException $e) { echo "Caught: " . $e->getMessage() . "\n"; echo "Code: " . $e->getCode() . "\n"; - echo "Wrapper: " . $e->getWrapperName() . "\n"; + + $error = $e->getError(); + if ($error) { + echo "Wrapper: " . $error->wrapperName . "\n"; + echo "Error code name: " . $error->code->name . "\n"; + } } ?> @@ -22,3 +27,4 @@ try { Caught: Failed to open stream: operation failed Code: 36 Wrapper: PHP +Error code name: OpenFailed diff --git a/ext/standard/tests/streams/stream_errors_invalid_types.phpt b/ext/standard/tests/streams/stream_errors_invalid_types.phpt new file mode 100644 index 0000000000000..22563920ed672 --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_invalid_types.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stream errors - invalid enum type throws TypeError +--FILE-- + [ + 'error_mode' => 'invalid', + ] + ]); + + fopen('php://nonexistent', 'r', false, $context); +} catch (TypeError $e) { + echo "Caught TypeError for error_mode\n"; +} + +try { + $context = stream_context_create([ + 'stream' => [ + 'error_store' => 123, + ] + ]); + + fopen('php://nonexistent', 'r', false, $context); +} catch (TypeError $e) { + echo "Caught TypeError for error_store\n"; +} + +?> +--EXPECT-- +Caught TypeError for error_mode +Caught TypeError for error_store diff --git a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt index cb765de62fe07..80dd85a4a386b 100644 --- a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt +++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt @@ -31,158 +31,117 @@ function stream_test_errors($title, $context) { $stream = fopen('test://foo', 'r', false, $context); try { echo $title . "\n"; - $readin = fopen( 'php://stdin', 'r' ); - $data = fread( $stream, 10 ); + $readin = fopen('php://stdin', 'r'); + $data = fread($stream, 10); - $read = [$readin, $stream]; - $write = NULL; + $read = [$readin, $stream]; + $write = NULL; $except = NULL; stream_select($read, $write, $except, 0); } catch (StreamException $e) { echo 'EXCEPTION: ' . $e->getMessage() . "\n"; } - $errors = stream_get_errors($stream); - var_dump($errors); + $error = stream_get_last_error(); + if ($error) { + echo "Error details:\n"; + echo "- Message: " . $error->message . "\n"; + echo "- Code: " . $error->code->name . " (" . $error->code->value . ")\n"; + echo "- Wrapper: " . $error->wrapperName . "\n"; + echo "- Terminating: " . ($error->terminating ? 'yes' : 'no') . "\n"; + echo "- Count: " . $error->count() . "\n"; + + // Show all errors in chain + $current = $error; + $idx = 0; + while ($current) { + echo " [$idx] " . $current->code->name . ": " . substr($current->message, 0, 50) . "...\n"; + $current = $current->next; + $idx++; + } + } else { + echo "No errors stored\n"; + } + echo "\n"; } stream_test_errors('ALL', stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_store' => STREAM_ERROR_STORE_ALL, + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::All, ] ])); + stream_test_errors('NON TERMINAL', stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_store' => STREAM_ERROR_STORE_NON_TERMINAL, + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::NonTerminal, ] ])); + stream_test_errors('TERMINAL', stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_store' => STREAM_ERROR_STORE_TERMINAL, + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::Terminal, ] ])); + stream_test_errors('AUTO EXCEPTION', stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, - 'error_store' => STREAM_ERROR_STORE_AUTO, + 'error_mode' => StreamErrorMode::Exception, + 'error_store' => StreamErrorStore::Auto, ] ])); + stream_test_errors('AUTO ERROR', stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_ERROR, - 'error_store' => STREAM_ERROR_STORE_AUTO, + 'error_mode' => StreamErrorMode::Error, + 'error_store' => StreamErrorStore::Auto, ] ])); ?> --EXPECTF-- ALL -array(3) { - [0]=> - array(5) { - ["message"]=> - string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" - ["code"]=> - int(161) - ["severity"]=> - int(2) - ["terminal"]=> - bool(false) - ["wrapper"]=> - string(10) "user-space" - } - [1]=> - array(5) { - ["message"]=> - string(43) "TestStream::stream_cast is not implemented!" - ["code"]=> - int(70) - ["severity"]=> - int(2) - ["terminal"]=> - bool(true) - ["wrapper"]=> - string(10) "user-space" - } - [2]=> - array(5) { - ["message"]=> - string(73) "Cannot represent a stream of type user-space as a select()able descriptor" - ["code"]=> - int(101) - ["severity"]=> - int(2) - ["terminal"]=> - bool(true) - ["wrapper"]=> - string(10) "user-space" - } -} +Error details: +- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost +- Code: UserspaceInvalidReturn (161) +- Wrapper: user-space +- Terminating: no +- Count: 3 + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... + [1] NotImplemented: TestStream::stream_cast is not implemented!... + [2] CastNotSupported: Cannot represent a stream of type user-space as... + NON TERMINAL -array(1) { - [0]=> - array(5) { - ["message"]=> - string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" - ["code"]=> - int(161) - ["severity"]=> - int(2) - ["terminal"]=> - bool(false) - ["wrapper"]=> - string(10) "user-space" - } -} +Error details: +- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost +- Code: UserspaceInvalidReturn (161) +- Wrapper: user-space +- Terminating: no +- Count: 1 + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... + TERMINAL -array(2) { - [0]=> - array(5) { - ["message"]=> - string(43) "TestStream::stream_cast is not implemented!" - ["code"]=> - int(70) - ["severity"]=> - int(2) - ["terminal"]=> - bool(true) - ["wrapper"]=> - string(10) "user-space" - } - [1]=> - array(5) { - ["message"]=> - string(73) "Cannot represent a stream of type user-space as a select()able descriptor" - ["code"]=> - int(101) - ["severity"]=> - int(2) - ["terminal"]=> - bool(true) - ["wrapper"]=> - string(10) "user-space" - } -} +Error details: +- Message: TestStream::stream_cast is not implemented! +- Code: NotImplemented (70) +- Wrapper: user-space +- Terminating: yes +- Count: 2 + [0] NotImplemented: TestStream::stream_cast is not implemented!... + [1] CastNotSupported: Cannot represent a stream of type user-space as... + AUTO EXCEPTION EXCEPTION: Cannot represent a stream of type user-space as a select()able descriptor -array(1) { - [0]=> - array(5) { - ["message"]=> - string(113) "TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost" - ["code"]=> - int(161) - ["severity"]=> - int(2) - ["terminal"]=> - bool(false) - ["wrapper"]=> - string(10) "user-space" - } -} +Error details: +- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost +- Code: UserspaceInvalidReturn (161) +- Wrapper: user-space +- Terminating: no +- Count: 1 + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... + AUTO ERROR Warning: fread(): TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost in %s on line %d @@ -190,5 +149,4 @@ Warning: fread(): TestStream::stream_read - read 10 bytes more data than request Warning: stream_select(): TestStream::stream_cast is not implemented! in %s on line %d Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d -array(0) { -} +No errors stored diff --git a/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt index bda445eabe428..cfc776c437db2 100644 --- a/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt +++ b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt @@ -6,20 +6,20 @@ Stream errors - error_store AUTO mode behavior // AUTO with ERROR mode should store NONE $context1 = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_ERROR, - 'error_store' => STREAM_ERROR_STORE_AUTO, + 'error_mode' => StreamErrorMode::Error, + 'error_store' => StreamErrorStore::Auto, ] ]); @fopen('php://nonexistent', 'r', false, $context1); -$errors1 = stream_get_errors('PHP'); -echo "ERROR mode AUTO: " . count($errors1) . "\n"; +$error1 = stream_get_last_error(); +echo "ERROR mode AUTO: " . ($error1 ? "has error" : "no error") . "\n"; // AUTO with EXCEPTION mode should store NON_TERM $context2 = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_EXCEPTION, - 'error_store' => STREAM_ERROR_STORE_AUTO, + 'error_mode' => StreamErrorMode::Exception, + 'error_store' => StreamErrorStore::Auto, ] ]); @@ -27,23 +27,23 @@ try { fopen('php://nonexistent2', 'r', false, $context2); } catch (StreamException $e) {} -$errors2 = stream_get_errors('PHP'); -echo "EXCEPTION mode AUTO: " . count($errors2) . "\n"; +$error2 = stream_get_last_error(); +echo "EXCEPTION mode AUTO: " . ($error2 ? "has error" : "no error") . "\n"; // AUTO with SILENT mode should store ALL $context3 = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_store' => STREAM_ERROR_STORE_AUTO, + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::Auto, ] ]); fopen('php://nonexistent3', 'r', false, $context3); -$errors3 = stream_get_errors('PHP'); -echo "SILENT mode AUTO: " . count($errors3) . "\n"; +$error3 = stream_get_last_error(); +echo "SILENT mode AUTO: " . ($error3 ? "has error" : "no error") . "\n"; ?> --EXPECTF-- -ERROR mode AUTO: 0 -EXCEPTION mode AUTO: %d -SILENT mode AUTO: %d +ERROR mode AUTO: no error +EXCEPTION mode AUTO: %s +SILENT mode AUTO: has error diff --git a/ext/standard/tests/streams/stream_errors_multi_store.phpt b/ext/standard/tests/streams/stream_errors_multi_store.phpt new file mode 100644 index 0000000000000..eccfb065d563c --- /dev/null +++ b/ext/standard/tests/streams/stream_errors_multi_store.phpt @@ -0,0 +1,23 @@ +--TEST-- +Stream errors - StreamError hasCode method +--FILE-- + [ + 'error_mode' => StreamErrorMode::Silent, + ] +]); + +$stream = fopen('php://nonexistent', 'r', false, $context); + +$error = stream_get_last_error(); +if ($error) { + echo "Has OpenFailed: " . ($error->hasCode(StreamErrorCode::OpenFailed) ? 'yes' : 'no') . "\n"; + echo "Has NotFound: " . ($error->hasCode(StreamErrorCode::NotFound) ? 'yes' : 'no') . "\n"; +} + +?> +--EXPECT-- +Has OpenFailed: yes +Has NotFound: no diff --git a/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt index 9ce86adb09658..2416a15529aa5 100644 --- a/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt +++ b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt @@ -7,14 +7,15 @@ $handler_called = false; $context = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_handler' => function($wrapper, $stream, $code, $message, $param) use (&$handler_called) { + 'error_mode' => StreamErrorMode::Silent, + 'error_handler' => function(StreamError $error) use (&$handler_called) { $handler_called = true; echo "Handler called\n"; - echo "Wrapper: $wrapper\n"; - echo "Code: $code\n"; - echo "Message: $message\n"; - echo "Param: " . ($param ?? 'null') . "\n"; + echo "Wrapper: " . $error->wrapperName . "\n"; + echo "Code: " . $error->code->name . "\n"; + echo "Message: " . $error->message . "\n"; + echo "Param: " . ($error->param ?? 'null') . "\n"; + echo "Terminating: " . ($error->terminating ? 'yes' : 'no') . "\n"; } ] ]); @@ -27,7 +28,8 @@ var_dump($handler_called); --EXPECT-- Handler called Wrapper: PHP -Code: 36 +Code: OpenFailed Message: Failed to open stream: operation failed Param: php://nonexistent +Terminating: yes bool(true) diff --git a/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt index 563857d308fff..0d3d627001788 100644 --- a/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt +++ b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt @@ -5,23 +5,25 @@ Stream errors - silent mode with error storage $context = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, + 'error_mode' => StreamErrorMode::Silent, ] ]); $stream = fopen('php://nonexistent', 'r', false, $context); var_dump($stream); -$errors = stream_get_errors(); -echo "Error count: " . count($errors) . "\n"; -if (count($errors) > 0) { - echo "First error code: " . $errors[0]['code'] . "\n"; - echo "First error wrapper: " . $errors[0]['wrapper'] . "\n"; +$error = stream_get_last_error(); +if ($error) { + echo "Has error: yes\n"; + echo "Error code: " . $error->code->name . "\n"; + echo "Error wrapper: " . $error->wrapperName . "\n"; + echo "Error message: " . $error->message . "\n"; } ?> --EXPECT-- bool(false) -Error count: 1 -First error code: 36 -First error wrapper: PHP +Has error: yes +Error code: OpenFailed +Error wrapper: PHP +Error message: Failed to open stream: operation failed diff --git a/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt index 6ed79378d8226..477bbd7401c26 100644 --- a/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt +++ b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt @@ -5,16 +5,16 @@ Stream errors - error_store NONE option $context = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_SILENT, - 'error_store' => STREAM_ERROR_STORE_NONE, + 'error_mode' => StreamErrorMode::Silent, + 'error_store' => StreamErrorStore::None, ] ]); $stream = fopen('php://nonexistent', 'r', false, $context); -$errors = stream_get_errors(); -echo "Error count: " . count($errors) . "\n"; +$error = stream_get_last_error(); +echo "Has error: " . ($error ? "yes" : "no") . "\n"; ?> --EXPECT-- -Error count: 0 +Has error: no diff --git a/ext/standard/tests/streams/stream_errors_standard_error.phpt b/ext/standard/tests/streams/stream_errors_standard_error.phpt index a9725f0b89b26..42de8cc3b17df 100644 --- a/ext/standard/tests/streams/stream_errors_standard_error.phpt +++ b/ext/standard/tests/streams/stream_errors_standard_error.phpt @@ -5,7 +5,7 @@ Stream errors - error mode with standard error reporting $context = stream_context_create([ 'stream' => [ - 'error_mode' => STREAM_ERROR_MODE_ERROR, + 'error_mode' => StreamErrorMode::Error, ] ]); diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index a519149285e66..783e9b5150e3f 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -22,214 +22,284 @@ BEGIN_EXTERN_C() -/* Error mode context options */ -#define PHP_STREAM_ERROR_MODE_ERROR 0 -#define PHP_STREAM_ERROR_MODE_EXCEPTION 1 -#define PHP_STREAM_ERROR_MODE_SILENT 2 - -/* Error store context options */ -#define PHP_STREAM_ERROR_STORE_AUTO 0 -#define PHP_STREAM_ERROR_STORE_NONE 1 -#define PHP_STREAM_ERROR_STORE_NON_TERM 2 -#define PHP_STREAM_ERROR_STORE_TERMINAL 3 -#define PHP_STREAM_ERROR_STORE_ALL 4 - -/* Stream Error Codes*/ -/* No error */ -#define STREAM_ERROR_CODE_NONE 0 -/* Generic unspecified error */ -#define STREAM_ERROR_CODE_GENERIC 1 -/* Stream I/O operations */ -#define STREAM_ERROR_CODE_READ_FAILED 10 -#define STREAM_ERROR_CODE_WRITE_FAILED 11 -#define STREAM_ERROR_CODE_SEEK_FAILED 12 -#define STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED 13 -#define STREAM_ERROR_CODE_FLUSH_FAILED 14 -#define STREAM_ERROR_CODE_TRUNCATE_FAILED 15 -#define STREAM_ERROR_CODE_CONNECT_FAILED 16 -#define STREAM_ERROR_CODE_BIND_FAILED 17 -#define STREAM_ERROR_CODE_LISTEN_FAILED 18 -#define STREAM_ERROR_CODE_NOT_WRITABLE 19 -#define STREAM_ERROR_CODE_NOT_READABLE 20 -/* File system operations */ -#define STREAM_ERROR_CODE_DISABLED 30 -#define STREAM_ERROR_CODE_NOT_FOUND 31 -#define STREAM_ERROR_CODE_PERMISSION_DENIED 32 -#define STREAM_ERROR_CODE_ALREADY_EXISTS 33 -#define STREAM_ERROR_CODE_INVALID_PATH 34 -#define STREAM_ERROR_CODE_PATH_TOO_LONG 35 -#define STREAM_ERROR_CODE_OPEN_FAILED 36 -#define STREAM_ERROR_CODE_CREATE_FAILED 37 -#define STREAM_ERROR_CODE_DUP_FAILED 38 -#define STREAM_ERROR_CODE_UNLINK_FAILED 39 -#define STREAM_ERROR_CODE_RENAME_FAILED 40 -#define STREAM_ERROR_CODE_MKDIR_FAILED 41 -#define STREAM_ERROR_CODE_RMDIR_FAILED 42 -#define STREAM_ERROR_CODE_STAT_FAILED 43 -#define STREAM_ERROR_CODE_META_FAILED 44 -#define STREAM_ERROR_CODE_CHMOD_FAILED 45 -#define STREAM_ERROR_CODE_CHOWN_FAILED 46 -#define STREAM_ERROR_CODE_COPY_FAILED 47 -#define STREAM_ERROR_CODE_TOUCH_FAILED 48 -#define STREAM_ERROR_CODE_INVALID_MODE 49 -#define STREAM_ERROR_CODE_INVALID_META 50 -#define STREAM_ERROR_CODE_MODE_NOT_SUPPORTED 51 -#define STREAM_ERROR_CODE_READONLY 52 -#define STREAM_ERROR_CODE_RECURSION_DETECTED 53 -/* Wrapper/protocol operations */ -#define STREAM_ERROR_CODE_NOT_IMPLEMENTED 70 -#define STREAM_ERROR_CODE_NO_OPENER 71 -#define STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED 72 -#define STREAM_ERROR_CODE_WRAPPER_NOT_FOUND 73 -#define STREAM_ERROR_CODE_WRAPPER_DISABLED 74 -#define STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED 75 -#define STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED 76 -#define STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED 77 -#define STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED 78 -/* Filter operations */ -#define STREAM_ERROR_CODE_FILTER_NOT_FOUND 90 -#define STREAM_ERROR_CODE_FILTER_FAILED 91 -/* Cast/conversion operations */ -#define STREAM_ERROR_CODE_CAST_FAILED 100 -#define STREAM_ERROR_CODE_CAST_NOT_SUPPORTED 101 -#define STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED 102 -#define STREAM_ERROR_CODE_BUFFERED_DATA_LOST 103 -/* Network/socket operations */ -#define STREAM_ERROR_CODE_NETWORK_SEND_FAILED 110 -#define STREAM_ERROR_CODE_NETWORK_RECV_FAILED 111 -#define STREAM_ERROR_CODE_SSL_NOT_SUPPORTED 112 -#define STREAM_ERROR_CODE_RESUMPTION_FAILED 113 -#define STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG 114 -#define STREAM_ERROR_CODE_OOB_NOT_SUPPORTED 115 -#define STREAM_ERROR_CODE_PROTOCOL_ERROR 116 -#define STREAM_ERROR_CODE_INVALID_URL 117 -#define STREAM_ERROR_CODE_INVALID_RESPONSE 118 -#define STREAM_ERROR_CODE_INVALID_HEADER 119 -#define STREAM_ERROR_CODE_INVALID_PARAM 110 -#define STREAM_ERROR_CODE_REDIRECT_LIMIT 121 -#define STREAM_ERROR_CODE_AUTH_FAILED 122 -/* Encoding/decoding/archiving operations */ -#define STREAM_ERROR_CODE_ARCHIVING_FAILED 130 -#define STREAM_ERROR_CODE_ENCODING_FAILED 131 -#define STREAM_ERROR_CODE_DECODING_FAILED 132 -#define STREAM_ERROR_CODE_INVALID_FORMAT 133 -/* Resource/allocation operations */ -#define STREAM_ERROR_CODE_ALLOCATION_FAILED 140 -#define STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED 141 -/* Locking operations */ -#define STREAM_ERROR_CODE_LOCK_FAILED 150 -#define STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED 151 -/* Userspace stream operations */ -#define STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED 160 -#define STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN 161 -#define STREAM_ERROR_CODE_USERSPACE_CALL_FAILED 162 +/* Error mode context options (internal C constants) */ +#define PHP_STREAM_ERROR_MODE_ERROR 0 +#define PHP_STREAM_ERROR_MODE_EXCEPTION 1 +#define PHP_STREAM_ERROR_MODE_SILENT 2 + +/* Error store context options (internal C constants) */ +#define PHP_STREAM_ERROR_STORE_AUTO 0 +#define PHP_STREAM_ERROR_STORE_NONE 1 +#define PHP_STREAM_ERROR_STORE_NON_TERM 2 +#define PHP_STREAM_ERROR_STORE_TERMINAL 3 +#define PHP_STREAM_ERROR_STORE_ALL 4 + +/* Maximum operation nesting depth */ +#define PHP_STREAM_ERROR_MAX_DEPTH 100 + +/* Error code ranges */ +#define STREAM_ERROR_CODE_IO_START 10 +#define STREAM_ERROR_CODE_IO_END 30 +#define STREAM_ERROR_CODE_FILESYSTEM_START 30 +#define STREAM_ERROR_CODE_FILESYSTEM_END 70 +#define STREAM_ERROR_CODE_WRAPPER_START 70 +#define STREAM_ERROR_CODE_WRAPPER_END 90 +#define STREAM_ERROR_CODE_FILTER_START 90 +#define STREAM_ERROR_CODE_FILTER_END 100 +#define STREAM_ERROR_CODE_CAST_START 100 +#define STREAM_ERROR_CODE_CAST_END 110 +#define STREAM_ERROR_CODE_NETWORK_START 110 +#define STREAM_ERROR_CODE_NETWORK_END 130 +#define STREAM_ERROR_CODE_ENCODING_START 130 +#define STREAM_ERROR_CODE_ENCODING_END 140 +#define STREAM_ERROR_CODE_RESOURCE_START 140 +#define STREAM_ERROR_CODE_RESOURCE_END 150 +#define STREAM_ERROR_CODE_LOCK_START 150 +#define STREAM_ERROR_CODE_LOCK_END 160 +#define STREAM_ERROR_CODE_USERSPACE_START 160 +#define STREAM_ERROR_CODE_USERSPACE_END 170 + +/* X-macro defining all error codes */ +#define PHP_STREAM_ERROR_CODES(V) \ + /* General errors */ \ + V(NONE, None, 0) \ + V(GENERIC, Generic, 1) \ + /* I/O operation errors (10-29) */ \ + V(READ_FAILED, ReadFailed, 10) \ + V(WRITE_FAILED, WriteFailed, 11) \ + V(SEEK_FAILED, SeekFailed, 12) \ + V(SEEK_NOT_SUPPORTED, SeekNotSupported, 13) \ + V(FLUSH_FAILED, FlushFailed, 14) \ + V(TRUNCATE_FAILED, TruncateFailed, 15) \ + V(CONNECT_FAILED, ConnectFailed, 16) \ + V(BIND_FAILED, BindFailed, 17) \ + V(LISTEN_FAILED, ListenFailed, 18) \ + V(NOT_WRITABLE, NotWritable, 19) \ + V(NOT_READABLE, NotReadable, 20) \ + /* File system operations (30-69) */ \ + V(DISABLED, Disabled, 30) \ + V(NOT_FOUND, NotFound, 31) \ + V(PERMISSION_DENIED, PermissionDenied, 32) \ + V(ALREADY_EXISTS, AlreadyExists, 33) \ + V(INVALID_PATH, InvalidPath, 34) \ + V(PATH_TOO_LONG, PathTooLong, 35) \ + V(OPEN_FAILED, OpenFailed, 36) \ + V(CREATE_FAILED, CreateFailed, 37) \ + V(DUP_FAILED, DupFailed, 38) \ + V(UNLINK_FAILED, UnlinkFailed, 39) \ + V(RENAME_FAILED, RenameFailed, 40) \ + V(MKDIR_FAILED, MkdirFailed, 41) \ + V(RMDIR_FAILED, RmdirFailed, 42) \ + V(STAT_FAILED, StatFailed, 43) \ + V(META_FAILED, MetaFailed, 44) \ + V(CHMOD_FAILED, ChmodFailed, 45) \ + V(CHOWN_FAILED, ChownFailed, 46) \ + V(COPY_FAILED, CopyFailed, 47) \ + V(TOUCH_FAILED, TouchFailed, 48) \ + V(INVALID_MODE, InvalidMode, 49) \ + V(INVALID_META, InvalidMeta, 50) \ + V(MODE_NOT_SUPPORTED, ModeNotSupported, 51) \ + V(READONLY, Readonly, 52) \ + V(RECURSION_DETECTED, RecursionDetected, 53) \ + /* Wrapper/protocol operations (70-89) */ \ + V(NOT_IMPLEMENTED, NotImplemented, 70) \ + V(NO_OPENER, NoOpener, 71) \ + V(PERSISTENT_NOT_SUPPORTED, PersistentNotSupported, 72) \ + V(WRAPPER_NOT_FOUND, WrapperNotFound, 73) \ + V(WRAPPER_DISABLED, WrapperDisabled, 74) \ + V(PROTOCOL_UNSUPPORTED, ProtocolUnsupported, 75) \ + V(WRAPPER_REGISTRATION_FAILED, WrapperRegistrationFailed, 76) \ + V(WRAPPER_UNREGISTRATION_FAILED, WrapperUnregistrationFailed, 77) \ + V(WRAPPER_RESTORATION_FAILED, WrapperRestorationFailed, 78) \ + /* Filter operations (90-99) */ \ + V(FILTER_NOT_FOUND, FilterNotFound, 90) \ + V(FILTER_FAILED, FilterFailed, 91) \ + /* Cast/conversion operations (100-109) */ \ + V(CAST_FAILED, CastFailed, 100) \ + V(CAST_NOT_SUPPORTED, CastNotSupported, 101) \ + V(MAKE_SEEKABLE_FAILED, MakeSeekableFailed, 102) \ + V(BUFFERED_DATA_LOST, BufferedDataLost, 103) \ + /* Network/socket operations (110-129) */ \ + V(NETWORK_SEND_FAILED, NetworkSendFailed, 110) \ + V(NETWORK_RECV_FAILED, NetworkRecvFailed, 111) \ + V(SSL_NOT_SUPPORTED, SslNotSupported, 112) \ + V(RESUMPTION_FAILED, ResumptionFailed, 113) \ + V(SOCKET_PATH_TOO_LONG, SocketPathTooLong, 114) \ + V(OOB_NOT_SUPPORTED, OobNotSupported, 115) \ + V(PROTOCOL_ERROR, ProtocolError, 116) \ + V(INVALID_URL, InvalidUrl, 117) \ + V(INVALID_RESPONSE, InvalidResponse, 118) \ + V(INVALID_HEADER, InvalidHeader, 119) \ + V(INVALID_PARAM, InvalidParam, 120) \ + V(REDIRECT_LIMIT, RedirectLimit, 121) \ + V(AUTH_FAILED, AuthFailed, 122) \ + /* Encoding/decoding/archiving operations (130-139) */ \ + V(ARCHIVING_FAILED, ArchivingFailed, 130) \ + V(ENCODING_FAILED, EncodingFailed, 131) \ + V(DECODING_FAILED, DecodingFailed, 132) \ + V(INVALID_FORMAT, InvalidFormat, 133) \ + /* Resource/allocation operations (140-149) */ \ + V(ALLOCATION_FAILED, AllocationFailed, 140) \ + V(TEMPORARY_FILE_FAILED, TemporaryFileFailed, 141) \ + /* Locking operations (150-159) */ \ + V(LOCK_FAILED, LockFailed, 150) \ + V(LOCK_NOT_SUPPORTED, LockNotSupported, 151) \ + /* Userspace stream operations (160-169) */ \ + V(USERSPACE_NOT_IMPLEMENTED, UserspaceNotImplemented, 160) \ + V(USERSPACE_INVALID_RETURN, UserspaceInvalidReturn, 161) \ + V(USERSPACE_CALL_FAILED, UserspaceCallFailed, 162) + +/* Generate C enum for internal use */ +typedef enum _StreamErrorCode { +#define V(uc_name, name, val) STREAM_ERROR_CODE_##uc_name = val, + PHP_STREAM_ERROR_CODES(V) +#undef V +} StreamErrorCode; /* Wrapper name for PHP errors */ #define PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME ":na" #define PHP_STREAM_ERROR_WRAPPER_NAME(_wrapper) \ - (_wrapper ? _wrapper->wops->label : PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME) - -/* Stored error entry */ -typedef struct { - zend_string *message; - int code; - char *wrapper_name; - char *docref; - char *param; - int severity; - bool terminal; - bool persistent; + (_wrapper ? _wrapper->wops->label : PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME) + +/* Error entry in chain */ +typedef struct _php_stream_error_entry { + zend_string *message; + StreamErrorCode code; + char *wrapper_name; + char *param; + char *docref; + int severity; + bool terminating; + struct _php_stream_error_entry *next; } php_stream_error_entry; -/* Main error reporting functions */ +/* Active error operation */ +typedef struct _php_stream_error_operation { + php_stream_error_entry *first_error; + php_stream_error_entry *last_error; + uint32_t error_count; + struct _php_stream_error_operation *parent; +} php_stream_error_operation; + +/* Stored completed operation */ +typedef struct _php_stream_stored_error { + php_stream_error_entry *first_error; + struct _php_stream_stored_error *next; +} php_stream_stored_error; + +typedef struct { + /* Stack of active operations (LIFO for nesting) */ + php_stream_error_operation *current_operation; + uint32_t operation_depth; + /* List of completed/stored operations (most recent first) */ + php_stream_stored_error *stored_errors; + uint32_t stored_count; +} php_stream_error_state; + +/* Error operation management */ +PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context); +PHPAPI void php_stream_error_operation_end(php_stream_context *context); +PHPAPI void php_stream_error_operation_abort(void); + +/* State cleanup function */ +PHPAPI void php_stream_error_state_cleanup(void); + +/* Error object creation */ +PHPAPI void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry); + +/* Wrapper error reporting functions */ PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); + php_stream_context *context, const char *docref, int options, int severity, + bool terminating, int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, - const char *docref, int options, int severity, bool terminal, int code, const char *fmt, + const char *docref, int options, int severity, bool terminating, int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, - const char *docref, int options, int severity, bool terminal, int code, const char *param, - const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); + const char *docref, int options, int severity, bool terminating, int code, + const char *param, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 9, 10); PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *param1, const char *param2, const char *fmt, ...) - ZEND_ATTRIBUTE_FORMAT(printf, 10, 11); + php_stream_context *context, const char *docref, int options, int severity, + bool terminating, int code, const char *param1, const char *param2, const char *fmt, ...) + ZEND_ATTRIBUTE_FORMAT(printf, 10, 11); -PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminal, - int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); +PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminating, + int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 6, 7); -/* Legacy wrapper error log - updated API */ +/* Legacy wrapper error log functions */ PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, + php_stream_context *context, int options, int severity, bool terminating, int code, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 7, 8); PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, + php_stream_context *context, int options, int severity, bool terminating, int code, const char *param, const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 8, 9); PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, - php_stream_context *context, int code, const char *path, const char *caption); + php_stream_context *context, int code, const char *path, const char *caption); PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - php_stream_context *context, int code, const char *path, const char *caption); + php_stream_context *context, int code, const char *path, const char *caption); PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name); - PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper); /* Convenience macros */ #define php_stream_wrapper_warn(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) #define php_stream_wrapper_warn_name(wrapper_name, context, options, code, ...) \ - php_stream_wrapper_error_with_name(wrapper_name, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) + php_stream_wrapper_error_with_name( \ + wrapper_name, context, NULL, options, E_WARNING, true, code, __VA_ARGS__) #define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, code, __VA_ARGS__) #define php_stream_wrapper_notice(wrapper, context, options, code, ...) \ - php_stream_wrapper_error(wrapper, context, NULL, options, E_NOTICE, false, code, __VA_ARGS__) + php_stream_wrapper_error(wrapper, context, NULL, options, E_NOTICE, false, code, __VA_ARGS__) #define php_stream_wrapper_warn_param(wrapper, context, options, code, param, ...) \ - php_stream_wrapper_error_param(wrapper, context, NULL, options, E_WARNING, true, code, param, __VA_ARGS__) + php_stream_wrapper_error_param( \ + wrapper, context, NULL, options, E_WARNING, true, code, param, __VA_ARGS__) #define php_stream_wrapper_warn_param_nt(wrapper, context, options, code, param, ...) \ - php_stream_wrapper_error_param(wrapper, context, NULL, options, E_WARNING, false, code, param, __VA_ARGS__) + php_stream_wrapper_error_param( \ + wrapper, context, NULL, options, E_WARNING, false, code, param, __VA_ARGS__) #define php_stream_wrapper_warn_param2(wrapper, context, options, code, param1, param2, ...) \ - php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, true, code, param1, param2, __VA_ARGS__) + php_stream_wrapper_error_param2( \ + wrapper, context, NULL, options, E_WARNING, true, code, param1, param2, __VA_ARGS__) #define php_stream_wrapper_warn_param2_nt(wrapper, context, options, code, param1, param2, ...) \ - php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING, false, code, param1, param2, __VA_ARGS__) + php_stream_wrapper_error_param2( \ + wrapper, context, NULL, options, E_WARNING, false, code, param1, param2, __VA_ARGS__) #define php_stream_warn(stream, code, ...) \ - php_stream_error(stream, NULL, E_WARNING, true, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_WARNING, true, code, __VA_ARGS__) #define php_stream_warn_nt(stream, code, ...) \ - php_stream_error(stream, NULL, E_WARNING, false, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_WARNING, false, code, __VA_ARGS__) #define php_stream_warn_docref(stream, docref, code, ...) \ - php_stream_error(stream, docref, E_WARNING, true, code, __VA_ARGS__) + php_stream_error(stream, docref, E_WARNING, true, code, __VA_ARGS__) #define php_stream_notice(stream, code, ...) \ - php_stream_error(stream, NULL, E_NOTICE, false, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_NOTICE, false, code, __VA_ARGS__) #define php_stream_fatal(stream, code, ...) \ - php_stream_error(stream, NULL, E_ERROR, true, code, __VA_ARGS__) + php_stream_error(stream, NULL, E_ERROR, true, code, __VA_ARGS__) /* Legacy log variants */ #define php_stream_wrapper_log_warn(wrapper, context, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, true, code, __VA_ARGS__) + php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, true, code, __VA_ARGS__) #define php_stream_wrapper_log_warn_nt(wrapper, context, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, false, code, __VA_ARGS__) + php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, false, code, __VA_ARGS__) #define php_stream_wrapper_log_notice(wrapper, context, options, code, ...) \ - php_stream_wrapper_log_error(wrapper, context, options, E_NOTICE, false, code, __VA_ARGS__) + php_stream_wrapper_log_error(wrapper, context, options, E_NOTICE, false, code, __VA_ARGS__) END_EXTERN_C() diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 0a199589370ae..5e17f6aa49146 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -18,31 +18,28 @@ #include "php_globals.h" #include "php_streams.h" #include "php_stream_errors.h" -#include "stream_errors_arginfo.h" +#include "zend_enum.h" #include "zend_exceptions.h" #include "ext/standard/file.h" +#include "stream_errors_arginfo.h" -/* StreamException class entry */ +/* Class entries */ +static zend_class_entry *php_ce_stream_error_code; +static zend_class_entry *php_ce_stream_error_mode; +static zend_class_entry *php_ce_stream_error_store; +static zend_class_entry *php_ce_stream_error; static zend_class_entry *php_ce_stream_exception; -static void php_stream_error_entry_dtor(void *error) -{ - php_stream_error_entry *entry = *(php_stream_error_entry **) error; - zend_string_release(entry->message); - pefree(entry->wrapper_name, entry->persistent); - pefree(entry->docref, entry->persistent); - // param is not currently supported for streams so cannot be persistent - ZEND_ASSERT(!entry->persistent || entry->param == NULL); - efree(entry->param); - pefree(entry, entry->persistent); -} +/* Lookup table for error code to case name */ +static const char *const php_stream_error_code_names[200] = { +#define V(uc_name, name, val) [val] = #name, + PHP_STREAM_ERROR_CODES(V) +#undef V +}; -static void php_stream_error_list_dtor(zval *item) -{ - zend_llist *list = (zend_llist *) Z_PTR_P(item); - zend_llist_destroy(list); - efree(list); -} +/* Forward declarations */ +static void php_stream_error_entry_free(php_stream_error_entry *entry); +static bool php_stream_error_code_in_range(zval *this_zv, int start, int end); /* Context option helpers */ @@ -67,8 +64,25 @@ static int php_stream_get_error_mode(php_stream_context *context) } zval *option = php_stream_context_get_option(context, "stream", "error_mode"); - if (option && Z_TYPE_P(option) == IS_LONG) { - return Z_LVAL_P(option); + if (!option) { + return PHP_STREAM_ERROR_MODE_ERROR; + } + + if (Z_TYPE_P(option) != IS_OBJECT + || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_mode)) { + zend_type_error("stream context option 'error_mode' must be of type StreamErrorMode"); + return PHP_STREAM_ERROR_MODE_ERROR; + } + + /* Get enum case name */ + zend_string *case_name = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(option))); + + if (zend_string_equals_literal(case_name, "Error")) { + return PHP_STREAM_ERROR_MODE_ERROR; + } else if (zend_string_equals_literal(case_name, "Exception")) { + return PHP_STREAM_ERROR_MODE_EXCEPTION; + } else if (zend_string_equals_literal(case_name, "Silent")) { + return PHP_STREAM_ERROR_MODE_SILENT; } return PHP_STREAM_ERROR_MODE_ERROR; @@ -81,540 +95,978 @@ static int php_stream_get_error_store_mode(php_stream_context *context, int erro } zval *option = php_stream_context_get_option(context, "stream", "error_store"); - if (option && Z_TYPE_P(option) == IS_LONG) { - int store_mode = Z_LVAL_P(option); + if (!option) { + return php_stream_auto_decide_error_store_mode(error_mode); + } - if (store_mode == PHP_STREAM_ERROR_STORE_AUTO) { - return php_stream_auto_decide_error_store_mode(error_mode); - } + if (Z_TYPE_P(option) != IS_OBJECT + || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_store)) { + zend_type_error("stream context option 'error_store' must be of type StreamErrorStore"); + return php_stream_auto_decide_error_store_mode(error_mode); + } + + /* Get enum case name */ + zend_string *case_name = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(option))); - return store_mode; + if (zend_string_equals_literal(case_name, "Auto")) { + return php_stream_auto_decide_error_store_mode(error_mode); + } else if (zend_string_equals_literal(case_name, "None")) { + return PHP_STREAM_ERROR_STORE_NONE; + } else if (zend_string_equals_literal(case_name, "NonTerminal")) { + return PHP_STREAM_ERROR_STORE_NON_TERM; + } else if (zend_string_equals_literal(case_name, "Terminal")) { + return PHP_STREAM_ERROR_STORE_TERMINAL; + } else if (zend_string_equals_literal(case_name, "All")) { + return PHP_STREAM_ERROR_STORE_ALL; } return php_stream_auto_decide_error_store_mode(error_mode); } -static bool php_stream_should_store_error(int store_mode, bool terminal) +/* Helper functions */ + +static bool php_stream_has_terminating_error(php_stream_error_operation *op) { - switch (store_mode) { - case PHP_STREAM_ERROR_STORE_NONE: - return false; - case PHP_STREAM_ERROR_STORE_NON_TERM: - return !terminal; - case PHP_STREAM_ERROR_STORE_TERMINAL: - return terminal; - case PHP_STREAM_ERROR_STORE_ALL: + php_stream_error_entry *entry = op->first_error; + while (entry) { + if (entry->terminating) { return true; - default: - return false; + } + entry = entry->next; } + return false; } -/* StreamException methods */ +static void php_stream_error_entry_free(php_stream_error_entry *entry) +{ + while (entry) { + php_stream_error_entry *next = entry->next; + zend_string_release(entry->message); + efree(entry->wrapper_name); + efree(entry->param); + efree(entry); + entry = next; + } +} -static void php_stream_throw_exception( - const char *message, int code, const char *wrapper_name, const char *param) +static void php_stream_error_operation_free(php_stream_error_operation *op) { - zval ex; + php_stream_error_entry_free(op->first_error); + efree(op); +} - object_init_ex(&ex, php_ce_stream_exception); +/* Cleanup function for request shutdown */ +PHPAPI void php_stream_error_state_cleanup(void) +{ + /* Clear active operations (shouldn't normally have any, but clean up just in case) */ + while (FG(stream_error_state).current_operation) { + php_stream_error_operation *op = FG(stream_error_state).current_operation; + FG(stream_error_state).current_operation = op->parent; + php_stream_error_operation_free(op); + } + + /* Clear stored errors */ + php_stream_stored_error *stored = FG(stream_error_state).stored_errors; + while (stored) { + php_stream_stored_error *next = stored->next; + php_stream_error_entry_free(stored->first_error); + efree(stored); + stored = next; + } + + FG(stream_error_state).stored_errors = NULL; + FG(stream_error_state).stored_count = 0; + FG(stream_error_state).operation_depth = 0; +} - zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"), message); - zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"), code); - zend_update_property_string( - php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("wrapperName"), wrapper_name); +/* Error operation stack management */ - if (param) { - zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("param"), param); +PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context) +{ + /* Check depth limit */ + if (FG(stream_error_state).operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) { + php_error_docref(NULL, E_WARNING, + "Stream error operation depth exceeded (%u), possible infinite recursion", + FG(stream_error_state).operation_depth); + return NULL; } - zend_throw_exception_object(&ex); + /* Create new operation with empty error list */ + php_stream_error_operation *op = emalloc(sizeof(php_stream_error_operation)); + op->first_error = NULL; + op->last_error = NULL; + op->error_count = 0; + op->parent = FG(stream_error_state).current_operation; + + /* Push onto stack */ + FG(stream_error_state).current_operation = op; + FG(stream_error_state).operation_depth++; + + return op; +} + +static void php_stream_error_add(StreamErrorCode code, const char *wrapper_name, + const char *message, const char *docref, const char *param, int severity, + bool terminating) +{ + php_stream_error_operation *op = FG(stream_error_state).current_operation; + + if (!op) { + /* No operation context - skip */ + return; + } + + php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + entry->message = zend_string_init(message, strlen(message), 0); + entry->code = code; + entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; + entry->param = param ? estrdup(param) : NULL; + entry->param = docref ? estrdup(docref) : NULL; + entry->severity = severity; + entry->terminating = terminating; + entry->next = NULL; + + /* Append to operation's error list */ + if (op->last_error) { + op->last_error->next = entry; + } else { + op->first_error = entry; + } + op->last_error = entry; + op->error_count++; } -/* Core error processing */ +/* Error reporting */ + +static void php_stream_call_error_handler(zval *handler, zval *error_obj) +{ + zend_fcall_info_cache fcc; + char *is_callable_error = NULL; + + if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) { + if (is_callable_error) { + zend_type_error("stream error handler must be a valid callback, %s", is_callable_error); + efree(is_callable_error); + } + return; + } + + zval retval; -static void php_stream_process_error(php_stream_context *context, const char *wrapper_name, - php_stream *stream, const char *docref, int code, const char *message, const char *param, - int severity, bool terminal) + call_user_function(NULL, NULL, handler, &retval, 1, error_obj); + + zval_ptr_dtor(&retval); +} + +static void php_stream_throw_exception_with_errors(php_stream_error_operation *op) { - int error_mode = php_stream_get_error_mode(context); + if (!op->first_error) { + return; + } + + /* Create StreamError object from error chain */ + zval error_obj; + php_stream_error_create_object(&error_obj, op->first_error); + + /* Create exception */ + zval ex; + object_init_ex(&ex, php_ce_stream_exception); + + /* Set message from first error */ + zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"), + ZSTR_VAL(op->first_error->message)); + + /* Set code from first error enum value */ + zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"), + (zend_long) op->first_error->code); + + /* Set the complete error object */ + zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("error"), &error_obj); + + zval_ptr_dtor(&error_obj); + zend_throw_exception_object(&ex); +} - /* Handle error based on error_mode */ +static void php_stream_report_errors(php_stream_context *context, php_stream_error_operation *op, + int error_mode, bool is_terminating) +{ switch (error_mode) { - case PHP_STREAM_ERROR_MODE_ERROR: - if (param) { - php_error_docref1(docref, param, severity, "%s", message); - } else { - php_error_docref(docref, severity, "%s", message); + case PHP_STREAM_ERROR_MODE_ERROR: { + /* Report all errors individually */ + php_stream_error_entry *entry = op->first_error; + while (entry) { + if (entry->param) { + php_error_docref1( + entry->docref, entry->param, entry->severity, "%s", ZSTR_VAL(entry->message)); + } else { + php_error_docref(entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message)); + } + entry = entry->next; } break; + } - case PHP_STREAM_ERROR_MODE_EXCEPTION: - if (terminal) { - php_stream_throw_exception(message, code, wrapper_name, param); + case PHP_STREAM_ERROR_MODE_EXCEPTION: { + if (is_terminating) { + php_stream_throw_exception_with_errors(op); } break; + } case PHP_STREAM_ERROR_MODE_SILENT: + /* Don't report */ break; } /* Call user error handler if set */ - if (context) { - zval *handler = php_stream_context_get_option(context, "stream", "error_handler"); - if (handler) { - zend_fcall_info_cache fcc; - char *is_callable_error = NULL; - zval retval; - zval args[5]; - - if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) { - if (is_callable_error) { - zend_type_error( - "stream error handler must be a valid callback, %s", is_callable_error); - efree(is_callable_error); - } else { - zend_type_error("stream error must be a valid callback"); - } - return; - } + zval *handler + = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL; - /* Arg 0: wrapper name */ - ZVAL_STRING(&args[0], wrapper_name); + if (handler) { + /* Create StreamError object from error chain */ + zval error_obj; + php_stream_error_create_object(&error_obj, op->first_error); - /* Arg 1: stream resource or null */ - if (stream && stream->res) { - ZVAL_RES(&args[1], stream->res); - GC_ADDREF(stream->res); - } else { - ZVAL_NULL(&args[1]); - } + /* Call handler(error) */ + php_stream_call_error_handler(handler, &error_obj); + + zval_ptr_dtor(&error_obj); + } +} + +/* Error storage - move and filter */ - /* Arg 2: error code */ - ZVAL_LONG(&args[2], code); +PHPAPI void php_stream_error_operation_end(php_stream_context *context) +{ + php_stream_error_operation *op = FG(stream_error_state).current_operation; + + if (!op) { + return; + } - /* Arg 3: message */ - ZVAL_STRING(&args[3], message); + /* Pop from stack */ + FG(stream_error_state).current_operation = op->parent; + FG(stream_error_state).operation_depth--; - /* Arg 4: param (or null) */ - if (param) { - ZVAL_STRING(&args[4], param); - } else { - ZVAL_NULL(&args[4]); + /* Process errors if we have any */ + if (op->error_count > 0) { + /* Get error handling settings */ + int error_mode = php_stream_get_error_mode(context); + int store_mode = php_stream_get_error_store_mode(context, error_mode); + + bool is_terminating = php_stream_has_terminating_error(op); + + /* Always report errors */ + php_stream_report_errors(context, op, error_mode, is_terminating); + + /* Handle storage */ + if (store_mode == PHP_STREAM_ERROR_STORE_NONE) { + /* Free all errors */ + php_stream_error_entry_free(op->first_error); + op->first_error = NULL; + op->last_error = NULL; + } else { + /* Filter and store */ + php_stream_error_entry *entry = op->first_error; + php_stream_error_entry *prev = NULL; + php_stream_error_entry *to_store_first = NULL; + php_stream_error_entry *to_store_last = NULL; + + while (entry) { + php_stream_error_entry *next = entry->next; + bool should_store = false; + + if (store_mode == PHP_STREAM_ERROR_STORE_ALL) { + should_store = true; + } else if (store_mode == PHP_STREAM_ERROR_STORE_NON_TERM && !entry->terminating) { + should_store = true; + } else if (store_mode == PHP_STREAM_ERROR_STORE_TERMINAL && entry->terminating) { + should_store = true; + } + + if (should_store) { + /* Move to storage chain */ + if (prev) { + prev->next = next; + } else { + op->first_error = next; + } + + entry->next = NULL; + if (to_store_last) { + to_store_last->next = entry; + } else { + to_store_first = entry; + } + to_store_last = entry; + } else { + /* Free this error */ + if (prev) { + prev->next = next; + } else { + op->first_error = next; + } + + zend_string_release(entry->message); + efree(entry->wrapper_name); + efree(entry->param); + efree(entry); + + /* Don't update prev */ + entry = next; + continue; + } + + prev = entry; + entry = next; } - call_user_function(NULL, NULL, handler, &retval, 5, args); + /* Store filtered errors if any */ + if (to_store_first) { + php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error)); + stored->first_error = to_store_first; + stored->next = FG(stream_error_state).stored_errors; + + FG(stream_error_state).stored_errors = stored; + FG(stream_error_state).stored_count++; + } - zval_ptr_dtor(&retval); - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[3]); - zval_ptr_dtor(&args[4]); + /* Free any remaining errors not moved to storage */ + php_stream_error_entry_free(op->first_error); + op->first_error = NULL; + op->last_error = NULL; } } + + /* Free operation structure */ + efree(op); } -/* Helper to create error entry */ -static php_stream_error_entry *php_stream_create_error_entry(zend_string *message, int code, - const char *wrapper_name, const char *docref, char *param, int severity, bool terminal, - bool persistent) +PHPAPI void php_stream_error_operation_abort(void) { - if (persistent) { - message = zend_string_dup(message, true); - } else { - zend_string_addref(message); + php_stream_error_operation *op = FG(stream_error_state).current_operation; + + if (!op) { + return; } - php_stream_error_entry *entry = pemalloc(sizeof(php_stream_error_entry), persistent); - entry->message = message; - entry->code = code; - entry->wrapper_name = wrapper_name ? pestrdup(wrapper_name, persistent) : NULL; - entry->docref = docref ? pestrdup(docref, persistent) : NULL; - entry->param = param; - entry->severity = severity; - entry->terminal = terminal; - entry->persistent = persistent; + FG(stream_error_state).current_operation = op->parent; + FG(stream_error_state).operation_depth--; - return entry; + php_stream_error_operation_free(op); } -/* Common storage function*/ -static void php_stream_store_error_common(php_stream_context *context, php_stream *stream, - zend_string *message, const char *docref, int code, const char *wrapper_name, char *param, - int severity, bool terminal) -{ - int error_mode = php_stream_get_error_mode(context); - int store_mode = php_stream_get_error_store_mode(context, error_mode); +/* Wrapper error reporting */ - if (!php_stream_should_store_error(store_mode, terminal)) { - efree(param); - return; +static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context, + const char *docref, int options, int severity, bool terminating, int code, char *param, + const char *message) +{ + /* If not in an operation, create one */ + bool implicit_operation = (FG(stream_error_state).current_operation == NULL); + if (implicit_operation) { + php_stream_error_operation_begin(context); } - zend_llist *list; - bool persistent = false; - if (stream) { - persistent = stream->is_persistent; - /* Store in stream's error list */ - if (!stream->error_list) { - stream->error_list = pemalloc(sizeof(zend_llist), persistent); - zend_llist_init(stream->error_list, sizeof(php_stream_error_entry *), - php_stream_error_entry_dtor, persistent); - } - list = stream->error_list; - } else { - /* Store in FG(stream_errors) for wrapper errors */ - if (!FG(wrapper_stored_errors)) { - ALLOC_HASHTABLE(FG(wrapper_stored_errors)); - zend_hash_init(FG(wrapper_stored_errors), 8, NULL, php_stream_error_list_dtor, 0); - list = NULL; - } else { - list = zend_hash_str_find_ptr( - FG(wrapper_stored_errors), wrapper_name, strlen(wrapper_name)); - } + /* Add to current operation (or skip if no operation) */ + php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating); - if (!list) { - zend_llist new_list; - zend_llist_init( - &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); - list = zend_hash_str_update_mem(FG(wrapper_stored_errors), wrapper_name, - strlen(wrapper_name), &new_list, sizeof(new_list)); - } + /* If we created implicit operation, end it immediately */ + if (implicit_operation) { + php_stream_error_operation_end(context); } +} - php_stream_error_entry *entry = php_stream_create_error_entry( - message, code, wrapper_name, docref, param, severity, terminal, persistent); +PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, + php_stream_context *context, const char *docref, int options, int severity, + bool terminating, int code, const char *fmt, ...) +{ + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + + char *message = NULL; + vspprintf(&message, 0, fmt, args); + va_end(args); + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, NULL, message); + + efree(message); +} - zend_llist_add_element(list, &entry); +PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, + php_stream_context *context, const char *docref, int options, + int severity, bool terminating, int code, const char *fmt, ...) +{ + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + + char *message = NULL; + vspprintf(&message, 0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, NULL, message); + + efree(message); } -/* Wrapper error reporting - stores in FG(wrapper_stored_errors) */ -static void php_stream_wrapper_error_internal_with_name_and_message(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, char *param, zend_string *message) +PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, + php_stream_context *context, const char *docref, int options, + int severity, bool terminating, int code, const char *param, + const char *fmt, ...) { - php_stream_process_error(context, wrapper_name, NULL, docref, code, ZSTR_VAL(message), param, - severity, terminal); + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + + char *message = NULL; + vspprintf(&message, 0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + char *param_copy = param ? estrdup(param) : NULL; + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, param_copy, message); + + efree(message); +} - php_stream_store_error_common( - context, NULL, message, docref, code, wrapper_name, param, severity, terminal); +PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, + php_stream_context *context, const char *docref, int options, + int severity, bool terminating, int code, const char *param1, + const char *param2, const char *fmt, ...) +{ + if (!(options & REPORT_ERRORS)) { + return; + } + + char *combined_param; + spprintf(&combined_param, 0, "%s,%s", param1, param2); + + va_list args; + va_start(args, fmt); + + char *message = NULL; + vspprintf(&message, 0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, combined_param, message); + + efree(message); } -static void php_stream_wrapper_error_internal_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, char *param, const char *fmt, va_list args) +/* Stream error reporting - delegates to wrapper errors */ + +PHPAPI void php_stream_error(php_stream *stream, const char *docref, + int severity, bool terminating, int code, const char *fmt, ...) { - zend_string *message = vstrpprintf(0, fmt, args); + va_list args; + va_start(args, fmt); + + char *message = NULL; + vspprintf(&message, 0, fmt, args); + va_end(args); + + const char *wrapper_name = stream->wrapper + ? stream->wrapper->wops->label + : "stream"; + + php_stream_context *context = PHP_STREAM_CONTEXT(stream); + + /* Just call the wrapper error function */ + php_stream_wrapper_error_internal( + wrapper_name, + context, + docref, + REPORT_ERRORS, + severity, + terminating, + code, + NULL, + message + ); + + efree(message); +} - php_stream_wrapper_error_internal_with_name_and_message( - wrapper_name, context, docref, options, severity, terminal, code, param, message); +/* Legacy wrapper error logging */ - zend_string_release(message); +static void php_stream_error_entry_dtor_legacy(void *error) +{ + php_stream_error_entry *entry = *(php_stream_error_entry **) error; + zend_string_release(entry->message); + efree(entry->wrapper_name); + efree(entry->param); + efree(entry->docref); + efree(entry); } -static void php_stream_wrapper_error_internal_with_name_variadic(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, char *param, const char *fmt, ...) +static void php_stream_error_list_dtor(zval *item) { - va_list args; - va_start(args, fmt); + zend_llist *list = (zend_llist *) Z_PTR_P(item); + zend_llist_destroy(list); + efree(list); +} - php_stream_wrapper_error_internal_with_name( - wrapper_name, context, docref, options, severity, terminal, code, param, fmt, args); +static void php_stream_wrapper_log_store_error(zend_string *message, int code, + const char *wrapper_name, const char *param, int severity, bool terminating) +{ + char *param_copy = param ? estrdup(param) : NULL; + + php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + entry->message = zend_string_copy(message); + entry->code = code; + entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; + entry->param = param_copy; + entry->severity = severity; + entry->terminating = terminating; + entry->next = NULL; + + if (!FG(wrapper_logged_errors)) { + ALLOC_HASHTABLE(FG(wrapper_logged_errors)); + zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); + } + + zend_llist *list = zend_hash_str_find_ptr( + FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + + if (!list) { + zend_llist new_list; + zend_llist_init(&new_list, sizeof(php_stream_error_entry *), + php_stream_error_entry_dtor_legacy, 0); + list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name, + strlen(wrapper_name), &new_list, sizeof(new_list)); + } + + zend_llist_add_element(list, &entry); +} - va_end(args); +static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminating, + int code, char *param, const char *fmt, va_list args) +{ + zend_string *message = vstrpprintf(0, fmt, args); + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + if (options & REPORT_ERRORS) { + /* Report immediately */ + php_stream_wrapper_error_internal( + wrapper_name, context, NULL, options, severity, terminating, code, param, ZSTR_VAL(message)); + } else { + /* Store for later display */ + php_stream_wrapper_log_store_error(message, code, wrapper_name, param, severity, terminating); + } + + zend_string_release(message); } -static void php_stream_wrapper_error_internal(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, char *param, const char *fmt, va_list args) +PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminating, + int code, const char *fmt, ...) { - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + va_list args; + va_start(args, fmt); + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminating, code, NULL, fmt, args); + va_end(args); +} - php_stream_wrapper_error_internal_with_name( - wrapper_name, context, docref, options, severity, terminal, code, param, fmt, args); +PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, + php_stream_context *context, int options, int severity, bool terminating, + int code, const char *param, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + char *param_copy = param ? estrdup(param) : NULL; + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminating, code, param_copy, fmt, args); + va_end(args); } -PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *fmt, ...) +static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) { - if (options & REPORT_ERRORS) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal_with_name( - wrapper_name, context, docref, options, severity, terminal, code, NULL, fmt, args); - va_end(args); - } + if (!FG(wrapper_logged_errors)) { + return NULL; + } + return (zend_llist *) zend_hash_str_find_ptr( + FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); } -PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, - const char *docref, int options, int severity, bool terminal, int code, const char *fmt, - ...) +PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, + php_stream_context *context, int code, const char *path, const char *caption) { - if (options & REPORT_ERRORS) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, NULL, fmt, args); - va_end(args); - } + char *msg; + char errstr[256]; + int free_msg = 0; + + if (EG(exception)) { + return; + } + + char *tmp = estrdup(path); + if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) { + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); + if (err_list) { + size_t l = 0; + int brlen; + int i; + int count = (int) zend_llist_count(err_list); + const char *br; + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + if (PG(html_errors)) { + brlen = 7; + br = "
\n"; + } else { + brlen = 1; + br = "\n"; + } + + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + l += ZSTR_LEN((*err_entry_p)->message); + if (i < count - 1) { + l += brlen; + } + } + msg = emalloc(l + 1); + msg[0] = '\0'; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + strcat(msg, br); + } + } + + free_msg = 1; + } else { + if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); + } else { + msg = "operation failed"; + } + } + } else { + msg = "no suitable wrapper could be found"; + } + + php_strip_url_passwd(tmp); + + char *full_msg; + spprintf(&full_msg, 0, "%s: %s", caption, msg); + + php_stream_wrapper_error_internal( + wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true, code, tmp, full_msg); + + efree(full_msg); + + if (free_msg) { + efree(msg); + } } -PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, - const char *docref, int options, int severity, bool terminal, int code, const char *param, - const char *fmt, ...) +PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, + php_stream_context *context, int code, const char *path, const char *caption) { - if (options & REPORT_ERRORS) { - va_list args; - va_start(args, fmt); - char *param_copy = param ? estrdup(param) : NULL; - php_stream_wrapper_error_internal( - wrapper, context, docref, options, severity, terminal, code, param_copy, fmt, args); - va_end(args); - } + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption); + } } -PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, int severity, bool terminal, - int code, const char *param1, const char *param2, const char *fmt, ...) +PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name) { - if (options & REPORT_ERRORS) { - char *combined_param; - spprintf(&combined_param, 0, "%s,%s", param1, param2); + if (FG(wrapper_logged_errors)) { + zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + } +} - va_list args; - va_start(args, fmt); - php_stream_wrapper_error_internal(wrapper, context, docref, options, severity, terminal, - code, combined_param, fmt, args); - va_end(args); - } +PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +{ + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_tidy_wrapper_name_error_log(wrapper_name); + } } -/* Wrapper error logging - stores in FG(wrapper_logged_errors) */ +/* StreamError object creation - no enum cache */ -static void php_stream_wrapper_log_store_error(zend_string *message, int code, - const char *wrapper_name, const char *param, int severity, bool terminal) +PHPAPI void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry) { - char *param_copy = param ? estrdup(param) : NULL; - php_stream_error_entry *entry = php_stream_create_error_entry( - message, code, wrapper_name, NULL, param_copy, severity, terminal, false); - - if (!FG(wrapper_logged_errors)) { - ALLOC_HASHTABLE(FG(wrapper_logged_errors)); - zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); + if (!entry) { + ZVAL_NULL(zv); + return; } - zend_llist *list - = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + object_init_ex(zv, php_ce_stream_error); + + /* Get enum case by value using lookup array */ + const char *case_name = NULL; + int code_value = (int) entry->code; - if (!list) { - zend_llist new_list; - zend_llist_init( - &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor, 0); - list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name, - strlen(wrapper_name), &new_list, sizeof(new_list)); + if (code_value >= 0 + && code_value < (int) (sizeof(php_stream_error_code_names) + / sizeof(php_stream_error_code_names[0]))) { + case_name = php_stream_error_code_names[code_value]; } - zend_llist_add_element(list, &entry); -} + if (!case_name) { + case_name = "Generic"; + } -static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, - char *param, const char *fmt, va_list args) -{ - zend_string *message = vstrpprintf(0, fmt, args); - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name); + ZEND_ASSERT(enum_obj != NULL); + + zval code_enum; + ZVAL_OBJ(&code_enum, enum_obj); + GC_ADDREF(enum_obj); + + zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum); + zval_ptr_dtor(&code_enum); + + /* Set other properties */ + zend_update_property_str( + php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->message); - if (options & REPORT_ERRORS) { - /* Report immediately using standard error functions */ - php_stream_wrapper_error_internal_with_name_and_message( - wrapper_name, context, NULL, options, severity, terminal, code, param, message); + zend_update_property_string(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("wrapperName"), + entry->wrapper_name ? entry->wrapper_name : ""); + + zend_update_property_long( + php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("severity"), entry->severity); + + zend_update_property_bool( + php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating); + + if (entry->param) { + zend_update_property_string( + php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"), entry->param); + } else { + zend_update_property_null(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param")); + } + + if (entry->next) { + zval next_error; + php_stream_error_create_object(&next_error, entry->next); + zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("next"), &next_error); + zval_ptr_dtor(&next_error); } else { - /* Store for later display in FG(wrapper_logged_errors) */ - php_stream_wrapper_log_store_error(message, code, wrapper_name, param, severity, terminal); + zend_update_property_null(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("next")); } - zend_string_release(message); } -PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, - const char *fmt, ...) + +/* StreamError methods */ + +PHP_METHOD(StreamError, hasCode) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_log_error_internal( - wrapper, context, options, severity, terminal, code, NULL, fmt, args); - va_end(args); + zval *search_code; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(search_code, php_ce_stream_error_code) + ZEND_PARSE_PARAMETERS_END(); + + zval *current_error_zv = ZEND_THIS; + + while (Z_TYPE_P(current_error_zv) == IS_OBJECT) { + zval *code_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), + ZEND_STRL("code"), 1, NULL); + + /* Compare enum objects */ + if (Z_TYPE_P(code_zv) == IS_OBJECT && Z_OBJ_P(code_zv) == Z_OBJ_P(search_code)) { + RETURN_TRUE; + } + + /* Move to next error */ + current_error_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), + ZEND_STRL("next"), 1, NULL); + + if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { + break; + } + } + + RETURN_FALSE; } -PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminal, int code, - const char *param, const char *fmt, ...) +PHP_METHOD(StreamError, count) { - va_list args; - va_start(args, fmt); - char *param_copy = param ? estrdup(param) : NULL; - php_stream_wrapper_log_error_internal( - wrapper, context, options, severity, terminal, code, param_copy, fmt, args); - va_end(args); + ZEND_PARSE_PARAMETERS_NONE(); + + zend_long count = 1; + zval *current_error_zv = ZEND_THIS; + + while (1) { + current_error_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), + ZEND_STRL("next"), 1, NULL); + + if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { + break; + } + + count++; + } + + RETURN_LONG(count); } -static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) +/* StreamException methods */ + +PHP_METHOD(StreamException, getError) { - if (!FG(wrapper_logged_errors)) { - return NULL; - } else { - return (zend_llist *) zend_hash_str_find_ptr( - FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); - } + ZEND_PARSE_PARAMETERS_NONE(); + + zval *error = zend_read_property( + php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), + ZEND_STRL("error"), 1, NULL); + + RETURN_COPY(error); } -PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, - php_stream_context *context, int code, const char *path, const char *caption) -{ - char *msg; - char errstr[256]; - int free_msg = 0; +/* StreamErrorCode helper */ - if (EG(exception)) { - /* Don't emit additional warnings if an exception has already been thrown. */ - return; +static bool php_stream_error_code_in_range(zval *this_zv, int start, int end) +{ + zval *backing = zend_enum_fetch_case_value(Z_OBJ_P(this_zv)); + if (!backing || Z_TYPE_P(backing) != IS_LONG) { + return false; } - char *tmp = estrdup(path); - if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) { - zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); - if (err_list) { - size_t l = 0; - int brlen; - int i; - int count = (int) zend_llist_count(err_list); - const char *br; - php_stream_error_entry **err_entry_p; - zend_llist_position pos; - - if (PG(html_errors)) { - brlen = 7; - br = "
\n"; - } else { - brlen = 1; - br = "\n"; - } - - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - l += ZSTR_LEN((*err_entry_p)->message); - if (i < count - 1) { - l += brlen; - } - } - msg = emalloc(l + 1); - msg[0] = '\0'; - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, ZSTR_VAL((*err_entry_p)->message)); - if (i < count - 1) { - strcat(msg, br); - } - } + zend_long value = Z_LVAL_P(backing); + return value >= start && value < end; +} - free_msg = 1; - } else { - if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { - msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); - } else { - msg = "operation failed"; - } - } - } else { - msg = "no suitable wrapper could be found"; - } +/* StreamErrorCode methods */ - php_strip_url_passwd(tmp); - php_stream_wrapper_error_internal_with_name_variadic(wrapper_name, context, NULL, REPORT_ERRORS, - E_WARNING, true, code, tmp, "%s: %s", caption, msg); +PHP_METHOD(StreamErrorCode, isIoError) +{ + ZEND_PARSE_PARAMETERS_NONE(); - if (free_msg) { - efree(msg); - } + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_IO_START, STREAM_ERROR_CODE_IO_END)); } -PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - php_stream_context *context, int code, const char *path, const char *caption) +PHP_METHOD(StreamErrorCode, isFileSystemError) { - if (wrapper) { - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption); - } + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_FILESYSTEM_START, STREAM_ERROR_CODE_FILESYSTEM_END)); } -PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name) +PHP_METHOD(StreamErrorCode, isWrapperError) { - if (FG(wrapper_logged_errors)) { - zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); - } + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_WRAPPER_START, STREAM_ERROR_CODE_WRAPPER_END)); } -PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) +PHP_METHOD(StreamErrorCode, isFilterError) { - if (wrapper) { - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_tidy_wrapper_name_error_log(wrapper_name); - } -} + ZEND_PARSE_PARAMETERS_NONE(); -/* Stream error reporting */ + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_FILTER_START, STREAM_ERROR_CODE_FILTER_END)); +} -PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminal, - int code, const char *fmt, ...) +PHP_METHOD(StreamErrorCode, isCastError) { - va_list args; - va_start(args, fmt); - zend_string *message = vstrpprintf(0, fmt, args); - va_end(args); + ZEND_PARSE_PARAMETERS_NONE(); - php_stream_wrapper *wrapper = stream->wrapper; - const char *wrapper_name = wrapper ? wrapper->wops->label : "stream"; + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_CAST_START, STREAM_ERROR_CODE_CAST_END)); +} - php_stream_context *context = PHP_STREAM_CONTEXT(stream); +PHP_METHOD(StreamErrorCode, isNetworkError) +{ + ZEND_PARSE_PARAMETERS_NONE(); - /* Process the error - always report for stream errors */ - php_stream_process_error(context, wrapper_name, stream, docref, code, ZSTR_VAL(message), NULL, - severity, terminal); + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_NETWORK_START, STREAM_ERROR_CODE_NETWORK_END)); +} - /* Store error */ - php_stream_store_error_common( - context, stream, message, docref, code, wrapper_name, NULL, severity, terminal); +PHP_METHOD(StreamErrorCode, isEncodingError) +{ + ZEND_PARSE_PARAMETERS_NONE(); - zend_string_release(message); + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_ENCODING_START, STREAM_ERROR_CODE_ENCODING_END)); } -/* StreamException class and error constants registration */ - -PHP_MINIT_FUNCTION(stream_errors) +PHP_METHOD(StreamErrorCode, isResourceError) { - register_stream_errors_symbols(module_number); - - php_ce_stream_exception = register_class_StreamException(zend_ce_exception); + ZEND_PARSE_PARAMETERS_NONE(); - return SUCCESS; + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_RESOURCE_START, STREAM_ERROR_CODE_RESOURCE_END)); } -PHP_METHOD(StreamException, getParam) +PHP_METHOD(StreamErrorCode, isLockError) { ZEND_PARSE_PARAMETERS_NONE(); - zval *param = zend_read_property( - php_ce_stream_exception, Z_OBJ_P(getThis()), ZEND_STRL("param"), 1, NULL); - RETURN_COPY(param); + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_LOCK_START, STREAM_ERROR_CODE_LOCK_END)); } -PHP_METHOD(StreamException, getWrapperName) +PHP_METHOD(StreamErrorCode, isUserspaceError) { ZEND_PARSE_PARAMETERS_NONE(); - zval *wrapper_name = zend_read_property( - php_ce_stream_exception, Z_OBJ_P(getThis()), ZEND_STRL("wrapperName"), 1, NULL); - RETURN_COPY(wrapper_name); + RETURN_BOOL(php_stream_error_code_in_range( + ZEND_THIS, STREAM_ERROR_CODE_USERSPACE_START, STREAM_ERROR_CODE_USERSPACE_END)); +} + +/* Module init */ + +PHP_MINIT_FUNCTION(stream_errors) +{ + /* Register enums */ + php_ce_stream_error_code = register_class_StreamErrorCode(); + php_ce_stream_error_mode = register_class_StreamErrorMode(); + php_ce_stream_error_store = register_class_StreamErrorStore(); + + /* Add cases to StreamErrorCode */ +#define V(uc_name, name, val) \ + { \ + zval enum_case_value; \ + ZVAL_LONG(&enum_case_value, val); \ + zend_enum_add_case_cstr(php_ce_stream_error_code, #name, &enum_case_value); \ + } + PHP_STREAM_ERROR_CODES(V) +#undef V + + /* Register classes */ + php_ce_stream_error = register_class_StreamError(); + php_ce_stream_exception = register_class_StreamException(zend_ce_exception); + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(stream_errors) +{ + return SUCCESS; } diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 61779037fdc6c..83e44ca75c9bf 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -2,434 +2,65 @@ /** @generate-class-entries static */ -class StreamException extends Exception +enum StreamErrorCode: int { - protected ?string $wrapperName = null; - protected ?string $param = null; + // Error code cases registered in code + + public function isIoError(): bool {} + + public function isFileSystemError(): bool {} + + public function isWrapperError(): bool {} + + public function isFilterError(): bool {} + + public function isCastError(): bool {} + + public function isNetworkError(): bool {} + + public function isEncodingError(): bool {} + + public function isResourceError(): bool {} + + public function isLockError(): bool {} + + public function isUserspaceError(): bool {} +} - public function getParam(): ?string {} - public function getWrapperName(): ?string {} +enum StreamErrorMode +{ + case Error; + case Exception; + case Silent; +} + +enum StreamErrorStore +{ + case Auto; + case None; + case NonTerminal; + case Terminal; + case All; } -/** - * @var int - * @cvalue PHP_STREAM_ERROR_MODE_ERROR - */ -const STREAM_ERROR_MODE_ERROR = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_MODE_EXCEPTION - */ -const STREAM_ERROR_MODE_EXCEPTION = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_MODE_SILENT - */ -const STREAM_ERROR_MODE_SILENT = UNKNOWN; +final readonly class StreamError +{ + public StreamErrorCode $code; + public string $message; + public string $wrapperName; + public int $severity; + public bool $terminating; + public ?string $param; + public ?StreamError $next; + + public function hasCode(StreamErrorCode $code): bool {} -/** - * @var int - * @cvalue PHP_STREAM_ERROR_STORE_AUTO - */ -const STREAM_ERROR_STORE_AUTO = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_STORE_NONE - */ -const STREAM_ERROR_STORE_NONE = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_STORE_NON_TERM - */ -const STREAM_ERROR_STORE_NON_TERMINAL = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_STORE_TERMINAL - */ -const STREAM_ERROR_STORE_TERMINAL = UNKNOWN; -/** - * @var int - * @cvalue PHP_STREAM_ERROR_STORE_ALL - */ -const STREAM_ERROR_STORE_ALL = UNKNOWN; + public function count(): int {} +} -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NONE - */ -const STREAM_ERROR_CODE_NONE = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_GENERIC - */ -const STREAM_ERROR_CODE_GENERIC = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_READ_FAILED - */ -const STREAM_ERROR_CODE_READ_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRITE_FAILED - */ -const STREAM_ERROR_CODE_WRITE_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_SEEK_FAILED - */ -const STREAM_ERROR_CODE_SEEK_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_FLUSH_FAILED - */ -const STREAM_ERROR_CODE_FLUSH_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_TRUNCATE_FAILED - */ -const STREAM_ERROR_CODE_TRUNCATE_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CONNECT_FAILED - */ -const STREAM_ERROR_CODE_CONNECT_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_BIND_FAILED - */ -const STREAM_ERROR_CODE_BIND_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_LISTEN_FAILED - */ -const STREAM_ERROR_CODE_LISTEN_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NOT_WRITABLE - */ -const STREAM_ERROR_CODE_NOT_WRITABLE = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NOT_READABLE - */ -const STREAM_ERROR_CODE_NOT_READABLE = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_DISABLED - */ -const STREAM_ERROR_CODE_DISABLED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NOT_FOUND - */ -const STREAM_ERROR_CODE_NOT_FOUND = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_PERMISSION_DENIED - */ -const STREAM_ERROR_CODE_PERMISSION_DENIED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_ALREADY_EXISTS - */ -const STREAM_ERROR_CODE_ALREADY_EXISTS = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_PATH - */ -const STREAM_ERROR_CODE_INVALID_PATH = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_PATH_TOO_LONG - */ -const STREAM_ERROR_CODE_PATH_TOO_LONG = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CREATE_FAILED - */ -const STREAM_ERROR_CODE_CREATE_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_DUP_FAILED - */ -const STREAM_ERROR_CODE_DUP_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_OPEN_FAILED - */ -const STREAM_ERROR_CODE_OPEN_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_UNLINK_FAILED - */ -const STREAM_ERROR_CODE_UNLINK_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_RENAME_FAILED - */ -const STREAM_ERROR_CODE_RENAME_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_MKDIR_FAILED - */ -const STREAM_ERROR_CODE_MKDIR_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_RMDIR_FAILED - */ -const STREAM_ERROR_CODE_RMDIR_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_STAT_FAILED - */ -const STREAM_ERROR_CODE_STAT_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_META_FAILED - */ -const STREAM_ERROR_CODE_META_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CHMOD_FAILED - */ -const STREAM_ERROR_CODE_CHMOD_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CHOWN_FAILED - */ -const STREAM_ERROR_CODE_CHOWN_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_COPY_FAILED - */ -const STREAM_ERROR_CODE_COPY_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_TOUCH_FAILED - */ -const STREAM_ERROR_CODE_TOUCH_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_MODE - */ -const STREAM_ERROR_CODE_INVALID_MODE = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_META - */ -const STREAM_ERROR_CODE_INVALID_META = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_MODE_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_MODE_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_READONLY - */ -const STREAM_ERROR_CODE_READONLY = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_RECURSION_DETECTED - */ -const STREAM_ERROR_CODE_RECURSION_DETECTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NOT_IMPLEMENTED - */ -const STREAM_ERROR_CODE_NOT_IMPLEMENTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NO_OPENER - */ -const STREAM_ERROR_CODE_NO_OPENER = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRAPPER_NOT_FOUND - */ -const STREAM_ERROR_CODE_WRAPPER_NOT_FOUND = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRAPPER_DISABLED - */ -const STREAM_ERROR_CODE_WRAPPER_DISABLED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED - */ -const STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED - */ -const STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED - */ -const STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED - */ -const STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_FILTER_NOT_FOUND - */ -const STREAM_ERROR_CODE_FILTER_NOT_FOUND = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_FILTER_FAILED - */ -const STREAM_ERROR_CODE_FILTER_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CAST_FAILED - */ -const STREAM_ERROR_CODE_CAST_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_CAST_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_CAST_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED - */ -const STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_BUFFERED_DATA_LOST - */ -const STREAM_ERROR_CODE_BUFFERED_DATA_LOST = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NETWORK_SEND_FAILED - */ -const STREAM_ERROR_CODE_NETWORK_SEND_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_NETWORK_RECV_FAILED - */ -const STREAM_ERROR_CODE_NETWORK_RECV_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_SSL_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_SSL_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_RESUMPTION_FAILED - */ -const STREAM_ERROR_CODE_RESUMPTION_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG - */ -const STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_OOB_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_OOB_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_PROTOCOL_ERROR - */ -const STREAM_ERROR_CODE_PROTOCOL_ERROR = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_URL - */ -const STREAM_ERROR_CODE_INVALID_URL = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_RESPONSE - */ -const STREAM_ERROR_CODE_INVALID_RESPONSE = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_HEADER - */ -const STREAM_ERROR_CODE_INVALID_HEADER = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_PARAM - */ -const STREAM_ERROR_CODE_INVALID_PARAM = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_REDIRECT_LIMIT - */ -const STREAM_ERROR_CODE_REDIRECT_LIMIT = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_AUTH_FAILED - */ -const STREAM_ERROR_CODE_AUTH_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_ARCHIVING_FAILED - */ -const STREAM_ERROR_CODE_ARCHIVING_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_ENCODING_FAILED - */ -const STREAM_ERROR_CODE_ENCODING_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_DECODING_FAILED - */ -const STREAM_ERROR_CODE_DECODING_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_INVALID_FORMAT - */ -const STREAM_ERROR_CODE_INVALID_FORMAT = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_ALLOCATION_FAILED - */ -const STREAM_ERROR_CODE_ALLOCATION_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED - */ -const STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_LOCK_FAILED - */ -const STREAM_ERROR_CODE_LOCK_FAILED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED - */ -const STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED - */ -const STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN - */ -const STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN = UNKNOWN; -/** - * @var int - * @cvalue STREAM_ERROR_CODE_USERSPACE_CALL_FAILED - */ -const STREAM_ERROR_CODE_USERSPACE_CALL_FAILED = UNKNOWN; +class StreamException extends Exception +{ + private ?StreamError $error = null; + + public function getError(): ?StreamError {} +} diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 5c21a632755a9..1675213fd99ae 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,126 +1,174 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: afc1e2ff4a74033102ca8b69ea55ab9ce30986da */ + * Stub hash: c9a7e6c0b34de0c6ee76c2a9d80c50deafc8079a */ -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getParam, 0, 0, IS_STRING, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamErrorCode_isIoError, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() -#define arginfo_class_StreamException_getWrapperName arginfo_class_StreamException_getParam +#define arginfo_class_StreamErrorCode_isFileSystemError arginfo_class_StreamErrorCode_isIoError -static ZEND_METHOD(StreamException, getParam); -static ZEND_METHOD(StreamException, getWrapperName); +#define arginfo_class_StreamErrorCode_isWrapperError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isFilterError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isCastError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isNetworkError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isEncodingError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isResourceError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isLockError arginfo_class_StreamErrorCode_isIoError + +#define arginfo_class_StreamErrorCode_isUserspaceError arginfo_class_StreamErrorCode_isIoError + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamError_hasCode, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, code, StreamErrorCode, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamError_count, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_StreamException_getError, 0, 0, StreamError, 1) +ZEND_END_ARG_INFO() + +static ZEND_METHOD(StreamErrorCode, isIoError); +static ZEND_METHOD(StreamErrorCode, isFileSystemError); +static ZEND_METHOD(StreamErrorCode, isWrapperError); +static ZEND_METHOD(StreamErrorCode, isFilterError); +static ZEND_METHOD(StreamErrorCode, isCastError); +static ZEND_METHOD(StreamErrorCode, isNetworkError); +static ZEND_METHOD(StreamErrorCode, isEncodingError); +static ZEND_METHOD(StreamErrorCode, isResourceError); +static ZEND_METHOD(StreamErrorCode, isLockError); +static ZEND_METHOD(StreamErrorCode, isUserspaceError); +static ZEND_METHOD(StreamError, hasCode); +static ZEND_METHOD(StreamError, count); +static ZEND_METHOD(StreamException, getError); + +static const zend_function_entry class_StreamErrorCode_methods[] = { + ZEND_ME(StreamErrorCode, isIoError, arginfo_class_StreamErrorCode_isIoError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isFileSystemError, arginfo_class_StreamErrorCode_isFileSystemError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isWrapperError, arginfo_class_StreamErrorCode_isWrapperError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isFilterError, arginfo_class_StreamErrorCode_isFilterError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isCastError, arginfo_class_StreamErrorCode_isCastError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isNetworkError, arginfo_class_StreamErrorCode_isNetworkError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isEncodingError, arginfo_class_StreamErrorCode_isEncodingError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isResourceError, arginfo_class_StreamErrorCode_isResourceError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isLockError, arginfo_class_StreamErrorCode_isLockError, ZEND_ACC_PUBLIC) + ZEND_ME(StreamErrorCode, isUserspaceError, arginfo_class_StreamErrorCode_isUserspaceError, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static const zend_function_entry class_StreamError_methods[] = { + ZEND_ME(StreamError, hasCode, arginfo_class_StreamError_hasCode, ZEND_ACC_PUBLIC) + ZEND_ME(StreamError, count, arginfo_class_StreamError_count, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; static const zend_function_entry class_StreamException_methods[] = { - ZEND_ME(StreamException, getParam, arginfo_class_StreamException_getParam, ZEND_ACC_PUBLIC) - ZEND_ME(StreamException, getWrapperName, arginfo_class_StreamException_getWrapperName, ZEND_ACC_PUBLIC) + ZEND_ME(StreamException, getError, arginfo_class_StreamException_getError, ZEND_ACC_PUBLIC) ZEND_FE_END }; -static void register_stream_errors_symbols(int module_number) +static zend_class_entry *register_class_StreamErrorCode(void) { - REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_ERROR", PHP_STREAM_ERROR_MODE_ERROR, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_EXCEPTION", PHP_STREAM_ERROR_MODE_EXCEPTION, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_MODE_SILENT", PHP_STREAM_ERROR_MODE_SILENT, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_AUTO", PHP_STREAM_ERROR_STORE_AUTO, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_NONE", PHP_STREAM_ERROR_STORE_NONE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_NON_TERMINAL", PHP_STREAM_ERROR_STORE_NON_TERM, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_TERMINAL", PHP_STREAM_ERROR_STORE_TERMINAL, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_STORE_ALL", PHP_STREAM_ERROR_STORE_ALL, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NONE", STREAM_ERROR_CODE_NONE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_GENERIC", STREAM_ERROR_CODE_GENERIC, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_READ_FAILED", STREAM_ERROR_CODE_READ_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRITE_FAILED", STREAM_ERROR_CODE_WRITE_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SEEK_FAILED", STREAM_ERROR_CODE_SEEK_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED", STREAM_ERROR_CODE_SEEK_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_FLUSH_FAILED", STREAM_ERROR_CODE_FLUSH_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TRUNCATE_FAILED", STREAM_ERROR_CODE_TRUNCATE_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CONNECT_FAILED", STREAM_ERROR_CODE_CONNECT_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_BIND_FAILED", STREAM_ERROR_CODE_BIND_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_LISTEN_FAILED", STREAM_ERROR_CODE_LISTEN_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_WRITABLE", STREAM_ERROR_CODE_NOT_WRITABLE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_READABLE", STREAM_ERROR_CODE_NOT_READABLE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DISABLED", STREAM_ERROR_CODE_DISABLED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_FOUND", STREAM_ERROR_CODE_NOT_FOUND, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PERMISSION_DENIED", STREAM_ERROR_CODE_PERMISSION_DENIED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALREADY_EXISTS", STREAM_ERROR_CODE_ALREADY_EXISTS, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_PATH", STREAM_ERROR_CODE_INVALID_PATH, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PATH_TOO_LONG", STREAM_ERROR_CODE_PATH_TOO_LONG, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CREATE_FAILED", STREAM_ERROR_CODE_CREATE_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DUP_FAILED", STREAM_ERROR_CODE_DUP_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_OPEN_FAILED", STREAM_ERROR_CODE_OPEN_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_UNLINK_FAILED", STREAM_ERROR_CODE_UNLINK_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RENAME_FAILED", STREAM_ERROR_CODE_RENAME_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MKDIR_FAILED", STREAM_ERROR_CODE_MKDIR_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RMDIR_FAILED", STREAM_ERROR_CODE_RMDIR_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_STAT_FAILED", STREAM_ERROR_CODE_STAT_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_META_FAILED", STREAM_ERROR_CODE_META_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CHMOD_FAILED", STREAM_ERROR_CODE_CHMOD_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CHOWN_FAILED", STREAM_ERROR_CODE_CHOWN_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_COPY_FAILED", STREAM_ERROR_CODE_COPY_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TOUCH_FAILED", STREAM_ERROR_CODE_TOUCH_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_MODE", STREAM_ERROR_CODE_INVALID_MODE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_META", STREAM_ERROR_CODE_INVALID_META, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MODE_NOT_SUPPORTED", STREAM_ERROR_CODE_MODE_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_READONLY", STREAM_ERROR_CODE_READONLY, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RECURSION_DETECTED", STREAM_ERROR_CODE_RECURSION_DETECTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NOT_IMPLEMENTED", STREAM_ERROR_CODE_NOT_IMPLEMENTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NO_OPENER", STREAM_ERROR_CODE_NO_OPENER, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED", STREAM_ERROR_CODE_PERSISTENT_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_NOT_FOUND", STREAM_ERROR_CODE_WRAPPER_NOT_FOUND, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_DISABLED", STREAM_ERROR_CODE_WRAPPER_DISABLED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED", STREAM_ERROR_CODE_PROTOCOL_UNSUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED", STREAM_ERROR_CODE_WRAPPER_REGISTRATION_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED", STREAM_ERROR_CODE_WRAPPER_UNREGISTRATION_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED", STREAM_ERROR_CODE_WRAPPER_RESTORATION_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_FILTER_NOT_FOUND", STREAM_ERROR_CODE_FILTER_NOT_FOUND, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_FILTER_FAILED", STREAM_ERROR_CODE_FILTER_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CAST_FAILED", STREAM_ERROR_CODE_CAST_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_CAST_NOT_SUPPORTED", STREAM_ERROR_CODE_CAST_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED", STREAM_ERROR_CODE_MAKE_SEEKABLE_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_BUFFERED_DATA_LOST", STREAM_ERROR_CODE_BUFFERED_DATA_LOST, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NETWORK_SEND_FAILED", STREAM_ERROR_CODE_NETWORK_SEND_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_NETWORK_RECV_FAILED", STREAM_ERROR_CODE_NETWORK_RECV_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SSL_NOT_SUPPORTED", STREAM_ERROR_CODE_SSL_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_RESUMPTION_FAILED", STREAM_ERROR_CODE_RESUMPTION_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG", STREAM_ERROR_CODE_SOCKET_PATH_TOO_LONG, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_OOB_NOT_SUPPORTED", STREAM_ERROR_CODE_OOB_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_PROTOCOL_ERROR", STREAM_ERROR_CODE_PROTOCOL_ERROR, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_URL", STREAM_ERROR_CODE_INVALID_URL, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_RESPONSE", STREAM_ERROR_CODE_INVALID_RESPONSE, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_HEADER", STREAM_ERROR_CODE_INVALID_HEADER, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_PARAM", STREAM_ERROR_CODE_INVALID_PARAM, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_REDIRECT_LIMIT", STREAM_ERROR_CODE_REDIRECT_LIMIT, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_AUTH_FAILED", STREAM_ERROR_CODE_AUTH_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ARCHIVING_FAILED", STREAM_ERROR_CODE_ARCHIVING_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ENCODING_FAILED", STREAM_ERROR_CODE_ENCODING_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_DECODING_FAILED", STREAM_ERROR_CODE_DECODING_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_INVALID_FORMAT", STREAM_ERROR_CODE_INVALID_FORMAT, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_ALLOCATION_FAILED", STREAM_ERROR_CODE_ALLOCATION_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED", STREAM_ERROR_CODE_TEMPORARY_FILE_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_LOCK_FAILED", STREAM_ERROR_CODE_LOCK_FAILED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED", STREAM_ERROR_CODE_LOCK_NOT_SUPPORTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED", STREAM_ERROR_CODE_USERSPACE_NOT_IMPLEMENTED, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN", STREAM_ERROR_CODE_USERSPACE_INVALID_RETURN, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("STREAM_ERROR_CODE_USERSPACE_CALL_FAILED", STREAM_ERROR_CODE_USERSPACE_CALL_FAILED, CONST_PERSISTENT); + zend_class_entry *class_entry = zend_register_internal_enum("StreamErrorCode", IS_LONG, class_StreamErrorCode_methods); + + return class_entry; } -static zend_class_entry *register_class_StreamException(zend_class_entry *class_entry_Exception) +static zend_class_entry *register_class_StreamErrorMode(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("StreamErrorMode", IS_UNDEF, NULL); + + zend_enum_add_case_cstr(class_entry, "Error", NULL); + + zend_enum_add_case_cstr(class_entry, "Exception", NULL); + + zend_enum_add_case_cstr(class_entry, "Silent", NULL); + + return class_entry; +} + +static zend_class_entry *register_class_StreamErrorStore(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("StreamErrorStore", IS_UNDEF, NULL); + + zend_enum_add_case_cstr(class_entry, "Auto", NULL); + + zend_enum_add_case_cstr(class_entry, "None", NULL); + + zend_enum_add_case_cstr(class_entry, "NonTerminal", NULL); + + zend_enum_add_case_cstr(class_entry, "Terminal", NULL); + + zend_enum_add_case_cstr(class_entry, "All", NULL); + + return class_entry; +} + +static zend_class_entry *register_class_StreamError(void) { zend_class_entry ce, *class_entry; - INIT_CLASS_ENTRY(ce, "StreamException", class_StreamException_methods); - class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Exception, 0); + INIT_CLASS_ENTRY(ce, "StreamError", class_StreamError_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_READONLY_CLASS); + + zval property_code_default_value; + ZVAL_UNDEF(&property_code_default_value); + zend_string *property_code_class_StreamErrorCode = zend_string_init("StreamErrorCode", sizeof("StreamErrorCode")-1, 1); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_CODE), &property_code_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_code_class_StreamErrorCode, 0, 0)); + + zval property_message_default_value; + ZVAL_UNDEF(&property_message_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_MESSAGE), &property_message_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zval property_wrapperName_default_value; - ZVAL_NULL(&property_wrapperName_default_value); + ZVAL_UNDEF(&property_wrapperName_default_value); zend_string *property_wrapperName_name = zend_string_init("wrapperName", sizeof("wrapperName") - 1, true); - zend_declare_typed_property(class_entry, property_wrapperName_name, &property_wrapperName_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_declare_typed_property(class_entry, property_wrapperName_name, &property_wrapperName_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release_ex(property_wrapperName_name, true); + zval property_severity_default_value; + ZVAL_UNDEF(&property_severity_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_SEVERITY), &property_severity_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + + zval property_terminating_default_value; + ZVAL_UNDEF(&property_terminating_default_value); + zend_string *property_terminating_name = zend_string_init("terminating", sizeof("terminating") - 1, true); + zend_declare_typed_property(class_entry, property_terminating_name, &property_terminating_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_terminating_name, true); + zval property_param_default_value; - ZVAL_NULL(&property_param_default_value); + ZVAL_UNDEF(&property_param_default_value); zend_string *property_param_name = zend_string_init("param", sizeof("param") - 1, true); - zend_declare_typed_property(class_entry, property_param_name, &property_param_default_value, ZEND_ACC_PROTECTED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_declare_typed_property(class_entry, property_param_name, &property_param_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); zend_string_release_ex(property_param_name, true); + zval property_next_default_value; + ZVAL_UNDEF(&property_next_default_value); + zend_string *property_next_name = zend_string_init("next", sizeof("next") - 1, true); + zend_string *property_next_class_StreamError = zend_string_init("StreamError", sizeof("StreamError")-1, 1); + zend_declare_typed_property(class_entry, property_next_name, &property_next_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_next_class_StreamError, 0, MAY_BE_NULL)); + zend_string_release_ex(property_next_name, true); + + return class_entry; +} + +static zend_class_entry *register_class_StreamException(zend_class_entry *class_entry_Exception) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "StreamException", class_StreamException_methods); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Exception, 0); + + zval property_error_default_value; + ZVAL_NULL(&property_error_default_value); + zend_string *property_error_name = zend_string_init("error", sizeof("error") - 1, true); + zend_string *property_error_class_StreamError = zend_string_init("StreamError", sizeof("StreamError")-1, 1); + zend_declare_typed_property(class_entry, property_error_name, &property_error_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_error_class_StreamError, 0, MAY_BE_NULL)); + zend_string_release_ex(property_error_name, true); + return class_entry; } diff --git a/main/streams/streams.c b/main/streams/streams.c index 446602b08bd74..040ba79441e69 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1754,11 +1754,7 @@ void php_shutdown_stream_hashes(void) FG(wrapper_logged_errors) = NULL; } - if (FG(wrapper_stored_errors)) { - zend_hash_destroy(FG(wrapper_stored_errors)); - efree(FG(wrapper_stored_errors)); - FG(wrapper_stored_errors) = NULL; - } + php_stream_error_state_cleanup(); } zend_result php_init_stream_wrappers(int module_number) From 37d8abccef400a0ffd847fb7e19b9773ecae5a6f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 28 Dec 2025 17:31:44 +0100 Subject: [PATCH 43/61] stream: clean up and optimize wrapper errors --- main/streams/stream_errors.c | 627 ++++++++++++++++------------------- 1 file changed, 291 insertions(+), 336 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 5e17f6aa49146..78c10b491deb6 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -164,7 +164,7 @@ PHPAPI void php_stream_error_state_cleanup(void) FG(stream_error_state).current_operation = op->parent; php_stream_error_operation_free(op); } - + /* Clear stored errors */ php_stream_stored_error *stored = FG(stream_error_state).stored_errors; while (stored) { @@ -173,7 +173,7 @@ PHPAPI void php_stream_error_state_cleanup(void) efree(stored); stored = next; } - + FG(stream_error_state).stored_errors = NULL; FG(stream_error_state).stored_count = 0; FG(stream_error_state).operation_depth = 0; @@ -206,21 +206,16 @@ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_c } static void php_stream_error_add(StreamErrorCode code, const char *wrapper_name, - const char *message, const char *docref, const char *param, int severity, - bool terminating) + zend_string *message, const char *docref, char *param, int severity, bool terminating) { php_stream_error_operation *op = FG(stream_error_state).current_operation; - - if (!op) { - /* No operation context - skip */ - return; - } + ZEND_ASSERT(op != NULL); php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); - entry->message = zend_string_init(message, strlen(message), 0); + entry->message = message; entry->code = code; entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; - entry->param = param ? estrdup(param) : NULL; + entry->param = param; entry->param = docref ? estrdup(docref) : NULL; entry->severity = severity; entry->terminating = terminating; @@ -296,10 +291,11 @@ static void php_stream_report_errors(php_stream_context *context, php_stream_err php_stream_error_entry *entry = op->first_error; while (entry) { if (entry->param) { - php_error_docref1( - entry->docref, entry->param, entry->severity, "%s", ZSTR_VAL(entry->message)); + php_error_docref1(entry->docref, entry->param, entry->severity, "%s", + ZSTR_VAL(entry->message)); } else { - php_error_docref(entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message)); + php_error_docref( + entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message)); } entry = entry->next; } @@ -460,7 +456,7 @@ PHPAPI void php_stream_error_operation_abort(void) static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context, const char *docref, int options, int severity, bool terminating, int code, char *param, - const char *message) + zend_string *message) { /* If not in an operation, create one */ bool implicit_operation = (FG(stream_error_state).current_operation == NULL); @@ -478,335 +474,299 @@ static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stre } PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, - php_stream_context *context, const char *docref, int options, int severity, - bool terminating, int code, const char *fmt, ...) + php_stream_context *context, const char *docref, int options, int severity, + bool terminating, int code, const char *fmt, ...) { - if (!(options & REPORT_ERRORS)) { - return; - } - - va_list args; - va_start(args, fmt); - - char *message = NULL; - vspprintf(&message, 0, fmt, args); - va_end(args); - - php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, NULL, message); - - efree(message); + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, NULL, message); } -PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, - int severity, bool terminating, int code, const char *fmt, ...) +PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminating, int code, const char *fmt, + ...) { - if (!(options & REPORT_ERRORS)) { - return; - } - - va_list args; - va_start(args, fmt); - - char *message = NULL; - vspprintf(&message, 0, fmt, args); - va_end(args); - - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - - php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, NULL, message); - - efree(message); + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + php_stream_wrapper_error_internal( + wrapper_name, context, docref, options, severity, terminating, code, NULL, message); } -PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, - int severity, bool terminating, int code, const char *param, - const char *fmt, ...) +PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context, + const char *docref, int options, int severity, bool terminating, int code, + const char *param, const char *fmt, ...) { - if (!(options & REPORT_ERRORS)) { - return; - } - - va_list args; - va_start(args, fmt); - - char *message = NULL; - vspprintf(&message, 0, fmt, args); - va_end(args); - - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - char *param_copy = param ? estrdup(param) : NULL; - - php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, param_copy, message); - - efree(message); + if (!(options & REPORT_ERRORS)) { + return; + } + + va_list args; + va_start(args, fmt); + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + char *param_copy = param ? estrdup(param) : NULL; + + php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating, + code, param_copy, message); } PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper, - php_stream_context *context, const char *docref, int options, - int severity, bool terminating, int code, const char *param1, - const char *param2, const char *fmt, ...) + php_stream_context *context, const char *docref, int options, int severity, + bool terminating, int code, const char *param1, const char *param2, const char *fmt, ...) { - if (!(options & REPORT_ERRORS)) { - return; - } - - char *combined_param; - spprintf(&combined_param, 0, "%s,%s", param1, param2); - - va_list args; - va_start(args, fmt); - - char *message = NULL; - vspprintf(&message, 0, fmt, args); - va_end(args); - - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - - php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, combined_param, message); - - efree(message); + if (!(options & REPORT_ERRORS)) { + return; + } + + char *combined_param; + spprintf(&combined_param, 0, "%s,%s", param1, param2); + + va_list args; + va_start(args, fmt); + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating, + code, combined_param, message); } /* Stream error reporting - delegates to wrapper errors */ -PHPAPI void php_stream_error(php_stream *stream, const char *docref, - int severity, bool terminating, int code, const char *fmt, ...) +PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity, bool terminating, + int code, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - - char *message = NULL; - vspprintf(&message, 0, fmt, args); - va_end(args); - - const char *wrapper_name = stream->wrapper - ? stream->wrapper->wops->label - : "stream"; - - php_stream_context *context = PHP_STREAM_CONTEXT(stream); - - /* Just call the wrapper error function */ - php_stream_wrapper_error_internal( - wrapper_name, - context, - docref, - REPORT_ERRORS, - severity, - terminating, - code, - NULL, - message - ); - - efree(message); + va_list args; + va_start(args, fmt); + + zend_string *message = vstrpprintf(0, fmt, args); + va_end(args); + + const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream"; + + php_stream_context *context = PHP_STREAM_CONTEXT(stream); + + /* Just call the wrapper error function */ + php_stream_wrapper_error_internal(wrapper_name, context, docref, REPORT_ERRORS, severity, + terminating, code, NULL, message); } /* Legacy wrapper error logging */ static void php_stream_error_entry_dtor_legacy(void *error) { - php_stream_error_entry *entry = *(php_stream_error_entry **) error; - zend_string_release(entry->message); - efree(entry->wrapper_name); - efree(entry->param); + php_stream_error_entry *entry = *(php_stream_error_entry **) error; + zend_string_release(entry->message); + efree(entry->wrapper_name); + efree(entry->param); efree(entry->docref); - efree(entry); + efree(entry); } static void php_stream_error_list_dtor(zval *item) { - zend_llist *list = (zend_llist *) Z_PTR_P(item); - zend_llist_destroy(list); - efree(list); + zend_llist *list = (zend_llist *) Z_PTR_P(item); + zend_llist_destroy(list); + efree(list); } static void php_stream_wrapper_log_store_error(zend_string *message, int code, - const char *wrapper_name, const char *param, int severity, bool terminating) + const char *wrapper_name, const char *param, int severity, bool terminating) { - char *param_copy = param ? estrdup(param) : NULL; - - php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); - entry->message = zend_string_copy(message); - entry->code = code; - entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; - entry->param = param_copy; - entry->severity = severity; - entry->terminating = terminating; - entry->next = NULL; - - if (!FG(wrapper_logged_errors)) { - ALLOC_HASHTABLE(FG(wrapper_logged_errors)); - zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); - } - - zend_llist *list = zend_hash_str_find_ptr( - FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); - - if (!list) { - zend_llist new_list; - zend_llist_init(&new_list, sizeof(php_stream_error_entry *), - php_stream_error_entry_dtor_legacy, 0); - list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name, - strlen(wrapper_name), &new_list, sizeof(new_list)); - } - - zend_llist_add_element(list, &entry); + char *param_copy = param ? estrdup(param) : NULL; + + php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + entry->message = message; + entry->code = code; + entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; + entry->param = param_copy; + entry->severity = severity; + entry->terminating = terminating; + entry->next = NULL; + + if (!FG(wrapper_logged_errors)) { + ALLOC_HASHTABLE(FG(wrapper_logged_errors)); + zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0); + } + + zend_llist *list + = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + + if (!list) { + zend_llist new_list; + zend_llist_init( + &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0); + list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name, + strlen(wrapper_name), &new_list, sizeof(new_list)); + } + + zend_llist_add_element(list, &entry); } static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminating, - int code, char *param, const char *fmt, va_list args) + php_stream_context *context, int options, int severity, bool terminating, int code, + char *param, const char *fmt, va_list args) { - zend_string *message = vstrpprintf(0, fmt, args); - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - - if (options & REPORT_ERRORS) { - /* Report immediately */ - php_stream_wrapper_error_internal( - wrapper_name, context, NULL, options, severity, terminating, code, param, ZSTR_VAL(message)); - } else { - /* Store for later display */ - php_stream_wrapper_log_store_error(message, code, wrapper_name, param, severity, terminating); - } - - zend_string_release(message); + zend_string *message = vstrpprintf(0, fmt, args); + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + + if (options & REPORT_ERRORS) { + /* Report immediately */ + php_stream_wrapper_error_internal( + wrapper_name, context, NULL, options, severity, terminating, code, param, message); + } else { + /* Store for later display */ + php_stream_wrapper_log_store_error( + message, code, wrapper_name, param, severity, terminating); + } } PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminating, - int code, const char *fmt, ...) + php_stream_context *context, int options, int severity, bool terminating, int code, + const char *fmt, ...) { - va_list args; - va_start(args, fmt); - php_stream_wrapper_log_error_internal( - wrapper, context, options, severity, terminating, code, NULL, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminating, code, NULL, fmt, args); + va_end(args); } PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper, - php_stream_context *context, int options, int severity, bool terminating, - int code, const char *param, const char *fmt, ...) + php_stream_context *context, int options, int severity, bool terminating, int code, + const char *param, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - char *param_copy = param ? estrdup(param) : NULL; - php_stream_wrapper_log_error_internal( - wrapper, context, options, severity, terminating, code, param_copy, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + char *param_copy = param ? estrdup(param) : NULL; + php_stream_wrapper_log_error_internal( + wrapper, context, options, severity, terminating, code, param_copy, fmt, args); + va_end(args); } static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name) { - if (!FG(wrapper_logged_errors)) { - return NULL; - } - return (zend_llist *) zend_hash_str_find_ptr( - FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + if (!FG(wrapper_logged_errors)) { + return NULL; + } + return (zend_llist *) zend_hash_str_find_ptr( + FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); } PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, - php_stream_context *context, int code, const char *path, const char *caption) + php_stream_context *context, int code, const char *path, const char *caption) { - char *msg; - char errstr[256]; - int free_msg = 0; - - if (EG(exception)) { - return; - } - - char *tmp = estrdup(path); - if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) { - zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); - if (err_list) { - size_t l = 0; - int brlen; - int i; - int count = (int) zend_llist_count(err_list); - const char *br; - php_stream_error_entry **err_entry_p; - zend_llist_position pos; - - if (PG(html_errors)) { - brlen = 7; - br = "
\n"; - } else { - brlen = 1; - br = "\n"; - } - - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - l += ZSTR_LEN((*err_entry_p)->message); - if (i < count - 1) { - l += brlen; - } - } - msg = emalloc(l + 1); - msg[0] = '\0'; - for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; - err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { - strcat(msg, ZSTR_VAL((*err_entry_p)->message)); - if (i < count - 1) { - strcat(msg, br); - } - } - - free_msg = 1; - } else { - if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { - msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); - } else { - msg = "operation failed"; - } - } - } else { - msg = "no suitable wrapper could be found"; - } - - php_strip_url_passwd(tmp); - - char *full_msg; - spprintf(&full_msg, 0, "%s: %s", caption, msg); - - php_stream_wrapper_error_internal( - wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true, code, tmp, full_msg); - - efree(full_msg); - - if (free_msg) { - efree(msg); - } + char *msg; + char errstr[256]; + int free_msg = 0; + + if (EG(exception)) { + return; + } + + char *tmp = estrdup(path); + if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) { + zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name); + if (err_list) { + size_t l = 0; + int brlen; + int i; + int count = (int) zend_llist_count(err_list); + const char *br; + php_stream_error_entry **err_entry_p; + zend_llist_position pos; + + if (PG(html_errors)) { + brlen = 7; + br = "
\n"; + } else { + brlen = 1; + br = "\n"; + } + + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + l += ZSTR_LEN((*err_entry_p)->message); + if (i < count - 1) { + l += brlen; + } + } + msg = emalloc(l + 1); + msg[0] = '\0'; + for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p; + err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) { + strcat(msg, ZSTR_VAL((*err_entry_p)->message)); + if (i < count - 1) { + strcat(msg, br); + } + } + + free_msg = 1; + } else { + if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) { + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); + } else { + msg = "operation failed"; + } + } + } else { + msg = "no suitable wrapper could be found"; + } + + php_strip_url_passwd(tmp); + + zend_string *message = strpprintf(0, "%s: %s", caption, msg); + + php_stream_wrapper_error_internal( + wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true, code, tmp, message); + + if (free_msg) { + efree(msg); + } } PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, - php_stream_context *context, int code, const char *path, const char *caption) + php_stream_context *context, int code, const char *path, const char *caption) { - if (wrapper) { - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption); - } + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption); + } } PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name) { - if (FG(wrapper_logged_errors)) { - zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); - } + if (FG(wrapper_logged_errors)) { + zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name)); + } } PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper) { - if (wrapper) { - const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); - php_stream_tidy_wrapper_name_error_log(wrapper_name); - } + if (wrapper) { + const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); + php_stream_tidy_wrapper_name_error_log(wrapper_name); + } } /* StreamError object creation - no enum cache */ @@ -874,75 +834,70 @@ PHPAPI void php_stream_error_create_object(zval *zv, php_stream_error_entry *ent } } - /* StreamError methods */ PHP_METHOD(StreamError, hasCode) { - zval *search_code; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_OBJECT_OF_CLASS(search_code, php_ce_stream_error_code) - ZEND_PARSE_PARAMETERS_END(); - - zval *current_error_zv = ZEND_THIS; - - while (Z_TYPE_P(current_error_zv) == IS_OBJECT) { - zval *code_zv = zend_read_property( - php_ce_stream_error, Z_OBJ_P(current_error_zv), - ZEND_STRL("code"), 1, NULL); - - /* Compare enum objects */ - if (Z_TYPE_P(code_zv) == IS_OBJECT && Z_OBJ_P(code_zv) == Z_OBJ_P(search_code)) { - RETURN_TRUE; - } - - /* Move to next error */ - current_error_zv = zend_read_property( - php_ce_stream_error, Z_OBJ_P(current_error_zv), - ZEND_STRL("next"), 1, NULL); - - if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { - break; - } - } - - RETURN_FALSE; + zval *search_code; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(search_code, php_ce_stream_error_code) + ZEND_PARSE_PARAMETERS_END(); + + zval *current_error_zv = ZEND_THIS; + + while (Z_TYPE_P(current_error_zv) == IS_OBJECT) { + zval *code_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), ZEND_STRL("code"), 1, NULL); + + /* Compare enum objects */ + if (Z_TYPE_P(code_zv) == IS_OBJECT && Z_OBJ_P(code_zv) == Z_OBJ_P(search_code)) { + RETURN_TRUE; + } + + /* Move to next error */ + current_error_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), ZEND_STRL("next"), 1, NULL); + + if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { + break; + } + } + + RETURN_FALSE; } PHP_METHOD(StreamError, count) { - ZEND_PARSE_PARAMETERS_NONE(); - - zend_long count = 1; - zval *current_error_zv = ZEND_THIS; - - while (1) { - current_error_zv = zend_read_property( - php_ce_stream_error, Z_OBJ_P(current_error_zv), - ZEND_STRL("next"), 1, NULL); - - if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { - break; - } - - count++; - } - - RETURN_LONG(count); + ZEND_PARSE_PARAMETERS_NONE(); + + zend_long count = 1; + zval *current_error_zv = ZEND_THIS; + + while (1) { + current_error_zv = zend_read_property( + php_ce_stream_error, Z_OBJ_P(current_error_zv), ZEND_STRL("next"), 1, NULL); + + if (Z_TYPE_P(current_error_zv) != IS_OBJECT) { + break; + } + + count++; + } + + RETURN_LONG(count); } /* StreamException methods */ PHP_METHOD(StreamException, getError) { - ZEND_PARSE_PARAMETERS_NONE(); - - zval *error = zend_read_property( - php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), - ZEND_STRL("error"), 1, NULL); - - RETURN_COPY(error); + ZEND_PARSE_PARAMETERS_NONE(); + + zval *error = zend_read_property( + php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("error"), 1, NULL); + + RETURN_COPY(error); } /* StreamErrorCode helper */ From cc5461f054aa62a28595cda756558029aadf0147 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 28 Dec 2025 18:04:22 +0100 Subject: [PATCH 44/61] stream: fix test names --- ...de_helpers copy.phpt => stream_errors_error_code_helpers.phpt} | 0 ...error_has_code copy.phpt => stream_errors_error_has_code.phpt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename ext/standard/tests/streams/{stream_errors_error_code_helpers copy.phpt => stream_errors_error_code_helpers.phpt} (100%) rename ext/standard/tests/streams/{stream_errors_error_has_code copy.phpt => stream_errors_error_has_code.phpt} (100%) diff --git a/ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt b/ext/standard/tests/streams/stream_errors_error_code_helpers.phpt similarity index 100% rename from ext/standard/tests/streams/stream_errors_error_code_helpers copy.phpt rename to ext/standard/tests/streams/stream_errors_error_code_helpers.phpt diff --git a/ext/standard/tests/streams/stream_errors_error_has_code copy.phpt b/ext/standard/tests/streams/stream_errors_error_has_code.phpt similarity index 100% rename from ext/standard/tests/streams/stream_errors_error_has_code copy.phpt rename to ext/standard/tests/streams/stream_errors_error_has_code.phpt From ab36995c14484389454d0304ce80b73ae4a4508d Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 28 Dec 2025 18:04:40 +0100 Subject: [PATCH 45/61] stream: fix docref initialization for stream errors --- main/streams/stream_errors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 78c10b491deb6..3c5c49b9300c7 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -216,7 +216,7 @@ static void php_stream_error_add(StreamErrorCode code, const char *wrapper_name, entry->code = code; entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; entry->param = param; - entry->param = docref ? estrdup(docref) : NULL; + entry->docref = docref ? estrdup(docref) : NULL; entry->severity = severity; entry->terminating = terminating; entry->next = NULL; From 79351a40e0bbede178c62a458183ad2845680ef9 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 28 Dec 2025 20:12:11 +0100 Subject: [PATCH 46/61] stream: optimize error operation handling --- main/streams/php_stream_errors.h | 11 ++- main/streams/stream_errors.c | 130 ++++++++++++++++++++++++------- 2 files changed, 110 insertions(+), 31 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index 783e9b5150e3f..ded43ce29a642 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -35,7 +35,9 @@ BEGIN_EXTERN_C() #define PHP_STREAM_ERROR_STORE_ALL 4 /* Maximum operation nesting depth */ -#define PHP_STREAM_ERROR_MAX_DEPTH 100 +#define PHP_STREAM_ERROR_MAX_DEPTH 1000 +/* Operations pool size to prevent extra allocations */ +#define PHP_STREAM_ERROR_OPERATION_POOL_SIZE 8 /* Error code ranges */ #define STREAM_ERROR_CODE_IO_START 10 @@ -194,11 +196,18 @@ typedef struct { /* List of completed/stored operations (most recent first) */ php_stream_stored_error *stored_errors; uint32_t stored_count; + /* Pre-allocated operation pool */ + php_stream_error_operation operation_pool[PHP_STREAM_ERROR_OPERATION_POOL_SIZE]; + /* Overflow operations (for deep nesting beyond pool size) */ + php_stream_error_operation *overflow_operations; + uint32_t overflow_capacity; } php_stream_error_state; /* Error operation management */ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context); PHPAPI void php_stream_error_operation_end(php_stream_context *context); +PHPAPI php_stream_error_operation *php_stream_error_operation_begin_for_stream(php_stream *stream); +PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream); PHPAPI void php_stream_error_operation_abort(void); /* State cleanup function */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 3c5c49b9300c7..3b2546f730d09 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -158,15 +158,25 @@ static void php_stream_error_operation_free(php_stream_error_operation *op) /* Cleanup function for request shutdown */ PHPAPI void php_stream_error_state_cleanup(void) { - /* Clear active operations (shouldn't normally have any, but clean up just in case) */ - while (FG(stream_error_state).current_operation) { - php_stream_error_operation *op = FG(stream_error_state).current_operation; - FG(stream_error_state).current_operation = op->parent; - php_stream_error_operation_free(op); + php_stream_error_state *state = &FG(stream_error_state); + + /* Clear active operations */ + while (state->current_operation) { + php_stream_error_operation *op = state->current_operation; + state->current_operation = op->parent; + + /* Free errors */ + php_stream_error_entry_free(op->first_error); + + /* Reset operation */ + op->first_error = NULL; + op->last_error = NULL; + op->error_count = 0; + op->parent = NULL; } /* Clear stored errors */ - php_stream_stored_error *stored = FG(stream_error_state).stored_errors; + php_stream_stored_error *stored = state->stored_errors; while (stored) { php_stream_stored_error *next = stored->next; php_stream_error_entry_free(stored->first_error); @@ -174,37 +184,72 @@ PHPAPI void php_stream_error_state_cleanup(void) stored = next; } - FG(stream_error_state).stored_errors = NULL; - FG(stream_error_state).stored_count = 0; - FG(stream_error_state).operation_depth = 0; -} + state->stored_errors = NULL; + state->stored_count = 0; + state->operation_depth = 0; + /* Free overflow operations array */ + if (state->overflow_operations) { + efree(state->overflow_operations); + state->overflow_operations = NULL; + state->overflow_capacity = 0; + } +} /* Error operation stack management */ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context) { + php_stream_error_state *state = &FG(stream_error_state); + /* Check depth limit */ - if (FG(stream_error_state).operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) { + if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) { php_error_docref(NULL, E_WARNING, "Stream error operation depth exceeded (%u), possible infinite recursion", - FG(stream_error_state).operation_depth); + state->operation_depth); return NULL; } - /* Create new operation with empty error list */ - php_stream_error_operation *op = emalloc(sizeof(php_stream_error_operation)); + php_stream_error_operation *op; + + /* Try to use pre-allocated pool first */ + if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) { + op = &state->operation_pool[state->operation_depth]; + } else { + /* Need overflow allocation */ + uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE; + + /* Grow overflow array if needed */ + if (overflow_index >= state->overflow_capacity) { + uint32_t new_capacity + = state->overflow_capacity == 0 ? 8 : state->overflow_capacity * 2; + php_stream_error_operation *new_overflow = erealloc( + state->overflow_operations, sizeof(php_stream_error_operation) * new_capacity); + state->overflow_operations = new_overflow; + state->overflow_capacity = new_capacity; + } + + op = &state->overflow_operations[overflow_index]; + } + + /* Initialize operation */ op->first_error = NULL; op->last_error = NULL; op->error_count = 0; - op->parent = FG(stream_error_state).current_operation; + op->parent = state->current_operation; /* Push onto stack */ - FG(stream_error_state).current_operation = op; - FG(stream_error_state).operation_depth++; + state->current_operation = op; + state->operation_depth++; return op; } +PHPAPI php_stream_error_operation *php_stream_error_operation_begin_for_stream(php_stream *stream) +{ + /* Don't fetch context yet - will be fetched in _end_for_stream if needed */ + return php_stream_error_operation_begin(NULL); +} + static void php_stream_error_add(StreamErrorCode code, const char *wrapper_name, zend_string *message, const char *docref, char *param, int severity, bool terminating) { @@ -334,15 +379,16 @@ static void php_stream_report_errors(php_stream_context *context, php_stream_err PHPAPI void php_stream_error_operation_end(php_stream_context *context) { - php_stream_error_operation *op = FG(stream_error_state).current_operation; + php_stream_error_state *state = &FG(stream_error_state); + php_stream_error_operation *op = state->current_operation; if (!op) { return; } /* Pop from stack */ - FG(stream_error_state).current_operation = op->parent; - FG(stream_error_state).operation_depth--; + state->current_operation = op->parent; + state->operation_depth--; /* Process errors if we have any */ if (op->error_count > 0) { @@ -359,8 +405,6 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) if (store_mode == PHP_STREAM_ERROR_STORE_NONE) { /* Free all errors */ php_stream_error_entry_free(op->first_error); - op->first_error = NULL; - op->last_error = NULL; } else { /* Filter and store */ php_stream_error_entry *entry = op->first_error; @@ -408,7 +452,6 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) efree(entry->param); efree(entry); - /* Don't update prev */ entry = next; continue; } @@ -421,21 +464,48 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) if (to_store_first) { php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error)); stored->first_error = to_store_first; - stored->next = FG(stream_error_state).stored_errors; + stored->next = state->stored_errors; - FG(stream_error_state).stored_errors = stored; - FG(stream_error_state).stored_count++; + state->stored_errors = stored; + state->stored_count++; } /* Free any remaining errors not moved to storage */ php_stream_error_entry_free(op->first_error); - op->first_error = NULL; - op->last_error = NULL; } } - /* Free operation structure */ - efree(op); + /* Reset operation for reuse (don't free if from pool or overflow) */ + op->first_error = NULL; + op->last_error = NULL; + op->error_count = 0; + op->parent = NULL; +} + +PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) +{ + php_stream_error_state *state = &FG(stream_error_state); + php_stream_error_operation *op = state->current_operation; + + if (!op) { + return; + } + + /* Fast path: no errors - just pop and return */ + if (op->error_count == 0) { + state->current_operation = op->parent; + state->operation_depth--; + + /* Reset operation for reuse */ + op->first_error = NULL; + op->last_error = NULL; + op->parent = NULL; + return; + } + + /* Slow path: have errors - fetch context and process */ + php_stream_context *context = PHP_STREAM_CONTEXT(stream); + php_stream_error_operation_end(context); } PHPAPI void php_stream_error_operation_abort(void) From b5937f05d883509bf079298ceea642c2da0cc5c8 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 00:21:12 +0100 Subject: [PATCH 47/61] stream: modify error begin and end api --- main/streams/php_stream_errors.h | 3 +-- main/streams/stream_errors.c | 8 +------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index ded43ce29a642..d34e549632a1a 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -204,9 +204,8 @@ typedef struct { } php_stream_error_state; /* Error operation management */ -PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context); +PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void); PHPAPI void php_stream_error_operation_end(php_stream_context *context); -PHPAPI php_stream_error_operation *php_stream_error_operation_begin_for_stream(php_stream *stream); PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream); PHPAPI void php_stream_error_operation_abort(void); diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 3b2546f730d09..a54a0c62f290d 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -197,7 +197,7 @@ PHPAPI void php_stream_error_state_cleanup(void) } /* Error operation stack management */ -PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_context *context) +PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void) { php_stream_error_state *state = &FG(stream_error_state); @@ -244,12 +244,6 @@ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(php_stream_c return op; } -PHPAPI php_stream_error_operation *php_stream_error_operation_begin_for_stream(php_stream *stream) -{ - /* Don't fetch context yet - will be fetched in _end_for_stream if needed */ - return php_stream_error_operation_begin(NULL); -} - static void php_stream_error_add(StreamErrorCode code, const char *wrapper_name, zend_string *message, const char *docref, char *param, int severity, bool terminating) { From 5275e8880c84fdd56341ca99562dd57521c47bcd Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 00:21:54 +0100 Subject: [PATCH 48/61] stream: add error begin and end to standard/file.c --- ext/standard/file.c | 138 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 25 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index b908f48645068..ead6faddece39 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -223,7 +223,9 @@ PHP_FUNCTION(flock) Z_PARAM_ZVAL(wouldblock) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); php_flock_common(stream, operation, 2, wouldblock, return_value); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -259,6 +261,8 @@ PHP_FUNCTION(get_meta_tags) RETURN_FALSE; } + php_stream_error_operation_begin(); + array_init(return_value); tok_last = TOK_EOF; @@ -370,6 +374,8 @@ PHP_FUNCTION(get_meta_tags) if (value) efree(value); if (name) efree(name); php_stream_close(md.stream); + + php_stream_error_operation_end_for_stream(md.stream); } /* }}} */ @@ -404,12 +410,13 @@ PHP_FUNCTION(file_get_contents) RETURN_THROWS(); } + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, 0); - stream = php_stream_open_wrapper_ex(filename, "rb", (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context); if (!stream) { + php_stream_error_operation_end(context); RETURN_FALSE; } @@ -422,6 +429,7 @@ PHP_FUNCTION(file_get_contents) if (offset != 0 && php_stream_seek(stream, offset, ((offset > 0) ? SEEK_SET : SEEK_END)) < 0) { php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", offset); php_stream_close(stream); + php_stream_error_operation_end(context); RETURN_FALSE; } @@ -432,6 +440,7 @@ PHP_FUNCTION(file_get_contents) } php_stream_close(stream); + php_stream_error_operation_end(context); } /* }}} */ @@ -461,6 +470,7 @@ PHP_FUNCTION(file_put_contents) php_stream_from_zval(srcstream, data); } + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); if (flags & PHP_FILE_APPEND) { @@ -479,12 +489,14 @@ PHP_FUNCTION(file_put_contents) stream = php_stream_open_wrapper_ex(filename, mode, ((flags & PHP_FILE_USE_INCLUDE_PATH) ? USE_PATH : 0) | REPORT_ERRORS, NULL, context); if (stream == NULL) { + php_stream_error_operation_end(context); RETURN_FALSE; } if ((flags & LOCK_EX) && (!php_stream_supports_lock(stream) || php_stream_lock(stream, LOCK_EX))) { php_stream_close(stream); php_error_docref(NULL, E_WARNING, "Exclusive locks are not supported for this stream"); + php_stream_error_operation_end(context); RETURN_FALSE; } @@ -566,6 +578,7 @@ PHP_FUNCTION(file_put_contents) break; } php_stream_close(stream); + php_stream_error_operation_end(context); if (numbytes < 0) { RETURN_FALSE; @@ -611,10 +624,12 @@ PHP_FUNCTION(file) include_new_line = !(flags & PHP_FILE_IGNORE_NEW_LINES); skip_blank_lines = flags & PHP_FILE_SKIP_EMPTY_LINES; + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); stream = php_stream_open_wrapper_ex(filename, "rb", (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context); if (!stream) { + php_stream_error_operation_end(context); RETURN_FALSE; } @@ -668,6 +683,7 @@ PHP_FUNCTION(file) } php_stream_close(stream); + php_stream_error_operation_end(context); } /* }}} */ @@ -707,7 +723,9 @@ PHP_FUNCTION(tmpfile) ZEND_PARSE_PARAMETERS_NONE(); + php_stream_error_operation_begin(); stream = php_stream_fopen_tmpfile(); + php_stream_error_operation_end_for_stream(stream); if (stream) { php_stream_to_zval(stream, return_value); @@ -735,9 +753,11 @@ PHP_FUNCTION(fopen) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, 0); stream = php_stream_open_wrapper_ex(filename, mode, (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context); + php_stream_error_operation_end(context); if (stream == NULL) { RETURN_FALSE; @@ -761,9 +781,11 @@ PHPAPI PHP_FUNCTION(fclose) RETURN_FALSE; } + php_stream_error_operation_begin(); php_stream_free(stream, PHP_STREAM_FREE_KEEP_RSRC | (stream->is_persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE)); + php_stream_error_operation_end_for_stream(stream); RETURN_TRUE; } @@ -811,6 +833,7 @@ PHP_FUNCTION(popen) RETURN_FALSE; } + php_stream_error_operation_begin(); stream = php_stream_fopen_from_pipe(fp, mode); if (stream == NULL) { @@ -819,6 +842,7 @@ PHP_FUNCTION(popen) } else { php_stream_to_zval(stream, return_value); } + php_stream_error_operation_end_for_stream(stream); efree(posix_mode); } @@ -833,9 +857,11 @@ PHP_FUNCTION(pclose) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); FG(pclose_wait) = 1; zend_list_close(stream->res); FG(pclose_wait) = 0; + php_stream_error_operation_end_for_stream(stream); RETURN_LONG(FG(pclose_ret)); } /* }}} */ @@ -849,11 +875,13 @@ PHPAPI PHP_FUNCTION(feof) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); if (php_stream_eof(stream)) { - RETURN_TRUE; + RETVAL_TRUE; } else { - RETURN_FALSE; + RETVAL_FALSE; } + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -873,9 +901,11 @@ PHPAPI PHP_FUNCTION(fgets) Z_PARAM_LONG_OR_NULL(len, len_is_null) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); if (len_is_null) { /* ask streams to give us a buffer of an appropriate size */ buf = php_stream_get_line(stream, NULL, 0, &line_len); + php_stream_error_operation_end_for_stream(stream); if (buf == NULL) { RETURN_FALSE; } @@ -889,7 +919,9 @@ PHPAPI PHP_FUNCTION(fgets) } str = zend_string_alloc(len, 0); - if (php_stream_get_line(stream, ZSTR_VAL(str), len, &line_len) == NULL) { + buf = php_stream_get_line(stream, ZSTR_VAL(str), len, &line_len); + php_stream_error_operation_end_for_stream(stream); + if (buf == NULL) { zend_string_efree(str); RETURN_FALSE; } @@ -914,7 +946,9 @@ PHPAPI PHP_FUNCTION(fgetc) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); int result = php_stream_getc(stream); + php_stream_error_operation_end_for_stream(stream); if (result == EOF) { RETVAL_FALSE; @@ -933,7 +967,6 @@ PHP_FUNCTION(fscanf) zval *file_handle; char *buf, *format; size_t len; - void *what; ZEND_PARSE_PARAMETERS_START(2, -1) Z_PARAM_RESOURCE(file_handle) @@ -941,16 +974,18 @@ PHP_FUNCTION(fscanf) Z_PARAM_VARIADIC('*', args, argc) ZEND_PARSE_PARAMETERS_END(); - what = zend_fetch_resource2(Z_RES_P(file_handle), "File-Handle", php_file_le_stream(), php_file_le_pstream()); + php_stream *stream = zend_fetch_resource2(Z_RES_P(file_handle), "File-Handle", php_file_le_stream(), php_file_le_pstream()); - /* we can't do a ZEND_VERIFY_RESOURCE(what), otherwise we end up + /* we can't do a ZEND_VERIFY_RESOURCE(stream), otherwise we end up * with a leak if we have an invalid filehandle. This needs changing * if the code behind ZEND_VERIFY_RESOURCE changed. - cc */ - if (!what) { + if (!stream) { RETURN_THROWS(); } - buf = php_stream_get_line((php_stream *) what, NULL, 0, &len); + php_stream_error_operation_begin(); + buf = php_stream_get_line(stream, NULL, 0, &len); + php_stream_error_operation_end_for_stream(stream); if (buf == NULL) { RETURN_FALSE; } @@ -996,7 +1031,9 @@ PHPAPI PHP_FUNCTION(fwrite) RETURN_LONG(0); } + php_stream_error_operation_begin(); ret = php_stream_write(stream, input, num_bytes); + php_stream_error_operation_end_for_stream(stream); if (ret < 0) { RETURN_FALSE; } @@ -1015,7 +1052,9 @@ PHPAPI PHP_FUNCTION(fflush) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); ret = php_stream_flush(stream); + php_stream_error_operation_end_for_stream(stream); if (ret) { RETURN_FALSE; } @@ -1026,13 +1065,17 @@ PHPAPI PHP_FUNCTION(fflush) /* {{{ Rewind the position of a file pointer */ PHPAPI PHP_FUNCTION(rewind) { + int ret; php_stream *stream; ZEND_PARSE_PARAMETERS_START(1, 1) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); - if (-1 == php_stream_rewind(stream)) { + php_stream_error_operation_begin(); + ret = php_stream_rewind(stream); + php_stream_error_operation_end_for_stream(stream); + if (-1 == ret) { RETURN_FALSE; } RETURN_TRUE; @@ -1049,7 +1092,9 @@ PHPAPI PHP_FUNCTION(ftell) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); ret = php_stream_tell(stream); + php_stream_error_operation_end_for_stream(stream); if (ret == -1) { RETURN_FALSE; } @@ -1070,7 +1115,9 @@ PHPAPI PHP_FUNCTION(fseek) Z_PARAM_LONG(whence) ZEND_PARSE_PARAMETERS_END(); - RETURN_LONG(php_stream_seek(stream, offset, (int) whence)); + php_stream_error_operation_begin(); + RETVAL_LONG(php_stream_seek(stream, offset, (int) whence)); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -1094,7 +1141,9 @@ PHP_FUNCTION(mkdir) context = php_stream_context_from_zval(zcontext, 0); - RETURN_BOOL(php_stream_mkdir(dir, (int)mode, (recursive ? PHP_STREAM_MKDIR_RECURSIVE : 0) | REPORT_ERRORS, context)); + php_stream_error_operation_begin(); + RETVAL_BOOL(php_stream_mkdir(dir, (int)mode, (recursive ? PHP_STREAM_MKDIR_RECURSIVE : 0) | REPORT_ERRORS, context)); + php_stream_error_operation_end(context); } /* }}} */ @@ -1114,7 +1163,9 @@ PHP_FUNCTION(rmdir) context = php_stream_context_from_zval(zcontext, 0); - RETURN_BOOL(php_stream_rmdir(dir, REPORT_ERRORS, context)); + php_stream_error_operation_begin(); + RETVAL_BOOL(php_stream_rmdir(dir, REPORT_ERRORS, context)); + php_stream_error_operation_end(context); } /* }}} */ @@ -1138,14 +1189,17 @@ PHP_FUNCTION(readfile) context = php_stream_context_from_zval(zcontext, 0); + php_stream_error_operation_begin(); stream = php_stream_open_wrapper_ex(filename, "rb", (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context); if (stream) { size = php_stream_passthru(stream); php_stream_close(stream); - RETURN_LONG(size); + RETVAL_LONG(size); + } else { + RETVAL_FALSE; } + php_stream_error_operation_end(context); - RETURN_FALSE; } /* }}} */ @@ -1187,7 +1241,9 @@ PHPAPI PHP_FUNCTION(fpassthru) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); size = php_stream_passthru(stream); + php_stream_error_operation_end_for_stream(stream); RETURN_LONG(size); } /* }}} */ @@ -1208,26 +1264,31 @@ PHP_FUNCTION(rename) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); + context = php_stream_context_from_zval(zcontext, 0); + wrapper = php_stream_locate_url_wrapper(old_name, NULL, 0); if (!wrapper || !wrapper->wops) { php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); + php_stream_error_operation_end(context); RETURN_FALSE; } if (!wrapper->wops->rename) { php_error_docref(NULL, E_WARNING, "%s wrapper does not support renaming", wrapper->wops->label ? wrapper->wops->label : "Source"); + php_stream_error_operation_end(context); RETURN_FALSE; } if (wrapper != php_stream_locate_url_wrapper(new_name, NULL, 0)) { php_error_docref(NULL, E_WARNING, "Cannot rename a file across wrapper types"); + php_stream_error_operation_end(context); RETURN_FALSE; } - context = php_stream_context_from_zval(zcontext, 0); - - RETURN_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, REPORT_ERRORS, context)); + RETVAL_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, REPORT_ERRORS, context)); + php_stream_error_operation_end(context); } /* }}} */ @@ -1246,20 +1307,24 @@ PHP_FUNCTION(unlink) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, 0); wrapper = php_stream_locate_url_wrapper(filename, NULL, 0); if (!wrapper || !wrapper->wops) { php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); + php_stream_error_operation_end(context); RETURN_FALSE; } if (!wrapper->wops->unlink) { php_error_docref(NULL, E_WARNING, "%s does not allow unlinking", wrapper->wops->label ? wrapper->wops->label : "Wrapper"); + php_stream_error_operation_end(context); RETURN_FALSE; } - RETURN_BOOL(wrapper->wops->unlink(wrapper, filename, REPORT_ERRORS, context)); + RETVAL_BOOL(wrapper->wops->unlink(wrapper, filename, REPORT_ERRORS, context)); + php_stream_error_operation_end(context); } /* }}} */ @@ -1271,12 +1336,15 @@ PHP_FUNCTION(fsync) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); if (!php_stream_sync_supported(stream)) { php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } - RETURN_BOOL(php_stream_sync(stream, /* data_only */ 0) == 0); + RETVAL_BOOL(php_stream_sync(stream, /* data_only */ 0) == 0); + php_stream_error_operation_end_for_stream(stream); } PHP_FUNCTION(fdatasync) @@ -1287,12 +1355,15 @@ PHP_FUNCTION(fdatasync) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); if (!php_stream_sync_supported(stream)) { php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } - RETURN_BOOL(php_stream_sync(stream, /* data_only */ 1) == 0); + RETVAL_BOOL(php_stream_sync(stream, /* data_only */ 1) == 0); + php_stream_error_operation_end_for_stream(stream); } /* {{{ Truncate file to 'size' length */ @@ -1311,12 +1382,16 @@ PHP_FUNCTION(ftruncate) RETURN_THROWS(); } + php_stream_error_operation_begin(); + if (!php_stream_truncate_supported(stream)) { php_error_docref(NULL, E_WARNING, "Can't truncate this stream!"); + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } - RETURN_BOOL(0 == php_stream_truncate_set_size(stream, size)); + RETVAL_BOOL(0 == php_stream_truncate_set_size(stream, size)); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ PHPAPI void php_fstat(php_stream *stream, zval *return_value) @@ -1400,7 +1475,9 @@ PHP_FUNCTION(fstat) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); php_fstat(stream, return_value); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -1419,13 +1496,16 @@ PHP_FUNCTION(copy) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); + context = php_stream_context_from_zval(zcontext, 0); + if (php_stream_locate_url_wrapper(source, NULL, 0) == &php_plain_files_wrapper && php_check_open_basedir(source)) { + php_stream_error_operation_end(context); RETURN_FALSE; } - context = php_stream_context_from_zval(zcontext, 0); - - RETURN_BOOL(php_copy_file_ctx(source, target, 0, context) == SUCCESS); + RETVAL_BOOL(php_copy_file_ctx(source, target, 0, context) == SUCCESS); + php_stream_error_operation_end(context); } /* }}} */ @@ -1552,7 +1632,9 @@ PHPAPI PHP_FUNCTION(fread) RETURN_THROWS(); } + php_stream_error_operation_begin(); str = php_stream_read_to_str(stream, len); + php_stream_error_operation_end_for_stream(stream); if (!str) { RETURN_FALSE; } @@ -1671,7 +1753,9 @@ PHP_FUNCTION(fputcsv) RETURN_THROWS(); } + php_stream_error_operation_begin(); ret = php_fputcsv(stream, fields, delimiter, enclosure, escape_char, eol_str); + php_stream_error_operation_end_for_stream(stream); if (ret < 0) { RETURN_FALSE; } @@ -1804,19 +1888,23 @@ PHP_FUNCTION(fgetcsv) RETURN_THROWS(); } + php_stream_error_operation_begin(); if (len < 0) { if ((buf = php_stream_get_line(stream, NULL, 0, &buf_len)) == NULL) { + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } } else { buf = emalloc(len + 1); if (php_stream_get_line(stream, buf, len + 1, &buf_len) == NULL) { efree(buf); + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } } HashTable *values = php_fgetcsv(stream, delimiter, enclosure, escape_char, buf_len, buf); + php_stream_error_operation_end_for_stream(stream); if (values == NULL) { values = php_bc_fgetcsv_empty_line(); } From 3c3f25bb47dd21c4642fb9603d7a704c56e388ae Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 12:08:32 +0100 Subject: [PATCH 49/61] stream: fix and refactore errors operation parent handling --- main/streams/php_stream_errors.h | 1 - main/streams/stream_errors.c | 62 ++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index d34e549632a1a..4d64e3905034a 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -180,7 +180,6 @@ typedef struct _php_stream_error_operation { php_stream_error_entry *first_error; php_stream_error_entry *last_error; uint32_t error_count; - struct _php_stream_error_operation *parent; } php_stream_error_operation; /* Stored completed operation */ diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index a54a0c62f290d..94578ab6341e3 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -137,6 +137,34 @@ static bool php_stream_has_terminating_error(php_stream_error_operation *op) return false; } +/* Helper to get operation at specific depth */ +static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint32_t depth) +{ + php_stream_error_state *state = &FG(stream_error_state); + + if (depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) { + return &state->operation_pool[depth]; + } else { + uint32_t overflow_index = depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE; + ZEND_ASSERT(overflow_index < state->overflow_capacity); + return &state->overflow_operations[overflow_index]; + } +} + +/* Helper to get parent operation */ +static inline php_stream_error_operation *php_stream_get_parent_operation(void) +{ + php_stream_error_state *state = &FG(stream_error_state); + + if (state->operation_depth <= 1) { + return NULL; + } + + return php_stream_get_operation_at_depth(state->operation_depth - 2); +} + +/* Clean up functions */ + static void php_stream_error_entry_free(php_stream_error_entry *entry) { while (entry) { @@ -155,7 +183,6 @@ static void php_stream_error_operation_free(php_stream_error_operation *op) efree(op); } -/* Cleanup function for request shutdown */ PHPAPI void php_stream_error_state_cleanup(void) { php_stream_error_state *state = &FG(stream_error_state); @@ -163,7 +190,8 @@ PHPAPI void php_stream_error_state_cleanup(void) /* Clear active operations */ while (state->current_operation) { php_stream_error_operation *op = state->current_operation; - state->current_operation = op->parent; + state->operation_depth--; + state->current_operation = php_stream_get_parent_operation(); /* Free errors */ php_stream_error_entry_free(op->first_error); @@ -172,7 +200,6 @@ PHPAPI void php_stream_error_state_cleanup(void) op->first_error = NULL; op->last_error = NULL; op->error_count = 0; - op->parent = NULL; } /* Clear stored errors */ @@ -195,6 +222,7 @@ PHPAPI void php_stream_error_state_cleanup(void) state->overflow_capacity = 0; } } + /* Error operation stack management */ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void) @@ -235,9 +263,8 @@ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void) op->first_error = NULL; op->last_error = NULL; op->error_count = 0; - op->parent = state->current_operation; - /* Push onto stack */ + /* Update stack state */ state->current_operation = op; state->operation_depth++; @@ -381,8 +408,8 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) } /* Pop from stack */ - state->current_operation = op->parent; state->operation_depth--; + state->current_operation = php_stream_get_parent_operation(); /* Process errors if we have any */ if (op->error_count > 0) { @@ -444,6 +471,7 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) zend_string_release(entry->message); efree(entry->wrapper_name); efree(entry->param); + efree(entry->docref); efree(entry); entry = next; @@ -469,11 +497,10 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) } } - /* Reset operation for reuse (don't free if from pool or overflow) */ + /* Reset operation for reuse */ op->first_error = NULL; op->last_error = NULL; op->error_count = 0; - op->parent = NULL; } PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) @@ -487,13 +514,12 @@ PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) /* Fast path: no errors - just pop and return */ if (op->error_count == 0) { - state->current_operation = op->parent; state->operation_depth--; + state->current_operation = php_stream_get_parent_operation(); /* Reset operation for reuse */ op->first_error = NULL; op->last_error = NULL; - op->parent = NULL; return; } @@ -504,16 +530,22 @@ PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream) PHPAPI void php_stream_error_operation_abort(void) { - php_stream_error_operation *op = FG(stream_error_state).current_operation; + php_stream_error_state *state = &FG(stream_error_state); + php_stream_error_operation *op = state->current_operation; if (!op) { return; } - FG(stream_error_state).current_operation = op->parent; - FG(stream_error_state).operation_depth--; + /* Pop from stack */ + state->operation_depth--; + state->current_operation = php_stream_get_parent_operation(); - php_stream_error_operation_free(op); + /* Free errors and reset operation */ + php_stream_error_entry_free(op->first_error); + op->first_error = NULL; + op->last_error = NULL; + op->error_count = 0; } /* Wrapper error reporting */ @@ -525,7 +557,7 @@ static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stre /* If not in an operation, create one */ bool implicit_operation = (FG(stream_error_state).current_operation == NULL); if (implicit_operation) { - php_stream_error_operation_begin(context); + php_stream_error_operation_begin(); } /* Add to current operation (or skip if no operation) */ From 9b45daecd556728f2d913964cfe2442e350cd22b Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 12:46:00 +0100 Subject: [PATCH 50/61] stream: fix incorrect stream error tests --- .../streams/stream_errors_error_code_helpers.phpt | 10 +++++----- .../tests/streams/stream_errors_invalid_types.phpt | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ext/standard/tests/streams/stream_errors_error_code_helpers.phpt b/ext/standard/tests/streams/stream_errors_error_code_helpers.phpt index c52bafb52c6f1..dc452e846fb4b 100644 --- a/ext/standard/tests/streams/stream_errors_error_code_helpers.phpt +++ b/ext/standard/tests/streams/stream_errors_error_code_helpers.phpt @@ -10,7 +10,7 @@ $context = stream_context_create([ ]); // Generate a network error -$stream = @fsockopen('invalid-host-12345.example.com', 80, $errno, $errstr, 1, $context); +$stream = fopen('php://nonexistent', 'r', false, $context); $error = stream_get_last_error(); if ($error) { @@ -22,7 +22,7 @@ if ($error) { ?> --EXPECTF-- -Is I/O error: %s -Is filesystem error: no -Is network error: %s -Is wrapper error: %s +Is I/O error: no +Is filesystem error: yes +Is network error: no +Is wrapper error: no diff --git a/ext/standard/tests/streams/stream_errors_invalid_types.phpt b/ext/standard/tests/streams/stream_errors_invalid_types.phpt index 22563920ed672..1b3ab5e8931e3 100644 --- a/ext/standard/tests/streams/stream_errors_invalid_types.phpt +++ b/ext/standard/tests/streams/stream_errors_invalid_types.phpt @@ -28,6 +28,10 @@ try { } ?> ---EXPECT-- +--EXPECTF-- + +Warning: fopen(php://nonexistent): Failed to open stream: operation failed in %s on line %d Caught TypeError for error_mode + +Warning: fopen(php://nonexistent): Failed to open stream: operation failed in %s on line %d Caught TypeError for error_store From 30156ce8ad329b03c5566ecb20279a3fca89fd82 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 14:07:21 +0100 Subject: [PATCH 51/61] stream: add error begin and end to standard/streamsfuncs.c --- ext/standard/streamsfuncs.c | 104 ++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 17 deletions(-) diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index e81e14c082175..89995889f9647 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -64,10 +64,13 @@ PHP_FUNCTION(stream_socket_pair) RETURN_FALSE; } + php_stream_error_operation_begin(); + s1 = php_stream_sock_open_from_socket(pair[0], 0); if (s1 == NULL) { close(pair[0]); close(pair[1]); + php_stream_error_operation_end(NULL); php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair"); RETURN_FALSE; } @@ -75,6 +78,7 @@ PHP_FUNCTION(stream_socket_pair) if (s2 == NULL) { php_stream_free(s1, PHP_STREAM_FREE_CLOSE); close(pair[1]); + php_stream_error_operation_end(NULL); php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair"); RETURN_FALSE; } @@ -88,6 +92,8 @@ PHP_FUNCTION(stream_socket_pair) add_next_index_resource(return_value, s1->res); add_next_index_resource(return_value, s2->res); + + php_stream_error_operation_end(NULL); } /* }}} */ #endif @@ -126,6 +132,7 @@ PHP_FUNCTION(stream_socket_client) RETURN_THROWS(); } + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); if (flags & PHP_STREAM_CLIENT_PERSISTENT) { @@ -160,6 +167,7 @@ PHP_FUNCTION(stream_socket_client) (flags & PHP_STREAM_CLIENT_ASYNC_CONNECT ? STREAM_XPORT_CONNECT_ASYNC : 0), hashkey, tv_pointer, context, &errstr, &err); + php_stream_error_operation_end(context); if (stream == NULL) { /* host might contain binary characters */ @@ -217,6 +225,7 @@ PHP_FUNCTION(stream_socket_server) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); if (zerrno) { @@ -230,6 +239,8 @@ PHP_FUNCTION(stream_socket_server) STREAM_XPORT_SERVER | (int)flags, NULL, NULL, context, &errstr, &err); + php_stream_error_operation_end(context); + if (stream == NULL) { php_error_docref(NULL, E_WARNING, "Unable to connect to %s (%s)", host, errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr)); } @@ -295,6 +306,8 @@ PHP_FUNCTION(stream_socket_accept) tv_pointer = &tv; } + php_stream_error_operation_begin(); + if (0 == php_stream_xport_accept(stream, &clistream, zpeername ? &peername : NULL, NULL, NULL, @@ -313,6 +326,8 @@ PHP_FUNCTION(stream_socket_accept) RETVAL_FALSE; } + php_stream_error_operation_end_for_stream(stream); + if (errstr) { zend_string_release_ex(errstr, 0); } @@ -331,10 +346,11 @@ PHP_FUNCTION(stream_socket_get_name) Z_PARAM_BOOL(want_peer) ZEND_PARSE_PARAMETERS_END(); - if (0 != php_stream_xport_get_name(stream, want_peer, - &name, - NULL, NULL - ) || !name) { + php_stream_error_operation_begin(); + int ret = php_stream_xport_get_name(stream, want_peer, &name, NULL, NULL); + php_stream_error_operation_end_for_stream(stream); + + if (0 != ret || !name) { RETURN_FALSE; } @@ -365,15 +381,18 @@ PHP_FUNCTION(stream_socket_sendto) Z_PARAM_STRING(target_addr, target_addr_len) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); if (target_addr_len) { /* parse the address */ if (FAILURE == php_network_parse_network_address_with_port(target_addr, target_addr_len, (struct sockaddr*)&sa, &sl)) { + php_stream_error_operation_end_for_stream(stream); php_error_docref(NULL, E_WARNING, "Failed to parse `%s' into a valid network address", target_addr); RETURN_FALSE; } } - RETURN_LONG(php_stream_xport_sendto(stream, data, datalen, (int)flags, target_addr_len ? &sa : NULL, sl)); + RETVAL_LONG(php_stream_xport_sendto(stream, data, datalen, (int)flags, target_addr_len ? &sa : NULL, sl)); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -407,9 +426,10 @@ PHP_FUNCTION(stream_socket_recvfrom) read_buf = zend_string_alloc(to_read, 0); + php_stream_error_operation_begin(); recvd = php_stream_xport_recvfrom(stream, ZSTR_VAL(read_buf), to_read, (int)flags, NULL, NULL, - zremote ? &remote_addr : NULL - ); + zremote ? &remote_addr : NULL); + php_stream_error_operation_end_for_stream(stream); if (recvd >= 0) { if (zremote && remote_addr) { @@ -447,6 +467,8 @@ PHP_FUNCTION(stream_get_contents) RETURN_THROWS(); } + php_stream_error_operation_begin(); + if (desiredpos >= 0) { int seek_res = 0; zend_off_t position; @@ -461,6 +483,7 @@ PHP_FUNCTION(stream_get_contents) } if (seek_res != 0) { + php_stream_error_operation_end_for_stream(stream); php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", desiredpos); RETURN_FALSE; @@ -468,10 +491,11 @@ PHP_FUNCTION(stream_get_contents) } if ((contents = php_stream_copy_to_mem(stream, maxlen, 0))) { - RETURN_STR(contents); + RETVAL_STR(contents); } else { - RETURN_EMPTY_STRING(); + RETVAL_EMPTY_STRING(); } + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -495,15 +519,20 @@ PHP_FUNCTION(stream_copy_to_stream) maxlen = PHP_STREAM_COPY_ALL; } + php_stream_error_operation_begin(); + if (pos > 0 && php_stream_seek(src, pos, SEEK_SET) < 0) { + php_stream_error_operation_end(NULL); php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", pos); RETURN_FALSE; } if (php_stream_copy_to_stream_ex(src, dest, maxlen, &len) != SUCCESS) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + RETVAL_LONG(len); } - RETURN_LONG(len); + php_stream_error_operation_end(NULL); } /* }}} */ @@ -518,11 +547,13 @@ PHP_FUNCTION(stream_get_meta_data) array_init(return_value); + php_stream_error_operation_begin(); if (!php_stream_populate_meta_data(stream, return_value)) { add_assoc_bool(return_value, "timed_out", 0); add_assoc_bool(return_value, "blocked", 1); add_assoc_bool(return_value, "eof", php_stream_eof(stream)); } + php_stream_error_operation_end_for_stream(stream); if (!Z_ISUNDEF(stream->wrapperdata)) { Z_ADDREF_P(&stream->wrapperdata); @@ -770,6 +801,8 @@ PHP_FUNCTION(stream_select) FD_ZERO(&wfds); FD_ZERO(&efds); + php_stream_error_operation_begin(); + if (r_array != NULL) { set_count = stream_array_to_fd_set(Z_ARR_P(r_array), &rfds, &max_fd); if (set_count > max_set_count) @@ -792,6 +825,7 @@ PHP_FUNCTION(stream_select) } if (!sets) { + php_stream_error_operation_end(NULL); zend_value_error("No stream arrays were passed"); RETURN_THROWS(); } @@ -802,6 +836,7 @@ PHP_FUNCTION(stream_select) if (secnull && !usecnull) { if (usec != 0) { + php_stream_error_operation_end(NULL); zend_argument_value_error(5, "must be null when argument #4 ($seconds) is null"); RETURN_THROWS(); } @@ -810,9 +845,11 @@ PHP_FUNCTION(stream_select) /* If seconds is not set to null, build the timeval, else we wait indefinitely */ if (!secnull) { if (sec < 0) { + php_stream_error_operation_end(NULL); zend_argument_value_error(4, "must be greater than or equal to 0"); RETURN_THROWS(); } else if (usec < 0) { + php_stream_error_operation_end(NULL); zend_argument_value_error(5, "must be greater than or equal to 0"); RETURN_THROWS(); } @@ -829,6 +866,7 @@ PHP_FUNCTION(stream_select) if (r_array != NULL) { retval = stream_array_emulate_read_fd_set(r_array); if (retval > 0) { + php_stream_error_operation_end(NULL); if (w_array != NULL) { zval_ptr_dtor(w_array); ZVAL_EMPTY_ARRAY(w_array); @@ -842,6 +880,7 @@ PHP_FUNCTION(stream_select) } retval = php_select(max_fd+1, &rfds, &wfds, &efds, tv_p); + php_stream_error_operation_end(NULL); if (retval == -1) { php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s (max_fd=" PHP_SOCKET_FMT ")", @@ -1362,11 +1401,13 @@ PHP_FUNCTION(stream_get_line) max_length = PHP_SOCK_CHUNK_SIZE; } + php_stream_error_operation_begin(); if ((buf = php_stream_get_record(stream, max_length, str, str_len))) { - RETURN_STR(buf); + RETVAL_STR(buf); } else { - RETURN_FALSE; + RETVAL_FALSE; } + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -1382,7 +1423,9 @@ PHP_FUNCTION(stream_set_blocking) Z_PARAM_BOOL(block) ZEND_PARSE_PARAMETERS_END(); - RETURN_BOOL(-1 != php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL)); + php_stream_error_operation_begin(); + RETVAL_BOOL(-1 != php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL)); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ @@ -1423,7 +1466,9 @@ PHP_FUNCTION(stream_set_timeout) } #endif - RETURN_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)); + php_stream_error_operation_begin(); + RETVAL_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)); + php_stream_error_operation_end_for_stream(stream); } #endif /* HAVE_SYS_TIME_H || defined(PHP_WIN32) */ /* }}} */ @@ -1443,12 +1488,14 @@ PHP_FUNCTION(stream_set_write_buffer) buff = arg2; + php_stream_error_operation_begin(); /* if buff is 0 then set to non-buffered */ if (buff == 0) { ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); } else { ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buff); } + php_stream_error_operation_end_for_stream(stream); RETURN_LONG(ret == 0 ? 0 : EOF); } @@ -1479,7 +1526,9 @@ PHP_FUNCTION(stream_set_chunk_size) RETURN_THROWS(); } + php_stream_error_operation_begin(); ret = php_stream_set_option(stream, PHP_STREAM_OPTION_SET_CHUNK_SIZE, (int)csize, NULL); + php_stream_error_operation_end_for_stream(stream); RETURN_LONG(ret > 0 ? (zend_long)ret : (zend_long)EOF); } @@ -1500,12 +1549,14 @@ PHP_FUNCTION(stream_set_read_buffer) buff = arg2; + php_stream_error_operation_begin(); /* if buff is 0 then set to non-buffered */ if (buff == 0) { ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); } else { ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_FULL, &buff); } + php_stream_error_operation_end_for_stream(stream); RETURN_LONG(ret == 0 ? 0 : EOF); } @@ -1527,11 +1578,14 @@ PHP_FUNCTION(stream_socket_enable_crypto) PHP_Z_PARAM_STREAM_OR_NULL(sessstream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); + if (enable) { if (cryptokindnull) { zval *val; if (!GET_CTX_OPT(stream, "ssl", "crypto_method", val)) { + php_stream_error_operation_end_for_stream(stream); zend_argument_value_error(3, "must be specified when enabling encryption"); RETURN_THROWS(); } @@ -1540,11 +1594,13 @@ PHP_FUNCTION(stream_socket_enable_crypto) } if (php_stream_xport_crypto_setup(stream, cryptokind, sessstream) < 0) { + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } } ret = php_stream_xport_crypto_enable(stream, enable); + php_stream_error_operation_end_for_stream(stream); switch (ret) { case -1: RETURN_FALSE; @@ -1596,7 +1652,9 @@ PHP_FUNCTION(stream_is_local) RETURN_THROWS(); } + php_stream_error_operation_begin(); wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, 0); + php_stream_error_operation_end(NULL); } RETURN_BOOL(wrapper && wrapper->is_url == 0); @@ -1612,7 +1670,9 @@ PHP_FUNCTION(stream_supports_lock) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); - RETURN_BOOL(php_stream_supports_lock(stream)); + php_stream_error_operation_begin(); + RETVAL_BOOL(php_stream_supports_lock(stream)); + php_stream_error_operation_end_for_stream(stream); } /* {{{ Check if a stream is a TTY. */ @@ -1625,6 +1685,8 @@ PHP_FUNCTION(stream_isatty) PHP_Z_PARAM_STREAM(stream) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); + /* get the fd. * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting. * It is only used here so that the buffered data warning is not displayed. @@ -1634,8 +1696,10 @@ PHP_FUNCTION(stream_isatty) } else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) { php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0); } else { + php_stream_error_operation_end_for_stream(stream); RETURN_FALSE; } + php_stream_error_operation_end_for_stream(stream); #ifdef PHP_WIN32 /* Check if the Windows standard handle is redirected to file */ @@ -1665,6 +1729,8 @@ PHP_FUNCTION(sapi_windows_vt100_support) Z_PARAM_BOOL_OR_NULL(enable, enable_is_null) ZEND_PARSE_PARAMETERS_END(); + php_stream_error_operation_begin(); + /* get the fd. * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting. * It is only used here so that the buffered data warning is not displayed. @@ -1674,6 +1740,7 @@ PHP_FUNCTION(sapi_windows_vt100_support) } else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) { php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0); } else { + php_stream_error_operation_end_for_stream(stream); if (!enable_is_null) { php_error_docref( NULL, @@ -1683,6 +1750,7 @@ PHP_FUNCTION(sapi_windows_vt100_support) } RETURN_FALSE; } + php_stream_error_operation_end_for_stream(stream); /* Check if the file descriptor is a console */ if (!php_win32_console_fileno_is_console(fileno)) { @@ -1722,7 +1790,9 @@ PHP_FUNCTION(stream_socket_shutdown) RETURN_THROWS(); } - RETURN_BOOL(php_stream_xport_shutdown(stream, (stream_shutdown_t)how) == 0); + php_stream_error_operation_begin(); + RETVAL_BOOL(php_stream_xport_shutdown(stream, (stream_shutdown_t)how) == 0); + php_stream_error_operation_end_for_stream(stream); } /* }}} */ #endif From 69c04f1c2b965e7247de8b9e2b0ee20680aaa2b4 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 14:13:59 +0100 Subject: [PATCH 52/61] stream: use default context in errors if no context set --- main/streams/stream_errors.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 94578ab6341e3..f9b2407242af3 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -413,6 +413,9 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) /* Process errors if we have any */ if (op->error_count > 0) { + if (context == NULL) { + context = FG(default_context); + } /* Get error handling settings */ int error_mode = php_stream_get_error_mode(context); int store_mode = php_stream_get_error_store_mode(context, error_mode); From dc1354e14984676c7c63596765dd7133fa4b6104 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 14:15:43 +0100 Subject: [PATCH 53/61] stream: remove php_stream_error_operation_free --- main/streams/stream_errors.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index f9b2407242af3..c800b25c117d9 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -177,12 +177,6 @@ static void php_stream_error_entry_free(php_stream_error_entry *entry) } } -static void php_stream_error_operation_free(php_stream_error_operation *op) -{ - php_stream_error_entry_free(op->first_error); - efree(op); -} - PHPAPI void php_stream_error_state_cleanup(void) { php_stream_error_state *state = &FG(stream_error_state); From 49966a43b24d038ef3a4b31e540d4e1f69f19a2a Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 17:54:57 +0100 Subject: [PATCH 54/61] stream: fix stored errors copying --- main/streams/stream_errors.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index c800b25c117d9..485a308fd17ef 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -410,6 +410,7 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) if (context == NULL) { context = FG(default_context); } + /* Get error handling settings */ int error_mode = php_stream_get_error_mode(context); int store_mode = php_stream_get_error_store_mode(context, error_mode); @@ -423,12 +424,14 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) if (store_mode == PHP_STREAM_ERROR_STORE_NONE) { /* Free all errors */ php_stream_error_entry_free(op->first_error); + op->first_error = NULL; } else { /* Filter and store */ php_stream_error_entry *entry = op->first_error; php_stream_error_entry *prev = NULL; php_stream_error_entry *to_store_first = NULL; php_stream_error_entry *to_store_last = NULL; + php_stream_error_entry *remaining_first = NULL; while (entry) { php_stream_error_entry *next = entry->next; @@ -444,12 +447,6 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) if (should_store) { /* Move to storage chain */ - if (prev) { - prev->next = next; - } else { - op->first_error = next; - } - entry->next = NULL; if (to_store_last) { to_store_last->next = entry; @@ -458,24 +455,16 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) } to_store_last = entry; } else { - /* Free this error */ + /* Keep in remaining chain (to be freed) */ + entry->next = NULL; if (prev) { - prev->next = next; + prev->next = entry; } else { - op->first_error = next; + remaining_first = entry; } - - zend_string_release(entry->message); - efree(entry->wrapper_name); - efree(entry->param); - efree(entry->docref); - efree(entry); - - entry = next; - continue; + prev = entry; } - prev = entry; entry = next; } @@ -489,8 +478,12 @@ PHPAPI void php_stream_error_operation_end(php_stream_context *context) state->stored_count++; } - /* Free any remaining errors not moved to storage */ - php_stream_error_entry_free(op->first_error); + /* Free remaining errors that were not stored */ + if (remaining_first) { + php_stream_error_entry_free(remaining_first); + } + + op->first_error = NULL; } } From ce2b1f438eaa991a0d8a65560b297ba64b8ceb1b Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 17:55:18 +0100 Subject: [PATCH 55/61] stream: fix mix modes storage error test --- .../stream_errors_mix_modes_storage.phpt | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt index 80dd85a4a386b..048949d5353b5 100644 --- a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt +++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt @@ -28,7 +28,8 @@ class TestStream { stream_wrapper_register('test', 'TestStream'); function stream_test_errors($title, $context) { - $stream = fopen('test://foo', 'r', false, $context); + stream_context_set_default($context); + $stream = fopen('test://foo', 'r', false); try { echo $title . "\n"; $readin = fopen('php://stdin', 'r'); @@ -55,7 +56,7 @@ function stream_test_errors($title, $context) { $current = $error; $idx = 0; while ($current) { - echo " [$idx] " . $current->code->name . ": " . substr($current->message, 0, 50) . "...\n"; + echo " [$idx] " . $current->code->name . ": " . $current->message . "\n"; $current = $current->next; $idx++; } @@ -65,53 +66,52 @@ function stream_test_errors($title, $context) { echo "\n"; } -stream_test_errors('ALL', stream_context_create([ +stream_test_errors('ALL', [ 'stream' => [ 'error_mode' => StreamErrorMode::Silent, 'error_store' => StreamErrorStore::All, ] -])); +]); -stream_test_errors('NON TERMINAL', stream_context_create([ +stream_test_errors('NON TERMINAL', [ 'stream' => [ 'error_mode' => StreamErrorMode::Silent, 'error_store' => StreamErrorStore::NonTerminal, ] -])); +]); -stream_test_errors('TERMINAL', stream_context_create([ +stream_test_errors('TERMINAL', [ 'stream' => [ 'error_mode' => StreamErrorMode::Silent, 'error_store' => StreamErrorStore::Terminal, ] -])); +]); -stream_test_errors('AUTO EXCEPTION', stream_context_create([ +stream_test_errors('AUTO EXCEPTION', [ 'stream' => [ 'error_mode' => StreamErrorMode::Exception, 'error_store' => StreamErrorStore::Auto, ] -])); +]); -stream_test_errors('AUTO ERROR', stream_context_create([ +stream_test_errors('AUTO ERROR', [ 'stream' => [ 'error_mode' => StreamErrorMode::Error, 'error_store' => StreamErrorStore::Auto, ] -])); +]); ?> --EXPECTF-- ALL Error details: -- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost -- Code: UserspaceInvalidReturn (161) +- Message: TestStream::stream_cast is not implemented! +- Code: NotImplemented (70) - Wrapper: user-space -- Terminating: no -- Count: 3 - [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... - [1] NotImplemented: TestStream::stream_cast is not implemented!... - [2] CastNotSupported: Cannot represent a stream of type user-space as... +- Terminating: yes +- Count: 2 + [0] NotImplemented: TestStream::stream_cast is not implemented! + [1] CastNotSupported: Cannot represent a stream of type user-space as a select()able descriptor NON TERMINAL Error details: @@ -120,7 +120,7 @@ Error details: - Wrapper: user-space - Terminating: no - Count: 1 - [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost TERMINAL Error details: @@ -129,18 +129,18 @@ Error details: - Wrapper: user-space - Terminating: yes - Count: 2 - [0] NotImplemented: TestStream::stream_cast is not implemented!... - [1] CastNotSupported: Cannot represent a stream of type user-space as... + [0] NotImplemented: TestStream::stream_cast is not implemented! + [1] CastNotSupported: Cannot represent a stream of type user-space as a select()able descriptor AUTO EXCEPTION -EXCEPTION: Cannot represent a stream of type user-space as a select()able descriptor +EXCEPTION: TestStream::stream_cast is not implemented! Error details: - Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost - Code: UserspaceInvalidReturn (161) - Wrapper: user-space - Terminating: no - Count: 1 - [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data ... + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost AUTO ERROR @@ -149,4 +149,10 @@ Warning: fread(): TestStream::stream_read - read 10 bytes more data than request Warning: stream_select(): TestStream::stream_cast is not implemented! in %s on line %d Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d -No errors stored +Error details: +- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost +- Code: UserspaceInvalidReturn (161) +- Wrapper: user-space +- Terminating: no +- Count: 1 + [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost From 0098eebf10f74a02666195df289846ded81ce65b Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 17:56:14 +0100 Subject: [PATCH 56/61] stream: remove chaining tests (already tested by mixed store) --- .../tests/streams/stream_errors_chaining.phpt | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 ext/standard/tests/streams/stream_errors_chaining.phpt diff --git a/ext/standard/tests/streams/stream_errors_chaining.phpt b/ext/standard/tests/streams/stream_errors_chaining.phpt deleted file mode 100644 index 859e468d3f5ee..0000000000000 --- a/ext/standard/tests/streams/stream_errors_chaining.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Stream errors - error chaining ---FILE-- - [ - 'error_mode' => StreamErrorMode::Silent, - 'error_store' => StreamErrorStore::All, - ] -]); - -$stream = fopen('test://foo', 'r', false, $context); - -$error = stream_get_last_error(); -if ($error) { - echo "Error count: " . $error->count() . "\n"; - echo "First error: " . $error->message . "\n"; - - // Check if it has next error - if ($error->next) { - echo "Has chained error: yes\n"; - } -} - -?> ---EXPECTF-- -Error count: %d -First error: %s -%a From 7bc37ffb4937a794c00d13431bc4e05109da7b62 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 17:57:26 +0100 Subject: [PATCH 57/61] stream: update reflection class names test --- .../tests/ReflectionExtension_getClassNames_basic.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt index dc72254abc076..4e985e674f229 100644 --- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt @@ -16,6 +16,10 @@ AssertionError Directory RoundingMode StreamBucket +StreamError +StreamErrorCode +StreamErrorMode +StreamErrorStore StreamException __PHP_Incomplete_Class php_user_filter From 7d266d576518076cc3e999c9ccea65e646e068d9 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 18:38:16 +0100 Subject: [PATCH 58/61] stream: rename terminal errors to terminating --- .../streams/stream_errors_mix_modes_storage.phpt | 12 ++++++------ main/streams/stream_errors.c | 4 ++-- main/streams/stream_errors.stub.php | 4 ++-- main/streams/stream_errors_arginfo.h | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt index 048949d5353b5..5e61108b63bda 100644 --- a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt +++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt @@ -73,17 +73,17 @@ stream_test_errors('ALL', [ ] ]); -stream_test_errors('NON TERMINAL', [ +stream_test_errors('NON TERMINATING', [ 'stream' => [ 'error_mode' => StreamErrorMode::Silent, - 'error_store' => StreamErrorStore::NonTerminal, + 'error_store' => StreamErrorStore::NonTerminating, ] ]); -stream_test_errors('TERMINAL', [ +stream_test_errors('TERMINATING', [ 'stream' => [ 'error_mode' => StreamErrorMode::Silent, - 'error_store' => StreamErrorStore::Terminal, + 'error_store' => StreamErrorStore::Terminating, ] ]); @@ -113,7 +113,7 @@ Error details: [0] NotImplemented: TestStream::stream_cast is not implemented! [1] CastNotSupported: Cannot represent a stream of type user-space as a select()able descriptor -NON TERMINAL +NON TERMINATING Error details: - Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost - Code: UserspaceInvalidReturn (161) @@ -122,7 +122,7 @@ Error details: - Count: 1 [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost -TERMINAL +TERMINATING Error details: - Message: TestStream::stream_cast is not implemented! - Code: NotImplemented (70) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 485a308fd17ef..555f6f38c12ec 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -112,9 +112,9 @@ static int php_stream_get_error_store_mode(php_stream_context *context, int erro return php_stream_auto_decide_error_store_mode(error_mode); } else if (zend_string_equals_literal(case_name, "None")) { return PHP_STREAM_ERROR_STORE_NONE; - } else if (zend_string_equals_literal(case_name, "NonTerminal")) { + } else if (zend_string_equals_literal(case_name, "NonTerminating")) { return PHP_STREAM_ERROR_STORE_NON_TERM; - } else if (zend_string_equals_literal(case_name, "Terminal")) { + } else if (zend_string_equals_literal(case_name, "Terminating")) { return PHP_STREAM_ERROR_STORE_TERMINAL; } else if (zend_string_equals_literal(case_name, "All")) { return PHP_STREAM_ERROR_STORE_ALL; diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 83e44ca75c9bf..8b6b549a68eb7 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -38,8 +38,8 @@ enum StreamErrorStore { case Auto; case None; - case NonTerminal; - case Terminal; + case NonTerminating; + case Terminating; case All; } diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 1675213fd99ae..7fded66ec89d5 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c9a7e6c0b34de0c6ee76c2a9d80c50deafc8079a */ + * Stub hash: 24a4229f4272983a6fb996ce136ea904183e14e0 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamErrorCode_isIoError, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -99,9 +99,9 @@ static zend_class_entry *register_class_StreamErrorStore(void) zend_enum_add_case_cstr(class_entry, "None", NULL); - zend_enum_add_case_cstr(class_entry, "NonTerminal", NULL); + zend_enum_add_case_cstr(class_entry, "NonTerminating", NULL); - zend_enum_add_case_cstr(class_entry, "Terminal", NULL); + zend_enum_add_case_cstr(class_entry, "Terminating", NULL); zend_enum_add_case_cstr(class_entry, "All", NULL); From 91566fee66f806f6fb5fbe4d6eba7fec1079473e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 19:18:38 +0100 Subject: [PATCH 59/61] stream: fix segfault with incompletely zeroed entry in dtor --- main/streams/stream_errors.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 555f6f38c12ec..4e2b68efd82fb 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -681,14 +681,13 @@ static void php_stream_wrapper_log_store_error(zend_string *message, int code, { char *param_copy = param ? estrdup(param) : NULL; - php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry)); + php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry)); entry->message = message; entry->code = code; entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; entry->param = param_copy; entry->severity = severity; entry->terminating = terminating; - entry->next = NULL; if (!FG(wrapper_logged_errors)) { ALLOC_HASHTABLE(FG(wrapper_logged_errors)); From b68b854e0a53b857dcd744b1b090d4567284371e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 19:22:32 +0100 Subject: [PATCH 60/61] stream: re-order some errors and fix test --- ext/standard/file.c | 20 ++++++++++---------- ext/standard/tests/streams/gh14506.phpt | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index ead6faddece39..5bd0bf1ccfc80 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -427,9 +427,9 @@ PHP_FUNCTION(file_get_contents) } if (offset != 0 && php_stream_seek(stream, offset, ((offset > 0) ? SEEK_SET : SEEK_END)) < 0) { - php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", offset); php_stream_close(stream); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", offset); RETURN_FALSE; } @@ -495,8 +495,8 @@ PHP_FUNCTION(file_put_contents) if ((flags & LOCK_EX) && (!php_stream_supports_lock(stream) || php_stream_lock(stream, LOCK_EX))) { php_stream_close(stream); - php_error_docref(NULL, E_WARNING, "Exclusive locks are not supported for this stream"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "Exclusive locks are not supported for this stream"); RETURN_FALSE; } @@ -1270,20 +1270,20 @@ PHP_FUNCTION(rename) wrapper = php_stream_locate_url_wrapper(old_name, NULL, 0); if (!wrapper || !wrapper->wops) { - php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); RETURN_FALSE; } if (!wrapper->wops->rename) { - php_error_docref(NULL, E_WARNING, "%s wrapper does not support renaming", wrapper->wops->label ? wrapper->wops->label : "Source"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "%s wrapper does not support renaming", wrapper->wops->label ? wrapper->wops->label : "Source"); RETURN_FALSE; } if (wrapper != php_stream_locate_url_wrapper(new_name, NULL, 0)) { - php_error_docref(NULL, E_WARNING, "Cannot rename a file across wrapper types"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "Cannot rename a file across wrapper types"); RETURN_FALSE; } @@ -1313,14 +1313,14 @@ PHP_FUNCTION(unlink) wrapper = php_stream_locate_url_wrapper(filename, NULL, 0); if (!wrapper || !wrapper->wops) { - php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper"); RETURN_FALSE; } if (!wrapper->wops->unlink) { - php_error_docref(NULL, E_WARNING, "%s does not allow unlinking", wrapper->wops->label ? wrapper->wops->label : "Wrapper"); php_stream_error_operation_end(context); + php_error_docref(NULL, E_WARNING, "%s does not allow unlinking", wrapper->wops->label ? wrapper->wops->label : "Wrapper"); RETURN_FALSE; } RETVAL_BOOL(wrapper->wops->unlink(wrapper, filename, REPORT_ERRORS, context)); @@ -1338,8 +1338,8 @@ PHP_FUNCTION(fsync) php_stream_error_operation_begin(); if (!php_stream_sync_supported(stream)) { - php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); php_stream_error_operation_end_for_stream(stream); + php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); RETURN_FALSE; } @@ -1357,8 +1357,8 @@ PHP_FUNCTION(fdatasync) php_stream_error_operation_begin(); if (!php_stream_sync_supported(stream)) { - php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); php_stream_error_operation_end_for_stream(stream); + php_error_docref(NULL, E_WARNING, "Can't fsync this stream!"); RETURN_FALSE; } @@ -1385,8 +1385,8 @@ PHP_FUNCTION(ftruncate) php_stream_error_operation_begin(); if (!php_stream_truncate_supported(stream)) { - php_error_docref(NULL, E_WARNING, "Can't truncate this stream!"); php_stream_error_operation_end_for_stream(stream); + php_error_docref(NULL, E_WARNING, "Can't truncate this stream!"); RETURN_FALSE; } diff --git a/ext/standard/tests/streams/gh14506.phpt b/ext/standard/tests/streams/gh14506.phpt index f83eba4f1ff30..3d82350221d55 100644 --- a/ext/standard/tests/streams/gh14506.phpt +++ b/ext/standard/tests/streams/gh14506.phpt @@ -86,10 +86,10 @@ Warning: fclose(): cannot close the provided stream, as it must not be manually Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d -Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d - Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d +Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d + Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d No stream arrays were passed fclose(): Argument #1 ($stream) must be an open stream resource From b5dac6e536165c3bd9d2b967e5e5da0e25d25242 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Mon, 29 Dec 2025 20:08:36 +0100 Subject: [PATCH 61/61] stream: update optimizer info for stream_get_last_error --- Zend/Optimizer/zend_func_infos.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 16d254b3a38fd..86d7f478fe111 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -596,7 +596,7 @@ static const func_info_t func_infos[] = { F1("stream_get_line", MAY_BE_STRING|MAY_BE_FALSE), F1("stream_resolve_include_path", MAY_BE_STRING|MAY_BE_FALSE), F1("stream_get_wrappers", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), - FN("stream_get_errors", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY), + F1("stream_get_last_error", MAY_BE_OBJECT|MAY_BE_NULL), F1("stream_get_transports", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), #if defined(HAVE_GETTIMEOFDAY) F1("uniqid", MAY_BE_STRING),