diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2017-07-25 15:50:24 -0700 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2017-08-22 06:56:25 +0000 |
commit | 9c112a37ba80537a287b5cf441f2f22ec7feffbf (patch) | |
tree | 6a5499ec923d8c5bf03c3be80b56d8667075880f /src/corelib | |
parent | 974b3adf8a099ca95fc2eabfc434038ce73f62c8 (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/corelib')
-rw-r--r-- | src/corelib/io/qfilesystemengine_unix.cpp | 45 |
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; |