diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 6b07edaf62b..63fc456c8ed 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -8,9 +8,20 @@ #include "hostosinfo.h" #include +#include #include #include +#ifdef Q_OS_WIN +#ifdef QTCREATOR_PCH_H +#define CALLBACK WINAPI +#endif +#include +#include +#else +#include +#endif + namespace Utils { // DeviceFileAccess @@ -220,6 +231,13 @@ qint64 DeviceFileAccess::bytesAvailable(const FilePath &filePath) const return -1; } +QByteArray DeviceFileAccess::fileId(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + QTC_CHECK(false); + return {}; +} + void DeviceFileAccess::asyncFileContents( const FilePath &filePath, const Continuation> &cont, @@ -509,6 +527,74 @@ qint64 DesktopDeviceFileAccess::bytesAvailable(const FilePath &filePath) const return QStorageInfo(filePath.path()).bytesAvailable(); } +// Copied from qfilesystemengine_win.cpp +#ifdef Q_OS_WIN + +// File ID for Windows up to version 7. +static inline QByteArray fileIdWin7(HANDLE handle) +{ + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(handle, &info)) { + char buffer[sizeof "01234567:0123456701234567\0"]; + qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", + info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + return QByteArray(buffer); + } + return QByteArray(); +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx; + if (GetFileInformationByHandleEx(handle, + static_cast(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { + result = QByteArray::number(infoEx.VolumeSerialNumber, 16); + result += ':'; + // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. + result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); + } + return result; +} + +static QByteArray fileIdWin(HANDLE fHandle) +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? + fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); +} +#endif + +QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const +{ + QByteArray result; + +#ifdef Q_OS_WIN + const HANDLE handle = + CreateFile((wchar_t*)filePath.toUserOutput().utf16(), 0, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + CloseHandle(handle); + } +#else // Copied from qfilesystemengine_unix.cpp + if (Q_UNLIKELY(filePath.isEmpty())) + return result; + + QT_STATBUF statResult; + if (QT_STAT(filePath.toString().toLocal8Bit().constData(), &statResult)) + return result; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); +#endif + return result; +} + OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const { Q_UNUSED(filePath); @@ -714,6 +800,15 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const return FileUtils::bytesAvailableFromDFOutput(result.stdOut); } +QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const +{ + const RunResult result = runInShell("stat", {"-L", "-c", "%D:%i", filePath.path()}); + if (result.exitCode != 0) + return {}; + + return result.stdOut; +} + FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const { const RunResult stat = runInShell("stat", {"-L", "-c", "%f %Y %s", filePath.path()}); diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index e1ef5db1ddb..f638d162606 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -44,6 +44,7 @@ protected: virtual bool setPermissions(const FilePath &filePath, QFile::Permissions) const; virtual qint64 fileSize(const FilePath &filePath) const; virtual qint64 bytesAvailable(const FilePath &filePath) const; + virtual QByteArray fileId(const FilePath &filePath) const; virtual void iterateDirectory( const FilePath &filePath, @@ -109,6 +110,7 @@ protected: bool setPermissions(const FilePath &filePath, QFile::Permissions) const override; qint64 fileSize(const FilePath &filePath) const override; qint64 bytesAvailable(const FilePath &filePath) const override; + QByteArray fileId(const FilePath &filePath) const override; void iterateDirectory( const FilePath &filePath, @@ -163,6 +165,7 @@ protected: bool setPermissions(const FilePath &filePath, QFile::Permissions) const override; qint64 fileSize(const FilePath &filePath) const override; qint64 bytesAvailable(const FilePath &filePath) const override; + QByteArray fileId(const FilePath &filePath) const override; void iterateDirectory( const FilePath &filePath, diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 57b3b197244..62dc52b2605 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -175,13 +175,7 @@ bool Environment::isSameExecutable(const QString &exe1, const QString &exe2) con for (const QString &i2 : exe2List) { const FilePath f1 = FilePath::fromString(i1); const FilePath f2 = FilePath::fromString(i2); - if (f1 == f2) - return true; - if (f1.needsDevice() != f2.needsDevice() || f1.scheme() != f2.scheme()) - return false; - if (f1.resolveSymlinks() == f2.resolveSymlinks()) - return true; - if (FileUtils::fileId(f1) == FileUtils::fileId(f2)) + if (f1.isSameFile(f2)) return true; } } diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 645ded358e5..057ed05847b 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -515,6 +515,25 @@ bool FilePath::isSameDevice(const FilePath &other) const return s_deviceHooks.isSameDevice(*this, other); } +bool FilePath::isSameFile(const FilePath &other) const +{ + if (*this == other) + return true; + + if (!isSameDevice(other)) + return false; + + const QByteArray fileId = fileAccess()->fileId(*this); + const QByteArray otherFileId = fileAccess()->fileId(other); + if (fileId.isEmpty() || otherFileId.isEmpty()) + return false; + + if (fileId == otherFileId) + return true; + + return false; +} + /// \returns an empty FilePath if this is not a symbolic linl FilePath FilePath::symLinkTarget() const { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 0cc9455eef7..97e5752ccf8 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -207,6 +207,7 @@ public: bool needsDevice() const; bool isSameDevice(const FilePath &other) const; + bool isSameFile(const FilePath &other) const; [[nodiscard]] QFileInfo toFileInfo() const; [[nodiscard]] static FilePath fromFileInfo(const QFileInfo &info); diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 989f6a3fcc9..ce5ac3a2497 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,7 +5,6 @@ #include "savefile.h" #include "algorithm.h" -#include "commandline.h" #include "qtcassert.h" #include "hostosinfo.h" @@ -15,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +23,6 @@ #ifdef QT_GUI_LIB #include -#include #include #endif @@ -37,7 +34,7 @@ #include #endif -#ifdef Q_OS_OSX +#ifdef Q_OS_MACOS #include "fileutils_mac.h" #endif @@ -337,47 +334,6 @@ FilePaths FileUtils::CopyAskingForOverwrite::files() const } #endif // QT_GUI_LIB -// Copied from qfilesystemengine_win.cpp -#ifdef Q_OS_WIN - -// File ID for Windows up to version 7. -static inline QByteArray fileIdWin7(HANDLE handle) -{ - BY_HANDLE_FILE_INFORMATION info; - if (GetFileInformationByHandle(handle, &info)) { - char buffer[sizeof "01234567:0123456701234567\0"]; - qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", - info.dwVolumeSerialNumber, - info.nFileIndexHigh, - info.nFileIndexLow); - return QByteArray(buffer); - } - return QByteArray(); -} - -// File ID for Windows starting from version 8. -static QByteArray fileIdWin8(HANDLE handle) -{ - QByteArray result; - FILE_ID_INFO infoEx; - if (GetFileInformationByHandleEx(handle, - static_cast(18), // FileIdInfo in Windows 8 - &infoEx, sizeof(FILE_ID_INFO))) { - result = QByteArray::number(infoEx.VolumeSerialNumber, 16); - result += ':'; - // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. - result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); - } - return result; -} - -static QByteArray fileIdWin(HANDLE fHandle) -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? - fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); -} -#endif - FilePath FileUtils::commonPath(const FilePaths &paths) { if (paths.isEmpty()) @@ -424,33 +380,6 @@ FilePath FileUtils::commonPath(const FilePaths &paths) return result; } -QByteArray FileUtils::fileId(const FilePath &fileName) -{ - QByteArray result; - -#ifdef Q_OS_WIN - const HANDLE handle = - CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (handle != INVALID_HANDLE_VALUE) { - result = fileIdWin(handle); - CloseHandle(handle); - } -#else // Copied from qfilesystemengine_unix.cpp - if (Q_UNLIKELY(fileName.isEmpty())) - return result; - - QT_STATBUF statResult; - if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) - return result; - result = QByteArray::number(quint64(statResult.st_dev), 16); - result += ':'; - result += QByteArray::number(quint64(statResult.st_ino)); -#endif - return result; -} - #ifdef Q_OS_WIN template <> void withNtfsPermissions(const std::function &task) diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 76ad91efbc8..bbeb906ee34 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -81,7 +81,6 @@ public: static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); } static FilePath commonPath(const FilePath &oldCommonPath, const FilePath &fileName); static FilePath commonPath(const FilePaths &paths); - static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); static FilePaths toFilePathList(const QStringList &paths); diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index 0248fb44891..4d600a68c2f 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -101,6 +101,9 @@ private slots: void cleanPath_data(); void cleanPath(); + void isSameFile_data(); + void isSameFile(); + private: QTemporaryDir tempDir; QString rootPath; @@ -1114,6 +1117,48 @@ void tst_fileutils::cleanPath() QCOMPARE(cleaned, expected); } +void tst_fileutils::isSameFile_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("shouldBeEqual"); + + QTest::addRow("/==/") + << FilePath::fromString("/") << FilePath::fromString("/") << true; + QTest::addRow("/!=tmp") + << FilePath::fromString("/") << FilePath::fromString(tempDir.path()) << false; + + + QDir dir(tempDir.path()); + touch(dir, "target-file", false); + + QFile file(dir.absoluteFilePath("target-file")); + if (file.link(dir.absoluteFilePath("source-file"))) { + QTest::addRow("real==link") + << FilePath::fromString(file.fileName()) + << FilePath::fromString(dir.absoluteFilePath("target-file")) + << true; + } + + QTest::addRow("/!=non-existing") + << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false; + + QTest::addRow("two-devices") << FilePath::fromString( + "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/" + "bin/aarch64-poky-linux/aarch64-poky-linux-g++") + << FilePath::fromString("docker://qt-linux:6/usr/bin/g++") + << false; +} + +void tst_fileutils::isSameFile() +{ + QFETCH(FilePath, left); + QFETCH(FilePath, right); + QFETCH(bool, shouldBeEqual); + + QCOMPARE(left.isSameFile(right), shouldBeEqual); +} + QTEST_GUILESS_MAIN(tst_fileutils) #include "tst_fileutils.moc"