Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions ext/standard/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
11 changes: 11 additions & 0 deletions ext/standard/tests/file/file_put_contents_lock_ex_device.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
file_put_contents() LOCK_EX permits writes to a device that cannot be truncated
--FILE--
<?php
$device = PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null';
var_dump(file_put_contents($device, 'data', LOCK_EX));
var_dump(file_put_contents($device, '', LOCK_EX));
?>
--EXPECT--
int(4)
int(0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
--TEST--
file_put_contents() LOCK_EX stops writing after a wrapper truncation failure
--FILE--
<?php
class FailingTruncateWrapper
{
public $context;
public static bool $truncateResult = false;

public function stream_open($path, $mode, $options, &$openedPath)
{
return true;
}

public function stream_lock($operation)
{
return true;
}

public function stream_truncate($newSize)
{
echo "truncate: $newSize\n";
return self::$truncateResult;
}

public function stream_write($data)
{
echo "write\n";
return strlen($data);
}

public function stream_close()
{
echo "close\n";
}
}

$source = fopen('php://memory', 'w+');
fwrite($source, 'data');
rewind($source);

var_dump(stream_wrapper_unregister('file'));
var_dump(stream_wrapper_register('file', FailingTruncateWrapper::class));
foreach (['string' => '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)
Loading