QFileSystemEngine/Unix: use copy_file_range(2) in cloneFile()

For remote filesystems on Linux, this can invoke a server-side copy to
avoid network traffic. For local filesystems, Linux can use an optimized
FS-specific call (which may be the same operation as the FICLONE, but
I'm not sure) or splice the data on its own.

Tested with:
 - FreeBSD ufs
 - FreeBSD ufs-to-tmpfs
 - FreeBSD tmpfs (tested ENOSPC with it)
 - Linux btrfs
 - Linux ext4
 - Linux tmpfs (tested ENOSPC with it)

Change-Id: I3ae11d555882bdbb0487fffd81cb5568171cee3f
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
This commit is contained in:
Thiago Macieira 2025-01-19 11:47:34 -08:00
parent fe75526542
commit 9b006eb91a
4 changed files with 58 additions and 12 deletions

View File

@ -143,6 +143,19 @@ int pipes[2];
}
")
# copy_file_range
qt_config_compile_test(copy_file_range
LABEL "copy_file_range()"
CODE
"#include <unistd.h>
int main()
{
off_t off_in = 0, off_out = 1024;
return copy_file_range(0, &off_in, 1, &off_out, 2147483647, 0) != 0;
}
")
# Check if __cxa_thread_atexit{,_impl} are present in the C library (hence why
# PROJECT_PATH instead of CODE for C++). Either one suffices to disable
# FEATURE_broken_threadlocal_dtors. See details in qthread_unix.cpp.
@ -615,6 +628,11 @@ qt_feature("close_range" PRIVATE
CONDITION QT_FEATURE_process AND TEST_close_range
AUTODETECT UNIX
)
qt_feature("copy_file_range" PRIVATE
LABEL "copy_file_range()"
CONDITION QT_FEATURE_process AND TEST_copy_file_range
AUTODETECT UNIX AND NOT DARWIN
)
qt_feature("doubleconversion" PRIVATE
LABEL "DoubleConversion"
)

View File

@ -41,6 +41,7 @@ QT_BEGIN_NAMESPACE
* - accept4 2.6.28
* - renameat2 3.16 QT_CONFIG(renameat2)
* - getrandom 3.17 QT_CONFIG(getentropy)
* - copy_file_range 4.5 QT_CONFIG(copy_file_range)
* - statx 4.11 STATX_BASIC_STATS
*/
@ -50,6 +51,10 @@ QT_BEGIN_NAMESPACE
# define QT_ELF_NOTE_OS_MAJOR 4
# define QT_ELF_NOTE_OS_MINOR 11
# define QT_ELF_NOTE_OS_PATCH 0
#elif QT_CONFIG(copy_file_range)
# define QT_ELF_NOTE_OS_MAJOR 4
# define QT_ELF_NOTE_OS_MINOR 5
# define QT_ELF_NOTE_OS_PATCH 0
#elif QT_CONFIG(getentropy)
# define QT_ELF_NOTE_OS_MAJOR 3
# define QT_ELF_NOTE_OS_MINOR 17
@ -59,7 +64,6 @@ QT_BEGIN_NAMESPACE
# define QT_ELF_NOTE_OS_MINOR 16
# define QT_ELF_NOTE_OS_PATCH 0
#else
# define QT_ELF_NOTE_OS_MAJOR 2
# define QT_ELF_NOTE_OS_MINOR 6
# define QT_ELF_NOTE_OS_PATCH 28

View File

@ -47,6 +47,7 @@
#define QT_FEATURE_cborstreamwriter 1
#define QT_FEATURE_commandlineparser 1
#define QT_NO_COMPRESS
#define QT_FEATURE_copy_file_range -1
#define QT_FEATURE_cxx17_filesystem -1
#define QT_NO_DATASTREAM
#define QT_FEATURE_datestring 1

View File

@ -1096,8 +1096,41 @@ auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat
// first, try FICLONE (only works on regular files and only on certain fs)
if (::ioctl(dstfd, FICLONE, srcfd) == 0)
return TriStateResult::Success;
#elif defined(Q_OS_DARWIN)
// try fcopyfile
if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
return TriStateResult::Success;
switch (errno) {
case ENOTSUP:
case ENOMEM:
return TriStateResult::NotSupported; // let QFile try
}
return TriStateResult::Failed;
#endif
// Second, try sendfile (it can send to some special types too).
#if QT_CONFIG(copy_file_range)
// Second, try copy_file_range. Tested on Linux & FreeBSD: FreeBSD can copy
// across mountpoints, Linux currently (6.12) can only if the source and
// destination mountpoints are the same filesystem type.
QT_OFF_T srcoffset = 0;
QT_OFF_T dstoffset = 0;
ssize_t copied;
do {
copied = ::copy_file_range(srcfd, &srcoffset, dstfd, &dstoffset, SSIZE_MAX, 0);
} while (copied > 0 || (copied < 0 && errno == EINTR));
if (copied == 0)
return TriStateResult::Success; // EOF -> success
if (srcoffset) {
// some bytes were copied, so this is a real error (like ENOSPC).
copied = ftruncate(dstfd, 0);
return TriStateResult::Failed;
}
if (errno != EXDEV)
return TriStateResult::Failed;
#endif
#if defined(Q_OS_LINUX)
// For Linux, try sendfile (it can send to some special types too).
// sendfile(2) is limited in the kernel to 2G - 4k
const size_t SendfileSize = 0x7ffff000;
@ -1125,16 +1158,6 @@ auto QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat
}
return TriStateResult::Success;
#elif defined(Q_OS_DARWIN)
// try fcopyfile
if (fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0)
return TriStateResult::Success;
switch (errno) {
case ENOTSUP:
case ENOMEM:
return TriStateResult::NotSupported; // let QFile try
}
return TriStateResult::Failed;
#else
Q_UNUSED(dstfd);
return TriStateResult::NotSupported;