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:
Friedemann Kleint 2013-01-09 09:11:17 +01:00 committed by The Qt Project
parent cd7ba89a07
commit d3dc0f2122
5 changed files with 114 additions and 3 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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);