Skip to content

Commit a12d33e

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 a12d33e

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ PHP_CHECK_FUNC(socketpair, socket, network)
364364
PHP_CHECK_FUNC(htonl, socket, network)
365365
PHP_CHECK_FUNC(gethostname, nsl, network)
366366
PHP_CHECK_FUNC(gethostbyaddr, nsl, network)
367+
PHP_CHECK_FUNC(copy_file_range)
367368
PHP_CHECK_FUNC(dlopen, dl, root)
368369
PHP_CHECK_FUNC(dlsym, dl, root)
369370
if test "$ac_cv_func_dlopen" = "yes"; then

main/streams/streams.c

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

1558+
#ifdef HAVE_COPY_FILE_RANGE
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+
/* clamp to INT_MAX to avoid EOVERFLOW */
1574+
const size_t cfr_max = MIN(maxlen, (size_t)SSIZE_MAX);
1575+
1576+
/* copy_file_range() is a Linux-specific system call
1577+
which allows efficient copying between two file
1578+
descriptors, eliminating the need to transfer data
1579+
from the kernel to userspace and back. For
1580+
networking file systems like NFS and Ceph, it even
1581+
eliminates copying data to the client, and local
1582+
filesystems like Btrfs and XFS can create shared
1583+
extents. */
1584+
1585+
ssize_t result = copy_file_range(src_fd, NULL,
1586+
dest_fd, NULL,
1587+
cfr_max, 0);
1588+
if (result > 0) {
1589+
size_t nbytes = (size_t)result;
1590+
haveread += nbytes;
1591+
1592+
src->position += nbytes;
1593+
dest->position += nbytes;
1594+
1595+
if ((maxlen != PHP_STREAM_COPY_ALL && nbytes == maxlen) ||
1596+
php_stream_eof(src)) {
1597+
/* the whole request was satisfied or
1598+
end-of-file reached - done */
1599+
*len = haveread;
1600+
return SUCCESS;
1601+
}
1602+
1603+
/* there may be more data; continue copying
1604+
using the fallback code below */
1605+
} else if (result == 0) {
1606+
/* end of file */
1607+
*len = haveread;
1608+
return SUCCESS;
1609+
} else if (result < 0) {
1610+
switch (errno) {
1611+
case EINVAL:
1612+
/* some formal error, e.g. overlapping
1613+
file ranges */
1614+
break;
1615+
1616+
case EXDEV:
1617+
/* pre Linux 5.3 error */
1618+
break;
1619+
1620+
case ENOSYS:
1621+
/* not implemented by this Linux kernel */
1622+
break;
1623+
1624+
default:
1625+
/* unexpected I/O error - give up, no
1626+
fallback */
1627+
*len = haveread;
1628+
return FAILURE;
1629+
}
1630+
1631+
/* fall back to classic copying */
1632+
}
1633+
}
1634+
#endif
1635+
15581636
if (maxlen == PHP_STREAM_COPY_ALL) {
15591637
maxlen = 0;
15601638
}

0 commit comments

Comments
 (0)