Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ae0eb89
io: add initial php_io copy structure and implementation
bukka Sep 28, 2025
f26bb6a
io: refactore and simplify the io API
bukka Nov 4, 2025
bb6f144
io: refactore fd types and add io copy function
bukka Nov 4, 2025
98d8208
io: modify and use io api in stream copy and add build
bukka Nov 5, 2025
13f4eef
io: fix file copy when dest open in append mode
bukka Nov 5, 2025
e5cfe16
io: skip io copying for userspace streams
bukka Nov 5, 2025
49def6a
io: use libc copy_file_range instead of syscall
bukka Nov 5, 2025
f4b0dfc
io: fix copying of large files
bukka Nov 5, 2025
a46117a
io: add missing header new lines
bukka Nov 5, 2025
2aa8d37
io: try to use loff_t for 32bit in copy_file_range
bukka Nov 5, 2025
f8770af
io: remove broken copy_file_range offset handling
bukka Nov 26, 2025
73dc3a0
io: fix windows io build
bukka Nov 26, 2025
a2b9dcf
io: fix zend_test copy_file_range wrapper len condition
bukka Dec 31, 2025
1b51242
io: rewrite and fix linux splice handling and add more tests
bukka Dec 31, 2025
0da6554
io: fix some sendfile issues and test it
bukka Dec 31, 2025
db5c697
io: add Mswsock.lib to win32 confutils libs for TransmitFile
bukka Dec 31, 2025
17bfbac
io: remove wrong pipe drain in splice
bukka Feb 28, 2026
abf6910
io: remove sendfile use in bsd, macos and solaris
bukka Feb 28, 2026
18a0105
io: do not use generic fallback on win
bukka Feb 28, 2026
b948c49
io: refactore to use single op and special cast
bukka Mar 7, 2026
5ae8209
io: add missing io copy skip for userspace streams
bukka Mar 8, 2026
dd2bb30
io: use ssize_t for total_copied in copy fallback
bukka Mar 8, 2026
e1585a5
io: rewrite streams socket copy tests to use client / server
bukka Mar 8, 2026
241e1af
io: remove left over io headers
bukka Mar 8, 2026
493e07f
io: add cast to xp_ssl
bukka Mar 15, 2026
e832cf2
io: revert some formatting chages in php_stdiop_cast
bukka Mar 15, 2026
015a52c
io: fix php_stream_copy_fallback return type
bukka Mar 15, 2026
f3acebf
io: add stream copy socket to stdout test
bukka Mar 17, 2026
13c6e5d
io: refactore win copy and add timeout polling
bukka Mar 18, 2026
ab6300e
io: fix linux timeout handling for splice
bukka Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -587,10 +587,12 @@ AC_CHECK_FUNCS(m4_normalize([
putenv
reallocarray
scandir
sendfile
setenv
setitimer
shutdown
sigprocmask
splice
statfs
statvfs
std_syslog
Expand Down Expand Up @@ -1682,6 +1684,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
Expand Down
14 changes: 13 additions & 1 deletion ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include "zend_exceptions.h"
#include "php_openssl.h"
#include "php_openssl_backend.h"
#include "php_network.h"
#include "php_io.h"
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
Expand Down Expand Up @@ -2605,6 +2605,18 @@ static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret)
*(php_socket_t *)ret = sslsock->s.socket;
}
return SUCCESS;
case PHP_STREAM_AS_FD_FOR_COPY:
if (sslsock->ssl_active) {
return FAILURE;
}
if (ret) {
php_io_fd *copy_fd = (php_io_fd *) ret;
copy_fd->socket = sslsock->s.socket;
copy_fd->fd_type = PHP_IO_FD_SOCKET;
copy_fd->timeout = sslsock->s.timeout;
copy_fd->is_blocked = sslsock->s.is_blocked;
}
return SUCCESS;
default:
return FAILURE;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
stream_copy_to_stream() 16k with file as $source and socket as $dest
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server);
$data = str_repeat('data', 4096);
$result = stream_get_contents($conn);

phpt_notify(message: strlen($result));
phpt_notify(message: $result === $data ? "match" : "mismatch");

fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$src = tmpfile();
$data = str_repeat('data', 4096);
fwrite($src, $data);
rewind($src);

$dest = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);
$copied = stream_copy_to_stream($src, $dest);
var_dump($copied);

fclose($dest);
fclose($src);

var_dump((int) trim(phpt_wait()));
var_dump(trim(phpt_wait()) === "match");
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
int(16384)
int(16384)
bool(true)
30 changes: 0 additions & 30 deletions ext/standard/tests/streams/stream_copy_to_stream_socket.phpt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
stream_copy_to_stream() 200k bytes with socket as $source and file as $dest
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server);
fwrite($conn, str_repeat("a", 200000));
stream_socket_shutdown($conn, STREAM_SHUT_WR);

/* Keep alive until client is done reading. */
fread($conn, 1);

fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$source = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);
$tmp = tmpfile();

stream_copy_to_stream($source, $tmp);

fseek($tmp, 0, SEEK_SET);
var_dump(strlen(stream_get_contents($tmp)));

fclose($tmp);
fclose($source);
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
int(200000)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
stream_copy_to_stream() single byte with socket as $source and file as $dest
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server);
fwrite($conn, "a");
stream_socket_shutdown($conn, STREAM_SHUT_WR);

/* Keep alive until client is done reading. */
fread($conn, 1);

fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$source = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);
$tmp = tmpfile();

stream_copy_to_stream($source, $tmp);

fseek($tmp, 0, SEEK_SET);
var_dump(stream_get_contents($tmp));

/* Second copy after EOF should be a no-op. */
stream_copy_to_stream($source, $tmp);

fseek($tmp, 0, SEEK_SET);
var_dump(stream_get_contents($tmp));

fclose($tmp);
fclose($source);
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
string(1) "a"
string(1) "a"
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
stream_copy_to_stream() 2048 bytes with socket as $source and file as $dest
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server);
fwrite($conn, str_repeat("a", 2048));
stream_socket_shutdown($conn, STREAM_SHUT_WR);

/* Keep alive until client is done reading. */
fread($conn, 1);

fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$source = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);
$tmp = tmpfile();

stream_copy_to_stream($source, $tmp);

fseek($tmp, 0, SEEK_SET);
var_dump(stream_get_contents($tmp));

fclose($tmp);
fclose($source);
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECTF--
string(2048) "aaaaa%saaa"
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
--TEST--
stream_copy_to_stream() socket to socket (splice both directions)
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$sourceCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

/* Send address again so the client can read it via phpt_wait(). */
phpt_notify(message: stream_socket_get_name($server, false));

$conn = stream_socket_accept($server);
$data = str_repeat('test data ', 1000);
fwrite($conn, $data);
stream_socket_shutdown($conn, STREAM_SHUT_WR);

/* Keep alive until client is done reading. */
fread($conn, 1);

fclose($conn);
fclose($server);
CODE;

$destCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server);
$result = stream_get_contents($conn);

phpt_notify(message: strlen($result));
phpt_notify(message: $result === str_repeat('test data ', 1000) ? "match" : "mismatch");

fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$sourceAddr = trim(phpt_wait("source"));
$source = stream_socket_client("tcp://$sourceAddr", $errno, $errstr, 10);
$dest = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);

$copied = stream_copy_to_stream($source, $dest);
var_dump($copied);

stream_socket_shutdown($dest, STREAM_SHUT_WR);
fclose($source);

var_dump((int) trim(phpt_wait("dest")));
var_dump(trim(phpt_wait("dest")) === "match");

fclose($dest);
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, [
'source' => $sourceCode,
'dest' => $destCode,
]);
?>
--EXPECT--
int(10000)
int(10000)
bool(true)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
stream_copy_to_stream() with socket as $source and STDOUT as $dest
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php

$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

$conn = stream_socket_accept($server, 5);
fwrite($conn, "data to stdout\n");
fclose($conn);
fclose($server);
CODE;

$clientCode = <<<'CODE'
$fd = stream_socket_client("tcp://{{ ADDR }}", $errno, $errstr, 10);

stream_copy_to_stream($fd, STDOUT);

fclose($fd);
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
data to stdout
2 changes: 1 addition & 1 deletion ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading