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>
This commit is contained in:
parent
974b3adf8a
commit
9c112a37ba
@ -1084,13 +1084,52 @@ bool QFileSystemEngine::cloneFile(int srcfd, int dstfd, const QFileSystemMetaDat
|
|||||||
} else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
|
} else if (knownData.hasFlags(QFileSystemMetaData::PosixStatFlags) &&
|
||||||
knownData.isDirectory()) {
|
knownData.isDirectory()) {
|
||||||
return false; // fcopyfile(3) returns success on directories
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX)
|
#if defined(Q_OS_LINUX)
|
||||||
// try FICLONE (only works on regular files and only on certain fs)
|
if (statBuffer.st_size == 0) {
|
||||||
return ::ioctl(dstfd, FICLONE, srcfd) == 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)
|
#elif defined(Q_OS_DARWIN)
|
||||||
// try fcopyfile
|
// try fcopyfile
|
||||||
return fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0;
|
return fcopyfile(srcfd, dstfd, nullptr, COPYFILE_DATA | COPYFILE_STAT) == 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user