Skip to content

Commit 171ae38

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 1dc831d commit 171ae38

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,90 @@ 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+
1560+
/* TODO: on FreeBSD, copy_file_range() works only with the
1561+
undocumented flag 0x01000000; until the problem is fixed
1562+
properly, copy_file_range() is not used on FreeBSD */
1563+
#ifndef __FreeBSD__
1564+
if (php_stream_is(src, PHP_STREAM_IS_STDIO) &&
1565+
php_stream_is(dest, PHP_STREAM_IS_STDIO) &&
1566+
src->writepos == src->readpos &&
1567+
php_stream_can_cast(src, PHP_STREAM_AS_FD) == SUCCESS &&
1568+
php_stream_can_cast(dest, PHP_STREAM_AS_FD) == SUCCESS) {
1569+
/* both php_stream instances are backed by a file
1570+
descriptor, are not filtered and the read buffer is
1571+
empty: we can use copy_file_range() */
1572+
1573+
int src_fd, dest_fd;
1574+
1575+
php_stream_cast(src, PHP_STREAM_AS_FD, (void*)&src_fd, 0);
1576+
php_stream_cast(dest, PHP_STREAM_AS_FD, (void*)&dest_fd, 0);
1577+
1578+
/* clamp to INT_MAX to avoid EOVERFLOW */
1579+
const size_t cfr_max = MIN(maxlen, (size_t)SSIZE_MAX);
1580+
1581+
/* copy_file_range() is a Linux-specific system call
1582+
which allows efficient copying between two file
1583+
descriptors, eliminating the need to transfer data
1584+
from the kernel to userspace and back. For
1585+
networking file systems like NFS and Ceph, it even
1586+
eliminates copying data to the client, and local
1587+
filesystems like Btrfs and XFS can create shared
1588+
extents. */
1589+
1590+
ssize_t result = copy_file_range(src_fd, NULL,
1591+
dest_fd, NULL,
1592+
cfr_max, 0);
1593+
if (result > 0) {
1594+
size_t nbytes = (size_t)result;
1595+
haveread += nbytes;
1596+
1597+
src->position += nbytes;
1598+
dest->position += nbytes;
1599+
1600+
if ((maxlen != PHP_STREAM_COPY_ALL && nbytes == maxlen) ||
1601+
php_stream_eof(src)) {
1602+
/* the whole request was satisfied or
1603+
end-of-file reached - done */
1604+
*len = haveread;
1605+
return SUCCESS;
1606+
}
1607+
1608+
/* there may be more data; continue copying
1609+
using the fallback code below */
1610+
} else if (result == 0) {
1611+
/* end of file */
1612+
*len = haveread;
1613+
return SUCCESS;
1614+
} else if (result < 0) {
1615+
switch (errno) {
1616+
case EINVAL:
1617+
/* some formal error, e.g. overlapping
1618+
file ranges */
1619+
break;
1620+
1621+
case EXDEV:
1622+
/* pre Linux 5.3 error */
1623+
break;
1624+
1625+
case ENOSYS:
1626+
/* not implemented by this Linux kernel */
1627+
break;
1628+
1629+
default:
1630+
/* unexpected I/O error - give up, no
1631+
fallback */
1632+
*len = haveread;
1633+
return FAILURE;
1634+
}
1635+
1636+
/* fall back to classic copying */
1637+
}
1638+
}
1639+
#endif // __FreeBSD__
1640+
#endif // HAVE_COPY_FILE_RANGE
1641+
15581642
if (maxlen == PHP_STREAM_COPY_ALL) {
15591643
maxlen = 0;
15601644
}

0 commit comments

Comments
 (0)