From d3dc0f21225845f404262ea563870044cbbbe53f Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 9 Jan 2013 09:11:17 +0100 Subject: 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 Reviewed-by: Thiago Macieira --- src/corelib/io/qfile.cpp | 15 ++++++- src/corelib/io/qfilesystemengine_p.h | 1 + src/corelib/io/qfilesystemengine_unix.cpp | 16 +++++++ src/corelib/io/qfilesystemengine_win.cpp | 75 +++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) (limited to 'src/corelib') 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 #include // for realpath() +#include +#include #include #include #include @@ -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) { 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 @@ -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) { -- cgit v1.2.3