diff --git a/configure.ac b/configure.ac
index 07d64902ebf0b..15fc7e4951649 100644
--- a/configure.ac
+++ b/configure.ac
@@ -580,10 +580,12 @@ AC_CHECK_FUNCS(m4_normalize([
putenv
reallocarray
scandir
+ sendfile
setenv
setitimer
shutdown
sigprocmask
+ splice
statfs
statvfs
std_syslog
@@ -1688,6 +1690,12 @@ PHP_ADD_SOURCES_X([main],
[PHP_FASTCGI_OBJS],
[no])
+PHP_ADD_SOURCES([main/io], m4_normalize([
+ php_io.c
+ php_io_copy_linux.c
+ ]),
+ [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
+
PHP_ADD_SOURCES([main/streams], m4_normalize([
cast.c
filter.c
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_file_to_socket_medium.phpt b/ext/standard/tests/streams/stream_copy_to_stream_file_to_socket_medium.phpt
new file mode 100644
index 0000000000000..c7bd9afeedacf
--- /dev/null
+++ b/ext/standard/tests/streams/stream_copy_to_stream_file_to_socket_medium.phpt
@@ -0,0 +1,48 @@
+--TEST--
+stream_copy_to_stream() 16k with file as $source and socket as $dest
+--SKIPIF--
+
+--FILE--
+run($clientCode, $serverCode);
+?>
+--EXPECT--
+int(16384)
+int(16384)
+bool(true)
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_socket.phpt b/ext/standard/tests/streams/stream_copy_to_stream_socket.phpt
deleted file mode 100644
index dafe90e40c405..0000000000000
--- a/ext/standard/tests/streams/stream_copy_to_stream_socket.phpt
+++ /dev/null
@@ -1,30 +0,0 @@
---TEST--
-stream_copy_to_stream() with socket as $source
---SKIPIF--
-
---FILE--
-
---EXPECT--
-string(1) "a"
-string(1) "a"
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_large.phpt b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_large.phpt
new file mode 100644
index 0000000000000..6d66bf4f81ba7
--- /dev/null
+++ b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_large.phpt
@@ -0,0 +1,42 @@
+--TEST--
+stream_copy_to_stream() 200k bytes with socket as $source and file as $dest
+--SKIPIF--
+
+--FILE--
+run($clientCode, $serverCode);
+?>
+--EXPECT--
+int(200000)
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_single.phpt b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_single.phpt
new file mode 100644
index 0000000000000..a5b694ce56507
--- /dev/null
+++ b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_single.phpt
@@ -0,0 +1,49 @@
+--TEST--
+stream_copy_to_stream() single byte with socket as $source and file as $dest
+--SKIPIF--
+
+--FILE--
+run($clientCode, $serverCode);
+?>
+--EXPECT--
+string(1) "a"
+string(1) "a"
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_small.phpt b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_small.phpt
new file mode 100644
index 0000000000000..9b4f8befce936
--- /dev/null
+++ b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_file_small.phpt
@@ -0,0 +1,42 @@
+--TEST--
+stream_copy_to_stream() 2048 bytes with socket as $source and file as $dest
+--SKIPIF--
+
+--FILE--
+run($clientCode, $serverCode);
+?>
+--EXPECTF--
+string(2048) "aaaaa%saaa"
diff --git a/ext/standard/tests/streams/stream_copy_to_stream_socket_to_socket.phpt b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_socket.phpt
new file mode 100644
index 0000000000000..d462d51170912
--- /dev/null
+++ b/ext/standard/tests/streams/stream_copy_to_stream_socket_to_socket.phpt
@@ -0,0 +1,69 @@
+--TEST--
+stream_copy_to_stream() socket to socket (splice both directions)
+--SKIPIF--
+
+--FILE--
+run($clientCode, [
+ 'source' => $sourceCode,
+ 'dest' => $destCode,
+]);
+?>
+--EXPECT--
+int(10000)
+int(10000)
+bool(true)
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index 0faf65f36437f..f0509d94ea54f 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -1820,7 +1820,7 @@ typedef off_t off64_t;
PHP_ZEND_TEST_API ssize_t copy_file_range(int fd_in, off64_t *off_in, int fd_out, off64_t *off_out, size_t len, unsigned int flags)
{
ssize_t (*original_copy_file_range)(int, off64_t *, int, off64_t *, size_t, unsigned int) = dlsym(RTLD_NEXT, "copy_file_range");
- if (ZT_G(limit_copy_file_range) >= Z_L(0)) {
+ if (ZT_G(limit_copy_file_range) >= Z_L(0) && ZT_G(limit_copy_file_range) < len) {
len = ZT_G(limit_copy_file_range);
}
return original_copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
diff --git a/main/io/php_io.c b/main/io/php_io.c
new file mode 100644
index 0000000000000..4f172983d27fa
--- /dev/null
+++ b/main/io/php_io.c
@@ -0,0 +1,81 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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"
+
+#ifdef PHP_WIN32
+#include
+#include
+#else
+#include
+#endif
+
+static php_io php_io_instance = {
+ .copy = PHP_IO_PLATFORM_COPY,
+ .platform_name = PHP_IO_PLATFORM_NAME,
+};
+
+PHPAPI php_io *php_io_get(void)
+{
+ return &php_io_instance;
+}
+
+PHPAPI ssize_t php_io_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen)
+{
+ return php_io_get()->copy(src, dest, maxlen);
+}
+
+ssize_t php_io_generic_copy_fallback(int src_fd, int dest_fd, size_t maxlen)
+{
+ char buf[8192];
+ ssize_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) {
+ return total_copied > 0 ? total_copied : -1;
+ } else if (bytes_read == 0) {
+ return total_copied;
+ }
+
+ char *writeptr = buf;
+ size_t to_write = (size_t) bytes_read;
+
+ while (to_write > 0) {
+ ssize_t bytes_written = write(dest_fd, writeptr, to_write);
+ if (bytes_written <= 0) {
+ return total_copied > 0 ? total_copied : -1;
+ }
+ total_copied += bytes_written;
+ writeptr += bytes_written;
+ to_write -= bytes_written;
+ }
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= bytes_read;
+ }
+ }
+
+ return total_copied;
+}
+
+ssize_t php_io_generic_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen)
+{
+ return php_io_generic_copy_fallback(src->fd, dest->fd, maxlen);
+}
diff --git a/main/io/php_io_copy_linux.c b/main/io/php_io_copy_linux.c
new file mode 100644
index 0000000000000..41838144b9fd0
--- /dev/null
+++ b/main/io/php_io_copy_linux.c
@@ -0,0 +1,249 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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
+
+#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
+
+static 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;
+
+ while (remaining > 0) {
+ size_t to_copy = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result = copy_file_range(src_fd, NULL, dest_fd, NULL, to_copy, 0);
+
+ if (result > 0) {
+ total_copied += result;
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ break;
+ } else {
+ switch (errno) {
+ case EINVAL:
+ case EXDEV:
+ case ENOSYS:
+ case EIO:
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ default:
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif
+
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+static ssize_t php_io_linux_sendfile(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;
+
+ while (remaining > 0) {
+ size_t to_send = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result = sendfile(dest_fd, src_fd, NULL, to_send);
+
+ if (result > 0) {
+ total_copied += result;
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ break;
+ } else {
+ switch (errno) {
+ case EINVAL:
+ case ENOSYS:
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ case EAGAIN:
+ break;
+ default:
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ if (total_copied > 0) {
+ return (ssize_t) total_copied;
+ }
+#endif
+
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+}
+
+#ifdef HAVE_SPLICE
+static ssize_t php_io_linux_splice_from_pipe(int pipe_fd, int dest_fd, size_t maxlen)
+{
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+
+ while (remaining > 0) {
+ size_t to_copy = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+ ssize_t result = splice(pipe_fd, NULL, dest_fd, NULL, to_copy, 0);
+
+ if (result > 0) {
+ total_copied += result;
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= result;
+ }
+ } else if (result == 0) {
+ break;
+ } else {
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(pipe_fd, dest_fd, maxlen);
+ }
+ break;
+ }
+ }
+
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+}
+
+static ssize_t php_io_linux_splice_via_pipe(int src_fd, int dest_fd, size_t maxlen)
+{
+ int pipefd[2];
+ if (pipe(pipefd) == -1) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+
+ size_t total_copied = 0;
+ size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
+
+ while (remaining > 0) {
+ size_t to_copy = (remaining < SSIZE_MAX) ? remaining : SSIZE_MAX;
+
+ ssize_t in_pipe = splice(src_fd, NULL, pipefd[1], NULL, to_copy, 0);
+ if (in_pipe < 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ if (total_copied == 0) {
+ return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
+ }
+ return (ssize_t) total_copied;
+ }
+ if (in_pipe == 0) {
+ break;
+ }
+
+ size_t pipe_remaining = in_pipe;
+ while (pipe_remaining > 0) {
+ ssize_t out = splice(pipefd[0], NULL, dest_fd, NULL, pipe_remaining, 0);
+ if (out <= 0) {
+ /* drain pipe before closing */
+ char drain_buf[1024];
+ while (pipe_remaining > 0) {
+ size_t to_drain = (pipe_remaining < sizeof(drain_buf))
+ ? pipe_remaining : sizeof(drain_buf);
+ ssize_t drained = read(pipefd[0], drain_buf, to_drain);
+ if (drained <= 0) {
+ break;
+ }
+ ssize_t written = write(dest_fd, drain_buf, drained);
+ if (written <= 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ pipe_remaining -= written;
+ total_copied += written;
+ }
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ pipe_remaining -= out;
+ total_copied += out;
+ }
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= in_pipe;
+ }
+ }
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+}
+#endif /* HAVE_SPLICE */
+
+ssize_t php_io_linux_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen)
+{
+ if (src->fd_type == PHP_IO_FD_FILE && dest->fd_type == PHP_IO_FD_FILE) {
+ return php_io_linux_copy_file_to_file(src->fd, dest->fd, maxlen);
+ }
+
+ if (src->fd_type == PHP_IO_FD_FILE && dest->fd_type == PHP_IO_FD_SOCKET) {
+ return php_io_linux_sendfile(src->fd, dest->fd, maxlen);
+ }
+
+ /* sendfile also works for file to pipe on Linux */
+ if (src->fd_type == PHP_IO_FD_FILE && dest->fd_type == PHP_IO_FD_PIPE) {
+ return php_io_linux_sendfile(src->fd, dest->fd, maxlen);
+ }
+
+#ifdef HAVE_SPLICE
+ if (src->fd_type == PHP_IO_FD_PIPE) {
+ return php_io_linux_splice_from_pipe(src->fd, dest->fd, maxlen);
+ }
+
+ if (src->fd_type == PHP_IO_FD_SOCKET) {
+ return php_io_linux_splice_via_pipe(src->fd, dest->fd, maxlen);
+ }
+#endif
+
+ return php_io_generic_copy_fallback(src->fd, dest->fd, maxlen);
+}
+
+#endif /* __linux__ */
diff --git a/main/io/php_io_copy_windows.c b/main/io/php_io_copy_windows.c
new file mode 100644
index 0000000000000..cb22a58784103
--- /dev/null
+++ b/main/io/php_io_copy_windows.c
@@ -0,0 +1,211 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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
+
+typedef ssize_t (*php_io_win_read_fn)(void *handle, char *buf, size_t len);
+typedef ssize_t (*php_io_win_write_fn)(void *handle, const char *buf, size_t len);
+
+static ssize_t php_io_win_read_handle(void *handle, char *buf, size_t len)
+{
+ DWORD to_read = (len > MAXDWORD) ? MAXDWORD : (DWORD) len;
+ DWORD bytes_read;
+ if (!ReadFile((HANDLE) handle, buf, to_read, &bytes_read, NULL)) {
+ return -1;
+ }
+ return (ssize_t) bytes_read;
+}
+
+static ssize_t php_io_win_write_handle(void *handle, const char *buf, size_t len)
+{
+ DWORD to_write = (len > MAXDWORD) ? MAXDWORD : (DWORD) len;
+ DWORD bytes_written;
+ if (!WriteFile((HANDLE) handle, buf, to_write, &bytes_written, NULL)) {
+ return -1;
+ }
+ return (ssize_t) bytes_written;
+}
+
+static ssize_t php_io_win_read_socket(void *handle, char *buf, size_t len)
+{
+ int to_recv = (len > INT_MAX) ? INT_MAX : (int) len;
+ int result = recv((SOCKET)(uintptr_t) handle, buf, to_recv, 0);
+ if (result == SOCKET_ERROR) {
+ return -1;
+ }
+ return (ssize_t) result;
+}
+
+static ssize_t php_io_win_write_socket(void *handle, const char *buf, size_t len)
+{
+ int to_send = (len > INT_MAX) ? INT_MAX : (int) len;
+ int result = send((SOCKET)(uintptr_t) handle, buf, to_send, 0);
+ if (result == SOCKET_ERROR) {
+ return -1;
+ }
+ return (ssize_t) result;
+}
+
+static ssize_t php_io_win_copy_loop(
+ void *src_handle, php_io_win_read_fn read_fn,
+ void *dest_handle, php_io_win_write_fn write_fn,
+ 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_fn(src_handle, buf, to_read);
+
+ if (bytes_read < 0) {
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ } else if (bytes_read == 0) {
+ return (ssize_t) total_copied;
+ }
+
+ const char *writeptr = buf;
+ size_t to_write = (size_t) bytes_read;
+
+ while (to_write > 0) {
+ ssize_t bytes_written = write_fn(dest_handle, writeptr, to_write);
+ if (bytes_written <= 0) {
+ return total_copied > 0 ? (ssize_t) total_copied : -1;
+ }
+ total_copied += bytes_written;
+ writeptr += bytes_written;
+ to_write -= bytes_written;
+ }
+
+ if (maxlen != PHP_IO_COPY_ALL) {
+ remaining -= bytes_read;
+ }
+ }
+
+ return (ssize_t) total_copied;
+}
+
+static ssize_t php_io_win_copy_handle_to_handle(int src_fd, int dest_fd, size_t maxlen)
+{
+ 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) {
+ return -1;
+ }
+
+ return php_io_win_copy_loop(
+ (void *) src_handle, php_io_win_read_handle,
+ (void *) dest_handle, php_io_win_write_handle,
+ maxlen);
+}
+
+static ssize_t php_io_win_copy_handle_to_socket(int src_fd, SOCKET dest_sock, size_t maxlen)
+{
+ HANDLE file_handle = (HANDLE) _get_osfhandle(src_fd);
+
+ if (file_handle == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+
+ /* Try TransmitFile for file to socket */
+ if (dest_sock != INVALID_SOCKET) {
+ LARGE_INTEGER file_pos;
+ file_pos.QuadPart = 0;
+ if (SetFilePointerEx(file_handle, file_pos, &file_pos, FILE_CURRENT)) {
+ DWORD bytes_to_send;
+
+ if (maxlen == PHP_IO_COPY_ALL) {
+ LARGE_INTEGER file_size;
+ if (GetFileSizeEx(file_handle, &file_size)) {
+ LONGLONG available = file_size.QuadPart - file_pos.QuadPart;
+ bytes_to_send = (available > MAXDWORD) ? 0 : (DWORD) available;
+ } else {
+ bytes_to_send = 0;
+ }
+ } else {
+ bytes_to_send = (DWORD) min(maxlen, MAXDWORD);
+ }
+
+ if (TransmitFile(dest_sock, file_handle, bytes_to_send, 0, NULL, NULL, 0)) {
+ if (bytes_to_send == 0 && maxlen == PHP_IO_COPY_ALL) {
+ LARGE_INTEGER new_pos;
+ LARGE_INTEGER zero = {0};
+ if (SetFilePointerEx(file_handle, zero, &new_pos, FILE_CURRENT)) {
+ return (ssize_t)(new_pos.QuadPart - file_pos.QuadPart);
+ }
+ return 0;
+ }
+ return (ssize_t) bytes_to_send;
+ }
+
+ if (WSAGetLastError() == WSAENOTSOCK) {
+ SetFilePointerEx(file_handle, file_pos, NULL, FILE_BEGIN);
+ }
+ }
+ }
+
+ return php_io_win_copy_loop(
+ (void *) file_handle, php_io_win_read_handle,
+ (void *)(uintptr_t) dest_sock, php_io_win_write_socket,
+ maxlen);
+}
+
+static ssize_t php_io_win_copy_socket_to_handle(SOCKET src_sock, int dest_fd, size_t maxlen)
+{
+ HANDLE dest_handle = (HANDLE) _get_osfhandle(dest_fd);
+
+ if (dest_handle == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+
+ return php_io_win_copy_loop(
+ (void *)(uintptr_t) src_sock, php_io_win_read_socket,
+ (void *) dest_handle, php_io_win_write_handle,
+ maxlen);
+}
+
+static ssize_t php_io_win_copy_socket_to_socket(SOCKET src_sock, SOCKET dest_sock, size_t maxlen)
+{
+ return php_io_win_copy_loop(
+ (void *)(uintptr_t) src_sock, php_io_win_read_socket,
+ (void *)(uintptr_t) dest_sock, php_io_win_write_socket,
+ maxlen);
+}
+
+ssize_t php_io_windows_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen)
+{
+ if (src->fd_type == PHP_IO_FD_SOCKET && dest->fd_type == PHP_IO_FD_SOCKET) {
+ return php_io_win_copy_socket_to_socket(src->socket, dest->socket, maxlen);
+ }
+
+ if (src->fd_type == PHP_IO_FD_SOCKET) {
+ return php_io_win_copy_socket_to_handle(src->socket, dest->fd, maxlen);
+ }
+
+ if (dest->fd_type == PHP_IO_FD_SOCKET) {
+ return php_io_win_copy_handle_to_socket(src->fd, dest->socket, maxlen);
+ }
+
+ return php_io_win_copy_handle_to_handle(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..f5a9478e3dbce
--- /dev/null
+++ b/main/io/php_io_generic.h
@@ -0,0 +1,23 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_GENERIC_H
+#define PHP_IO_GENERIC_H
+
+#define PHP_IO_PLATFORM_COPY php_io_generic_copy
+#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..69a1db5dee19f
--- /dev/null
+++ b/main/io/php_io_internal.h
@@ -0,0 +1,33 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_INTERNAL_H
+#define PHP_IO_INTERNAL_H
+
+#include "php_io.h"
+
+ssize_t php_io_generic_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen);
+ssize_t php_io_generic_copy_fallback(int src_fd, int dest_fd, size_t maxlen);
+
+#ifdef __linux__
+#include "php_io_linux.h"
+#elif defined(PHP_WIN32)
+#include "php_io_windows.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..40eedf014f055
--- /dev/null
+++ b/main/io/php_io_linux.h
@@ -0,0 +1,23 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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
+
+ssize_t php_io_linux_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen);
+
+#define PHP_IO_PLATFORM_COPY php_io_linux_copy
+#define PHP_IO_PLATFORM_NAME "linux"
+
+#endif /* PHP_IO_LINUX_H */
diff --git a/main/io/php_io_windows.h b/main/io/php_io_windows.h
new file mode 100644
index 0000000000000..a80a8af63564c
--- /dev/null
+++ b/main/io/php_io_windows.h
@@ -0,0 +1,23 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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
+
+ssize_t php_io_windows_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen);
+
+#define PHP_IO_PLATFORM_COPY php_io_windows_copy
+#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..dce3ea4a76553
--- /dev/null
+++ b/main/php_io.h
@@ -0,0 +1,49 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Jakub Zelenka |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef PHP_IO_H
+#define PHP_IO_H
+
+#include "php.h"
+#include "php_network.h"
+
+#define PHP_IO_COPY_ALL SIZE_MAX
+
+#define PHP_IO_FD_FILE 1
+#define PHP_IO_FD_SOCKET 2
+#define PHP_IO_FD_PIPE 3
+
+typedef struct {
+ union {
+ int fd;
+ php_socket_t socket;
+ };
+ int fd_type;
+} php_io_fd;
+
+typedef ssize_t (*php_io_copy_fn)(php_io_fd *src, php_io_fd *dest, size_t maxlen);
+
+typedef struct php_io {
+ php_io_copy_fn copy;
+ const char *platform_name;
+} php_io;
+
+PHPAPI php_io *php_io_get(void);
+
+/* Returns bytes copied on success, -1 on error */
+PHPAPI ssize_t php_io_copy(php_io_fd *src, php_io_fd *dest, size_t maxlen);
+
+#endif /* PHP_IO_H */
diff --git a/main/php_streams.h b/main/php_streams.h
index 1c52539cfcaee..d017a1d76b22f 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -554,6 +554,8 @@ END_EXTERN_C()
#define PHP_STREAM_AS_SOCKETD 2
/* cast as fd/socket for select purposes */
#define PHP_STREAM_AS_FD_FOR_SELECT 3
+/* cast as fd/socket for copy purposes */
+#define PHP_STREAM_AS_FD_FOR_COPY 4
/* try really, really hard to make sure the cast happens (avoid using this flag if possible) */
#define PHP_STREAM_CAST_TRY_HARD 0x80000000
diff --git a/main/streams/cast.c b/main/streams/cast.c
index 4dc8ddb5f6a30..a29a10ef323bb 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -197,7 +197,7 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
castas &= ~PHP_STREAM_CAST_MASK;
/* synchronize our buffer (if possible) */
- if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT) {
+ if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT && castas != PHP_STREAM_AS_FD_FOR_COPY) {
php_stream_flush(stream);
if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
zend_off_t dummy;
@@ -207,6 +207,16 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
}
}
+ if (castas == PHP_STREAM_AS_FD_FOR_COPY) {
+ if (php_stream_is_filtered(stream)) {
+ return FAILURE;
+ }
+ if (stream->ops->cast && stream->ops->cast(stream, castas, ret) == SUCCESS) {
+ return SUCCESS;
+ }
+ return FAILURE;
+ }
+
/* filtered streams can only be cast as stdio, and only when fopencookie is present */
if (castas == PHP_STREAM_AS_STDIO) {
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index 688d271db8147..ea8c143bb02a7 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -34,6 +34,7 @@
#endif
#include "SAPI.h"
+#include "php_io.h"
#include "php_streams_int.h"
#ifdef PHP_WIN32
# include "win32/winutil.h"
@@ -641,7 +642,6 @@ static int php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, ze
return ret;
}
}
-
static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
{
php_socket_t fd;
@@ -649,16 +649,10 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
assert(data != NULL);
- /* as soon as someone touches the stdio layer, buffering may ensue,
- * so we need to stop using the fd directly in that case */
-
switch (castas) {
case PHP_STREAM_AS_STDIO:
if (ret) {
-
if (data->file == NULL) {
- /* we were opened as a plain file descriptor, so we
- * need fdopen now */
char fixed_mode[5];
php_stream_mode_sanitize_fdopen_fopencookie(stream, fixed_mode);
data->file = fdopen(data->fd, fixed_mode);
@@ -666,7 +660,6 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
return FAILURE;
}
}
-
*(FILE**)ret = data->file;
data->fd = SOCK_ERR;
}
@@ -684,7 +677,6 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
case PHP_STREAM_AS_FD:
PHP_STDIOP_GET_FD(fd, data);
-
if (SOCK_ERR == fd) {
return FAILURE;
}
@@ -695,6 +687,22 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
*(php_socket_t *)ret = fd;
}
return SUCCESS;
+
+ case PHP_STREAM_AS_FD_FOR_COPY:
+ PHP_STDIOP_GET_FD(fd, data);
+ if (SOCK_ERR == fd) {
+ return FAILURE;
+ }
+ if (data->file) {
+ fflush(data->file);
+ }
+ if (ret) {
+ php_io_fd *copy_fd = (php_io_fd *) ret;
+ copy_fd->fd = fd;
+ copy_fd->fd_type = data->is_pipe ? PHP_IO_FD_PIPE : PHP_IO_FD_FILE;
+ }
+ return SUCCESS;
+
default:
return FAILURE;
}
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 85d2947c28a6c..e0f7a5485c987 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) */
@@ -1635,165 +1636,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 using stream read/write API */
+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,14 +1661,14 @@ 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;
while (towrite) {
ssize_t didwrite = php_stream_write(dest, writeptr, towrite);
if (didwrite <= 0) {
- *len = haveread - (didread - towrite);
+ *len = haveread - towrite;
return FAILURE;
}
@@ -1832,6 +1685,55 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
return SUCCESS;
}
+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 dummy;
+
+ if (!len) {
+ len = &dummy;
+ }
+
+ if (maxlen == 0) {
+ *len = 0;
+ return SUCCESS;
+ }
+
+ /* Try optimized fd-level copy if both streams support it and read buffer is empty */
+ if (!php_stream_is(src, PHP_STREAM_IS_USERSPACE) && !php_stream_is(dest, PHP_STREAM_IS_USERSPACE) &&
+ src->writepos == src->readpos && !php_stream_is_filtered(src) && !php_stream_is_filtered(dest)) {
+ php_io_fd src_copy_fd, dest_copy_fd;
+
+ if (php_stream_cast(src, PHP_STREAM_AS_FD_FOR_COPY, (void *) &src_copy_fd, 0) == SUCCESS &&
+ php_stream_cast(dest, PHP_STREAM_AS_FD_FOR_COPY, (void *) &dest_copy_fd, 0) == SUCCESS) {
+
+ /* copy_file_range does not work with O_APPEND */
+ if (src_copy_fd.fd_type == PHP_IO_FD_FILE && dest_copy_fd.fd_type == PHP_IO_FD_FILE) {
+ int dest_flags = 0;
+ if (php_stream_parse_fopen_modes(dest->mode, &dest_flags) == SUCCESS
+ && (dest_flags & O_APPEND)) {
+ goto fallback;
+ }
+ }
+
+ size_t io_maxlen = (maxlen == PHP_STREAM_COPY_ALL) ? PHP_IO_COPY_ALL : maxlen;
+ ssize_t result = php_io_copy(&src_copy_fd, &dest_copy_fd, io_maxlen);
+
+ if (result >= 0) {
+ src->position += result;
+ dest->position += result;
+ *len = result;
+ return SUCCESS;
+ }
+
+ *len = 0;
+ return FAILURE;
+ }
+ }
+
+fallback:
+ 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/main/streams/xp_socket.c b/main/streams/xp_socket.c
index edf1751ec33b5..92b949c21369a 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -17,7 +17,7 @@
#include "php.h"
#include "ext/standard/file.h"
#include "php_streams.h"
-#include "php_network.h"
+#include "php_io.h"
#if defined(PHP_WIN32) || defined(__riscos__)
# undef AF_UNIX
@@ -527,11 +527,17 @@ static int php_sockop_cast(php_stream *stream, int castas, void **ret)
if (ret)
*(php_socket_t *)ret = sock->socket;
return SUCCESS;
+ case PHP_STREAM_AS_FD_FOR_COPY:
+ if (ret) {
+ php_io_fd *copy_fd = (php_io_fd *) ret;
+ copy_fd->socket = sock->socket;
+ copy_fd->fd_type = PHP_IO_FD_SOCKET;
+ }
+ return SUCCESS;
default:
return FAILURE;
}
}
-/* }}} */
/* These may look identical, but we need them this way so that
* we can determine which type of socket we are dealing with
diff --git a/win32/build/config.w32 b/win32/build/config.w32
index aefcfb5f82474..fa75404748b0d 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/io", "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");
@@ -309,7 +312,7 @@ ADD_SOURCES("win32", "dllmain.c readdir.c \
ADD_FLAG("CFLAGS_BD_WIN32", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
-PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/streams/ win32/");
+PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/io main/streams/ win32/");
PHP_INSTALL_HEADERS("Zend/Optimizer", "zend_call_graph.h zend_cfg.h zend_dfg.h zend_dump.h zend_func_info.h zend_inference.h zend_optimizer.h zend_ssa.h zend_worklist.h");
STDOUT.WriteBlankLines(1);
diff --git a/win32/build/confutils.js b/win32/build/confutils.js
index e516fd410bcd5..be5d026e1b1b1 100644
--- a/win32/build/confutils.js
+++ b/win32/build/confutils.js
@@ -3445,7 +3445,7 @@ function toolset_setup_common_ldflags()
function toolset_setup_common_libs()
{
// urlmon.lib ole32.lib oleaut32.lib uuid.lib gdi32.lib winspool.lib comdlg32.lib
- DEFINE("LIBS", "kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib Dnsapi.lib psapi.lib bcrypt.lib Pathcch.lib");
+ DEFINE("LIBS", "kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib Dnsapi.lib psapi.lib bcrypt.lib Pathcch.lib Mswsock.lib");
}
function toolset_setup_build_mode()