diff options
-rw-r--r-- | src/corelib/io/qfsfileengine.cpp | 2 | ||||
-rw-r--r-- | src/corelib/io/qfsfileengine_win.cpp | 18 | ||||
-rw-r--r-- | tests/auto/qfileinfo/tst_qfileinfo.cpp | 113 | ||||
-rw-r--r-- | tests/benchmarks/corelib/io/qfileinfo/main.cpp | 44 | ||||
-rw-r--r-- | tests/shared/filesystem.h | 58 |
5 files changed, 231 insertions, 4 deletions
diff --git a/src/corelib/io/qfsfileengine.cpp b/src/corelib/io/qfsfileengine.cpp index a1ffb81..3cf08a7 100644 --- a/src/corelib/io/qfsfileengine.cpp +++ b/src/corelib/io/qfsfileengine.cpp @@ -208,6 +208,8 @@ QString QFSFileEnginePrivate::canonicalized(const QString &path) fi.setFile(prefix); if (fi.isSymLink()) { QString target = fi.symLinkTarget(); + if(QFileInfo(target).isRelative()) + target = fi.absolutePath() + slash + target; if (separatorPos != -1) { if (fi.isDir() && !target.endsWith(slash)) target.append(slash); diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index eeca07e..668379f 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -1275,7 +1275,12 @@ static QString readSymLink(const QString &link) REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)qMalloc(bufsize); DWORD retsize = 0; if (::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, 0, 0, rdb, bufsize, &retsize, 0)) { - if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { + int length = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t); + int offset = rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); + const wchar_t* PathBuffer = &rdb->MountPointReparseBuffer.PathBuffer[offset]; + result = QString::fromWCharArray(PathBuffer, length); + } else if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) { int length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t); int offset = rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t); const wchar_t* PathBuffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[offset]; @@ -1287,6 +1292,15 @@ static QString readSymLink(const QString &link) } qFree(rdb); CloseHandle(handle); + + QRegExp matchVolName(QLatin1String("^Volume\\{([a-z]|[0-9]|-)+\\}\\\\"), Qt::CaseInsensitive); + if(matchVolName.indexIn(result) == 0) { + DWORD len; + wchar_t buffer[MAX_PATH]; + QString volumeName = result.mid(0, matchVolName.matchedLength()).prepend(QLatin1String("\\\\?\\")); + if(GetVolumePathNamesForVolumeNameW(volumeName.utf16(), buffer, MAX_PATH, &len) != 0) + result.replace(0,matchVolName.matchedLength(), QString::fromWCharArray(buffer)); + } } #else Q_UNUSED(link); @@ -1538,7 +1552,7 @@ bool QFSFileEnginePrivate::isSymlink() const if (hFind != INVALID_HANDLE_VALUE) { ::FindClose(hFind); if ((findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) - && findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + && (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK || findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) { is_link = true; } } diff --git a/tests/auto/qfileinfo/tst_qfileinfo.cpp b/tests/auto/qfileinfo/tst_qfileinfo.cpp index 403c5f9..d482cbc 100644 --- a/tests/auto/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/qfileinfo/tst_qfileinfo.cpp @@ -56,6 +56,7 @@ #endif #ifdef Q_OS_WIN #include <qt_windows.h> +#include <qlibrary.h> #endif #include <qplatformdefs.h> #include <qdebug.h> @@ -65,6 +66,7 @@ #endif #include "../network-settings.h" #include <private/qfileinfo_p.h> +#include "../../shared/filesystem.h" #if defined(Q_OS_SYMBIAN) # define SRCDIR "" @@ -161,6 +163,8 @@ private slots: void refresh(); #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + void ntfsJunctionPointsAndSymlinks_data(); + void ntfsJunctionPointsAndSymlinks(); void brokenShortcut(); #endif @@ -194,8 +198,15 @@ tst_QFileInfo::~tst_QFileInfo() #if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) QDir().rmdir("./.hidden-directory"); #endif -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) QDir().rmdir("./hidden-directory"); + QDir().rmdir("abs_symlink"); + QDir().rmdir("rel_symlink"); + QDir().rmdir("junction_pwd"); + QDir().rmdir("junction_root"); + QDir().rmdir("mountpoint"); + QFile::remove("abs_symlink.cpp"); + QFile::remove("rel_symlink.cpp"); #endif } @@ -1236,6 +1247,106 @@ void tst_QFileInfo::refresh() } #if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +void tst_QFileInfo::ntfsJunctionPointsAndSymlinks_data() +{ + QTest::addColumn<QString>("path"); + QTest::addColumn<bool>("isSymLink"); + QTest::addColumn<QString>("linkTarget"); + QTest::addColumn<QString>("canonicalFilePath"); + + QDir pwd; + pwd.mkdir("target"); + + QLibrary kernel32("kernel32"); + typedef BOOLEAN (WINAPI *PtrCreateSymbolicLink)(LPCWSTR, LPCWSTR, DWORD); + PtrCreateSymbolicLink createSymbolicLinkW = 0; + createSymbolicLinkW = (PtrCreateSymbolicLink) kernel32.resolve("CreateSymbolicLinkW"); + if (!createSymbolicLinkW) + QSKIP("symbolic links not supported by operating system",SkipSingle); + + { + //Directory symlinks + QDir target("target"); + QVERIFY(target.exists()); + + QString absTarget = QDir::toNativeSeparators(target.absolutePath()); + QString absSymlink = QDir::toNativeSeparators(pwd.absolutePath()).append("\\abs_symlink"); + QString relTarget = "target"; + QString relSymlink = "rel_symlink"; + QString fileInTarget(absTarget); + fileInTarget.append("\\file"); + QString fileInSymlink(absSymlink); + fileInSymlink.append("\\file"); + QFile file(fileInTarget); + file.open(QIODevice::ReadWrite); + file.close(); + + QVERIFY(pwd.exists("abs_symlink") || createSymbolicLinkW(absSymlink.utf16(),absTarget.utf16(),0x1)); + QVERIFY(pwd.exists(relSymlink) || createSymbolicLinkW(relSymlink.utf16(),relTarget.utf16(),0x1)); + QVERIFY(file.exists()); + + QTest::newRow("absolute dir symlink") << absSymlink << true << QDir::fromNativeSeparators(absTarget) << target.canonicalPath(); + QTest::newRow("relative dir symlink") << relSymlink << true << QDir::fromNativeSeparators(relTarget) << target.canonicalPath(); + QTest::newRow("file in symlink dir") << fileInSymlink << false << "" << target.canonicalPath().append("/file"); + } + { + //File symlinks + QFileInfo target(SRCDIR "tst_qfileinfo.cpp"); + QString absTarget = QDir::toNativeSeparators(target.absoluteFilePath()); + QString absSymlink = QDir::toNativeSeparators(pwd.absolutePath()).append("\\abs_symlink.cpp"); + QString relTarget = QDir::toNativeSeparators(pwd.relativeFilePath(target.absoluteFilePath())); + QString relSymlink = "rel_symlink.cpp"; + QVERIFY(pwd.exists("abs_symlink.cpp") || createSymbolicLinkW(absSymlink.utf16(),absTarget.utf16(),0x0)); + QVERIFY(pwd.exists(relSymlink) || createSymbolicLinkW(relSymlink.utf16(),relTarget.utf16(),0x0)); + + QTest::newRow("absolute file symlink") << absSymlink << true << QDir::fromNativeSeparators(absTarget) << target.canonicalFilePath(); + QTest::newRow("relative file symlink") << relSymlink << true << QDir::fromNativeSeparators(relTarget) << target.canonicalFilePath(); + } + + //Junctions + QString target = "target"; + QString junction = "junction_pwd"; + FileSystem::createNtfsJunction(target, junction); + QFileInfo targetInfo(target); + QTest::newRow("junction_pwd") << junction << true << targetInfo.absoluteFilePath() << targetInfo.canonicalFilePath(); + + QFileInfo fileInJunction(targetInfo.absoluteFilePath().append("/file")); + QFile file(fileInJunction.absoluteFilePath()); + file.open(QIODevice::ReadWrite); + file.close(); + QVERIFY(file.exists()); + QTest::newRow("file in junction") << fileInJunction.absoluteFilePath() << false << "" << fileInJunction.canonicalFilePath(); + + target = QDir::rootPath(); + junction = "junction_root"; + FileSystem::createNtfsJunction(target, junction); + targetInfo.setFile(target); + QTest::newRow("junction_root") << junction << true << targetInfo.absoluteFilePath() << targetInfo.canonicalFilePath(); + + //Mountpoint + wchar_t buffer[MAX_PATH]; + QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); + QVERIFY(GetVolumeNameForVolumeMountPointW(rootPath.utf16(), buffer, MAX_PATH)); + QString rootVolume = QString::fromWCharArray(buffer); + junction = "mountpoint"; + rootVolume.replace("\\\\?\\","\\??\\"); + FileSystem::createNtfsJunction(rootVolume, junction); + QTest::newRow("mountpoint") << junction << true << QDir::fromNativeSeparators(rootPath) << QDir::rootPath(); +} + +void tst_QFileInfo::ntfsJunctionPointsAndSymlinks() +{ + QFETCH(QString, path); + QFETCH(bool, isSymLink); + QFETCH(QString, linkTarget); + QFETCH(QString, canonicalFilePath); + + QFileInfo fi(path); + QCOMPARE(fi.isSymLink(), isSymLink); + QCOMPARE(fi.symLinkTarget(), linkTarget); + QCOMPARE(fi.canonicalFilePath(), canonicalFilePath); +} + void tst_QFileInfo::brokenShortcut() { QString linkName("borkenlink.lnk"); diff --git a/tests/benchmarks/corelib/io/qfileinfo/main.cpp b/tests/benchmarks/corelib/io/qfileinfo/main.cpp index 025787f..b272bda 100644 --- a/tests/benchmarks/corelib/io/qfileinfo/main.cpp +++ b/tests/benchmarks/corelib/io/qfileinfo/main.cpp @@ -43,15 +43,20 @@ #include <QtTest/QtTest> #include <QtCore/QCoreApplication> #include <QtCore/QFileInfo> +#include <QtCore/QFile> #include "private/qfsfileengine_p.h" +#include "../../../../shared/filesystem.h" class qfileinfo : public QObject { Q_OBJECT private slots: void canonicalFileNamePerformance(); - +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + void symLinkTargetPerformanceLNK(); + void symLinkTargetPerformanceMounpoint(); +#endif void initTestCase(); void cleanupTestCase(); public: @@ -78,6 +83,43 @@ void qfileinfo::canonicalFileNamePerformance() } } +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +void qfileinfo::symLinkTargetPerformanceLNK() +{ + QVERIFY(QFile::link("file","link.lnk")); + QFileInfo info("link.lnk"); + info.setCaching(false); + QVERIFY(info.isSymLink()); + QString linkTarget; + QBENCHMARK { + for(int i=0; i<100; i++) + linkTarget = info.readLink(); + } + QVERIFY(QFile::remove("link.lnk")); +} + +void qfileinfo::symLinkTargetPerformanceMounpoint() +{ + wchar_t buffer[MAX_PATH]; + QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); + QVERIFY(GetVolumeNameForVolumeMountPointW(rootPath.utf16(), buffer, MAX_PATH)); + QString rootVolume = QString::fromWCharArray(buffer); + QString mountpoint = "mountpoint"; + rootVolume.replace("\\\\?\\","\\??\\"); + FileSystem::createNtfsJunction(rootVolume, mountpoint); + + QFileInfo info(mountpoint); + info.setCaching(false); + QVERIFY(info.isSymLink()); + QString linkTarget; + QBENCHMARK { + for(int i=0; i<100; i++) + linkTarget = info.readLink(); + } + QVERIFY(QDir().rmdir(mountpoint)); +} +#endif + QTEST_MAIN(qfileinfo) #include "main.moc" diff --git a/tests/shared/filesystem.h b/tests/shared/filesystem.h index 2d46c0d..cc1781e 100644 --- a/tests/shared/filesystem.h +++ b/tests/shared/filesystem.h @@ -48,6 +48,11 @@ #include <QDir> #include <QFile> +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +# define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) +# define REPARSE_MOUNTPOINT_HEADER_SIZE 8 +#endif + struct FileSystem { ~FileSystem() @@ -86,6 +91,59 @@ struct FileSystem } return false; } +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + static void createNtfsJunction(QString target, QString linkName) + { + typedef struct { + DWORD ReparseTag; + DWORD ReparseDataLength; + WORD Reserved; + WORD ReparseTargetLength; + WORD ReparseTargetMaximumLength; + WORD Reserved1; + WCHAR ReparseTarget[1]; + } REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER; + + char reparseBuffer[MAX_PATH*3]; + HANDLE hFile; + DWORD returnedLength; + wchar_t fileSystem[MAX_PATH] = L""; + PREPARSE_MOUNTPOINT_DATA_BUFFER reparseInfo = (PREPARSE_MOUNTPOINT_DATA_BUFFER) reparseBuffer; + + QFileInfo junctionInfo(linkName); + linkName = QDir::toNativeSeparators(junctionInfo.absoluteFilePath()); + + GetVolumeInformationW( linkName.left(3).utf16(), NULL, 0, NULL, NULL, NULL, + fileSystem, sizeof(fileSystem)/sizeof(WCHAR)); + if(QString().fromWCharArray(fileSystem) != "NTFS") + QSKIP("This seems not to be an NTFS volume. Junctions are not allowed.",SkipSingle); + + if (!target.startsWith("\\??\\") && !target.startsWith("\\\\?\\")) { + QFileInfo targetInfo(target); + target = QDir::toNativeSeparators(targetInfo.absoluteFilePath()); + target.prepend("\\??\\"); + if(target.endsWith('\\') && target.at(target.length()-2) != ':') + target.chop(1); + } + QDir().mkdir(linkName); + hFile = CreateFileW( linkName.utf16(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL ); + QVERIFY(hFile != INVALID_HANDLE_VALUE ); + + memset( reparseInfo, 0, sizeof( *reparseInfo )); + reparseInfo->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseInfo->ReparseTargetLength = target.size() * sizeof(wchar_t); + reparseInfo->ReparseTargetMaximumLength = reparseInfo->ReparseTargetLength + sizeof(wchar_t); + target.toWCharArray(reparseInfo->ReparseTarget); + reparseInfo->ReparseDataLength = reparseInfo->ReparseTargetLength + 12; + + bool ioc = DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, reparseInfo, + reparseInfo->ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, + NULL, 0, &returnedLength, NULL); + CloseHandle( hFile ); + QVERIFY(ioc); + } +#endif private: QDir currentDir; |