Fix renaming of files that differ only in case.
This currently fails on case-insensitive file systems since the check for existence then triggered and indicated "file already exists". Check on the file id (inode or file id) whether the target file is really a different file for a case-changing rename. Task-number: QTBUG-3570 Change-Id: I1b2d40850692e02142ee23d2c753428de00aedc6 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
cd7ba89a07
commit
d3dc0f2122
@ -48,6 +48,7 @@
|
||||
#include "qfileinfo.h"
|
||||
#include "private/qiodevice_p.h"
|
||||
#include "private/qfile_p.h"
|
||||
#include "private/qfilesystemengine_p.h"
|
||||
#include "private/qsystemerror_p.h"
|
||||
#if defined(QT_BUILD_CORE_LIB)
|
||||
# include "qcoreapplication.h"
|
||||
@ -548,7 +549,19 @@ QFile::rename(const QString &newName)
|
||||
qWarning("QFile::rename: Empty or null file name");
|
||||
return false;
|
||||
}
|
||||
if (QFile(newName).exists()) {
|
||||
if (d->fileName == newName) {
|
||||
d->setError(QFile::RenameError, tr("Destination file is the same file."));
|
||||
return false;
|
||||
}
|
||||
if (!exists()) {
|
||||
d->setError(QFile::RenameError, tr("Source file does not exist."));
|
||||
return false;
|
||||
}
|
||||
// If the file exists and it is a case-changing rename ("foo" -> "Foo"),
|
||||
// compare Ids to make sure it really is a different file.
|
||||
if (QFile::exists(newName)
|
||||
&& (d->fileName.compare(newName, Qt::CaseInsensitive)
|
||||
|| QFileSystemEngine::id(QFileSystemEntry(d->fileName)) != QFileSystemEngine::id(QFileSystemEntry(newName)))) {
|
||||
// ### Race condition. If a file is moved in after this, it /will/ be
|
||||
// overwritten. On Unix, the proper solution is to use hardlinks:
|
||||
// return ::link(old, new) && ::remove(old);
|
||||
|
@ -75,6 +75,7 @@ public:
|
||||
static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data);
|
||||
static QFileSystemEntry canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data);
|
||||
static QFileSystemEntry absoluteName(const QFileSystemEntry &entry);
|
||||
static QByteArray id(const QFileSystemEntry &entry);
|
||||
static QString resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &data);
|
||||
static QString resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &data);
|
||||
|
||||
|
@ -46,6 +46,8 @@
|
||||
#include <QtCore/qvarlengtharray.h>
|
||||
|
||||
#include <stdlib.h> // for realpath()
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
@ -258,6 +260,20 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
|
||||
return QFileSystemEntry(stringVersion);
|
||||
}
|
||||
|
||||
//static
|
||||
QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
|
||||
{
|
||||
struct stat statResult;
|
||||
if (stat(entry.nativeFilePath().constData(), &statResult)) {
|
||||
qErrnoWarning("stat() failed for '%s'", entry.nativeFilePath().constData());
|
||||
return QByteArray();
|
||||
}
|
||||
QByteArray result = QByteArray::number(quint64(statResult.st_dev), 16);
|
||||
result += ':';
|
||||
result += QByteArray::number(quint64(statResult.st_ino), 16);
|
||||
return result;
|
||||
}
|
||||
|
||||
//static
|
||||
QString QFileSystemEngine::resolveUserName(uint userId)
|
||||
{
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "qfilesystemengine_p.h"
|
||||
|
||||
#include "qplatformdefs.h"
|
||||
#include "qsysinfo.h"
|
||||
#include "private/qabstractfileengine_p.h"
|
||||
#include "private/qfsfileengine_p.h"
|
||||
#include <private/qsystemlibrary_p.h>
|
||||
@ -563,6 +564,80 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry)
|
||||
return QFileSystemEntry(ret, QFileSystemEntry::FromInternalPath());
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WINCE
|
||||
|
||||
// FILE_INFO_BY_HANDLE_CLASS has been extended by FileIdInfo = 18 as of VS2012.
|
||||
typedef enum { Q_FileIdInfo = 18 } Q_FILE_INFO_BY_HANDLE_CLASS;
|
||||
|
||||
# if defined(Q_CC_MINGW) || (defined(Q_CC_MSVC) && _MSC_VER < 1700)
|
||||
|
||||
typedef struct _FILE_ID_128 {
|
||||
BYTE Identifier[16];
|
||||
} FILE_ID_128, *PFILE_ID_128;
|
||||
|
||||
typedef struct _FILE_ID_INFO {
|
||||
ULONGLONG VolumeSerialNumber;
|
||||
FILE_ID_128 FileId;
|
||||
} FILE_ID_INFO, *PFILE_ID_INFO;
|
||||
# endif // if defined (Q_CC_MINGW) || (defined(Q_CC_MSVC) && _MSC_VER < 1700))
|
||||
|
||||
// File ID for Windows up to version 7.
|
||||
static inline QByteArray fileId(HANDLE handle)
|
||||
{
|
||||
QByteArray result;
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
if (GetFileInformationByHandle(handle, &info)) {
|
||||
result = QByteArray::number(uint(info.nFileIndexLow), 16);
|
||||
result += ':';
|
||||
result += QByteArray::number(uint(info.nFileIndexHigh), 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// File ID for Windows starting from version 8.
|
||||
QByteArray fileIdWin8(HANDLE handle)
|
||||
{
|
||||
typedef BOOL (WINAPI* GetFileInformationByHandleExType)(HANDLE, Q_FILE_INFO_BY_HANDLE_CLASS, void *, DWORD);
|
||||
|
||||
// Dynamically resolve GetFileInformationByHandleEx (Vista onwards).
|
||||
static GetFileInformationByHandleExType getFileInformationByHandleEx = 0;
|
||||
if (!getFileInformationByHandleEx) {
|
||||
QSystemLibrary library(QLatin1String("kernel32"));
|
||||
getFileInformationByHandleEx = (GetFileInformationByHandleExType)library.resolve("GetFileInformationByHandleEx");
|
||||
}
|
||||
QByteArray result;
|
||||
if (getFileInformationByHandleEx) {
|
||||
FILE_ID_INFO infoEx;
|
||||
if (getFileInformationByHandleEx(handle, Q_FileIdInfo,
|
||||
&infoEx, sizeof(FILE_ID_INFO))) {
|
||||
result = QByteArray::number(infoEx.VolumeSerialNumber, 16);
|
||||
result += ':';
|
||||
result += QByteArray((char *)infoEx.FileId.Identifier, sizeof(infoEx.FileId.Identifier)).toHex();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif // !Q_OS_WINCE
|
||||
|
||||
//static
|
||||
QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry)
|
||||
{
|
||||
#ifndef Q_OS_WINCE
|
||||
QByteArray result;
|
||||
const HANDLE handle =
|
||||
CreateFile((wchar_t*)entry.nativeFilePath().utf16(), GENERIC_READ,
|
||||
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (handle) {
|
||||
result = QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS8 ?
|
||||
fileIdWin8(handle) : fileId(handle);
|
||||
CloseHandle(handle);
|
||||
}
|
||||
return result;
|
||||
#else // !Q_OS_WINCE
|
||||
return entry.nativeFilePath().toLower().toLatin1();
|
||||
#endif
|
||||
}
|
||||
|
||||
//static
|
||||
QString QFileSystemEngine::owner(const QFileSystemEntry &entry, QAbstractFileEngine::FileOwner own)
|
||||
{
|
||||
|
@ -51,6 +51,7 @@
|
||||
|
||||
#include <private/qabstractfileengine_p.h>
|
||||
#include <private/qfsfileengine_p.h>
|
||||
#include <private/qfilesystemengine_p.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -2418,6 +2419,7 @@ void tst_QFile::rename_data()
|
||||
#endif
|
||||
QTest::newRow("renamefile -> renamedfile") << QString::fromLatin1(renameSourceFile) << QString("renamedfile") << true;
|
||||
QTest::newRow("renamefile -> ..") << QString::fromLatin1(renameSourceFile) << QString("..") << false;
|
||||
QTest::newRow("renamefile -> rEnAmEfIlE") << QString::fromLatin1(renameSourceFile) << QStringLiteral("rEnAmEfIlE") << true;
|
||||
}
|
||||
|
||||
void tst_QFile::rename()
|
||||
@ -2435,7 +2437,8 @@ void tst_QFile::rename()
|
||||
}
|
||||
#endif
|
||||
|
||||
QFile sourceFile(QString::fromLatin1(renameSourceFile));
|
||||
const QString sourceFileName = QString::fromLatin1(renameSourceFile);
|
||||
QFile sourceFile(sourceFileName);
|
||||
QVERIFY2(sourceFile.open(QFile::WriteOnly | QFile::Text), qPrintable(sourceFile.errorString()));
|
||||
QVERIFY2(sourceFile.write(content), qPrintable(sourceFile.errorString()));
|
||||
sourceFile.close();
|
||||
@ -2445,7 +2448,10 @@ void tst_QFile::rename()
|
||||
if (result) {
|
||||
QVERIFY2(success, qPrintable(file.errorString()));
|
||||
QCOMPARE(file.error(), QFile::NoError);
|
||||
QVERIFY(!sourceFile.exists());
|
||||
// This will report the source file still existing for a rename changing the case
|
||||
// on Windows, Mac.
|
||||
if (sourceFileName.compare(destination, Qt::CaseInsensitive))
|
||||
QVERIFY(!sourceFile.exists());
|
||||
QFile destinationFile(destination);
|
||||
QVERIFY2(destinationFile.open(QFile::ReadOnly | QFile::Text), qPrintable(destinationFile.errorString()));
|
||||
QCOMPARE(destinationFile.readAll(), content);
|
||||
|
Loading…
x
Reference in New Issue
Block a user