Skip to content

Commit b8d013a

Browse files
committed
Merge branch 'PHP-8.2'
2 parents 365178a + b732d80 commit b8d013a

File tree

3 files changed

+89
-74
lines changed

3 files changed

+89
-74
lines changed

configure.ac

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ if test "$ac_cv_func_getaddrinfo" = yes; then
694694
AC_DEFINE(HAVE_GETADDRINFO,1,[Define if you have the getaddrinfo function])
695695
fi
696696

697+
dnl on FreeBSD, copy_file_range() works only with the undocumented flag 0x01000000;
698+
dnl until the problem is fixed properly, copy_file_range() is used only on Linux
697699
AC_CACHE_CHECK([for copy_file_range], ac_cv_copy_file_range,
698700
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
699701
#ifdef __linux__

ext/standard/tests/file/gh9779.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Bug GH-9779 (stream_copy_to_stream doesn't work anymore with resource opened in a append mode)
3+
--FILE--
4+
<?php
5+
6+
$src = __DIR__.'/gh9779_src.txt';
7+
$dest = __DIR__.'/gh9779_dest.txt';
8+
9+
file_put_contents($src, "bar");
10+
file_put_contents($dest, "foo");
11+
$sourceHandle = fopen($src, "r");
12+
$destHandle = fopen($dest, "a");
13+
stream_copy_to_stream($sourceHandle, $destHandle);
14+
fclose($sourceHandle);
15+
fclose($destHandle);
16+
var_dump(file_get_contents($dest));
17+
?>
18+
--CLEAN--
19+
<?php
20+
$src = __DIR__.'/gh9779_src.txt';
21+
$dest = __DIR__.'/gh9779_dest.txt';
22+
23+
@unlink($src);
24+
@unlink($dest);
25+
?>
26+
--EXPECTF--
27+
string(6) "foobar"

main/streams/streams.c

Lines changed: 60 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,87 +1558,73 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
15581558
}
15591559

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

1631-
default:
1632-
/* unexpected I/O error - give up, no
1633-
fallback */
1634-
*len = haveread;
1635-
return FAILURE;
1624+
/* fall back to classic copying */
16361625
}
1637-
1638-
/* fall back to classic copying */
16391626
}
16401627
}
1641-
#endif // __FreeBSD__
16421628
#endif // HAVE_COPY_FILE_RANGE
16431629

16441630
if (maxlen == PHP_STREAM_COPY_ALL) {

0 commit comments

Comments
 (0)