From 9c112a37ba80537a287b5cf441f2f22ec7feffbf Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 25 Jul 2017 15:50:24 -0700 Subject: [PATCH] QFileSystemEngine::cloneFile: expand the Linux cloning process FICLONE only works on (currently) btrfs and xfs, and then only if it's the same mount, so it's of very limited use. There are a couple other techniques we may try that do not involve I/O through user-space, though not immediate: - sendfile(2) can be used on regular files, even across mountpoints - sendfile(2) can be used on block devices too, but we need to get the device's size first - splice(2) can be used on pipes (FIFOs) We only implement the first technique (earlier iterations of this patch implemented all). Change-Id: I81480fdb578d4d43b3fcfffd14d4b47cd70495a3 Reviewed-by: Simon Hausmann --- src/corelib/io/qfilesystemengine_unix.cpp | 45 +++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 806926d674..3f2bc04b9c 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -1084,13 +1084,52 @@ bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat } else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) && knownData.isDirectory()) { return false; // fcopyfile(3) returns success on directories - } else if (QT_FSTAT(srcfd, &statBuffer) == -1 || S_ISDIR(statBuffer.st_mode)) { + } else if (QT_FSTAT(srcfd, &statBuffer) == -1) { + return false; + } else if (!S_ISREG((statBuffer.st_mode))) { + // not a regular file, let QFile do the copy return false; } #if defined(Q_OS_LINUX) - // try FICLONE (only works on regular files and only on certain fs) - return ::ioctl(dstfd, FICLONE, srcfd) == 0; + if (statBuffer.st_size == 0) { + // empty file? we're done. + return true; + } + + // first, try FICLONE (only works on regular files and only on certain fs) + if (::ioctl(dstfd, FICLONE, srcfd) == 0) + return true; + + // Second, try sendfile (it can send to some special types too). + // sendfile(2) is limited in the kernel to 2G - 4k + auto sendfileSize = [](QT_OFF_T size) { return size_t(qMin(0x7ffff000, size)); }; + + ssize_t n = ::sendfile(dstfd, srcfd, NULL, sendfileSize(statBuffer.st_size)); + if (n == -1) { + // if we got an error here, give up and try at an upper layer + return false; + } + + statBuffer.st_size -= n; + while (statBuffer.st_size) { + n = ::sendfile(dstfd, srcfd, NULL, sendfileSize(statBuffer.st_size)); + if (n == 0) { + // uh oh, this is probably a real error (like ENOSPC), but we have + // no way to notify QFile of partial success, so just erase any work + // done (hopefully we won't get any errors, because there's nothing + // we can do about them) + n = ftruncate(dstfd, 0); + n = lseek(srcfd, 0, SEEK_SET); + n = lseek(dstfd, 0, SEEK_SET); + return false; + } + if (n == 0) + return true; + statBuffer.st_size -= n; + } + + return true; #elif defined(Q_OS_DARWIN) // try fcopyfile return fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0;