Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2c23c06
io: add initial php_io copy structure and implementation
bukka Sep 28, 2025
ac9a68f
io: refactore and simplify the io API
bukka Nov 4, 2025
cddb4b2
io: refactore fd types and add io copy function
bukka Nov 4, 2025
3c85331
io: modify and use io api in stream copy and add build
bukka Nov 5, 2025
78e28bd
io: fix file copy when dest open in append mode
bukka Nov 5, 2025
d2c8467
io: skip io copying for userspace streams
bukka Nov 5, 2025
950caaf
io: use libc copy_file_range instead of syscall
bukka Nov 5, 2025
bfcc3e7
io: fix copying of large files
bukka Nov 5, 2025
1cb0a20
io: add missing header new lines
bukka Nov 5, 2025
b04885f
io: try to use loff_t for 32bit in copy_file_range
bukka Nov 5, 2025
280cc5c
io: remove broken copy_file_range offset handling
bukka Nov 26, 2025
9f31164
io: fix windows io build
bukka Nov 26, 2025
7560a44
io: fix zend_test copy_file_range wrapper len condition
bukka Dec 31, 2025
abf5aac
io: rewrite and fix linux splice handling and add more tests
bukka Dec 31, 2025
3e02f08
io: fix some sendfile issues and test it
bukka Dec 31, 2025
78a9355
io: add Mswsock.lib to win32 confutils libs for TransmitFile
bukka Dec 31, 2025
0083726
io: remove wrong pipe drain in splice
bukka Feb 28, 2026
114435d
io: remove sendfile use in bsd, macos and solaris
bukka Feb 28, 2026
742b7db
io: do not use generic fallback on win
bukka Feb 28, 2026
bd300ad
io: refactore to use single op and special cast
bukka Mar 7, 2026
7d87d92
io: add missing io copy skip for userspace streams
bukka Mar 8, 2026
aab3617
io: use ssize_t for total_copied in copy fallback
bukka Mar 8, 2026
6c74a44
io: rewrite streams socket copy tests to use client / server
bukka Mar 8, 2026
c035d13
io: remove left over io headers
bukka Mar 8, 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 @@ -580,10 +580,12 @@ AC_CHECK_FUNCS(m4_normalize([
putenv
reallocarray
scandir
sendfile
setenv
setitimer
shutdown
sigprocmask
splice
statfs
statvfs
std_syslog
Expand Down Expand Up @@ -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
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)
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