diff --git a/NEWS b/NEWS index 9e93da5c8d88..53440c2058a0 100644 --- a/NEWS +++ b/NEWS @@ -230,6 +230,8 @@ PHP NEWS properties). (Lazizbek Ergashev) - Standard: + . Fixed file_put_contents() with LOCK_EX ignoring truncation failures on + regular files. (Ilia Alshanetsky) . Fixed bug GH-24308 (Request shutdown calls closelog() even when syslog was not opened by PHP). (lazerg) . Fixed bug GH-24399 (usleep() and time_nanosleep() ignore queued APCs on diff --git a/ext/standard/file.c b/ext/standard/file.c index 933747ef8cf9..ff7bbbdd741e 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -493,8 +493,24 @@ PHP_FUNCTION(file_put_contents) RETURN_FALSE; } - if (mode[0] == 'c') { - php_stream_truncate_set_size(stream, 0); + if (mode[0] == 'c' && php_stream_truncate_set_size(stream, 0) != PHP_STREAM_OPTION_RETURN_OK) { + bool non_regular = false; + if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { +#ifdef PHP_WIN32 + php_socket_t fd; + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, 0) == SUCCESS) { + DWORD file_type = GetFileType((HANDLE) _get_osfhandle((int) fd)); + non_regular = file_type == FILE_TYPE_CHAR || file_type == FILE_TYPE_PIPE; + } +#else + php_stream_statbuf ssb; + non_regular = php_stream_stat(stream, &ssb) == 0 && !S_ISREG(ssb.sb.st_mode); +#endif + } + if (!non_regular) { + php_stream_close(stream); + RETURN_FALSE; + } } switch (Z_TYPE_P(data)) { diff --git a/ext/standard/tests/file/file_put_contents_lock_ex_device.phpt b/ext/standard/tests/file/file_put_contents_lock_ex_device.phpt new file mode 100644 index 000000000000..2e219364a6aa --- /dev/null +++ b/ext/standard/tests/file/file_put_contents_lock_ex_device.phpt @@ -0,0 +1,11 @@ +--TEST-- +file_put_contents() LOCK_EX permits writes to a device that cannot be truncated +--FILE-- + +--EXPECT-- +int(4) +int(0) diff --git a/ext/standard/tests/file/file_put_contents_lock_ex_truncate_failure.phpt b/ext/standard/tests/file/file_put_contents_lock_ex_truncate_failure.phpt new file mode 100644 index 000000000000..9f78595d42e9 --- /dev/null +++ b/ext/standard/tests/file/file_put_contents_lock_ex_truncate_failure.phpt @@ -0,0 +1,88 @@ +--TEST-- +file_put_contents() LOCK_EX stops writing after a wrapper truncation failure +--FILE-- + 'data', 'empty' => '', 'array' => ['data'], 'stream' => $source] as $label => $data) { + echo "$label:\n"; + var_dump(file_put_contents('file:///test', $data, LOCK_EX)); +} +echo "source position: ", ftell($source), "\n"; +fclose($source); + +echo "successful truncation:\n"; +FailingTruncateWrapper::$truncateResult = true; +var_dump(file_put_contents('file:///test', 'data', LOCK_EX)); + +echo "append:\n"; +FailingTruncateWrapper::$truncateResult = false; +var_dump(file_put_contents('file:///test', 'data', FILE_APPEND | LOCK_EX)); +stream_wrapper_restore('file'); +?> +--EXPECT-- +bool(true) +bool(true) +string: +truncate: 0 +close +bool(false) +empty: +truncate: 0 +close +bool(false) +array: +truncate: 0 +close +bool(false) +stream: +truncate: 0 +close +bool(false) +source position: 0 +successful truncation: +truncate: 0 +write +close +int(4) +append: +write +close +int(4)