Skip to content

Commit 19c8445

Browse files
committed
Fix mmap copying
Instead of attempting to map large files into memory at once, we map chunks of at most `PHP_STREAM_MMAP_MAX` bytes, and repeat that until we hit the point where `php_stream_seek()` fails (see bug 54902), and copy the rest of the file by reading and writing small chunks. We also fix the mapping behavior for zero bytes on Windows, which did not error (as with `mmap()`), but would have mapped the remaining file.
1 parent d1feeed commit 19c8445

File tree

4 files changed

+47
-17
lines changed

4 files changed

+47
-17
lines changed

‎NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? 2020, PHP 7.4.12
44

5+
- Core:
6+
. Fixed bug #80061 (Copying large files may have suboptimal performance).
7+
(cmb)
8+
59
- MySQLnd:
610
. Fixed bug #80115 (mysqlnd.debug doesn't recognize absolute paths with
711
slashes). (cmb)

‎main/streams/php_stream_mmap.h

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ typedef struct {
5858

5959
#define PHP_STREAM_MMAP_ALL 0
6060

61+
#define PHP_STREAM_MMAP_MAX (512 * 1024 * 1024)
62+
6163
#define php_stream_mmap_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_SUPPORTED, NULL) == 0 ? 1 : 0)
6264

6365
/* Returns 1 if the stream in its current state can be memory mapped,

‎main/streams/plain_wrapper.c

+5
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,11 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void
826826
delta = (DWORD)range->offset - loffs;
827827
}
828828

829+
/* MapViewOfFile()ing zero bytes would map to the end of the file; match *nix behavior instead */
830+
if (range->length + delta == 0) {
831+
return PHP_STREAM_OPTION_RETURN_ERR;
832+
}
833+
829834
data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta);
830835

831836
if (data->last_mapped_addr) {

‎main/streams/streams.c

+36-17
Original file line numberDiff line numberDiff line change
@@ -1573,29 +1573,48 @@ PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size
15731573

15741574
if (php_stream_mmap_possible(src)) {
15751575
char *p;
1576-
size_t mapped;
15771576

1578-
p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1577+
do {
1578+
size_t chunk_size = (maxlen == 0 || maxlen > PHP_STREAM_MMAP_MAX) ? PHP_STREAM_MMAP_MAX : maxlen;
1579+
size_t mapped;
15791580

1580-
if (p) {
1581-
ssize_t didwrite = php_stream_write(dest, p, mapped);
1582-
if (didwrite < 0) {
1583-
*len = 0;
1584-
return FAILURE;
1585-
}
1581+
p = php_stream_mmap_range(src, php_stream_tell(src), chunk_size, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1582+
1583+
if (p) {
1584+
ssize_t didwrite;
1585+
1586+
if (php_stream_seek(src, mapped, SEEK_CUR) != 0) {
1587+
php_stream_mmap_unmap(src);
1588+
break;
1589+
}
1590+
1591+
didwrite = php_stream_write(dest, p, mapped);
1592+
if (didwrite < 0) {
1593+
*len = haveread;
1594+
return FAILURE;
1595+
}
15861596

1587-
php_stream_mmap_unmap_ex(src, mapped);
1597+
php_stream_mmap_unmap(src);
15881598

1589-
*len = didwrite;
1599+
*len = haveread += didwrite;
15901600

1591-
/* we've got at least 1 byte to read
1592-
* less than 1 is an error
1593-
* AND read bytes match written */
1594-
if (mapped > 0 && mapped == didwrite) {
1595-
return SUCCESS;
1601+
/* we've got at least 1 byte to read
1602+
* less than 1 is an error
1603+
* AND read bytes match written */
1604+
if (mapped == 0 || mapped != didwrite) {
1605+
return FAILURE;
1606+
}
1607+
if (mapped < chunk_size) {
1608+
return SUCCESS;
1609+
}
1610+
if (maxlen != 0) {
1611+
maxlen -= mapped;
1612+
if (maxlen == 0) {
1613+
return SUCCESS;
1614+
}
1615+
}
15961616
}
1597-
return FAILURE;
1598-
}
1617+
} while (p);
15991618
}
16001619

16011620
while(1) {

0 commit comments

Comments
 (0)