Skip to content

Fix #81145: copy() and stream_copy_to_stream() fail for +4GB files #7158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
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
48 changes: 48 additions & 0 deletions ext/standard/tests/file/bug81145.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
Bug #81145 (copy() and stream_copy_to_stream() fail for +4GB files)
--SKIPIF--
<?php
if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
if (PHP_INT_SIZE !== 8) die("skip this test is for 64bit platforms only");
if (disk_free_space(__DIR__) < 0x220000000) die("skip insuffient disk space");
if (PHP_OS_FAMILY !== "Windows") {
exec("fallocate -h", $output, $status);
if ($status !== 0) die("skip fallocate(1) not available");
}
?>
--FILE--
<?php
$src = __DIR__ . "/bug81145_src.bin";
$dst = __DIR__ . "/bug81145_dst.bin";
define('SIZE_4G', 0x100000000);

//Create file and append random content at the 4GB boundary
if (PHP_OS_FAMILY !== "Windows") {
exec("fallocate -l " . (SIZE_4G-0x100) . " " . escapeshellarg($src));
} else {
exec("fsutil file createnew " . escapeshellarg($src) . " " . (SIZE_4G-0x100));
}
$fp = fopen($src, "ab");
fwrite($fp, random_bytes(0x200));
fclose($fp);
copy($src, $dst);
if (filesize($src) !== filesize($dst)) {
die("Files have different sizes!");
}
$f1 = fopen($src,'rb') or die("src open failed");
$f2 = fopen($dst,'rb') or die("dst open failed");

//Seek to 4 GB boundary, as this is the location where the problem occurs
fseek($f1, SIZE_4G - 0x100, SEEK_SET);
fseek($f2, SIZE_4G - 0x100, SEEK_SET);
echo (fread($f1,0x200) === fread($f2,0x200) ? "Identical" : "Copy failed");
fclose($f1);
fclose($f2);
?>
--CLEAN--
<?php
@unlink(__DIR__ . "/bug81145_src.bin");
@unlink(__DIR__ . "/bug81145_dst.bin");
?>
--EXPECT--
Identical
11 changes: 7 additions & 4 deletions main/streams/plain_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void
{
php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam;
HANDLE hfile = (HANDLE)_get_osfhandle(fd);
DWORD prot, acc, loffs = 0, delta = 0;
DWORD prot, acc, loffs = 0, hoffs = 0, delta = 0;
LARGE_INTEGER file_size;

switch (value) {
Expand Down Expand Up @@ -838,16 +838,19 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void

GetSystemInfo(&info);
gran = info.dwAllocationGranularity;
loffs = ((DWORD)range->offset / gran) * gran;
delta = (DWORD)range->offset - loffs;
ZEND_ASSERT(gran != 0 && (gran & (gran - 1)) == 0);
size_t rounded_offset = (range->offset / gran) * gran;
delta = range->offset - rounded_offset;
loffs = (DWORD)rounded_offset;
hoffs = (DWORD)(rounded_offset >> 32);
}

/* MapViewOfFile()ing zero bytes would map to the end of the file; match *nix behavior instead */
if (range->length + delta == 0) {
return PHP_STREAM_OPTION_RETURN_ERR;
}

data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta);
data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, hoffs, loffs, range->length + delta);

if (data->last_mapped_addr) {
/* give them back the address of the start offset they requested */
Expand Down