QFileSystemEngine::renameFile: use calls that don't overwrite

The renameat2(2) Linux system call, new in 3.16, allows for the atomic
renaming of a file if and only if it won't clobber an existing
file. None of the Linux libcs have enabled this syscall as an API, so we
use syscall(3) to place the call.

If your libc has SYS_renameat2 but your kernel doesn't support it, we'll
keep issuing the unknown syscall, every time. Users in that situation
should upgrade (3.16 is from 2014).

On Darwin, there's a similar renameatx_np (guessing "np" stands for
"non-portable"). I haven't found anything similar on the other BSDs.

Change-Id: I1eba2b016de74620bfc8fffd14ccb4e455a3ec9e
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Thiago Macieira 2017-06-29 14:27:48 -07:00
parent 4934138be2
commit 88c30618d5
4 changed files with 57 additions and 1 deletions

View File

@ -379,6 +379,15 @@
]
}
},
"renameat2": {
"label": "renameat2()",
"type": "compile",
"test": {
"head": "#define _ATFILE_SOURCE 1",
"include": [ "fcntl.h", "stdio.h" ],
"main": "renameat2(AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_NOREPLACE | RENAME_WHITEOUT);"
}
},
"syslog": {
"label": "syslog",
"type": "compile",
@ -576,6 +585,11 @@
"condition": "features.statemachine",
"output": [ "publicFeature" ]
},
"renameat2": {
"label": "renameat2()",
"condition": "config.linux && tests.renameat2",
"output": [ "privateFeature" ]
},
"slog2": {
"label": "slog2",
"condition": "libs.slog2",

View File

@ -86,6 +86,7 @@
* - eventfd 2.6.23
* - pipe2 & dup3 2.6.27
* - accept4 2.6.28
* - renameat2 3.16 QT_CONFIG(renameat2)
* - getrandom 3.17 QT_CONFIG(getentropy)
*/
@ -93,6 +94,10 @@
.long 3
.long 17
.long 0
#elif QT_CONFIG(renameat2)
.long 3
.long 16
.long 0
#else
.long 2
.long 6

View File

@ -88,6 +88,7 @@
#define QT_NO_QOBJECT
#define QT_FEATURE_process -1
#define QT_NO_SYSTEMLOCALE
#define QT_FEATURE_renameat2 -1
#define QT_FEATURE_slog2 -1
#define QT_FEATURE_syslog -1
#define QT_FEATURE_temporaryfile 1

View File

@ -54,7 +54,6 @@
#include <stdio.h>
#include <errno.h>
#if defined(Q_OS_MAC)
# include <QtCore/private/qcore_mac_p.h>
# include <CoreFoundation/CFBundle.h>
@ -78,6 +77,16 @@ Q_FORWARD_DECLARE_OBJC_CLASS(NSString);
extern "C" NSString *NSTemporaryDirectory();
#endif
#if defined(Q_OS_LINUX)
# include <sys/syscall.h>
# include <linux/fs.h>
# if !QT_CONFIG(renameat2) && defined(SYS_renameat2)
static int renameat2(int oldfd, const char *oldpath, int newfd, const char *newpath, unsigned flags)
{ return syscall(SYS_renameat2, oldfd, oldpath, newfd, newpath, flags); }
# endif
#endif
QT_BEGIN_NAMESPACE
#if defined(Q_OS_DARWIN)
@ -653,6 +662,33 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy
{
QFileSystemEntry::NativePath srcPath = source.nativeFilePath();
QFileSystemEntry::NativePath tgtPath = target.nativeFilePath();
#if defined(RENAME_NOREPLACE) && (QT_CONFIG(renameat2) || defined(SYS_renameat2))
if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0)
return true;
// If we're using syscall(), check for ENOSYS;
// if renameat2 came from libc, we don't accept ENOSYS.
if (QT_CONFIG(renameat2) || errno != ENOSYS) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
#endif
#if defined(Q_OS_DARWIN) && defined(RENAME_EXCL)
const auto current = QOperatingSystemVersion::current();
if (current >= QOperatingSystemVersion::MacOSSierra ||
current >= QOperatingSystemVersion(QOperatingSystemVersion::IOS, 10) ||
current >= QOperatingSystemVersion(QOperatingSystemVersion::TvOS, 10) ||
current >= QOperatingSystemVersion(QOperatingSystemVersion::WatchOS, 3)) {
if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0)
return true;
if (errno != ENOTSUP) {
error = QSystemError(errno, QSystemError::StandardLibraryError);
return false;
}
}
#endif
if (::link(srcPath, tgtPath) == 0) {
if (::unlink(srcPath) == 0)
return true;