Skip to content

Commit e3df41c

Browse files
committed
main/streams/streams: use copy_file_range() on Linux
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.
1 parent ebd922d commit e3df41c

File tree

1 file changed

+75
-0
lines changed

1 file changed

+75
-0
lines changed

main/streams/streams.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,81 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
15551555
return SUCCESS;
15561556
}
15571557

1558+
#ifdef __linux__
1559+
if (php_stream_is(src, PHP_STREAM_IS_STDIO) &&
1560+
php_stream_is(dest, PHP_STREAM_IS_STDIO) &&
1561+
src->writepos == src->readpos &&
1562+
php_stream_can_cast(src, PHP_STREAM_AS_FD) == SUCCESS &&
1563+
php_stream_can_cast(dest, PHP_STREAM_AS_FD) == SUCCESS) {
1564+
/* both php_stream instances are backed by a file
1565+
descriptor, are not filtered and the read buffer is
1566+
empty: we can use copy_file_range() */
1567+
1568+
int src_fd, dest_fd;
1569+
1570+
php_stream_cast(src, PHP_STREAM_AS_FD, (void*)&src_fd, 0);
1571+
php_stream_cast(dest, PHP_STREAM_AS_FD, (void*)&dest_fd, 0);
1572+
1573+
/* copy_file_range() is a Linux-specific system call
1574+
which allows efficient copying between two file
1575+
descriptors, eliminating the need to transfer data
1576+
from the kernel to userspace and back. For
1577+
networking file systems like NFS and Ceph, it even
1578+
eliminates copying data to the client, and local
1579+
filesystems like Btrfs and XFS can create shared
1580+
extents. */
1581+
1582+
ssize_t cfr = copy_file_range(src_fd, NULL,
1583+
dest_fd, NULL,
1584+
maxlen, 0);
1585+
if (cfr > 0) {
1586+
size_t nbytes = (size_t)cfr;
1587+
haveread += nbytes;
1588+
1589+
src->position += nbytes;
1590+
dest->position += nbytes;
1591+
1592+
if ((maxlen != PHP_STREAM_COPY_ALL && nbytes == maxlen) ||
1593+
php_stream_eof(src)) {
1594+
/* the whole request was satisfied or
1595+
end-of-file reached - done */
1596+
*len = haveread;
1597+
return SUCCESS;
1598+
}
1599+
1600+
/* there may be more data; continue copying
1601+
using the fallback code below */
1602+
} else if (cfr == 0) {
1603+
/* end of file */
1604+
*len = haveread;
1605+
return SUCCESS;
1606+
} else if (cfr < 0) {
1607+
switch (errno) {
1608+
case EINVAL:
1609+
/* some formal error, e.g. overlapping
1610+
file ranges */
1611+
break;
1612+
1613+
case EXDEV:
1614+
/* pre Linux 5.3 error */
1615+
break;
1616+
1617+
case ENOSYS:
1618+
/* not implemented by this Linux kernel */
1619+
break;
1620+
1621+
default:
1622+
/* unexpected I/O error - give up, no
1623+
fallback */
1624+
*len = haveread;
1625+
return FAILURE;
1626+
}
1627+
1628+
/* fall back to classic copying */
1629+
}
1630+
}
1631+
#endif
1632+
15581633
if (maxlen == PHP_STREAM_COPY_ALL) {
15591634
maxlen = 0;
15601635
}

0 commit comments

Comments
 (0)