Skip to content

Commit 742b7db

Browse files
committed
io: do not use generic fallback on win
1 parent 114435d commit 742b7db

File tree

2 files changed

+185
-64
lines changed

2 files changed

+185
-64
lines changed

main/io/php_io_copy_windows.c

Lines changed: 181 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,80 +20,213 @@
2020
#include <winsock2.h>
2121
#include <mswsock.h>
2222

23-
ssize_t php_io_windows_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen)
23+
/* Read from a socket using recv() */
24+
static inline ssize_t php_io_windows_socket_read(SOCKET sock, char *buf, size_t len)
2425
{
25-
/* Use ReadFile/WriteFile for file-to-file copying */
26-
HANDLE src_handle = (HANDLE) _get_osfhandle(src_fd);
27-
HANDLE dest_handle = (HANDLE) _get_osfhandle(dest_fd);
26+
int to_recv = (len > INT_MAX) ? INT_MAX : (int) len;
27+
int result = recv(sock, buf, to_recv, 0);
28+
if (result == SOCKET_ERROR) {
29+
return -1;
30+
}
31+
return (ssize_t) result;
32+
}
2833

29-
if (src_handle != INVALID_HANDLE_VALUE && dest_handle != INVALID_HANDLE_VALUE) {
30-
char buffer[65536];
31-
DWORD total_copied = 0;
32-
DWORD remaining = (maxlen == PHP_IO_COPY_ALL) ? MAXDWORD : (DWORD) min(maxlen, MAXDWORD);
34+
/* Write to a socket using send() */
35+
static inline ssize_t php_io_windows_socket_write(SOCKET sock, const char *buf, size_t len)
36+
{
37+
int to_send = (len > INT_MAX) ? INT_MAX : (int) len;
38+
int result = send(sock, buf, to_send, 0);
39+
if (result == SOCKET_ERROR) {
40+
return -1;
41+
}
42+
return (ssize_t) result;
43+
}
3344

34-
while (remaining > 0) {
35-
DWORD to_read = min(sizeof(buffer), remaining);
36-
DWORD bytes_read, bytes_written;
45+
/* Read from a file HANDLE using ReadFile() */
46+
static inline ssize_t php_io_windows_file_read(HANDLE handle, char *buf, size_t len)
47+
{
48+
DWORD to_read = (len > MAXDWORD) ? MAXDWORD : (DWORD) len;
49+
DWORD bytes_read;
50+
if (!ReadFile(handle, buf, to_read, &bytes_read, NULL)) {
51+
return -1;
52+
}
53+
return (ssize_t) bytes_read;
54+
}
3755

38-
if (!ReadFile(src_handle, buffer, to_read, &bytes_read, NULL)) {
39-
/* Read error */
40-
return total_copied > 0 ? (ssize_t) total_copied : -1;
41-
}
56+
/* Write to a file HANDLE using WriteFile() */
57+
static inline ssize_t php_io_windows_file_write(HANDLE handle, const char *buf, size_t len)
58+
{
59+
DWORD to_write = (len > MAXDWORD) ? MAXDWORD : (DWORD) len;
60+
DWORD bytes_written;
61+
if (!WriteFile(handle, buf, to_write, &bytes_written, NULL)) {
62+
return -1;
63+
}
64+
return (ssize_t) bytes_written;
65+
}
4266

43-
if (bytes_read == 0) {
44-
/* EOF */
45-
return (ssize_t) total_copied;
46-
}
67+
/* Generic copy loop parameterized by read/write function pointers */
68+
typedef ssize_t (*php_io_windows_read_fn)(void *handle, char *buf, size_t len);
69+
typedef ssize_t (*php_io_windows_write_fn)(void *handle, const char *buf, size_t len);
70+
71+
static ssize_t php_io_windows_copy_loop(
72+
void *src_handle, php_io_windows_read_fn read_fn,
73+
void *dest_handle, php_io_windows_write_fn write_fn,
74+
size_t maxlen)
75+
{
76+
char buf[8192];
77+
size_t total_copied = 0;
78+
size_t remaining = (maxlen == PHP_IO_COPY_ALL) ? SIZE_MAX : maxlen;
79+
80+
while (remaining > 0) {
81+
size_t to_read = (remaining < sizeof(buf)) ? remaining : sizeof(buf);
82+
ssize_t bytes_read = read_fn(src_handle, buf, to_read);
83+
84+
if (bytes_read < 0) {
85+
return total_copied > 0 ? (ssize_t) total_copied : -1;
86+
} else if (bytes_read == 0) {
87+
return (ssize_t) total_copied;
88+
}
4789

48-
if (!WriteFile(dest_handle, buffer, bytes_read, &bytes_written, NULL)) {
49-
/* Write error */
90+
const char *writeptr = buf;
91+
size_t to_write = (size_t) bytes_read;
92+
93+
while (to_write > 0) {
94+
ssize_t bytes_written = write_fn(dest_handle, writeptr, to_write);
95+
if (bytes_written <= 0) {
5096
return total_copied > 0 ? (ssize_t) total_copied : -1;
5197
}
52-
5398
total_copied += bytes_written;
54-
if (maxlen != PHP_IO_COPY_ALL) {
55-
remaining -= bytes_written;
56-
}
99+
writeptr += bytes_written;
100+
to_write -= bytes_written;
101+
}
57102

58-
if (bytes_written != bytes_read) {
59-
/* Partial write */
60-
return (ssize_t) total_copied;
61-
}
103+
if (maxlen != PHP_IO_COPY_ALL) {
104+
remaining -= bytes_read;
62105
}
106+
}
107+
108+
return (ssize_t) total_copied;
109+
}
110+
111+
/* Wrapper functions to match the generic function pointer signatures */
112+
static ssize_t php_io_windows_read_file(void *handle, char *buf, size_t len)
113+
{
114+
return php_io_windows_file_read((HANDLE) handle, buf, len);
115+
}
63116

64-
return (ssize_t) total_copied;
117+
static ssize_t php_io_windows_write_file(void *handle, const char *buf, size_t len)
118+
{
119+
return php_io_windows_file_write((HANDLE) handle, buf, len);
120+
}
121+
122+
static ssize_t php_io_windows_read_socket(void *handle, char *buf, size_t len)
123+
{
124+
return php_io_windows_socket_read((SOCKET)(uintptr_t) handle, buf, len);
125+
}
126+
127+
static ssize_t php_io_windows_write_socket(void *handle, const char *buf, size_t len)
128+
{
129+
return php_io_windows_socket_write((SOCKET)(uintptr_t) handle, buf, len);
130+
}
131+
132+
ssize_t php_io_windows_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen)
133+
{
134+
HANDLE src_handle = (HANDLE) _get_osfhandle(src_fd);
135+
HANDLE dest_handle = (HANDLE) _get_osfhandle(dest_fd);
136+
137+
if (src_handle == INVALID_HANDLE_VALUE || dest_handle == INVALID_HANDLE_VALUE) {
138+
return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
65139
}
66140

67-
/* Fallback to generic implementation */
68-
return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
141+
return php_io_windows_copy_loop(
142+
(void *) src_handle, php_io_windows_read_file,
143+
(void *) dest_handle, php_io_windows_write_file,
144+
maxlen);
69145
}
70146

71147
ssize_t php_io_windows_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen)
72148
{
73-
/* Use TransmitFile for zero-copy file to socket transfer */
74149
HANDLE file_handle = (HANDLE) _get_osfhandle(src_fd);
75150
SOCKET sock = (SOCKET) dest_fd;
76151

77-
if (file_handle != INVALID_HANDLE_VALUE && sock != INVALID_SOCKET) {
78-
/* TransmitFile can send entire file or partial */
79-
DWORD bytes_to_send = (maxlen == PHP_IO_COPY_ALL) ? 0 : (DWORD) min(maxlen, MAXDWORD);
152+
if (file_handle == INVALID_HANDLE_VALUE) {
153+
return -1;
154+
}
80155

81-
if (TransmitFile(sock, file_handle, bytes_to_send, 0, NULL, NULL, 0)) {
82-
/* TransmitFile succeeded - but we don't know exactly how much was sent without extra
83-
* syscalls */
84-
/* For simplicity, assume the requested amount was sent */
85-
return (maxlen == PHP_IO_COPY_ALL) ? 0 : (ssize_t) bytes_to_send;
86-
}
156+
/* Try TransmitFile for zero-copy transfer first */
157+
if (sock != INVALID_SOCKET) {
158+
LARGE_INTEGER file_size;
159+
LARGE_INTEGER file_pos;
160+
161+
/* Get current file position to calculate bytes available */
162+
file_pos.QuadPart = 0;
163+
if (SetFilePointerEx(file_handle, file_pos, &file_pos, FILE_CURRENT)) {
164+
DWORD bytes_to_send;
165+
166+
if (maxlen == PHP_IO_COPY_ALL) {
167+
if (GetFileSizeEx(file_handle, &file_size)) {
168+
LONGLONG available = file_size.QuadPart - file_pos.QuadPart;
169+
bytes_to_send = (available > MAXDWORD) ? 0 : (DWORD) available;
170+
} else {
171+
bytes_to_send = 0; /* Let TransmitFile send everything */
172+
}
173+
} else {
174+
bytes_to_send = (DWORD) min(maxlen, MAXDWORD);
175+
}
176+
177+
if (TransmitFile(sock, file_handle, bytes_to_send, 0, NULL, NULL, 0)) {
178+
/* For COPY_ALL with bytes_to_send=0, we need to figure out how much was sent */
179+
if (bytes_to_send == 0 && maxlen == PHP_IO_COPY_ALL) {
180+
LARGE_INTEGER new_pos;
181+
LARGE_INTEGER zero = {0};
182+
if (SetFilePointerEx(file_handle, zero, &new_pos, FILE_CURRENT)) {
183+
return (ssize_t)(new_pos.QuadPart - file_pos.QuadPart);
184+
}
185+
/* Can't determine size, but succeeded */
186+
return 0;
187+
}
188+
return (ssize_t) bytes_to_send;
189+
}
87190

88-
/* TransmitFile failed, check if it's a recoverable error */
89-
int error = WSAGetLastError();
90-
if (error == WSAENOTSOCK) {
91-
/* dest_fd is not a socket, fall back to generic copy */
191+
/* TransmitFile failed - check if dest is actually a socket */
192+
if (WSAGetLastError() == WSAENOTSOCK) {
193+
/* Reset file position for fallback */
194+
SetFilePointerEx(file_handle, file_pos, NULL, FILE_BEGIN);
195+
}
92196
}
93197
}
94198

95-
/* Fallback to generic implementation */
96-
return php_io_generic_copy_fallback(src_fd, dest_fd, maxlen);
199+
/* Fallback: file read → socket send */
200+
return php_io_windows_copy_loop(
201+
(void *) file_handle, php_io_windows_read_file,
202+
(void *)(uintptr_t) sock, php_io_windows_write_socket,
203+
maxlen);
204+
}
205+
206+
ssize_t php_io_windows_copy_generic_to_file(int src_fd, int dest_fd, size_t maxlen)
207+
{
208+
HANDLE dest_handle = (HANDLE) _get_osfhandle(dest_fd);
209+
SOCKET sock = (SOCKET) src_fd;
210+
211+
if (dest_handle == INVALID_HANDLE_VALUE) {
212+
return -1;
213+
}
214+
215+
return php_io_windows_copy_loop(
216+
(void *)(uintptr_t) sock, php_io_windows_read_socket,
217+
(void *) dest_handle, php_io_windows_write_file,
218+
maxlen);
219+
}
220+
221+
ssize_t php_io_windows_copy_generic_to_generic(int src_fd, int dest_fd, size_t maxlen)
222+
{
223+
SOCKET src_sock = (SOCKET) src_fd;
224+
SOCKET dest_sock = (SOCKET) dest_fd;
225+
226+
return php_io_windows_copy_loop(
227+
(void *)(uintptr_t) src_sock, php_io_windows_read_socket,
228+
(void *)(uintptr_t) dest_sock, php_io_windows_write_socket,
229+
maxlen);
97230
}
98231

99232
#endif /* PHP_WIN32 */

main/io/php_io_windows.h

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,19 @@
1-
/*
2-
+----------------------------------------------------------------------+
3-
| Copyright © The PHP Group and Contributors. |
4-
+----------------------------------------------------------------------+
5-
| This source file is subject to the Modified BSD License that is |
6-
| bundled with this package in the file LICENSE, and is available |
7-
| through the World Wide Web at <https://www.php.net/license/>. |
8-
| |
9-
| SPDX-License-Identifier: BSD-3-Clause |
10-
+----------------------------------------------------------------------+
11-
| Authors: Jakub Zelenka <bukka@php.net> |
12-
+----------------------------------------------------------------------+
13-
*/
14-
151
#ifndef PHP_IO_WINDOWS_H
162
#define PHP_IO_WINDOWS_H
173

184
/* Copy operations */
195
ssize_t php_io_windows_copy_file_to_file(int src_fd, int dest_fd, size_t maxlen);
206
ssize_t php_io_windows_copy_file_to_generic(int src_fd, int dest_fd, size_t maxlen);
7+
ssize_t php_io_windows_copy_generic_to_file(int src_fd, int dest_fd, size_t maxlen);
8+
ssize_t php_io_windows_copy_generic_to_generic(int src_fd, int dest_fd, size_t maxlen);
219

2210
/* Instance initialization macros */
2311
#define PHP_IO_PLATFORM_COPY_OPS \
2412
{ \
2513
.file_to_file = php_io_windows_copy_file_to_file, \
2614
.file_to_generic = php_io_windows_copy_file_to_generic, \
27-
.generic_to_file = php_io_generic_copy_fallback, \
28-
.generic_to_generic = php_io_generic_copy_fallback, \
15+
.generic_to_file = php_io_windows_copy_generic_to_file, \
16+
.generic_to_generic = php_io_windows_copy_generic_to_generic, \
2917
}
3018

3119
#define PHP_IO_PLATFORM_NAME "windows"

0 commit comments

Comments
 (0)