Commit f2b371e747d for php.net
commit f2b371e747d26351cacba99d7fd696a5844e83fb
Author: Jakub Zelenka <bukka@php.net>
Date: Thu May 28 22:54:36 2026 +0200
Add new stream erros API
This introduces new stream error handling that allows configurable
handling of stream errors. This can be configured in stream contex.
All streams, ext/standard and ext/phar stream errors are converted to
use the new API.
RFC: https://wiki.php.net/rfc/stream_errors
Closes GH-20524
diff --git a/NEWS b/NEWS
index 700b958f7df..3c4d6993f46 100644
--- a/NEWS
+++ b/NEWS
@@ -234,10 +234,15 @@ PHP NEWS
http(s) stream wrapper). (David Carlier)
- Streams:
+ . Added new stream errors API including new StreamException, StreamError
+ classes, StreamErrorStore, StreamErrorMode, StreamErrorCode enums,
+ stream_last_errors() and stream_clear_errors() functions, error_mode,
+ error_store and error_handler stream context options and extending some
+ stream functions with context param. (Jakub Zelenka)
. Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream
- socket context options.
+ socket context options. (Jakub Zelenka)
. Added so_reuseaddr streams context socket option that allows disabling
- address reuse.
+ address reuse. (Jakub Zelenka)
. Fixed bug GH-20370 (User stream filters could violate typed property
constraints). (alexandre-daubois)
. Allowed filtered streams to be casted as fd for select. (Jakub Zelenka)
diff --git a/UPGRADING b/UPGRADING
index 3d6c89e90e0..c996f963a34 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -207,6 +207,10 @@ PHP 8.6 UPGRADE NOTES
This makes it possible to override the timestamp and names of files.
- Streams:
+ . Added new stream errors API including new classes, enums, functions and
+ internal API. It is controlled using error_mode, error_store and
+ error_handler stream context options.
+ RFC: https://wiki.php.net/rfc/stream_errors
. Added stream socket context option so_reuseaddr that allows disabling
address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
Windows.
@@ -309,6 +313,8 @@ PHP 8.6 UPGRADE NOTES
. `clamp()` returns the given value if in range, else return the nearest
bound.
RFC: https://wiki.php.net/rfc/clamp_v2
+ . `stream_last_errors()` and `stream_clear_errors()`.
+ RFC: https://wiki.php.net/rfc/stream_errors
- Zip:
. Added ZipArchive::openString() method.
@@ -326,6 +332,12 @@ PHP 8.6 UPGRADE NOTES
- Standard:
. enum SortDirection
RFC: https://wiki.php.net/rfc/sort_direction_enum
+ . StreamError
+ . StreamException
+ . enum StreamErrorStore
+ . enum StreamErrorMode
+ . enum StreamErrorCode
+ RFC: https://wiki.php.net/rfc/stream_errors
========================================
8. Removed Extensions and SAPIs
diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h
index b7b118c710c..b76785283dc 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),
+ F1("stream_last_errors", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_OBJECT),
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),
diff --git a/build/gen_stub.php b/build/gen_stub.php
index 619c4c905b6..34bbd34fd37 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -3385,6 +3385,7 @@ public function __construct(
private /* readonly */ array $propertyInfos,
public array $funcInfos,
private readonly array $enumCaseInfos,
+ private readonly bool $generateCNameTable,
public readonly ?string $cond,
public ?int $phpVersionIdMinimumCompatibility,
public readonly bool $isUndocumentable,
@@ -3599,6 +3600,25 @@ public function getCDeclarations(): string
$code .= "} {$cEnumName};\n";
+ $caseCount = count($this->enumCaseInfos);
+
+ if ($this->generateCNameTable && $caseCount > 0) {
+ $cPrefix = 'ZEND_ENUM_' . str_replace('\\', '_', $this->name->toString());
+ $useGuard = $cPrefix . '_USE_NAME_TABLE';
+
+ $code .= "\n#define {$cPrefix}_CASE_COUNT {$caseCount}\n";
+ $code .= "\n#ifdef {$useGuard}\n";
+ $code .= "static const char *{$cEnumName}_case_names[{$cPrefix}_CASE_COUNT + 1] = {\n";
+
+ foreach ($this->enumCaseInfos as $case) {
+ $cName = $cPrefix . '_' . $case->name->case;
+ $code .= "\t[{$cName}] = \"{$case->name->case}\",\n";
+ }
+
+ $code .= "};\n";
+ $code .= "#endif\n";
+ }
+
if ($this->cond) {
$code .= "#endif\n";
}
@@ -5149,6 +5169,7 @@ function parseClass(
$isStrictProperties = array_key_exists('strict-properties', $tagMap);
$isNotSerializable = array_key_exists('not-serializable', $tagMap);
$isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap);
+ $generateCNameTable = array_key_exists('c-name-table', $tagMap);
foreach ($tags as $tag) {
if ($tag->name === 'alias') {
$alias = $tag->getValue();
@@ -5210,6 +5231,7 @@ function parseClass(
$properties,
$methods,
$enumCases,
+ $generateCNameTable,
$cond,
$minimumPhpVersionIdCompatibility,
$isUndocumentable
diff --git a/configure.ac b/configure.ac
index 6c517ecc0a1..7b1fc3af784 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1689,6 +1689,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/phar/dirstream.c b/ext/phar/dirstream.c
index 6c356c04e5e..b1c8c4747cc 100644
--- a/ext/phar/dirstream.c
+++ b/ext/phar/dirstream.c
@@ -247,27 +247,32 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path,
{
char *error;
- php_url *resource = phar_parse_url(wrapper, path, mode, options);
- if (!resource) {
- php_stream_wrapper_log_error(wrapper, options, "phar url \"%s\" is unknown", path);
+ php_url *resource = phar_parse_url(wrapper, context, path, mode, options);
+ if (resource == NULL) {
+ php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+ "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, context, options, InvalidPath,
+ "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, context, options, InvalidUrl,
+ "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, context, options, ProtocolUnsupported,
+ "phar error: not a phar url \"%s\"", path);
return NULL;
}
@@ -276,10 +281,11 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path,
const phar_archive_data *phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
if (!phar) {
if (error) {
- php_stream_wrapper_log_error(wrapper, options, "%s", error);
+ php_stream_wrapper_log_warn(wrapper, context, options, NotFound, "%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, context, options, NotFound,
+ "phar file \"%s\" is unknown", ZSTR_VAL(resource->host));
}
php_url_free(resource);
return NULL;
@@ -344,7 +350,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 */
zend_string *arch = phar_split_fname(url_from, strlen(url_from), NULL, 2, 2);
if (!arch) {
- 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, context, options, NotFound,
+ "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
return 0;
}
@@ -353,30 +360,35 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
zend_string_release_ex(arch, false);
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, context, options, Readonly,
+ "phar error: cannot create directory \"%s\", write operations disabled", url_from);
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;
}
/* 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, context, options, InvalidUrl,
+ "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, context, options, ProtocolUnsupported,
+ "phar error: not a phar stream url \"%s\"", url_from);
return 0;
}
phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
if (!phar) {
- 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, context, options, MkdirFailed,
+ "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;
@@ -389,13 +401,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, context, options, AlreadyExists,
+ "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, context, options, MkdirFailed,
+ "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;
@@ -403,13 +419,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, context, options, AlreadyExists,
+ "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, context, options, MkdirFailed,
+ "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;
@@ -437,9 +457,10 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
entry.flags = PHAR_ENT_PERM_DEF_DIR;
entry.old_flags = PHAR_ENT_PERM_DEF_DIR;
- void *had_been_added = zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info));
- if (!had_been_added) {
- php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname));
+ if (NULL == zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info))) {
+ php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+ "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed",
+ ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname));
zend_string_efree(entry.filename);
return 0;
}
@@ -447,7 +468,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), ZSTR_VAL(phar->fname), error);
+ php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+ "phar error: cannot create directory \"%s\" in phar \"%s\", %s",
+ ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname), error);
zend_hash_del(&phar->manifest, entry.filename);
efree(error);
return 0;
@@ -468,7 +491,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 */
zend_string *arch = phar_split_fname(url, strlen(url), NULL, 2, 2);
if (!arch) {
- 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, context, options, NotFound,
+ "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
return 0;
}
@@ -477,11 +501,12 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
zend_string_release_ex(arch, false);
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, context, options, Readonly,
+ "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
return 0;
}
- php_url *resource = phar_parse_url(wrapper, url, "w", options);
+ php_url *resource = phar_parse_url(wrapper, context, url, "w", options);
if (!resource) {
return 0;
}
@@ -489,19 +514,23 @@ 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, context, options, InvalidUrl,
+ "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, context, options, ProtocolUnsupported,
+ "phar error: not a phar stream url \"%s\"", url);
return 0;
}
phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
if (!phar) {
- 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, context, options, RmdirFailed,
+ "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;
@@ -512,10 +541,14 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
phar_entry_info *entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, true);
if (!entry) {
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, context, options, RmdirFailed,
+ "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, context, options, NotFound,
+ "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;
@@ -529,7 +562,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, context, options, RmdirFailed,
+ "phar error: Directory not empty");
if (entry->is_temp_dir) {
zend_string_efree(entry->filename);
efree(entry);
@@ -545,7 +579,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, context, options, RmdirFailed,
+ "phar error: Directory not empty");
if (entry->is_temp_dir) {
zend_string_efree(entry->filename);
efree(entry);
@@ -566,7 +601,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), ZSTR_VAL(phar->fname), error);
+ php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+ "phar error: cannot remove directory \"%s\" in phar \"%s\", %s",
+ ZSTR_VAL(entry->filename), ZSTR_VAL(phar->fname), error);
php_url_free(resource);
efree(error);
return 0;
diff --git a/ext/phar/dirstream.h b/ext/phar/dirstream.h
index 41899f88a99..179150229e3 100644
--- a/ext/phar/dirstream.h
+++ b/ext/phar/dirstream.h
@@ -22,7 +22,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 772f9c2769a..3fee251d258 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -55,7 +55,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 *error;
@@ -65,7 +66,8 @@ 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, context, options, ModeNotSupported,
+ "phar error: open mode append not supported");
}
return NULL;
}
@@ -76,9 +78,13 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
if (!arch) {
if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
if (arch_error && !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_error);
+ php_stream_wrapper_log_warn(wrapper, context, options, InvalidPath,
+ "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_error);
+ 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, context, options, InvalidUrl,
+ "phar error: invalid url or non-existent phar \"%s\"", filename);
}
}
return NULL;
@@ -109,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, context, options, Readonly,
+ "phar error: write operations disabled by the php.ini setting phar.readonly");
}
php_url_free(resource);
return NULL;
@@ -118,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, NULL, options, OpenFailed, "%s", error);
}
efree(error);
}
@@ -129,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, context, options, OpenFailed, "%s", error);
}
efree(error);
}
@@ -141,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, context, options, NotFound, "%s", error);
}
efree(error);
}
@@ -151,7 +158,6 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
}
return resource;
}
-/* }}} */
/**
* used for fopen('phar://...') and company
@@ -166,7 +172,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
php_stream *fpf;
zval *pzoption, *metadata;
- php_url *resource = phar_parse_url(wrapper, path, mode, options);
+ php_url *resource = phar_parse_url(wrapper, context, path, mode, options);
if (!resource) {
return NULL;
}
@@ -174,13 +180,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, context, options, InvalidUrl,
+ "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, context, options, ProtocolUnsupported,
+ "phar error: not a phar stream url \"%s\"", path);
return NULL;
}
@@ -191,10 +199,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(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, context, options, CreateFailed, "%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, context, options, CreateFailed,
+ "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
}
efree(internal_file);
php_url_free(resource);
@@ -232,7 +241,8 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
/* retrieve the stub */
phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, NULL);
if (!phar) {
- 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, context, options, InvalidFormat,
+ "file %s is not a valid phar archive", ZSTR_VAL(resource->host));
efree(internal_file);
php_url_free(resource);
return NULL;
@@ -252,7 +262,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, context, options, OpenFailed,
+ "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host));
efree(internal_file);
php_url_free(resource);
return NULL;
@@ -289,10 +300,11 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
if ((FAILURE == phar_get_entry_data(&idata, 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, context, options, NotFound, "%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, context, options, NotFound,
+ "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
}
efree(internal_file);
php_url_free(resource);
@@ -310,7 +322,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, context, options, ArchivingFailed, "%s", error);
efree(error);
phar_entry_delref(idata);
efree(internal_file);
@@ -441,7 +453,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), ZSTR_VAL(data->phar->fname));
+ php_stream_warn(stream, WriteFailed,
+ "phar error: Could not write %zu characters to \"%s\" in phar \"%s\"",
+ count, ZSTR_VAL(data->internal_file->filename), ZSTR_VAL(data->phar->fname));
return -1;
}
data->position = php_stream_tell(data->fp) - data->zero;
@@ -468,7 +482,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_warn(stream, FlushFailed, "%s", error);
efree(error);
}
return ret;
@@ -557,7 +571,7 @@ static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int f
char *internal_file;
size_t internal_file_len;
- php_url *resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET);
+ php_url *resource = phar_parse_url(wrapper, context, url, "r", flags|PHP_STREAM_URL_STAT_QUIET);
if (!resource) {
return FAILURE;
}
@@ -660,22 +674,25 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
size_t internal_file_len;
phar_entry_data *idata;
- php_url *resource = phar_parse_url(wrapper, url, "rb", options);
+ php_url *resource = phar_parse_url(wrapper, context, url, "rb", options);
if (!resource) {
- php_stream_wrapper_log_error(wrapper, options, "phar error: unlink failed");
+ php_stream_wrapper_log_warn(wrapper, context, options, UnlinkFailed,
+ "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, context, options, InvalidUrl,
+ "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, context, options, ProtocolUnsupported,
+ "phar error: not a phar stream url \"%s\"", url);
return 0;
}
@@ -684,7 +701,8 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
const phar_archive_data *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, context, options, Readonly,
+ "phar error: write operations disabled by the php.ini setting phar.readonly");
return 0;
}
@@ -694,10 +712,12 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
if (FAILURE == phar_get_entry_data(&idata, 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, context, options, UnlinkFailed,
+ "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, context, options, NotFound,
+ "unlink of \"%s\" failed, file does not exist", url);
}
efree(internal_file);
php_url_free(resource);
@@ -705,7 +725,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, context, options, UnlinkFailed,
+ "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);
@@ -715,7 +737,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, context, options, UnlinkFailed, "%s", error);
efree(error);
}
return 1;
@@ -730,23 +752,28 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
error = NULL;
- php_url *resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET);
+ php_url *resource_from = phar_parse_url(wrapper, context, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET);
if (!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_from);
+ php_stream_wrapper_warn(wrapper, context, options, InvalidUrl,
+ "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"",
+ url_from, url_to, url_from);
return 0;
}
phar_archive_data *pfrom = phar_get_archive(ZSTR_VAL(resource_from->host), ZSTR_LEN(resource_from->host), NULL, 0, NULL);
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, Readonly,
+ "phar error: Write operations disabled by the php.ini setting phar.readonly");
return 0;
}
- php_url *resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET);
+ php_url *resource_to = phar_parse_url(wrapper, context, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET);
if (!resource_to) {
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, InvalidUrl,
+ "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"",
+ url_from, url_to, url_to);
return 0;
}
@@ -754,14 +781,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, 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, RenameFailed,
+ "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive",
+ url_from, url_to);
return 0;
}
@@ -769,28 +799,36 @@ 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, InvalidUrl,
+ "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, InvalidUrl,
+ "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, ProtocolUnsupported,
+ "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, ProtocolUnsupported,
+ "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"",
+ url_from, url_to, url_to);
return 0;
}
@@ -798,7 +836,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
if (!phar) {
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, RenameFailed,
+ "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
efree(error);
return 0;
}
@@ -806,7 +845,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, RenameFailed,
+ "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable",
+ url_from, url_to);
return 0;
}
@@ -818,7 +859,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, NotFound,
+ "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 */
@@ -839,7 +882,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, RenameFailed,
+ "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
efree(error);
zend_hash_del(&phar->manifest, entry->filename);
return 0;
@@ -853,7 +897,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, NotFound,
+ "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist",
+ url_from, url_to);
return 0;
}
@@ -932,7 +978,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, RenameFailed,
+ "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
efree(error);
return 0;
}
diff --git a/ext/phar/stream.h b/ext/phar/stream.h
index cf40e3129c6..ebac093402b 100644
--- a/ext/phar/stream.h
+++ b/ext/phar/stream.h
@@ -18,7 +18,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);
diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
index 9d121070509..9ef87f42b22 100644
--- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
+++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
@@ -17,5 +17,10 @@
RoundingMode
SortDirection
StreamBucket
+StreamError
+StreamErrorCode
+StreamErrorMode
+StreamErrorStore
+StreamException
__PHP_Incomplete_Class
php_user_filter
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index 2a0e7a78673..0c1e2f8222a 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -335,6 +335,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/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index 1999c9b92be..b63d4f404a7 100644
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -3385,7 +3385,10 @@ function soundex(string $string): string {}
/* streamsfuncs.c */
-function stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null): int|false {}
+/**
+ * @param resource|null $context
+ */
+function stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null, $context = null): int|false {}
/**
* @return resource
@@ -3488,17 +3491,19 @@ function stream_socket_shutdown($stream, int $mode): bool {}
#ifdef HAVE_SOCKETPAIR
/**
+ * @param resource|null $context
* @return array<int, resource>|false
* @refcount 1
*/
-function stream_socket_pair(int $domain, int $type, int $protocol): array|false {}
+function stream_socket_pair(int $domain, int $type, int $protocol, $context = null): array|false {}
#endif
/**
* @param resource $from
* @param resource $to
+ * @param resource|null $context
*/
-function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0): int|false {}
+function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0, $context = null): int|false {}
/**
* @param resource $stream
@@ -3558,14 +3563,25 @@ function stream_resolve_include_path(string $filename): string|false {}
*/
function stream_get_wrappers(): array {}
+/**
+ * @refcount 1
+ * @return array<int, StreamError>
+ */
+function stream_last_errors(): array {}
+
+function stream_clear_errors(): void {}
+
/**
* @return array<int, string>
* @refcount 1
*/
function stream_get_transports(): array {}
-/** @param resource|string $stream */
-function stream_is_local($stream): bool {}
+/**
+ * @param resource|string $stream
+ * @param resource|null $context
+ */
+function stream_is_local($stream, $context = null): bool {}
/** @param resource $stream */
function stream_isatty($stream): bool {}
diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index e51a837ffa4..ca72962d34e 100644
Binary files a/ext/standard/basic_functions_arginfo.h and b/ext/standard/basic_functions_arginfo.h differ
diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h
index b3eb25c5d98..8e92337ba37 100644
Binary files a/ext/standard/basic_functions_decl.h and b/ext/standard/basic_functions_decl.h differ
diff --git a/ext/standard/file.c b/ext/standard/file.c
index ba5b26f6d22..db0fc45385d 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -221,7 +221,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);
}
/* }}} */
@@ -257,6 +259,8 @@ PHP_FUNCTION(get_meta_tags)
RETURN_FALSE;
}
+ php_stream_error_operation_begin();
+
array_init(return_value);
tok_last = TOK_EOF;
@@ -368,6 +372,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);
}
/* }}} */
@@ -402,12 +408,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;
}
@@ -418,8 +425,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;
}
@@ -430,6 +438,7 @@ PHP_FUNCTION(file_get_contents)
}
php_stream_close(stream);
+ php_stream_error_operation_end(context);
}
/* }}} */
@@ -459,6 +468,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) {
@@ -477,11 +487,13 @@ 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_stream_error_operation_end(context);
php_error_docref(NULL, E_WARNING, "Exclusive locks are not supported for this stream");
RETURN_FALSE;
}
@@ -564,6 +576,7 @@ PHP_FUNCTION(file_put_contents)
break;
}
php_stream_close(stream);
+ php_stream_error_operation_end(context);
if (numbytes < 0) {
RETURN_FALSE;
@@ -609,10 +622,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;
}
@@ -666,6 +681,7 @@ PHP_FUNCTION(file)
}
php_stream_close(stream);
+ php_stream_error_operation_end(context);
}
/* }}} */
@@ -705,7 +721,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);
@@ -733,9 +751,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;
@@ -759,9 +779,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;
}
@@ -809,6 +831,7 @@ PHP_FUNCTION(popen)
RETURN_FALSE;
}
+ php_stream_error_operation_begin();
stream = php_stream_fopen_from_pipe(fp, mode);
if (stream == NULL) {
@@ -817,6 +840,7 @@ PHP_FUNCTION(popen)
} else {
php_stream_to_zval(stream, return_value);
}
+ php_stream_error_operation_end_for_stream(stream);
efree(posix_mode);
}
@@ -831,9 +855,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));
}
/* }}} */
@@ -847,11 +873,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);
}
/* }}} */
@@ -871,9 +899,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;
}
@@ -887,7 +917,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;
}
@@ -912,7 +944,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;
@@ -931,7 +965,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)
@@ -939,16 +972,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;
}
@@ -994,7 +1029,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;
}
@@ -1013,8 +1050,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);
RETURN_BOOL(!ret);
}
/* }}} */
@@ -1022,13 +1060,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();
- RETURN_BOOL(-1 != php_stream_rewind(stream));
+ php_stream_error_operation_begin();
+ ret = php_stream_rewind(stream);
+ php_stream_error_operation_end_for_stream(stream);
+ RETURN_BOOL(-1 != ret);
}
/* }}} */
@@ -1042,7 +1084,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;
}
@@ -1063,7 +1107,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);
}
/* }}} */
@@ -1087,7 +1133,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);
}
/* }}} */
@@ -1107,7 +1155,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);
}
/* }}} */
@@ -1131,14 +1181,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;
}
/* }}} */
@@ -1180,7 +1233,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);
}
/* }}} */
@@ -1201,26 +1256,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_stream_error_operation_end(context);
php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper");
RETURN_FALSE;
}
if (!wrapper->wops->rename) {
+ 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_stream_error_operation_end(context);
php_error_docref(NULL, E_WARNING, "Cannot rename a file across wrapper types");
RETURN_FALSE;
}
- context = php_stream_context_from_zval(zcontext, 0);
-
- RETURN_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, 0, context));
+ RETVAL_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, REPORT_ERRORS, context));
+ php_stream_error_operation_end(context);
}
/* }}} */
@@ -1239,20 +1299,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_stream_error_operation_end(context);
php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper");
RETURN_FALSE;
}
if (!wrapper->wops->unlink) {
+ 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;
}
- 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);
}
/* }}} */
@@ -1264,12 +1328,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_stream_error_operation_end_for_stream(stream);
php_error_docref(NULL, E_WARNING, "Can't fsync this 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)
@@ -1280,12 +1347,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_stream_error_operation_end_for_stream(stream);
php_error_docref(NULL, E_WARNING, "Can't fsync this 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 */
@@ -1304,12 +1374,16 @@ PHP_FUNCTION(ftruncate)
RETURN_THROWS();
}
+ php_stream_error_operation_begin();
+
if (!php_stream_truncate_supported(stream)) {
+ php_stream_error_operation_end_for_stream(stream);
php_error_docref(NULL, E_WARNING, "Can't truncate this 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)
@@ -1393,7 +1467,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);
}
/* }}} */
@@ -1412,13 +1488,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);
}
/* }}} */
@@ -1543,7 +1622,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;
}
@@ -1662,7 +1743,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;
}
@@ -1795,19 +1878,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();
}
diff --git a/ext/standard/file.h b/ext/standard/file.h
index 3c6160fd4bb..ac218169599 100644
--- a/ext/standard/file.h
+++ b/ext/standard/file.h
@@ -32,6 +32,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);
@@ -98,7 +99,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 */
+ php_stream_error_state stream_error_state;
int pclose_wait;
#ifdef HAVE_GETHOSTBYNAME_R
struct hostent tmp_host_info;
diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c
index e1f3a88d249..f99ae5e4b4e 100644
--- a/ext/standard/ftp_fopen_wrapper.c
+++ b/ext/standard/ftp_fopen_wrapper.c
@@ -106,7 +106,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,
+ ProtocolError,
+ "FTP server error %d:%s", result, tmp_line);
ret = EOF;
}
}
@@ -184,7 +186,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, context, options, SslNotSupported,
+ "Server doesn't support FTPS.");
goto connect_errexit;
} else {
/* we must reuse the old SSL session id */
@@ -203,7 +206,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, context, options, SslNotSupported,
+ "Unable to activate SSL mode");
php_stream_close(stream);
stream = NULL;
goto connect_errexit;
@@ -234,7 +238,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((unsigned char)*s)) { \
- php_stream_wrapper_log_error(wrapper, options, err_msg, val); \
+ php_stream_wrapper_log_warn(wrapper, context, options, AuthFailed, err_msg, val); \
goto connect_errexit; \
} \
s++; \
@@ -431,7 +435,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, context, options, ModeNotSupported,
+ "FTP does not support simultaneous read/write connections");
return NULL;
}
if (strchr(mode, 'a')) {
@@ -442,7 +447,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, context, options, InvalidMode,
+ "Unknown file open mode");
return NULL;
}
@@ -453,7 +459,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, context, options, ModeNotSupported,
+ "FTP proxy may only be used in read mode");
return NULL;
}
}
@@ -505,7 +512,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, context, options, AlreadyExists,
+ "Remote file already exists and overwrite context option not specified");
errno = EEXIST;
goto errexit;
}
@@ -529,7 +537,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, context, options, ResumptionFailed,
+ "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval));
goto errexit;
}
}
@@ -574,7 +583,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, context, options, SslNotSupported,
+ "Unable to activate SSL mode");
php_stream_close(datastream);
datastream = NULL;
tmp_line[0]='\0';
@@ -596,10 +606,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, context, options, ProtocolError,
+ "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, context, options, NetworkSendFailed,
+ "Failed to set up data channel: %s", ZSTR_VAL(error_message));
zend_string_release(error_message);
}
return NULL;
@@ -744,7 +756,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, context, options, SslNotSupported,
+ "Unable to activate SSL mode");
php_stream_close(datastream);
datastream = NULL;
goto opendir_errexit;
@@ -768,7 +781,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, context, options, ProtocolError,
+ "FTP server reports %s", tmp_line);
}
return NULL;
}
@@ -907,14 +921,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, AuthFailed,
+ "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, InvalidPath,
+ "Invalid path provided in %s", url);
}
goto unlink_errexit;
}
@@ -925,7 +941,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, UnlinkFailed,
+ "Error Deleting file: %s", tmp_line);
}
goto unlink_errexit;
}
@@ -989,7 +1006,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, AuthFailed,
+ "Unable to connect to %s", ZSTR_VAL(resource_from->host));
}
goto rename_errexit;
}
@@ -1000,7 +1018,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, RenameFailed,
+ "Error Renaming file: %s", tmp_line);
}
goto rename_errexit;
}
@@ -1011,7 +1030,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, RenameFailed,
+ "Error Renaming file: %s", tmp_line);
}
goto rename_errexit;
}
@@ -1044,14 +1064,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, AuthFailed,
+ "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, InvalidPath,
+ "Invalid path provided in %s", url);
}
goto mkdir_errexit;
}
@@ -1092,7 +1114,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, MkdirFailed,
+ "%s", tmp_line);
}
break;
}
@@ -1136,14 +1159,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, AuthFailed,
+ "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, InvalidPath,
+ "Invalid path provided in %s", url);
}
goto rmdir_errexit;
}
@@ -1153,7 +1178,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, RmdirFailed,
+ "%s", tmp_line);
}
goto rmdir_errexit;
}
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 95a6b1930a1..9e3db671604 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -243,7 +243,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, context, options, InvalidResponse,
"HTTP invalid response format (space in header name)!");
zend_string_efree(last_header_line_str);
return NULL;
@@ -261,7 +261,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, context, options, InvalidResponse,
"HTTP invalid response format (no colon in header line)!");
zend_string_efree(last_header_line_str);
return NULL;
@@ -285,7 +285,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, context, options, InvalidResponse,
"HTTP Location header size is over the limit of %d bytes",
HTTP_HEADER_MAX_LOCATION_SIZE);
zend_string_efree(last_header_line_str);
@@ -386,7 +386,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, context, options, RedirectLimit,
+ "Redirection limit reached, aborting");
return NULL;
}
@@ -419,7 +420,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, context, options, ModeNotSupported,
+ "HTTP wrapper does not support writeable connections");
php_uri_struct_free(resource);
return NULL;
}
@@ -448,7 +450,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, context, options, InvalidUrl,
+ "HTTP wrapper full URI path does not allow CR or LF characters");
php_uri_struct_free(resource);
zend_string_release(transport_string);
return NULL;
@@ -463,7 +466,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, context, options, InvalidParam,
+ "timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax);
zend_string_release(transport_string);
php_uri_struct_free(resource);
return NULL;
@@ -493,7 +497,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, context, options, ProtocolError,
+ "%s", ZSTR_VAL(errstr));
zend_string_release_ex(errstr, 0);
errstr = NULL;
}
@@ -547,8 +552,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
if (reset_ssl_peer_name) {
php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
}
-
- php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
+ php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+ "Cannot connect to HTTPS server through proxy");
php_stream_close(stream);
stream = NULL;
}
@@ -573,7 +578,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
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, context, options, SslNotSupported,
+ "Cannot connect to HTTPS server through proxy");
php_stream_close(stream);
stream = NULL;
}
@@ -830,7 +836,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, InvalidHeader,
+ "Cannot construct User-agent header");
}
efree(ua);
}
@@ -869,7 +876,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, InvalidHeader,
+ "Content-type not specified assuming application/x-www-form-urlencoded");
}
smart_str_appends(&req_buf, "\r\n");
smart_str_append(&req_buf, Z_STR_P(tmpzval));
@@ -956,7 +964,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, context, options, ProtocolError,
+ "HTTP request failed!");
goto out;
}
}
@@ -974,7 +983,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, context, options, InvalidResponse,
"HTTP invalid header name (cannot start with CR character)!");
goto out;
}
@@ -1005,7 +1014,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, context, options, InvalidResponse,
"HTTP invalid response format (folding header at the start)!");
goto out;
}
@@ -1101,7 +1110,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, context, options, InvalidUrl,
+ "Invalid redirect URL! %s", new_path);
efree(new_path);
goto out;
}
@@ -1113,7 +1123,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, context, options, InvalidUrl, \
+ "Invalid redirect URL! %s", new_path); \
efree(new_path); \
goto out; \
} \
@@ -1139,7 +1150,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, context, options, ProtocolError,
+ "HTTP request failed! %s", tmp_line);
}
}
out:
diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c
index 89c2d9c49b0..3f062bf2ea1 100644
--- a/ext/standard/php_fopen_wrapper.c
+++ b/ext/standard/php_fopen_wrapper.c
@@ -156,14 +156,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,
+ CreateFailed,
+ "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,
+ CreateFailed,
+ "Unable to create filter (%s)", p);
}
}
p = php_strtok_r(NULL, "|", &token);
@@ -217,7 +221,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,
+ Disabled,
+ "URL file-access is disabled in the server configuration");
}
return NULL;
}
@@ -236,7 +242,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,
+ Disabled,
+ "URL file-access is disabled in the server configuration");
}
return NULL;
}
@@ -295,14 +303,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,
+ 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,
+ Disabled,
+ "URL file-access is disabled in the server configuration");
}
return NULL;
}
@@ -310,7 +322,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,
+ InvalidUrl,
"php://fd/ stream must be specified in the form php://fd/<orig fd>");
return NULL;
}
@@ -322,14 +335,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,
+ InvalidParam,
"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,
+ DupFailed,
"Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: "
"[%d]: %s", fildes_ori, errno, strerror(errno));
return NULL;
@@ -378,7 +393,8 @@ 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,
+ InvalidUrl, "Invalid php:// URL specified");
return NULL;
}
diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 68f79783c6f..3609ee1f3f2 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -48,11 +48,15 @@ PHP_FUNCTION(stream_socket_pair)
zend_long domain, type, protocol;
php_stream *s1, *s2;
php_socket_t pair[2];
+ zval *zcontext = NULL;
+ php_stream_context *context = NULL;
- ZEND_PARSE_PARAMETERS_START(3, 3)
+ ZEND_PARSE_PARAMETERS_START(3, 4)
Z_PARAM_LONG(domain)
Z_PARAM_LONG(type)
Z_PARAM_LONG(protocol)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_RESOURCE_OR_NULL(zcontext)
ZEND_PARSE_PARAMETERS_END();
if (0 != socketpair((int)domain, (int)type, (int)protocol, pair)) {
@@ -62,10 +66,14 @@ PHP_FUNCTION(stream_socket_pair)
RETURN_FALSE;
}
+ php_stream_error_operation_begin();
+ context = php_stream_context_from_zval(zcontext, 0);
+
s1 = php_stream_sock_open_from_socket(pair[0], 0);
if (s1 == NULL) {
close(pair[0]);
close(pair[1]);
+ php_stream_error_operation_end(context);
php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
RETURN_FALSE;
}
@@ -73,6 +81,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(context);
php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
RETURN_FALSE;
}
@@ -86,6 +95,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(context);
}
/* }}} */
#endif
@@ -124,6 +135,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) {
@@ -158,6 +170,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 */
@@ -215,6 +228,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) {
@@ -228,6 +242,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));
}
@@ -293,6 +309,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,
@@ -311,6 +329,8 @@ PHP_FUNCTION(stream_socket_accept)
RETVAL_FALSE;
}
+ php_stream_error_operation_end_for_stream(stream);
+
if (errstr) {
zend_string_release_ex(errstr, 0);
}
@@ -329,10 +349,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;
}
@@ -363,15 +384,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);
}
/* }}} */
@@ -405,9 +429,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) {
@@ -445,6 +470,8 @@ PHP_FUNCTION(stream_get_contents)
RETURN_THROWS();
}
+ php_stream_error_operation_begin();
+
if (desiredpos >= 0) {
int seek_res = 0;
zend_off_t position;
@@ -459,6 +486,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;
@@ -466,10 +494,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);
}
/* }}} */
@@ -480,28 +509,37 @@ PHP_FUNCTION(stream_copy_to_stream)
zend_long maxlen, pos = 0;
bool maxlen_is_null = 1;
size_t len;
+ zval *zcontext = NULL;
+ php_stream_context *context = NULL;
- ZEND_PARSE_PARAMETERS_START(2, 4)
+ ZEND_PARSE_PARAMETERS_START(2, 5)
PHP_Z_PARAM_STREAM(src)
PHP_Z_PARAM_STREAM(dest)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null)
Z_PARAM_LONG(pos)
+ Z_PARAM_RESOURCE_OR_NULL(zcontext)
ZEND_PARSE_PARAMETERS_END();
if (maxlen_is_null) {
maxlen = PHP_STREAM_COPY_ALL;
}
+ php_stream_error_operation_begin();
+ context = php_stream_context_from_zval(zcontext, 0);
+
if (pos > 0 && php_stream_seek(src, pos, SEEK_SET) < 0) {
+ php_stream_error_operation_end(context);
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(context);
}
/* }}} */
@@ -516,11 +554,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);
@@ -592,6 +632,18 @@ PHP_FUNCTION(stream_get_wrappers)
}
/* }}} */
+PHP_FUNCTION(stream_last_errors)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+ php_stream_error_get_last(return_value);
+}
+
+PHP_FUNCTION(stream_clear_errors)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+ php_stream_error_clear_stored();
+}
+
/* {{{ stream_select related functions */
static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, php_socket_t *max_fd)
{
@@ -724,7 +776,7 @@ static int stream_array_emulate_read_fd_set(zval *stream_array)
/* {{{ Runs the select() system call on the sets of streams with a timeout specified by tv_sec and tv_usec */
PHP_FUNCTION(stream_select)
{
- zval *r_array, *w_array, *e_array;
+ zval *r_array, *w_array, *e_array, *zcontext = NULL;
struct timeval tv, *tv_p = NULL;
fd_set rfds, wfds, efds;
php_socket_t max_fd = 0;
@@ -733,20 +785,25 @@ PHP_FUNCTION(stream_select)
bool secnull;
bool usecnull = 1;
int set_count, max_set_count = 0;
+ php_stream_context *context = NULL;
- ZEND_PARSE_PARAMETERS_START(4, 5)
+ ZEND_PARSE_PARAMETERS_START(4, 6)
Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0)
Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0)
Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0)
Z_PARAM_LONG_OR_NULL(sec, secnull)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(usec, usecnull)
+ Z_PARAM_RESOURCE_OR_NULL(zcontext)
ZEND_PARSE_PARAMETERS_END();
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
+ php_stream_error_operation_begin();
+ context = php_stream_context_from_zval(zcontext, 0);
+
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)
@@ -769,6 +826,7 @@ PHP_FUNCTION(stream_select)
}
if (!sets) {
+ php_stream_error_operation_end(context);
zend_value_error("No stream arrays were passed");
RETURN_THROWS();
}
@@ -779,6 +837,7 @@ PHP_FUNCTION(stream_select)
if (secnull && !usecnull) {
if (usec != 0) {
+ php_stream_error_operation_end(context);
zend_argument_value_error(5, "must be null when argument #4 ($seconds) is null");
RETURN_THROWS();
}
@@ -787,9 +846,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(context);
zend_argument_value_error(4, "must be greater than or equal to 0");
RETURN_THROWS();
} else if (usec < 0) {
+ php_stream_error_operation_end(context);
zend_argument_value_error(5, "must be greater than or equal to 0");
RETURN_THROWS();
}
@@ -806,6 +867,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(context);
if (w_array != NULL) {
zval_ptr_dtor(w_array);
ZVAL_EMPTY_ARRAY(w_array);
@@ -819,6 +881,7 @@ PHP_FUNCTION(stream_select)
}
retval = php_select(max_fd+1, &rfds, &wfds, &efds, tv_p);
+ php_stream_error_operation_end(context);
if (retval == -1) {
php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s (max_fd=" PHP_SOCKET_FMT ")",
@@ -1145,6 +1208,24 @@ PHP_FUNCTION(stream_context_get_default)
}
/* }}} */
+/* Check if options contain stream error handling settings */
+static bool php_stream_context_options_has_error_settings(const HashTable *options)
+{
+ zval *stream_options = zend_hash_str_find(options, ZEND_STRL("stream"));
+ if (!stream_options) {
+ return false;
+ }
+
+ ZVAL_DEREF(stream_options);
+ if (Z_TYPE_P(stream_options) != IS_ARRAY) {
+ return false;
+ }
+
+ return zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_mode"))
+ || zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_store"))
+ || zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_handler"));
+}
+
/* {{{ Set default file/stream context, returns the context as a resource */
PHP_FUNCTION(stream_context_set_default)
{
@@ -1160,6 +1241,11 @@ PHP_FUNCTION(stream_context_set_default)
}
context = FG(default_context);
+ if (php_stream_context_options_has_error_settings(options)) {
+ zend_value_error("Stream error handling options cannot be set on the default context");
+ RETURN_THROWS();
+ }
+
if (parse_context_options(context, options) == FAILURE) {
RETURN_THROWS();
}
@@ -1339,11 +1425,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);
}
/* }}} */
@@ -1359,7 +1447,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);
}
/* }}} */
@@ -1400,7 +1490,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) */
/* }}} */
@@ -1420,12 +1512,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);
}
@@ -1456,7 +1550,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);
}
@@ -1477,12 +1573,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);
}
@@ -1504,11 +1602,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();
}
@@ -1517,11 +1618,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;
@@ -1557,12 +1660,15 @@ PHP_FUNCTION(stream_resolve_include_path)
/* {{{ */
PHP_FUNCTION(stream_is_local)
{
- zval *zstream;
+ zval *zstream, *zcontext = NULL;
php_stream *stream = NULL;
php_stream_wrapper *wrapper = NULL;
+ php_stream_context *context = NULL;
- ZEND_PARSE_PARAMETERS_START(1, 1)
+ ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ZVAL(zstream)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_RESOURCE_OR_NULL(zcontext)
ZEND_PARSE_PARAMETERS_END();
if (Z_TYPE_P(zstream) == IS_RESOURCE) {
@@ -1573,7 +1679,10 @@ PHP_FUNCTION(stream_is_local)
RETURN_THROWS();
}
+ php_stream_error_operation_begin();
+ context = php_stream_context_from_zval(zcontext, 0);
wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, 0);
+ php_stream_error_operation_end(context);
}
RETURN_BOOL(wrapper && wrapper->is_url == 0);
@@ -1589,7 +1698,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. */
@@ -1602,6 +1713,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.
@@ -1611,8 +1724,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 */
@@ -1642,6 +1757,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.
@@ -1651,6 +1768,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,
@@ -1660,6 +1778,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)) {
@@ -1699,7 +1818,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
diff --git a/ext/standard/tests/file/php_fd_wrapper_03.phpt b/ext/standard/tests/file/php_fd_wrapper_03.phpt
index 991c497f5e1..a19d1f5acd9 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 @@
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
diff --git a/ext/standard/tests/streams/gh14506.phpt b/ext/standard/tests/streams/gh14506.phpt
index f83eba4f1ff..3d82350221d 100644
--- a/ext/standard/tests/streams/gh14506.phpt
+++ b/ext/standard/tests/streams/gh14506.phpt
@@ -86,10 +86,10 @@ function stream_flush(): bool
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
diff --git a/ext/standard/tests/streams/stream_errors_error_has_code.phpt b/ext/standard/tests/streams/stream_errors_error_has_code.phpt
new file mode 100644
index 00000000000..6cd6b70411a
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_error_has_code.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Stream errors - multiple operations stored
+--FILE--
+<?php
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::All,
+ ]
+]);
+
+// First operation
+$stream1 = fopen('php://nonexistent1', 'r', false, $context);
+$error1 = stream_last_errors()[0];
+
+// Second operation
+$stream2 = fopen('php://nonexistent2', 'r', false, $context);
+$error2 = stream_last_errors()[0];
+
+// 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
new file mode 100644
index 00000000000..37f5d39bfa7
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Stream errors - exception mode for terminal errors
+--FILE--
+<?php
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Exception,
+ ]
+]);
+
+try {
+ $stream = fopen('php://nonexistent', 'r', false, $context);
+} catch (StreamException $e) {
+ echo "Caught: " . $e->getMessage() . "\n";
+ echo "Code: " . $e->getCode() . "\n";
+
+ $errors = $e->getErrors();
+ if (!empty($errors)) {
+ $error = $errors[0];
+ echo "Wrapper: " . $error->wrapperName . "\n";
+ echo "Error code name: " . $error->code->name . "\n";
+ }
+}
+
+?>
+--EXPECTF--
+Caught: Failed to open stream: operation failed
+Code: 20
+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 00000000000..1b3ab5e8931
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_invalid_types.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Stream errors - invalid enum type throws TypeError
+--FILE--
+<?php
+
+try {
+ $context = stream_context_create([
+ 'stream' => [
+ '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";
+}
+
+?>
+--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
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 00000000000..49c18616dad
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt
@@ -0,0 +1,149 @@
+--TEST--
+Stream errors - silent mode with mixed error stores
+--FILE--
+<?php
+
+class TestStream {
+ public $context;
+ private $position = 0;
+
+ public function stream_open($path, $mode, $options, &$opened_path) {
+ return true;
+ }
+
+ public function stream_read($count) {
+ $this->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, $contextOptions) {
+ stream_clear_errors();
+ $context = stream_context_create($contextOptions);
+ $stream = fopen('test://foo', 'r', false, $context);
+ try {
+ echo $title . "\n";
+ $readin = fopen('php://stdin', 'r', false, $context);
+ $data = fread($stream, 10);
+
+ $read = [$readin, $stream];
+ $write = NULL;
+ $except = NULL;
+ stream_select($read, $write, $except, 0, 0, $context);
+ } catch (StreamException $e) {
+ echo 'EXCEPTION: ' . $e->getMessage() . "\n";
+ }
+
+ $errors = stream_last_errors();
+ if ($errors) {
+ $first = $errors[0];
+ echo "Error details:\n";
+ echo "- Message: $first->message\n";
+ echo "- Code: " . $first->code->name . "\n";
+ echo "- Wrapper: $first->wrapperName\n";
+ echo "- Terminating: " . ($first->terminating ? 'yes' : 'no') . "\n";
+ echo "- Count: " . count($errors) . "\n";
+
+ foreach ($errors as $idx => $error) {
+ echo " [$idx] " . $error->code->name . ": " . $error->message . "\n";
+ }
+ } else {
+ echo "No errors stored\n";
+ }
+ echo "\n";
+}
+
+stream_test_errors('ALL', [
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::All,
+ ]
+]);
+
+stream_test_errors('NON TERMINATING', [
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::NonTerminating,
+ ]
+]);
+
+stream_test_errors('TERMINATING', [
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::Terminating,
+ ]
+]);
+
+stream_test_errors('AUTO EXCEPTION', [
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Exception,
+ 'error_store' => StreamErrorStore::Auto,
+ ]
+]);
+
+stream_test_errors('AUTO ERROR', [
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Error,
+ 'error_store' => StreamErrorStore::Auto,
+ ]
+]);
+
+?>
+--EXPECTF--
+ALL
+Error details:
+- Message: TestStream::stream_cast is not implemented!
+- Code: NotImplemented
+- 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 a select()able descriptor
+
+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
+- 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
+
+TERMINATING
+Error details:
+- Message: TestStream::stream_cast is not implemented!
+- Code: NotImplemented
+- 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 a select()able descriptor
+
+AUTO EXCEPTION
+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
+- 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
+
+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
+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
new file mode 100644
index 00000000000..2575a8a795f
--- /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--
+<?php
+
+// AUTO with ERROR mode should store NONE
+$context1 = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Error,
+ 'error_store' => StreamErrorStore::Auto,
+ ]
+]);
+
+@fopen('php://nonexistent', 'r', false, $context1);
+$errors1 = stream_last_errors();
+echo "ERROR mode AUTO: " . (!empty($errors1) ? "has error" : "no error") . "\n";
+
+// AUTO with EXCEPTION mode should store NON_TERM
+$context2 = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Exception,
+ 'error_store' => StreamErrorStore::Auto,
+ ]
+]);
+
+try {
+ fopen('php://nonexistent2', 'r', false, $context2);
+} catch (StreamException $e) {}
+
+$errors2 = stream_last_errors();
+echo "EXCEPTION mode AUTO: " . (!empty($errors2) ? "has error" : "no error") . "\n";
+
+// AUTO with SILENT mode should store ALL
+$context3 = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::Auto,
+ ]
+]);
+
+fopen('php://nonexistent3', 'r', false, $context3);
+$errors3 = stream_last_errors();
+echo "SILENT mode AUTO: " . (!empty($errors3) ? "has error" : "no error") . "\n";
+
+?>
+--EXPECTF--
+ERROR mode AUTO: no error
+EXCEPTION mode AUTO: %s
+SILENT mode AUTO: has error
diff --git a/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt b/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt
new file mode 100644
index 00000000000..46fc0f7fd34
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Stream errors - prohibit setting error mode in default context
+--FILE--
+<?php
+
+try {
+ stream_context_set_default([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Exception,
+ ]
+ ]);
+} catch (\ValueError $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Stream error handling options cannot be set on the default context
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 00000000000..456575affc3
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Stream errors - custom error handler
+--FILE--
+<?php
+
+$handler_called = false;
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_handler' => function(array $errors) use (&$handler_called) {
+ $handler_called = true;
+ echo "Handler called\n";
+ foreach ($errors as $error) {
+ 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";
+ }
+ }
+ ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+
+var_dump($handler_called);
+
+?>
+--EXPECT--
+Handler called
+Wrapper: PHP
+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
new file mode 100644
index 00000000000..9e356216b0d
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Stream errors - silent mode with error storage
+--FILE--
+<?php
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+var_dump($stream);
+
+$errors = stream_last_errors();
+foreach ($errors as $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)
+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
new file mode 100644
index 00000000000..8155ce60bfe
--- /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--
+<?php
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::Silent,
+ 'error_store' => StreamErrorStore::None,
+ ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+
+$errors = stream_last_errors();
+echo "Has error: " . (!empty($error) ? "yes" : "no") . "\n";
+
+?>
+--EXPECT--
+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
new file mode 100644
index 00000000000..42de8cc3b17
--- /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--
+<?php
+
+$context = stream_context_create([
+ 'stream' => [
+ 'error_mode' => StreamErrorMode::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)
diff --git a/main/php_streams.h b/main/php_streams.h
index d248de7a816..7622a7295af 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -246,6 +246,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) \
@@ -537,6 +539,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"
@@ -640,10 +643,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/cast.c b/main/streams/cast.c
index 10c93cbb3ff..f480f3cd59a 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -187,7 +187,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)
{
@@ -257,7 +256,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, CastFailed, "fopencookie failed");
return FAILURE;
#endif
@@ -297,7 +296,8 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
if (php_stream_is_filtered(stream) && castas != PHP_STREAM_AS_FD_FOR_SELECT) {
if (show_err) {
- php_error_docref(NULL, E_WARNING, "Cannot cast a filtered stream on this system");
+ php_stream_warn(stream, CastNotSupported,
+ "Cannot cast a filtered stream on this system");
}
return FAILURE;
} else if (stream->ops->cast && stream->ops->cast(stream, castas, ret) == SUCCESS) {
@@ -313,7 +313,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, CastNotSupported,
+ "Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]);
}
return FAILURE;
@@ -328,7 +329,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, BufferedDataLost,
+ ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!",
+ (zend_long)(stream->writepos - stream->readpos));
}
if (castas == PHP_STREAM_AS_STDIO && ret) {
diff --git a/main/streams/filter.c b/main/streams/filter.c
index 3a19f5ce918..35dbef455a3 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -391,7 +391,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, FilterFailed,
+ "Filter failed to process pre-buffered data");
return FAILURE;
case PSFS_FEED_ME:
/* We don't actually need data yet,
diff --git a/main/streams/memory.c b/main/streams/memory.c
index 44336f0e3e2..af54c46dd9a 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,
+ PermissionDenied,
+ "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,
+ PermissionDenied, "Unable to create temporary file.");
return FAILURE;
}
@@ -639,7 +642,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
}
if ((comma = (char *) memchr(path, ',', dlen)) == NULL) {
- php_stream_wrapper_log_error(wrapper, options, "rfc2397: no comma in URL");
+ php_stream_wrapper_log_warn(wrapper, context, options,
+ InvalidUrl, "rfc2397: no comma in URL");
return NULL;
}
@@ -651,7 +655,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,
+ InvalidUrl, "rfc2397: illegal media type");
return NULL;
}
@@ -666,7 +671,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,
+ InvalidUrl, "rfc2397: illegal media type");
return NULL;
}
/* get parameters and potentially ';base64' */
@@ -679,7 +685,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,
+ InvalidParam, "rfc2397: illegal parameter");
return NULL;
}
base64 = 1;
@@ -699,7 +706,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,
+ InvalidUrl, "rfc2397: illegal URL");
return NULL;
}
} else {
@@ -715,7 +723,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,
+ DecodingFailed, "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
new file mode 100644
index 00000000000..c55c06e37f2
--- /dev/null
+++ b/main/streams/php_stream_errors.h
@@ -0,0 +1,239 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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 <bukka@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+#ifndef PHP_STREAM_ERRORS_H
+#define PHP_STREAM_ERRORS_H
+
+#include "php.h"
+#include "php_streams.h"
+#include "stream_errors_decl.h"
+
+BEGIN_EXTERN_C()
+
+/* 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 1000
+/* Operations pool size to prevent extra allocations */
+#define PHP_STREAM_ERROR_OPERATION_POOL_SIZE 8
+
+/* Shorthand for error code enum values */
+#define PHP_STREAM_EC(name) ZEND_ENUM_StreamErrorCode_##name
+
+/* Error code range boundaries (case_id based, ranges are [start, end)) */
+#define STREAM_EC_IO_START PHP_STREAM_EC(ReadFailed)
+#define STREAM_EC_IO_END PHP_STREAM_EC(Disabled)
+#define STREAM_EC_FS_START PHP_STREAM_EC(Disabled)
+#define STREAM_EC_FS_END PHP_STREAM_EC(NotImplemented)
+#define STREAM_EC_WRAPPER_START PHP_STREAM_EC(NotImplemented)
+#define STREAM_EC_WRAPPER_END PHP_STREAM_EC(FilterNotFound)
+#define STREAM_EC_FILTER_START PHP_STREAM_EC(FilterNotFound)
+#define STREAM_EC_FILTER_END PHP_STREAM_EC(CastFailed)
+#define STREAM_EC_CAST_START PHP_STREAM_EC(CastFailed)
+#define STREAM_EC_CAST_END PHP_STREAM_EC(NetworkSendFailed)
+#define STREAM_EC_NETWORK_START PHP_STREAM_EC(NetworkSendFailed)
+#define STREAM_EC_NETWORK_END PHP_STREAM_EC(ArchivingFailed)
+#define STREAM_EC_ENCODING_START PHP_STREAM_EC(ArchivingFailed)
+#define STREAM_EC_ENCODING_END PHP_STREAM_EC(AllocationFailed)
+#define STREAM_EC_RESOURCE_START PHP_STREAM_EC(AllocationFailed)
+#define STREAM_EC_RESOURCE_END PHP_STREAM_EC(LockFailed)
+#define STREAM_EC_LOCK_START PHP_STREAM_EC(LockFailed)
+#define STREAM_EC_LOCK_END PHP_STREAM_EC(UserspaceNotImplemented)
+#define STREAM_EC_USERSPACE_START PHP_STREAM_EC(UserspaceNotImplemented)
+#define STREAM_EC_USERSPACE_END (PHP_STREAM_EC(UserspaceCallFailed) + 1)
+
+/* 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)
+
+/* Error entry in chain (internal linked list) */
+typedef struct _php_stream_error_entry {
+ zend_string *message;
+ zend_enum_StreamErrorCode code;
+ char *wrapper_name;
+ char *param;
+ char *docref;
+ int severity;
+ bool terminating;
+ struct _php_stream_error_entry *next;
+} php_stream_error_entry;
+
+/* 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;
+} php_stream_error_operation;
+
+/* Stored completed operation */
+typedef struct _php_stream_stored_error {
+ php_stream_error_entry *first_error;
+ uint32_t error_count;
+ struct _php_stream_stored_error *next;
+} php_stream_stored_error;
+
+typedef struct {
+ php_stream_error_operation *current_operation;
+ uint32_t operation_depth;
+ php_stream_stored_error *stored_errors;
+ uint32_t stored_count;
+ php_stream_error_operation operation_pool[PHP_STREAM_ERROR_OPERATION_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(void);
+PHPAPI void php_stream_error_operation_end(php_stream_context *context);
+PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream);
+PHPAPI void php_stream_error_operation_abort(void);
+
+/* State cleanup function */
+PHPAPI void php_stream_error_state_cleanup(void);
+
+/* Retrieve last stored errors as array of StreamError objects */
+PHPAPI void php_stream_error_get_last(zval *return_value);
+
+/* Clear all stored errors */
+PHPAPI void php_stream_error_clear_stored(void);
+
+/* 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 terminating, zend_enum_StreamErrorCode 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 terminating,
+ zend_enum_StreamErrorCode 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 terminating,
+ zend_enum_StreamErrorCode 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 terminating, zend_enum_StreamErrorCode 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 terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+ ZEND_ATTRIBUTE_FORMAT(printf, 6, 7);
+
+/* 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 terminating,
+ zend_enum_StreamErrorCode 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 terminating,
+ zend_enum_StreamErrorCode 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, zend_enum_StreamErrorCode code, const char *path,
+ const char *caption);
+
+PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper,
+ php_stream_context *context, zend_enum_StreamErrorCode 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 - code argument is the bare case name (e.g. RenameFailed) */
+#define php_stream_wrapper_warn(wrapper, context, options, code, ...) \
+ php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \
+ php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, \
+ PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_notice(wrapper, context, options, code, ...) \
+ php_stream_wrapper_error(wrapper, context, NULL, options, E_NOTICE, false, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(code), param1, param2, __VA_ARGS__)
+
+#define php_stream_warn(stream, code, ...) \
+ php_stream_error(stream, NULL, E_WARNING, true, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_warn_nt(stream, code, ...) \
+ php_stream_error(stream, NULL, E_WARNING, false, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_warn_docref(stream, docref, code, ...) \
+ php_stream_error(stream, docref, E_WARNING, true, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_notice(stream, code, ...) \
+ php_stream_error(stream, NULL, E_NOTICE, false, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_fatal(stream, code, ...) \
+ php_stream_error(stream, NULL, E_ERROR, true, PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(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, \
+ PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_log_notice(wrapper, context, options, code, ...) \
+ php_stream_wrapper_log_error(wrapper, context, options, E_NOTICE, false, \
+ PHP_STREAM_EC(code), __VA_ARGS__)
+
+END_EXTERN_C()
+
+#endif /* PHP_STREAM_ERRORS_H */
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index 69258158fbe..9335ab3fdb6 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -392,7 +392,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, WriteFailed,
+ "Write of %zu bytes failed with errno=%d %s",
count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
}
@@ -470,7 +471,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, ReadFailed,
+ "Read of %zu bytes failed with errno=%d %s",
count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
@@ -619,7 +621,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,
+ SeekNotSupported, "Cannot seek on this stream");
return -1;
}
@@ -1156,7 +1159,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,
+ InvalidMode, "`%s' is not a valid mode for fopen", mode);
return NULL;
}
@@ -1309,8 +1313,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,
+ UnlinkFailed, url,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
return 0;
}
@@ -1377,20 +1382,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, NULL, options, E_WARNING,
+ !success, PHP_STREAM_EC(ChownFailed), 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, NULL, options, E_WARNING,
+ !success, PHP_STREAM_EC(ChownFailed), url_from, url_to,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
}
# endif
@@ -1398,12 +1405,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, StatFailed,
+ 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, CopyFailed,
+ url_from, url_to,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
# if !defined(ZTS) && !defined(TSRM_WIN32)
umask(oldmask);
@@ -1416,8 +1425,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,
+ RenameFailed, url_from, url_to,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
#endif
return 0;
}
@@ -1441,7 +1451,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,
+ MkdirFailed, "%s", strerror(errno));
return 0;
}
@@ -1450,7 +1461,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,
+ InvalidPath, "Invalid path");
return 0;
}
@@ -1502,7 +1514,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,
+ MkdirFailed,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
return 0;
}
@@ -1522,7 +1536,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,
+ MkdirFailed,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
}
return 0;
}
@@ -1544,13 +1560,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,
+ NotFound, 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,
+ RmdirFailed, url,
+ "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
return 0;
}
@@ -1573,7 +1593,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,
+ NotFound, url,
+ "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
return 0;
}
#endif
@@ -1592,7 +1614,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,
+ PermissionDenied, url,
+ "Unable to create file %s because %s", url,
php_socket_strerror_s(errno, errstr, sizeof(errstr)));
return 0;
}
@@ -1606,7 +1630,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,
+ MetaFailed, url,
+ "Unable to find uid for %s", (char *)value);
return 0;
}
} else {
@@ -1618,7 +1644,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,
+ MetaFailed, url,
+ "Unable to find gid for %s", (char *)value);
return 0;
}
} else {
@@ -1636,8 +1664,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,
+ MetaFailed, url,
+ "Operation failed: %s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
return 0;
}
php_clear_stat_cache(0, NULL, 0);
@@ -1730,7 +1759,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,
+ PathTooLong,
+ "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
}
efree(cwd);
@@ -1785,7 +1816,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,
+ PathTooLong,
+ "%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.c b/main/streams/stream_errors.c
new file mode 100644
index 00000000000..1131f70ec29
--- /dev/null
+++ b/main/streams/stream_errors.c
@@ -0,0 +1,921 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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 <bukka@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+#define ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
+#include "php.h"
+#include "php_globals.h"
+#include "php_streams.h"
+#include "php_stream_errors.h"
+#include "zend_enum.h"
+#include "zend_exceptions.h"
+#include "ext/standard/file.h"
+#include "stream_errors_arginfo.h"
+
+/* 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;
+
+/* Forward declarations */
+static void php_stream_error_entry_free(php_stream_error_entry *entry);
+
+/* Helper to create a single StreamError object from an entry */
+static void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry)
+{
+ object_init_ex(zv, php_ce_stream_error);
+
+ const char *case_name = NULL;
+ if (entry->code > 0 && entry->code <= ZEND_ENUM_StreamErrorCode_CASE_COUNT) {
+ case_name = zend_enum_StreamErrorCode_case_names[entry->code];
+ }
+ if (!case_name) {
+ case_name = "Generic";
+ }
+
+ 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_COPY(&code_enum, enum_obj);
+
+ zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum);
+ zval_ptr_dtor(&code_enum);
+
+ zend_update_property_str(
+ php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->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"));
+ }
+}
+
+/* Create array of StreamError objects from error chain */
+PHPAPI void php_stream_error_create_array(zval *zv, php_stream_error_entry *first)
+{
+ array_init(zv);
+
+ php_stream_error_entry *entry = first;
+ while (entry) {
+ zval error_obj;
+ php_stream_error_create_object(&error_obj, entry);
+ zend_hash_next_index_insert_new(Z_ARRVAL_P(zv), &error_obj);
+ entry = entry->next;
+ }
+}
+
+/* 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) {
+ 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;
+ }
+
+ switch ((zend_enum_StreamErrorMode) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
+ case ZEND_ENUM_StreamErrorMode_Error:
+ return PHP_STREAM_ERROR_MODE_ERROR;
+ case ZEND_ENUM_StreamErrorMode_Exception:
+ return PHP_STREAM_ERROR_MODE_EXCEPTION;
+ case ZEND_ENUM_StreamErrorMode_Silent:
+ return PHP_STREAM_ERROR_MODE_SILENT;
+ }
+
+ 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) {
+ 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);
+ }
+
+ switch ((zend_enum_StreamErrorStore) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
+ case ZEND_ENUM_StreamErrorStore_Auto:
+ return php_stream_auto_decide_error_store_mode(error_mode);
+ case ZEND_ENUM_StreamErrorStore_None:
+ return PHP_STREAM_ERROR_STORE_NONE;
+ case ZEND_ENUM_StreamErrorStore_NonTerminating:
+ return PHP_STREAM_ERROR_STORE_NON_TERM;
+ case ZEND_ENUM_StreamErrorStore_Terminating:
+ return PHP_STREAM_ERROR_STORE_TERMINAL;
+ case ZEND_ENUM_StreamErrorStore_All:
+ return PHP_STREAM_ERROR_STORE_ALL;
+ }
+
+ return php_stream_auto_decide_error_store_mode(error_mode);
+}
+
+/* Helper functions */
+
+static bool php_stream_has_terminating_error(php_stream_error_operation *op)
+{
+ php_stream_error_entry *entry = op->first_error;
+ while (entry) {
+ if (entry->terminating) {
+ return true;
+ }
+ entry = entry->next;
+ }
+ return false;
+}
+
+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];
+ }
+}
+
+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) {
+ php_stream_error_entry *next = entry->next;
+ zend_string_release(entry->message);
+ efree(entry->wrapper_name);
+ efree(entry->param);
+ efree(entry);
+ entry = next;
+ }
+}
+
+PHPAPI void php_stream_error_state_cleanup(void)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+
+ while (state->current_operation) {
+ php_stream_error_operation *op = state->current_operation;
+ state->operation_depth--;
+ state->current_operation = php_stream_get_parent_operation();
+
+ php_stream_error_entry_free(op->first_error);
+
+ op->first_error = NULL;
+ op->last_error = NULL;
+ op->error_count = 0;
+ }
+
+ 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);
+ efree(stored);
+ stored = next;
+ }
+
+ state->stored_errors = NULL;
+ state->stored_count = 0;
+ state->operation_depth = 0;
+
+ if (state->overflow_operations) {
+ efree(state->overflow_operations);
+ state->overflow_operations = NULL;
+ state->overflow_capacity = 0;
+ }
+}
+
+PHPAPI void php_stream_error_get_last(zval *return_value)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+
+ if (!state->stored_errors) {
+ ZVAL_EMPTY_ARRAY(return_value);
+ return;
+ }
+
+ php_stream_error_create_array(return_value, state->stored_errors->first_error);
+}
+
+PHPAPI void php_stream_error_clear_stored(void)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+
+ 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);
+ efree(stored);
+ stored = next;
+ }
+
+ state->stored_errors = NULL;
+ state->stored_count = 0;
+}
+
+/* Error operation stack management */
+
+PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+
+ if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) {
+ php_error_docref(NULL, E_WARNING,
+ "Stream error operation depth exceeded (%u), possible infinite recursion",
+ state->operation_depth);
+ return NULL;
+ }
+
+ php_stream_error_operation *op;
+
+ if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
+ op = &state->operation_pool[state->operation_depth];
+ } else {
+ uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
+
+ 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];
+ }
+
+ op->first_error = NULL;
+ op->last_error = NULL;
+ op->error_count = 0;
+
+ state->current_operation = op;
+ state->operation_depth++;
+
+ return op;
+}
+
+static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name,
+ zend_string *message, const char *docref, char *param, int severity, bool terminating)
+{
+ php_stream_error_operation *op = FG(stream_error_state).current_operation;
+ ZEND_ASSERT(op != 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;
+ entry->docref = docref ? estrdup(docref) : NULL;
+ entry->severity = severity;
+ entry->terminating = terminating;
+ entry->next = NULL;
+
+ if (op->last_error) {
+ op->last_error->next = entry;
+ } else {
+ op->first_error = entry;
+ }
+ op->last_error = entry;
+ op->error_count++;
+}
+
+/* Error reporting */
+
+static void php_stream_call_error_handler(zval *handler, zval *errors_array)
+{
+ 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;
+
+ call_user_function(NULL, NULL, handler, &retval, 1, errors_array);
+
+ zval_ptr_dtor(&retval);
+}
+
+static void php_stream_throw_exception_with_errors(php_stream_error_operation *op)
+{
+ if (!op->first_error) {
+ return;
+ }
+
+ 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 */
+ zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"),
+ (zend_long) op->first_error->code);
+
+ /* Build errors array and set it */
+ zval errors_array;
+ php_stream_error_create_array(&errors_array, op->first_error);
+ zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("errors"), &errors_array);
+ zval_ptr_dtor(&errors_array);
+
+ zend_throw_exception_object(&ex);
+}
+
+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: {
+ 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 (is_terminating) {
+ php_stream_throw_exception_with_errors(op);
+ }
+ break;
+ }
+
+ case PHP_STREAM_ERROR_MODE_SILENT:
+ break;
+ }
+
+ /* Call user error handler if set */
+ zval *handler
+ = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
+
+ if (handler) {
+ zval errors_array;
+ php_stream_error_create_array(&errors_array, op->first_error);
+
+ php_stream_call_error_handler(handler, &errors_array);
+
+ zval_ptr_dtor(&errors_array);
+ }
+}
+
+/* Error storage */
+
+PHPAPI void php_stream_error_operation_end(php_stream_context *context)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+ php_stream_error_operation *op = state->current_operation;
+
+ if (!op) {
+ return;
+ }
+
+ state->operation_depth--;
+ state->current_operation = php_stream_get_parent_operation();
+
+ if (op->error_count > 0) {
+ if (context == NULL) {
+ context = FG(default_context);
+ }
+
+ 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);
+
+ php_stream_report_errors(context, op, error_mode, is_terminating);
+
+ if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
+ php_stream_error_entry_free(op->first_error);
+ op->first_error = NULL;
+ } else {
+ 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;
+ uint32_t to_store_count = 0;
+ php_stream_error_entry *remaining_first = 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) {
+ entry->next = NULL;
+ if (to_store_last) {
+ to_store_last->next = entry;
+ } else {
+ to_store_first = entry;
+ }
+ to_store_last = entry;
+ to_store_count++;
+ } else {
+ entry->next = NULL;
+ if (prev) {
+ prev->next = entry;
+ } else {
+ remaining_first = entry;
+ }
+ prev = entry;
+ }
+
+ entry = next;
+ }
+
+ if (to_store_first) {
+ php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error));
+ stored->first_error = to_store_first;
+ stored->error_count = to_store_count;
+ stored->next = state->stored_errors;
+
+ state->stored_errors = stored;
+ state->stored_count++;
+ }
+
+ if (remaining_first) {
+ php_stream_error_entry_free(remaining_first);
+ }
+
+ op->first_error = NULL;
+ }
+ }
+
+ op->first_error = NULL;
+ op->last_error = NULL;
+ op->error_count = 0;
+}
+
+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;
+ }
+
+ if (op->error_count == 0) {
+ state->operation_depth--;
+ state->current_operation = php_stream_get_parent_operation();
+
+ op->first_error = NULL;
+ op->last_error = NULL;
+ return;
+ }
+
+ php_stream_context *context = PHP_STREAM_CONTEXT(stream);
+ php_stream_error_operation_end(context);
+}
+
+PHPAPI void php_stream_error_operation_abort(void)
+{
+ php_stream_error_state *state = &FG(stream_error_state);
+ php_stream_error_operation *op = state->current_operation;
+
+ if (!op) {
+ return;
+ }
+
+ state->operation_depth--;
+ state->current_operation = php_stream_get_parent_operation();
+
+ php_stream_error_entry_free(op->first_error);
+ op->first_error = NULL;
+ op->last_error = NULL;
+ op->error_count = 0;
+}
+
+/* Wrapper error reporting */
+
+static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context,
+ const char *docref, int options, int severity, bool terminating,
+ zend_enum_StreamErrorCode code, char *param, zend_string *message)
+{
+ bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
+ if (implicit_operation) {
+ php_stream_error_operation_begin();
+ }
+
+ php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating);
+
+ if (implicit_operation) {
+ php_stream_error_operation_end(context);
+ }
+}
+
+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, zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+ 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,
+ zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+ 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,
+ zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
+{
+ 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, zend_enum_StreamErrorCode 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);
+ 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 */
+
+PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity,
+ bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+ 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);
+
+ 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);
+ efree(entry->docref);
+ 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);
+}
+
+static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_StreamErrorCode code,
+ const char *wrapper_name, const char *param, int severity, bool terminating)
+{
+ char *param_copy = param ? estrdup(param) : NULL;
+
+ 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;
+
+ 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,
+ zend_enum_StreamErrorCode 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) {
+ php_stream_wrapper_error_internal(
+ wrapper_name, context, NULL, options, severity, terminating, code, param, message);
+ } else {
+ 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,
+ zend_enum_StreamErrorCode 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);
+}
+
+PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper,
+ php_stream_context *context, int options, int severity, bool terminating,
+ zend_enum_StreamErrorCode 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);
+}
+
+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));
+}
+
+PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
+ php_stream_context *context, zend_enum_StreamErrorCode 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 = "<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, zend_enum_StreamErrorCode 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);
+ }
+}
+
+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);
+ }
+}
+
+/* StreamException methods */
+
+PHP_METHOD(StreamException, getErrors)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ zval *errors = zend_read_property(
+ php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL);
+
+ RETURN_COPY(errors);
+}
+
+/* Module init */
+
+PHP_MINIT_FUNCTION(stream_errors)
+{
+ php_ce_stream_error_code = register_class_StreamErrorCode();
+ php_ce_stream_error_mode = register_class_StreamErrorMode();
+ php_ce_stream_error_store = register_class_StreamErrorStore();
+
+ 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
new file mode 100644
index 00000000000..2c56932c485
--- /dev/null
+++ b/main/streams/stream_errors.stub.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @generate-class-entries static
+ * @generate-c-enums
+ */
+
+/** @c-name-table */
+enum StreamErrorCode
+{
+ /* General errors */
+ case None;
+ case Generic;
+
+ /* I/O operation errors */
+ case ReadFailed;
+ case WriteFailed;
+ case SeekFailed;
+ case SeekNotSupported;
+ case FlushFailed;
+ case TruncateFailed;
+ case ConnectFailed;
+ case BindFailed;
+ case ListenFailed;
+ case NotWritable;
+ case NotReadable;
+
+ /* File system operations */
+ case Disabled;
+ case NotFound;
+ case PermissionDenied;
+ case AlreadyExists;
+ case InvalidPath;
+ case PathTooLong;
+ case OpenFailed;
+ case CreateFailed;
+ case DupFailed;
+ case UnlinkFailed;
+ case RenameFailed;
+ case MkdirFailed;
+ case RmdirFailed;
+ case StatFailed;
+ case MetaFailed;
+ case ChmodFailed;
+ case ChownFailed;
+ case CopyFailed;
+ case TouchFailed;
+ case InvalidMode;
+ case InvalidMeta;
+ case ModeNotSupported;
+ case Readonly;
+ case RecursionDetected;
+
+ /* Wrapper/protocol operations */
+ case NotImplemented;
+ case NoOpener;
+ case PersistentNotSupported;
+ case WrapperNotFound;
+ case WrapperDisabled;
+ case ProtocolUnsupported;
+ case WrapperRegistrationFailed;
+ case WrapperUnregistrationFailed;
+ case WrapperRestorationFailed;
+
+ /* Filter operations */
+ case FilterNotFound;
+ case FilterFailed;
+
+ /* Cast/conversion operations */
+ case CastFailed;
+ case CastNotSupported;
+ case MakeSeekableFailed;
+ case BufferedDataLost;
+
+ /* Network/socket operations */
+ case NetworkSendFailed;
+ case NetworkRecvFailed;
+ case SslNotSupported;
+ case ResumptionFailed;
+ case SocketPathTooLong;
+ case OobNotSupported;
+ case ProtocolError;
+ case InvalidUrl;
+ case InvalidResponse;
+ case InvalidHeader;
+ case InvalidParam;
+ case RedirectLimit;
+ case AuthFailed;
+
+ /* Encoding/decoding/archiving operations */
+ case ArchivingFailed;
+ case EncodingFailed;
+ case DecodingFailed;
+ case InvalidFormat;
+
+ /* Resource/allocation operations */
+ case AllocationFailed;
+ case TemporaryFileFailed;
+
+ /* Locking operations */
+ case LockFailed;
+ case LockNotSupported;
+
+ /* Userspace stream operations */
+ case UserspaceNotImplemented;
+ case UserspaceInvalidReturn;
+ case UserspaceCallFailed;
+}
+
+enum StreamErrorMode
+{
+ case Error;
+ case Exception;
+ case Silent;
+}
+
+enum StreamErrorStore
+{
+ case Auto;
+ case None;
+ case NonTerminating;
+ case Terminating;
+ case All;
+}
+
+final readonly class StreamError
+{
+ public StreamErrorCode $code;
+ public string $message;
+ public string $wrapperName;
+ public int $severity;
+ public bool $terminating;
+ public ?string $param;
+}
+
+class StreamException extends Exception
+{
+ /** @var array<int, StreamError> */
+ private array $errors = [];
+
+ /** @return array<int, StreamError> */
+ public function getErrors(): array {}
+}
diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h
new file mode 100644
index 00000000000..3a10e9bc5a4
Binary files /dev/null and b/main/streams/stream_errors_arginfo.h differ
diff --git a/main/streams/stream_errors_decl.h b/main/streams/stream_errors_decl.h
new file mode 100644
index 00000000000..4748768195f
Binary files /dev/null and b/main/streams/stream_errors_decl.h differ
diff --git a/main/streams/streams.c b/main/streams/streams.c
index e638c52159a..715bbcfe037 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -138,141 +138,6 @@ PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream *
return PHP_STREAM_PERSISTENT_NOT_EXIST;
}
-/* }}} */
-
-static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper)
-{
- if (!FG(wrapper_errors)) {
- return NULL;
- } else {
- return (zend_llist*) zend_hash_str_find_ptr(FG(wrapper_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_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;
- const char **err_buf_p;
- zend_llist_position pos;
-
- if (PG(html_errors)) {
- brlen = 7;
- br = "<br />\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 */
@@ -511,6 +376,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;
@@ -1318,7 +1188,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, NotWritable, "Stream is not writable");
return (ssize_t) -1;
}
@@ -1513,7 +1383,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, SeekNotSupported,
+ "Stream does not support seeking");
return -1;
}
@@ -1937,11 +1808,13 @@ 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;
}
+
+ php_stream_error_state_cleanup();
}
zend_result php_init_stream_wrappers(int module_number)
@@ -2108,7 +1981,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,
+ ProtocolUnsupported,
+ "Remote host file access not supported, %s", path);
}
return NULL;
}
@@ -2147,7 +2022,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,
+ Disabled,
+ "file:// wrapper is disabled in the server configuration");
}
return NULL;
}
@@ -2245,10 +2122,12 @@ 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,
+ NoOpener, "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, PHP_STREAM_EC(OpenFailed),
+ path, "Failed to open directory");
}
php_stream_tidy_wrapper_error_log(wrapper);
@@ -2315,16 +2194,25 @@ 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,
+ ProtocolUnsupported,
+ "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);
}
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_error(wrapper, options & ~REPORT_ERRORS,
+ php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
+ NoOpener,
"wrapper does not support stream open");
} else {
stream = wrapper->wops->stream_opener(wrapper,
@@ -2335,7 +2223,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,
+ PersistentNotSupported,
"wrapper does not support persistent streams");
php_stream_close(stream);
stream = NULL;
@@ -2359,6 +2248,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)) {
@@ -2371,6 +2263,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) {
@@ -2380,6 +2273,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);
@@ -2387,8 +2281,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,
+ SeekNotSupported, tmp,
+ "could not make seekable - %s", tmp);
efree(tmp);
options &= ~REPORT_ERRORS;
@@ -2406,13 +2301,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, path, "Failed to open stream");
+ php_stream_display_wrapper_name_errors(wrapper_name, context, PHP_STREAM_EC(OpenFailed),
+ 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);
}
diff --git a/main/streams/transports.c b/main/streams/transports.c
index 014e435cfb0..3231670dd9a 100644
--- a/main/streams/transports.c
+++ b/main/streams/transports.c
@@ -37,13 +37,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; } \
}
@@ -114,7 +114,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(WrapperNotFound, error_string,
+ "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
wrapper_name);
return NULL;
@@ -123,7 +124,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,
+ WrapperNotFound, "Could not find a factory !?");
return NULL;
}
@@ -144,7 +146,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(ConnectFailed, error_string, error_text, "connect() failed: %s");
failed = true;
}
@@ -154,7 +156,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(BindFailed, error_string, error_text, "bind() failed: %s");
failed = true;
} else if (flags & STREAM_XPORT_LISTEN) {
zval *zbacklog = NULL;
@@ -165,7 +167,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(ListenFailed, error_string, error_text, "listen() failed: %s");
failed = true;
}
}
@@ -368,7 +370,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", SslNotSupported,
+ "This stream does not support SSL/crypto");
return ret;
}
@@ -388,7 +391,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", SslNotSupported,
+ "This stream does not support SSL/crypto");
return ret;
}
@@ -410,7 +414,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, FilterFailed,
+ "Cannot peek or fetch OOB data from a filtered stream");
return -1;
}
@@ -480,7 +485,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, FilterFailed,
+ "Cannot write OOB data, or data to a targeted address on a filtered stream");
return -1;
}
diff --git a/main/streams/userspace.c b/main/streams/userspace.c
index 335ef3aa4f2..bf6ffa50096 100644
--- a/main/streams/userspace.c
+++ b/main/streams/userspace.c
@@ -291,7 +291,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,
+ RecursionDetected, "infinite recursion prevented");
return NULL;
}
FG(user_stream_current_filename) = filename;
@@ -332,8 +333,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,NotImplemented,
+ "\"%s::" USERSTREAM_OPEN "\" is not implemented", ZSTR_VAL(us->wrapper->ce->name));
zval_ptr_dtor(&args[3]);
goto end;
}
@@ -355,8 +356,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,
+ UserspaceCallFailed,
+ "\"%s::" USERSTREAM_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
}
zval_ptr_dtor(&zretval);
@@ -392,7 +394,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,
+ RecursionDetected, "infinite recursion prevented");
return NULL;
}
FG(user_stream_current_filename) = filename;
@@ -417,8 +420,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, NotImplemented,
+ "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented",
+ ZSTR_VAL(us->wrapper->ce->name));
goto end;
}
/* Exception occurred in call */
@@ -433,8 +437,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,
+ UserspaceCallFailed,
+ "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
}
zval_ptr_dtor(&zretval);
@@ -477,10 +482,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,
+ WrapperRegistrationFailed,
+ "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,
+ WrapperRegistrationFailed,
+ "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://",
+ ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol));
}
zend_list_delete(rsrc);
@@ -500,7 +510,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,
+ WrapperUnregistrationFailed,
+ "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
RETURN_FALSE;
}
@@ -528,13 +540,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,
+ WrapperNotFound,
+ "%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,
+ WrapperRestorationFailed,
+ "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
RETURN_TRUE;
}
@@ -542,7 +558,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,
+ WrapperRestorationFailed,
+ "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
RETURN_FALSE;
}
@@ -570,8 +588,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, NotImplemented,
+ "%s::" USERSTREAM_WRITE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
}
stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
@@ -591,7 +609,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, UserspaceInvalidReturn,
+ "%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;
@@ -622,8 +642,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, NotImplemented,
+ "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
goto err;
}
@@ -639,8 +659,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, UserspaceInvalidReturn,
+ "%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);
@@ -656,7 +680,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, NotImplemented,
"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
ZSTR_VAL(us->wrapper->ce->name));
stream->eof = 1;
@@ -772,7 +796,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, NotImplemented,
+ "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
ret = -1;
} else {
ret = -1;
@@ -836,8 +861,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, NotImplemented,
+ "%s::" USERSTREAM_STAT " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
return -1;
}
if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -855,7 +880,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;
@@ -864,7 +889,7 @@ 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, NotImplemented,
"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
ZSTR_VAL(us->wrapper->ce->name));
return PHP_STREAM_OPTION_RETURN_ERR;
@@ -875,15 +900,15 @@ 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, UserspaceInvalidReturn,
+ "%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;
@@ -918,9 +943,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, NotImplemented,
+ "%s::" USERSTREAM_LOCK " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
return PHP_STREAM_OPTION_RETURN_ERR;
}
if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -932,14 +956,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, UserspaceInvalidReturn,
+ // "%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) {
@@ -967,9 +992,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, NotImplemented,
+ "%s::" USERSTREAM_TRUNCATE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
return PHP_STREAM_OPTION_RETURN_ERR;
}
if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -978,15 +1002,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, UserspaceInvalidReturn,
+ "%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);
@@ -1011,7 +1036,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, NotImplemented,
"%s::" USERSTREAM_SET_OPTION " is not implemented!",
ZSTR_VAL(us->wrapper->ce->name));
return PHP_STREAM_OPTION_RETURN_ERR;
@@ -1036,19 +1061,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;
@@ -1081,7 +1106,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, NotImplemented,
+ "%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;
}
@@ -1119,7 +1145,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, NotImplemented,
+ "%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;
}
@@ -1157,7 +1184,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, NotImplemented,
+ "%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;
}
@@ -1194,7 +1222,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, NotImplemented,
+ "%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;
}
@@ -1233,7 +1262,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,
+ InvalidMeta,
+ "Unknown option %d for " USERSTREAM_METADATA, option);
return ret;
}
@@ -1256,7 +1287,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, NotImplemented,
+ "%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;
}
@@ -1294,8 +1326,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, NotImplemented,
+ "%s::" USERSTREAM_STATURL " is not implemented!", ZSTR_VAL(uwrap->ce->name));
return -1;
}
if (UNEXPECTED(Z_ISUNDEF(zretval))) {
@@ -1330,8 +1362,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, NotImplemented,
+ "%s::" USERSTREAM_DIR_READ " is not implemented!",
+ ZSTR_VAL(us->wrapper->ce->name));
return -1;
}
if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -1416,7 +1449,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, NotImplemented,
+ "%s::" USERSTREAM_CAST " is not implemented!",
ZSTR_VAL(us->wrapper->ce->name));
}
goto out;
@@ -1430,14 +1464,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, UserspaceInvalidReturn,
+ "%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, UserspaceInvalidReturn,
+ "%s::" USERSTREAM_CAST " must not return itself",
ZSTR_VAL(us->wrapper->ce->name));
}
intstream = NULL;
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index f2b2bb54ec6..d18b0e90195 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -114,9 +114,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, NetworkSendFailed,
+ "Send of %zu bytes failed with errno=%d %s", count, err, estr);
efree(estr);
}
}
@@ -452,8 +451,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, NetworkSendFailed, "%s", err);
efree(err);
}
return PHP_STREAM_OPTION_RETURN_OK;
@@ -593,7 +591,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;
@@ -612,9 +611,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, InvalidPath,
+ "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);
@@ -695,7 +694,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);
int result = bind(sock->socket, (const struct sockaddr *)&unix_addr,
(socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
@@ -831,7 +830,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) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
diff --git a/win32/build/config.w32 b/win32/build/config.w32
index aefcfb5f824..6cd6907f282 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");