diff --git a/configure.ac b/configure.ac
index 77fc8c89cdf40..24d7d981c178f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -580,10 +580,13 @@ AC_CHECK_FUNCS(m4_normalize([
putenv
reallocarray
scandir
+ sendfile
+ sendfilev
setenv
setitimer
shutdown
sigprocmask
+ splice
statfs
statvfs
std_syslog
@@ -1688,6 +1691,15 @@ PHP_ADD_SOURCES_X([main],
[PHP_FASTCGI_OBJS],
[no])
+PHP_ADD_SOURCES([main/io], m4_normalize([
+ php_io.c
+ php_io_copy_bsd.c
+ php_io_copy_linux.c
+ php_io_copy_macos.c
+ php_io_copy_solaris.c
+ ]),
+ [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
+
PHP_ADD_SOURCES([main/streams], m4_normalize([
cast.c
filter.c
diff --git a/main/io/php_io.c b/main/io/php_io.c
new file mode 100644
index 0000000000000..675a3b858eee7
--- /dev/null
+++ b/main/io/php_io.c
@@ -0,0 +1,98 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "php_io.h"
+#include "php_io_internal.h"
+#include
+
+#ifdef PHP_WIN32
+#include
+#include
+#else
+#include
+#endif
+
+/* Global instance - initialized at compile time */
+static php_io php_io_instance = {
+ .copy = PHP_IO_PLATFORM_COPY_OPS,
+ .platform_name = PHP_IO_PLATFORM_NAME,
+};
+
+/* Get global instance */
+PHPAPI php_io *php_io_get(void)
+{
+ return &php_io_instance;
+}
+
+/* High-level copy function with dispatch */
+PHPAPI ssize_t php_io_copy(
+ int src_fd, php_io_fd_type src_type, int dest_fd, php_io_fd_type dest_type, size_t maxlen)
+{
+ php_io *io = php_io_get();
+
+ /* Dispatch to appropriate copy function based on fd types */
+ if (src_type == PHP_IO_FD_FILE && dest_type == PHP_IO_FD_FILE) {
+ return io->copy.file_to_file(src_fd, dest_fd, maxlen);
+ } else if (src_type == PHP_IO_FD_FILE && dest_type == PHP_IO_FD_GENERIC) {
+ return io->copy.file_to_generic(src_fd, dest_fd, maxlen);
+ } else if (src_type == PHP_IO_FD_GENERIC && dest_type == PHP_IO_FD_FILE) {
+ return io->copy.generic_to_file(src_fd, dest_fd, maxlen);
+ } else {
+ /* generic to generic */
+ return io->copy.generic_to_generic(src_fd, dest_fd, maxlen);
+ }
+}
+
+/* Generic read/write fallback implementation */
+ssize_t php_io_generic_copy_fallback(int src_fd, int dest_fd, size_t maxlen)
+{
+ char buf[8192];
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+
+ while (remaining > 0) {
+ size_t to_read = (remaining < sizeof(buf)) ? remaining : sizeof(buf);
+ ssize_t bytes_read = read(src_fd, buf, to_read);
+
+ if (bytes_read < 0) {
+ /* Read error */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ } else if (bytes_read == 0) {
+ /* EOF reached */
+ return (ssize_t) total_copied;
+ }
+
+ ssize_t bytes_written = write(dest_fd, buf, bytes_read);
+ if (bytes_written < 0) {
+ /* Write error */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ } else if (bytes_written == 0) {
+ /* Couldn't write anything */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+
+ total_copied += bytes_written;
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= bytes_written;
+ }
+
+ if (bytes_written != bytes_read) {
+ /* Partial write - stop here */
+ return (ssize_t) total_copied;
+ }
+ }
+
+ return (ssize_t) total_copied;
+}
diff --git a/main/io/php_io_bsd.h b/main/io/php_io_bsd.h
new file mode 100644
index 0000000000000..e22cb26b50d23
--- /dev/null
+++ b/main/io/php_io_bsd.h
@@ -0,0 +1,32 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_BSD_H
+#define PHP_IO_BSD_H
+
+/* Copy operations */
+ssize_t php_io_bsd_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
+
+/* Instance initialization macros */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_generic_copy_fallback, \
+ .file_to_generic = php_io_bsd_copy_file_to_generic, \
+ .generic_to_file = php_io_generic_copy_fallback, \
+ .generic_to_generic = php_io_generic_copy_fallback, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "bsd"
+
+#endif /* PHP_IO_BSD_H */
diff --git a/main/io/php_io_copy_bsd.c b/main/io/php_io_copy_bsd.c
new file mode 100644
index 0000000000000..3bac6bcdb662a
--- /dev/null
+++ b/main/io/php_io_copy_bsd.c
@@ -0,0 +1,98 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+
+#include "php_io_internal.h"
+#include
+#include
+
+#ifdef HAVE_SENDFILE
+#include
+#include
+#endif
+
+ssize_t php_io_bsd_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_SENDFILE
+ /* BSD sendfile signature: sendfile(fd, s, offset, nbytes, hdtr, sbytes, flags) */
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+ off_t src_offset = 0;
+
+ /* Get current source file position */
+ src_offset = lseek(src_fd, 0, SEEK_CUR);
+
+ if (src_offset == (off_t) -1) {
+ /* Can't get position, fall back to generic copy */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ while (remaining > 0) {
+ off_t to_send = (remaining < OFF_MAX) ? (off_t) remaining : OFF_MAX;
+ off_t sbytes = 0;
+ int result = sendfile(src_fd, dest_fd, src_offset, to_send, NULL, &sbytes, 0);
+
+ if (result == 0 || sbytes > 0) {
+ /* Success or partial send */
+ total_copied += sbytes;
+ src_offset += sbytes;
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= sbytes;
+ }
+
+ /* If result != 0, error occurred but some data was transferred */
+ if (result != 0) {
+ break;
+ }
+ } else {
+ /* Error occurred with no data transferred */
+ switch (errno) {
+ case EAGAIN:
+ case EBUSY:
+ case EINVAL:
+ case ENOTCONN:
+ /* Various errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ default:
+ /* Other errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ }
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif /* HAVE_SENDFILE */
+
+ /* Fallback to generic implementation */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#endif /* FreeBSD, OpenBSD, NetBSD */
diff --git a/main/io/php_io_copy_linux.c b/main/io/php_io_copy_linux.c
new file mode 100644
index 0000000000000..0184632339822
--- /dev/null
+++ b/main/io/php_io_copy_linux.c
@@ -0,0 +1,234 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef __linux__
+
+#include "php_io_internal.h"
+#include
+#include
+
+#include
+
+/* Provide copy_file_range wrapper if libc doesn't have it but kernel does */
+#if !defined(HAVE_COPY_FILE_RANGE) && defined(__NR_copy_file_range)
+#define HAVE_COPY_FILE_RANGE 1
+static inline ssize_t copy_file_range(
+ int fd_in, off_t *off_in, int fd_out, off_t *off_out, size_t len, unsigned int flags)
+{
+ return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags);
+}
+#endif
+
+#ifdef HAVE_SENDFILE
+#include
+#endif
+
+#ifdef HAVE_SPLICE
+#include
+#endif
+
+ssize_t php_io_linux_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_COPY_FILE_RANGE
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+ loff_t src_offset = 0;
+ loff_t dest_offset = 0;
+
+ /* Get current file positions */
+ off_t current_src = lseek(src_fd, 0, SEEK_CUR);
+ off_t current_dest = lseek(dest_fd, 0, SEEK_CUR);
+
+ if (current_src == (off_t) -1 || current_dest == (off_t) -1) {
+ /* Can't get positions, fall back to generic copy */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ src_offset = current_src;
+ dest_offset = current_dest;
+
+ while (remaining > 0) {
+ /* Clamp to SSIZE_MAX to avoid issues */
+ size_t to_copy = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result = copy_file_range(src_fd, &src_offset, dest_fd, &dest_offset, to_copy, 0);
+
+ if (result > 0) {
+ total_copied += result;
+ /* Offsets are automatically updated by copy_file_range */
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ /* EOF - done */
+ break;
+ } else {
+ /* Error occurred */
+ switch (errno) {
+ case EINVAL:
+ case EXDEV:
+ case ENOSYS:
+ /* Expected failures - fall back to generic copy */
+ if (total_copied == 0) {
+ /* Haven't copied anything yet, can safely fall back */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* If we already copied some, return what we have */
+ break;
+ default:
+ /* Unexpected error */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif
+
+ /* Fallback to generic read/write loop */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+ssize_t php_io_linux_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_SENDFILE
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+ off_t src_offset = 0;
+
+ /* Get current source file position */
+ src_offset = lseek(src_fd, 0, SEEK_CUR);
+
+ if (src_offset == (off_t) -1) {
+ /* Can't get position, fall back to generic copy */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ while (remaining > 0) {
+ /* Clamp to SSIZE_MAX */
+ size_t to_send = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result = sendfile(dest_fd, src_fd, &src_offset, to_send);
+
+ if (result > 0) {
+ total_copied += result;
+ /* src_offset is automatically updated by sendfile */
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ /* EOF - done */
+ break;
+ } else {
+ /* Error occurred */
+ if (errno == EAGAIN) {
+ /* Would block - return what we have */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ /* Other errors - fall back if we haven't copied anything yet */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif
+
+ /* Fallback to generic read/write loop */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+ssize_t php_io_linux_copy_generic_to_any(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_SPLICE
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+
+ /* splice doesn't take offsets - it uses fd's current position */
+ while (remaining > 0) {
+ /* Clamp to SSIZE_MAX */
+ size_t to_splice = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result
+ = splice(src_fd, NULL, dest_fd, NULL, to_splice, SPLICE_F_MOVE | SPLICE_F_MORE);
+
+ if (result > 0) {
+ total_copied += result;
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ /* EOF - done */
+ break;
+ } else {
+ /* Error occurred */
+ switch (errno) {
+ case EAGAIN:
+ /* Would block */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ case EINVAL:
+ /* splice not supported for these fds */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ case EPIPE:
+ /* Broken pipe */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ default:
+ /* Other errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ }
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif /* HAVE_SPLICE */
+
+ /* Fallback to generic read/write loop */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#endif /* __linux__ */
diff --git a/main/io/php_io_copy_macos.c b/main/io/php_io_copy_macos.c
new file mode 100644
index 0000000000000..169d3ff2d3472
--- /dev/null
+++ b/main/io/php_io_copy_macos.c
@@ -0,0 +1,103 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef __APPLE__
+
+#include "php_io_internal.h"
+#include
+#include
+
+#ifdef HAVE_SENDFILE
+#include
+#include
+#endif
+
+#ifdef HAVE_COPYFILE
+#include
+#endif
+
+ssize_t php_io_macos_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_SENDFILE
+ /* macOS sendfile signature: sendfile(fd, s, offset, len, hdtr, flags) */
+ /* Note: len is passed by reference and updated with bytes sent */
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+ off_t src_offset = 0;
+
+ /* Get current source file position */
+ src_offset = lseek(src_fd, 0, SEEK_CUR);
+
+ if (src_offset == (off_t) -1) {
+ /* Can't get position, fall back to generic copy */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ while (remaining > 0) {
+ off_t to_send = (remaining < OFF_MAX) ? (off_t) remaining : OFF_MAX;
+ off_t len_sent = to_send;
+ int result = sendfile(src_fd, dest_fd, src_offset, &len_sent, NULL, 0);
+
+ if (result == 0 || len_sent > 0) {
+ /* Success or partial send */
+ total_copied += len_sent;
+ src_offset += len_sent;
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= len_sent;
+ }
+
+ /* If result != 0, error occurred but some data was transferred */
+ if (result != 0) {
+ break;
+ }
+ } else {
+ /* Error occurred */
+ switch (errno) {
+ case EAGAIN:
+ case EINVAL:
+ case ENOTCONN:
+ case EPIPE:
+ /* Various errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ default:
+ /* Other errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ }
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif /* HAVE_SENDFILE */
+
+ /* Fallback to generic implementation */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#endif /* __APPLE__ */
diff --git a/main/io/php_io_copy_solaris.c b/main/io/php_io_copy_solaris.c
new file mode 100644
index 0000000000000..4464581337796
--- /dev/null
+++ b/main/io/php_io_copy_solaris.c
@@ -0,0 +1,107 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef __sun
+
+#include "php_io_internal.h"
+#include
+#include
+
+#ifdef HAVE_SENDFILEV
+#include
+#endif
+
+ssize_t php_io_solaris_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
+{
+#ifdef HAVE_SENDFILEV
+ /* Solaris sendfilev - very powerful but complex API */
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+ off_t src_offset = 0;
+
+ /* Get current source file position */
+ src_offset = lseek(src_fd, 0, SEEK_CUR);
+
+ if (src_offset == (off_t) -1) {
+ /* Can't get position, fall back to generic copy */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ while (remaining > 0) {
+ struct sendfilevec sfv;
+ size_t xferred = 0;
+ size_t to_send = (remaining < SIZE_MAX) ? remaining : SIZE_MAX;
+
+ /* Set up the sendfile vector */
+ sfv.sfv_fd = src_fd;
+ sfv.sfv_flag = SFV_FD;
+ sfv.sfv_off = src_offset;
+ sfv.sfv_len = to_send;
+
+ /* Perform the sendfile operation */
+ ssize_t result = sendfilev(dest_fd, &sfv, 1, &xferred);
+
+ if (result == 0 || xferred > 0) {
+ /* Success or partial transfer */
+ total_copied += xferred;
+ src_offset += xferred;
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= xferred;
+ }
+
+ /* If result != 0, error occurred but some data was transferred */
+ if (result != 0) {
+ break;
+ }
+ } else {
+ /* Error occurred with no data transferred */
+ switch (errno) {
+ case EAGAIN:
+ case EINVAL:
+ case ENOTCONN:
+ case EPIPE:
+ case EAFNOSUPPORT:
+ /* Various errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ /* Already copied some, return what we have */
+ break;
+ default:
+ /* Other errors */
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ }
+ break;
+ }
+
+ /* For bounded copies, stop if we reached maxlen */
+ if (maxlen != PHP_IO_COPY_ALL && remaining == 0) {
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif /* HAVE_SENDFILEV */
+
+ /* Fallback to generic implementation */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#endif /* __sun */
diff --git a/main/io/php_io_copy_windows.c b/main/io/php_io_copy_windows.c
new file mode 100644
index 0000000000000..867ade9be6ef5
--- /dev/null
+++ b/main/io/php_io_copy_windows.c
@@ -0,0 +1,99 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php_io_internal.h"
+
+#ifdef PHP_WIN32
+
+#include
+#include
+#include
+
+ssize_t php_io_windows_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen)
+{
+ /* Use ReadFile/WriteFile for file-to-file copying */
+ HANDLE src_handle = (HANDLE) _get_osfhandle(src_fd);
+ HANDLE dest_handle = (HANDLE) _get_osfhandle(dest_fd);
+
+ if (src_handle != INVALID_HANDLE_VALUE && dest_handle != INVALID_HANDLE_VALUE) {
+ char buffer[65536];
+ DWORD total_copied = 0;
+ DWORD remaining = (maxlen == PHP_IO_COPY_ALL) ? MAXDWORD : (DWORD) min(maxlen, MAXDWORD);
+
+ while (remaining > 0) {
+ DWORD to_read = min(sizeof(buffer), remaining);
+ DWORD bytes_read, bytes_written;
+
+ if (!ReadFile(src_handle, buffer, to_read, &bytes_read, NULL)) {
+ /* Read error */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+
+ if (bytes_read == 0) {
+ /* EOF */
+ return (ssize_t) total_copied;
+ }
+
+ if (!WriteFile(dest_handle, buffer, bytes_read, &bytes_written, NULL)) {
+ /* Write error */
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+
+ total_copied += bytes_written;
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= bytes_written;
+ }
+
+ if (bytes_written != bytes_read) {
+ /* Partial write */
+ return (ssize_t) total_copied;
+ }
+ }
+
+ return (ssize_t) total_copied;
+ }
+
+ /* Fallback to generic implementation */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+ssize_t php_io_windows_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
+{
+ /* Use TransmitFile for zero-copy file to socket transfer */
+ HANDLE file_handle = (HANDLE) _get_osfhandle(src_fd);
+ SOCKET sock = (SOCKET) dest_fd;
+
+ if (file_handle != INVALID_HANDLE_VALUE && sock != INVALID_SOCKET) {
+ /* TransmitFile can send entire file or partial */
+ DWORD bytes_to_send = (maxlen == PHP_IO_COPY_ALL) ? 0 : (DWORD) min(maxlen, MAXDWORD);
+
+ if (TransmitFile(sock, file_handle, bytes_to_send, 0, NULL, NULL, 0)) {
+ /* TransmitFile succeeded - but we don't know exactly how much was sent without extra
+ * syscalls */
+ /* For simplicity, assume the requested amount was sent */
+ return (maxlen == PHP_IO_COPY_ALL) ? 0 : (ssize_t) bytes_to_send;
+ }
+
+ /* TransmitFile failed, check if it's a recoverable error */
+ int error = WSAGetLastError();
+ if (error == WSAENOTSOCK) {
+ /* dest_fd is not a socket, fall back to generic copy */
+ }
+ }
+
+ /* Fallback to generic implementation */
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#endif /* PHP_WIN32 */
diff --git a/main/io/php_io_generic.h b/main/io/php_io_generic.h
new file mode 100644
index 0000000000000..f33995dcae7ff
--- /dev/null
+++ b/main/io/php_io_generic.h
@@ -0,0 +1,29 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_GENERIC_H
+#define PHP_IO_GENERIC_H
+
+/* Instance initialization macros - all use the generic fallback */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_generic_copy_fallback, \
+ .file_to_generic = php_io_generic_copy_fallback, \
+ .generic_to_file = php_io_generic_copy_fallback, \
+ .generic_to_generic = php_io_generic_copy_fallback, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "generic"
+
+#endif /* PHP_IO_GENERIC_H */
diff --git a/main/io/php_io_internal.h b/main/io/php_io_internal.h
new file mode 100644
index 0000000000000..66b1dbf80e59e
--- /dev/null
+++ b/main/io/php_io_internal.h
@@ -0,0 +1,38 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_INTERNAL_H
+#define PHP_IO_INTERNAL_H
+
+#include "php_io.h"
+
+/* Internal utility functions */
+ssize_t php_io_generic_copy_fallback(int src_fd, int dest_fd, size_t maxlen);
+
+/* Platform-specific headers */
+#ifdef __linux__
+#include "php_io_linux.h"
+#elif defined(PHP_WIN32)
+#include "php_io_windows.h"
+#elif defined(__APPLE__)
+#include "php_io_macos.h"
+#elif defined(__sun)
+#include "php_io_solaris.h"
+#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+#include "php_io_bsd.h"
+#else
+#include "php_io_generic.h"
+#endif
+
+#endif /* PHP_IO_INTERNAL_H */
diff --git a/main/io/php_io_linux.h b/main/io/php_io_linux.h
new file mode 100644
index 0000000000000..962ab2e88294b
--- /dev/null
+++ b/main/io/php_io_linux.h
@@ -0,0 +1,34 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_LINUX_H
+#define PHP_IO_LINUX_H
+
+/* Copy operations */
+ssize_t php_io_linux_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen);
+ssize_t php_io_linux_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
+ssize_t php_io_linux_copy_generic_to_any(int src_fd, int dest_fd, size_t maxlen);
+
+/* Instance initialization macros */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_linux_copy_file_to_file, \
+ .file_to_generic = php_io_linux_copy_file_to_generic, \
+ .generic_to_file = php_io_linux_copy_generic_to_any, \
+ .generic_to_generic = php_io_linux_copy_generic_to_any, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "linux"
+
+#endif /* PHP_IO_LINUX_H */
diff --git a/main/io/php_io_macos.h b/main/io/php_io_macos.h
new file mode 100644
index 0000000000000..6e11ba5d0daa5
--- /dev/null
+++ b/main/io/php_io_macos.h
@@ -0,0 +1,32 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_MACOS_H
+#define PHP_IO_MACOS_H
+
+/* Copy operations */
+ssize_t php_io_macos_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
+
+/* Instance initialization macros */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_generic_copy_fallback, \
+ .file_to_generic = php_io_macos_copy_file_to_generic, \
+ .generic_to_file = php_io_generic_copy_fallback, \
+ .generic_to_generic = php_io_generic_copy_fallback, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "macos"
+
+#endif /* PHP_IO_MACOS_H */
diff --git a/main/io/php_io_solaris.h b/main/io/php_io_solaris.h
new file mode 100644
index 0000000000000..ce3f1842cc6eb
--- /dev/null
+++ b/main/io/php_io_solaris.h
@@ -0,0 +1,32 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_SOLARIS_H
+#define PHP_IO_SOLARIS_H
+
+/* Copy operations */
+ssize_t php_io_solaris_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
+
+/* Instance initialization macros */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_generic_copy_fallback, \
+ .file_to_generic = php_io_solaris_copy_file_to_generic, \
+ .generic_to_file = php_io_generic_copy_fallback, \
+ .generic_to_generic = php_io_generic_copy_fallback, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "solaris"
+
+#endif /* PHP_IO_SOLARIS_H */
diff --git a/main/io/php_io_windows.h b/main/io/php_io_windows.h
new file mode 100644
index 0000000000000..781e65e1ea564
--- /dev/null
+++ b/main/io/php_io_windows.h
@@ -0,0 +1,33 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_WINDOWS_H
+#define PHP_IO_WINDOWS_H
+
+/* Copy operations */
+ssize_t php_io_windows_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen);
+ssize_t php_io_windows_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
+
+/* Instance initialization macros */
+#define PHP_IO_PLATFORM_COPY_OPS \
+ { \
+ .file_to_file = php_io_windows_copy_file_to_file, \
+ .file_to_generic = php_io_windows_copy_file_to_generic, \
+ .generic_to_file = php_io_generic_copy_fallback, \
+ .generic_to_generic = php_io_generic_copy_fallback, \
+ }
+
+#define PHP_IO_PLATFORM_NAME "windows"
+
+#endif /* PHP_IO_WINDOWS_H */
diff --git a/main/php_io.h b/main/php_io.h
new file mode 100644
index 0000000000000..6e9d2fd2b078b
--- /dev/null
+++ b/main/php_io.h
@@ -0,0 +1,60 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_H
+#define PHP_IO_H
+
+#include "php.h"
+
+/* Forward declarations */
+typedef struct php_io php_io;
+
+/* File descriptor types */
+typedef enum {
+ PHP_IO_FD_FILE, /* Regular file - can use optimized file operations */
+ PHP_IO_FD_GENERIC, /* Socket, pipe, or other - use generic operations */
+} php_io_fd_type;
+
+/* Copy as much as possible */
+#define PHP_IO_COPY_ALL SIZE_MAX
+
+/* Synchronous copy operations vtable */
+typedef struct php_io_copy_ops {
+ ssize_t (*file_to_file)(int src_fd, int dest_fd, size_t maxlen);
+ ssize_t (*file_to_generic)(int src_fd, int dest_fd, size_t maxlen);
+ ssize_t (*generic_to_file)(int src_fd, int dest_fd, size_t maxlen);
+ ssize_t (*generic_to_generic)(int src_fd, int dest_fd, size_t maxlen);
+} php_io_copy_ops;
+
+/* Main php_io structure */
+typedef struct php_io {
+ php_io_copy_ops copy;
+ const char *platform_name;
+} php_io;
+
+/* IO struct accessor function */
+PHPAPI php_io *php_io_get(void);
+
+/* High-level copy function - automatically selects best method based on fd types
+ * Copies up to maxlen bytes from src_fd to dest_fd
+ * If maxlen is PHP_IO_COPY_ALL, copies until EOF
+ *
+ * Returns:
+ * >= 0 - number of bytes copied (may be less than maxlen if EOF reached)
+ * -1 - I/O error occurred (errno is set)
+ */
+PHPAPI ssize_t php_io_copy(
+ int src_fd, php_io_fd_type src_type, int dest_fd, php_io_fd_type dest_type, size_t maxlen);
+
+#endif /* PHP_IO_H */
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 85d2947c28a6c..90b0b591afb3c 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -24,6 +24,7 @@
#include "php_globals.h"
#include "php_memory_streams.h"
#include "php_network.h"
+#include "php_io.h"
#include "php_open_temporary_file.h"
#include "ext/standard/file.h"
#include "ext/standard/basic_functions.h" /* for BG(CurrentStatFile) */
@@ -1636,164 +1637,17 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool
return result;
}
-/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
-PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC)
+/* Fallback copy stream function */
+static ssize_t php_stream_copy_fallback(php_stream *src, php_stream *dest, size_t maxlen, size_t *len)
{
char buf[CHUNK_SIZE];
size_t haveread = 0;
- size_t towrite;
- size_t dummy;
-
- if (!len) {
- len = &dummy;
- }
-
- if (maxlen == 0) {
- *len = 0;
- return SUCCESS;
- }
-
-#ifdef HAVE_COPY_FILE_RANGE
- if (php_stream_is(src, PHP_STREAM_IS_STDIO) &&
- php_stream_is(dest, PHP_STREAM_IS_STDIO) &&
- src->writepos == src->readpos) {
- /* both php_stream instances are backed by a file descriptor, are not filtered and the
- * read buffer is empty: we can use copy_file_range() */
- int src_fd, dest_fd, dest_open_flags = 0;
-
- /* copy_file_range does not work with O_APPEND */
- if (php_stream_cast(src, PHP_STREAM_AS_FD, (void*)&src_fd, 0) == SUCCESS &&
- php_stream_cast(dest, PHP_STREAM_AS_FD, (void*)&dest_fd, 0) == SUCCESS &&
- /* get dest open flags to check if the stream is open in append mode */
- php_stream_parse_fopen_modes(dest->mode, &dest_open_flags) == SUCCESS &&
- !(dest_open_flags & O_APPEND)) {
-
- /* clamp to INT_MAX to avoid EOVERFLOW */
- const size_t cfr_max = MIN(maxlen, (size_t)SSIZE_MAX);
-
- /* copy_file_range() is a Linux-specific system call which allows efficient copying
- * between two file descriptors, eliminating the need to transfer data from the kernel
- * to userspace and back. For networking file systems like NFS and Ceph, it even
- * eliminates copying data to the client, and local filesystems like Btrfs and XFS can
- * create shared extents. */
- ssize_t result = copy_file_range(src_fd, NULL, dest_fd, NULL, cfr_max, 0);
- if (result > 0) {
- size_t nbytes = (size_t)result;
- haveread += nbytes;
-
- src->position += nbytes;
- dest->position += nbytes;
-
- if ((maxlen != PHP_STREAM_COPY_ALL && nbytes == maxlen) || php_stream_eof(src)) {
- /* the whole request was satisfied or end-of-file reached - done */
- *len = haveread;
- return SUCCESS;
- }
-
- /* there may be more data; continue copying using the fallback code below */
- } else if (result == 0) {
- /* end of file */
- *len = haveread;
- return SUCCESS;
- } else if (result < 0) {
- switch (errno) {
- case EINVAL:
- /* some formal error, e.g. overlapping file ranges */
- break;
-
- case EXDEV:
- /* pre Linux 5.3 error */
- break;
-
- case ENOSYS:
- /* not implemented by this Linux kernel */
- break;
-
- case EIO:
- /* Some filesystems will cause failures if the max length is greater than the file length
- * in certain circumstances and configuration. In those cases the errno is EIO and we will
- * fall back to other methods. We cannot use stat to determine the file length upfront because
- * that is prone to races and outdated caching. */
- break;
-
- default:
- /* unexpected I/O error - give up, no fallback */
- *len = haveread;
- return FAILURE;
- }
-
- /* fall back to classic copying */
- }
- }
- }
-#endif // HAVE_COPY_FILE_RANGE
if (maxlen == PHP_STREAM_COPY_ALL) {
maxlen = 0;
}
- if (php_stream_mmap_possible(src)) {
- char *p;
-
- do {
- /* We must not modify maxlen here, because otherwise the file copy fallback below can fail */
- size_t chunk_size, must_read, mapped;
- if (maxlen == 0) {
- /* Unlimited read */
- must_read = chunk_size = PHP_STREAM_MMAP_MAX;
- } else {
- must_read = maxlen - haveread;
- if (must_read >= PHP_STREAM_MMAP_MAX) {
- chunk_size = PHP_STREAM_MMAP_MAX;
- } else {
- /* In case the length we still have to read from the file could be smaller than the file size,
- * chunk_size must not get bigger the size we're trying to read. */
- chunk_size = must_read;
- }
- }
-
- 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);
- break;
- }
-
- didwrite = php_stream_write(dest, p, mapped);
- if (didwrite < 0) {
- *len = haveread;
- php_stream_mmap_unmap(src);
- return FAILURE;
- }
-
- php_stream_mmap_unmap(src);
-
- *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 FAILURE;
- }
- if (mapped < chunk_size) {
- return SUCCESS;
- }
- /* If we're not reading as much as possible, so a bounded read */
- if (maxlen != 0) {
- must_read -= mapped;
- if (must_read == 0) {
- return SUCCESS;
- }
- }
- }
- } while (p);
- }
-
- while(1) {
+ while (1) {
size_t readchunk = sizeof(buf);
ssize_t didread;
char *writeptr;
@@ -1808,7 +1662,7 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
return didread < 0 ? FAILURE : SUCCESS;
}
- towrite = didread;
+ size_t towrite = didread;
writeptr = buf;
haveread += didread;
@@ -1832,6 +1686,71 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
return SUCCESS;
}
+/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
+PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC)
+{
+ size_t haveread = 0;
+ size_t dummy;
+
+ if (!len) {
+ len = &dummy;
+ }
+
+ if (maxlen == 0) {
+ *len = 0;
+ return SUCCESS;
+ }
+
+ /* Try to use optimized I/O if both streams are castable to fd and not filtered */
+ if (!php_stream_is(src, PHP_STREAM_IS_USERSPACE) && !php_stream_is(dest, PHP_STREAM_IS_USERSPACE) &&
+ src->writepos == src->readpos) { /* Read buffer must be empty */
+ int src_fd, dest_fd;
+
+ if (php_stream_cast(src, PHP_STREAM_AS_FD, (void*)&src_fd, 0) == SUCCESS &&
+ php_stream_cast(dest, PHP_STREAM_AS_FD, (void*)&dest_fd, 0) == SUCCESS) {
+
+ /* Determine fd types based on stream type */
+ php_io_fd_type src_type = php_stream_is(src, PHP_STREAM_IS_STDIO) ?
+ PHP_IO_FD_FILE : PHP_IO_FD_GENERIC;
+ php_io_fd_type dest_type = php_stream_is(dest, PHP_STREAM_IS_STDIO) ?
+ PHP_IO_FD_FILE : PHP_IO_FD_GENERIC;
+
+ /* Check if destination file is opened in append mode as copy_file_range does not respect O_APPEND */
+ zend_bool can_use_optimized = 1;
+
+ if (dest_type == PHP_IO_FD_FILE && src_type == PHP_IO_FD_FILE) {
+ int dest_flags = 0;
+ if (php_stream_parse_fopen_modes(dest->mode, &dest_flags) == SUCCESS && (dest_flags & O_APPEND)) {
+ /* Append mode with file destination and source - cannot use optimized copy */
+ can_use_optimized = 0;
+ }
+ }
+
+ if (can_use_optimized) {
+ /* Try optimized copy */
+ size_t io_maxlen = (maxlen == PHP_STREAM_COPY_ALL) ? PHP_IO_COPY_ALL : maxlen;
+ ssize_t result = php_io_copy(src_fd, src_type, dest_fd, dest_type, io_maxlen);
+
+ if (result >= 0) {
+ /* Success - update positions */
+ haveread = result;
+ src->position += result;
+ dest->position += result;
+ *len = haveread;
+ return SUCCESS;
+ }
+
+ /* I/O error occurred */
+ *len = 0;
+ return FAILURE;
+ }
+ }
+ }
+
+ /* Classic read/write loop fallback (if cast failed) */
+ return php_stream_copy_fallback(src, dest, maxlen, len);
+}
+
/* Returns the number of bytes moved.
* Returns 1 when source len is 0.
* Deprecated in favor of php_stream_copy_to_stream_ex() */
diff --git a/win32/build/config.w32 b/win32/build/config.w32
index 403f0aa6efbfe..1f4c4a0ecefbf 100644
--- a/win32/build/config.w32
+++ b/win32/build/config.w32
@@ -298,6 +298,9 @@ AC_DEFINE('HAVE_STRNLEN', 1);
AC_DEFINE('ZEND_CHECK_STACK_LIMIT', 1)
+ADD_SOURCES("main/streams", "php_io.c php_io_copy_windows.c");
+ADD_FLAG("CFLAGS_BD_MAIN_IO", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
+
ADD_SOURCES("main/streams", "streams.c cast.c memory.c filter.c plain_wrapper.c \
userspace.c transports.c xp_socket.c mmap.c glob_wrapper.c");
ADD_FLAG("CFLAGS_BD_MAIN_STREAMS", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1");