From 21f8ed8acbdc461860078b968b1c868b9403defe Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Fri, 4 Sep 2020 18:54:30 +0200 Subject: [PATCH 1/3] 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. --- main/streams/php_stream_mmap.h | 2 ++ main/streams/plain_wrapper.c | 5 ++++ main/streams/streams.c | 53 +++++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/main/streams/php_stream_mmap.h b/main/streams/php_stream_mmap.h index 132f3214c117d..40288cc27f238 100644 --- a/main/streams/php_stream_mmap.h +++ b/main/streams/php_stream_mmap.h @@ -58,6 +58,8 @@ typedef struct { #define PHP_STREAM_MMAP_ALL 0 +#define PHP_STREAM_MMAP_MAX (512 * 1024 * 1024) + #define php_stream_mmap_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_MMAP_API, PHP_STREAM_MMAP_SUPPORTED, NULL) == 0 ? 1 : 0) /* Returns 1 if the stream in its current state can be memory mapped, diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 50ca925dea372..298950cf9ed7f 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -826,6 +826,11 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void delta = (DWORD)range->offset - loffs; } + /* 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); if (data->last_mapped_addr) { diff --git a/main/streams/streams.c b/main/streams/streams.c index f1f8bf7eab609..d5b23d5fe602b 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1573,29 +1573,48 @@ PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size if (php_stream_mmap_possible(src)) { char *p; - size_t mapped; - p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); + do { + size_t chunk_size = (maxlen == 0 || maxlen > PHP_STREAM_MMAP_MAX) ? PHP_STREAM_MMAP_MAX : maxlen; + size_t mapped; - if (p) { - ssize_t didwrite = php_stream_write(dest, p, mapped); - if (didwrite < 0) { - *len = 0; - return FAILURE; - } + p = php_stream_mmap_range(src, php_stream_tell(src), chunk_size, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped); + + if (p) { + ssize_t didwrite; + + if (php_stream_seek(src, mapped, SEEK_CUR) != 0) { + php_stream_mmap_unmap(src, mapped); + break; + } + + didwrite = php_stream_write(dest, p, mapped); + if (didwrite < 0) { + *len = 0; + return FAILURE; + } - php_stream_mmap_unmap_ex(src, mapped); + php_stream_mmap_unmap(src, mapped); - *len = didwrite; + *len = haveread += didwrite; - /* we've got at least 1 byte to read - * less than 1 is an error - * AND read bytes match written */ - if (mapped > 0 && mapped == didwrite) { - return SUCCESS; + /* we've got at least 1 byte to read + * less than 1 is an error + * AND read bytes match written */ + if (mapped == 0 || mapped != didwrite) { + return FAILURE; + } + if (mapped < chunk_size) { + return SUCCESS; + } + if (maxlen != 0) { + maxlen -= mapped; + if (maxlen == 0) { + return SUCCESS; + } + } } - return FAILURE; - } + } while (p); } while(1) { From d44bcdcf8167559fa8d74d93db4baf18c2ed381f Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Fri, 4 Sep 2020 19:22:42 +0200 Subject: [PATCH 2/3] Remove extraneous parameter --- main/streams/streams.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/streams/streams.c b/main/streams/streams.c index d5b23d5fe602b..98fbc78c4f6b6 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1584,7 +1584,7 @@ PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size ssize_t didwrite; if (php_stream_seek(src, mapped, SEEK_CUR) != 0) { - php_stream_mmap_unmap(src, mapped); + php_stream_mmap_unmap(src); break; } @@ -1594,7 +1594,7 @@ PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size return FAILURE; } - php_stream_mmap_unmap(src, mapped); + php_stream_mmap_unmap(src); *len = haveread += didwrite; From 0f2a5c0cd1819829a508c34a167f1e7c68893888 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 21 Sep 2020 13:57:05 +0200 Subject: [PATCH 3/3] Report proper length in case of write failure --- main/streams/streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/streams/streams.c b/main/streams/streams.c index 98fbc78c4f6b6..cf411a1dd3751 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1590,7 +1590,7 @@ PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size didwrite = php_stream_write(dest, p, mapped); if (didwrite < 0) { - *len = 0; + *len = haveread; return FAILURE; }