summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2017-07-25 15:50:24 -0700
committerSimon Hausmann <simon.hausmann@qt.io>2017-08-22 06:56:25 +0000
commit9c112a37ba80537a287b5cf441f2f22ec7feffbf (patch)
tree6a5499ec923d8c5bf03c3be80b56d8667075880f /src
parent974b3adf8a099ca95fc2eabfc434038ce73f62c8 (diff)
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 <simon.hausmann@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/io/qfilesystemengine_unix.cpp45
1 files 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<qint64>(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;