diff options
author | Friedemann Kleint <Friedemann.Kleint@digia.com> | 2013-01-09 09:11:17 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-01-15 16:36:20 +0100 |
commit | d3dc0f21225845f404262ea563870044cbbbe53f (patch) | |
tree | 595c5553aa6abd5fba4c6c22bb8762c812dafc11 | |
parent | cd7ba89a07f794b17fc66ba29515b104c4d21f27 (diff) |
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>
-rw-r--r-- | src/corelib/io/qfile.cpp | 15 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine_p.h | 1 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine_unix.cpp | 16 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine_win.cpp | 75 | ||||
-rw-r--r-- | tests/auto/corelib/io/qfile/tst_qfile.cpp | 10 |
5 files changed, 114 insertions, 3 deletions
diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp index d9f2c5c605..24f0eba1b7 100644 --- a/src/corelib/io/qfile.cpp +++ b/src/corelib/io/qfile.cpp @@ -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); diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index bb91cf1142..9d03a6b459 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -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); diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index f8cb130997..fa0e07b045 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -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> @@ -259,6 +261,20 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) } //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) { #if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 5364a44f91..cc40d4af3f 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -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) { diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 61a1fea343..f523225ef1 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -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); |