diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2021-05-31 16:25:21 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2021-06-05 01:16:06 +0200 |
commit | ded82d1b073adb769afd28104515d240e8e1dd3f (patch) | |
tree | 933acea7f79e2e21af71e9e38c37868974a87d7e | |
parent | 0564ebdb3641d7325f73dbbf2cbb04e6dca92d83 (diff) | |
download | qtbase-ded82d1b073adb769afd28104515d240e8e1dd3f.tar.gz |
Implement QFileInfo::junctionTarget(), adjust auto-test
The change in 004e3e0dc2cab4a4534d2ed3ace41aad6bfbe45d introduces
Windows junction awareness, though users were still unable to resolve
the junction target. This change adds the ability to solve this.
Fixes: QTBUG-93869
Change-Id: I9f4d4ed87b92e757f7b6d8739e2a61b58c096f63
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r-- | src/corelib/io/qabstractfileengine.cpp | 2 | ||||
-rw-r--r-- | src/corelib/io/qabstractfileengine_p.h | 3 | ||||
-rw-r--r-- | src/corelib/io/qfileinfo.cpp | 32 | ||||
-rw-r--r-- | src/corelib/io/qfileinfo.h | 5 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine.cpp | 13 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine_p.h | 3 | ||||
-rw-r--r-- | src/corelib/io/qfilesystemengine_win.cpp | 20 | ||||
-rw-r--r-- | src/corelib/io/qfsfileengine_unix.cpp | 2 | ||||
-rw-r--r-- | src/corelib/io/qfsfileengine_win.cpp | 2 | ||||
-rw-r--r-- | tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp | 28 | ||||
-rw-r--r-- | tests/benchmarks/corelib/io/qfileinfo/main.cpp | 10 |
11 files changed, 110 insertions, 10 deletions
diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp index cc603dd63f..44d5d1b76f 100644 --- a/src/corelib/io/qabstractfileengine.cpp +++ b/src/corelib/io/qabstractfileengine.cpp @@ -267,6 +267,8 @@ QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName) \value CanonicalName Often very similar to LinkName. Will return the true path to the file. \value CanonicalPathName Same as CanonicalName, excluding the base name. \value BundleName Returns the name of the bundle implies BundleType is set. + \value JunctionName The full name of the directory that this NTFS junction + is linked to. (This will be empty if this file is not an NTFS junction.) \omitvalue NFileNames diff --git a/src/corelib/io/qabstractfileengine_p.h b/src/corelib/io/qabstractfileengine_p.h index c88b66c7ce..bed4aa56d9 100644 --- a/src/corelib/io/qabstractfileengine_p.h +++ b/src/corelib/io/qabstractfileengine_p.h @@ -106,7 +106,8 @@ public: CanonicalName, CanonicalPathName, BundleName, - NFileNames = 9 + JunctionName, + NFileNames // Must be last. }; enum FileOwner { OwnerUser, diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index 25091b0d3f..7ca7468317 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -70,6 +70,9 @@ QString QFileInfoPrivate::getFileName(QAbstractFileEngine::FileName name) const case QAbstractFileEngine::LinkName: ret = QFileSystemEngine::getLinkTarget(fileEntry, metaData).filePath(); break; + case QAbstractFileEngine::JunctionName: + ret = QFileSystemEngine::getJunctionTarget(fileEntry, metaData).filePath(); + break; case QAbstractFileEngine::BundleName: ret = QFileSystemEngine::bundleName(fileEntry); break; @@ -1225,6 +1228,28 @@ QString QFileInfo::symLinkTarget() const } /*! + \since 6.2 + + Resolves an NTFS junction to the path it references. + + Returns the absolute path to the directory an NTFS junction points to, or + an empty string if the object is not an NTFS junction. + + There is no guarantee that the directory named by the NTFS junction actually + exists. + + \sa isJunction(), isFile(), isDir(), isSymLink(), isSymbolicLink(), + isShortcut() +*/ +QString QFileInfo::junctionTarget() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return QLatin1String(""); + return d->getFileName(QAbstractFileEngine::JunctionName); +} + +/*! Returns the owner of the file. On systems where files do not have owners, or if an error occurs, an empty string is returned. @@ -1631,6 +1656,13 @@ QDebug operator<<(QDebug dbg, const QFileInfo &fi) \sa symLinkTarget() */ /*! + \fn std::filesystem::path QFileInfo::filesystemJunctionTarget() const + \since 6.2 + + Returns junctionTarget() as a \c{std::filesystem::path}. + \sa junctionTarget() +*/ +/*! \macro QT_IMPLICIT_QFILEINFO_CONSTRUCTION \since 6.0 \relates QFileInfo diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index 90be1c2540..4bc5db9e2b 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -161,9 +161,14 @@ public: bool isBundle() const; QString symLinkTarget() const; + QString junctionTarget() const; + #if QT_CONFIG(cxx17_filesystem) || defined(Q_CLANG_QDOC) std::filesystem::path filesystemSymLinkTarget() const { return QtPrivate::toFilesystemPath(symLinkTarget()); } + + std::filesystem::path filesystemJunctionTarget() const + { return QtPrivate::toFilesystemPath(junctionTarget()); } #endif // QT_CONFIG(cxx17_filesystem) QString owner() const; diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp index 1a9a01ac92..f00aba78c3 100644 --- a/src/corelib/io/qfilesystemengine.cpp +++ b/src/corelib/io/qfilesystemengine.cpp @@ -232,4 +232,17 @@ QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFile #endif } +//static +QFileSystemEntry QFileSystemEngine::getJunctionTarget(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ +#if defined(Q_OS_WIN) + return junctionTarget(link, data); +#else + Q_UNUSED(link); + Q_UNUSED(data); + return {}; +#endif +} + QT_END_NAMESPACE diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index dffd304282..b5daa3b88e 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -101,6 +101,7 @@ public: } static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); + static QFileSystemEntry getJunctionTarget(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); @@ -130,7 +131,7 @@ public: QFileSystemMetaData *data = nullptr); #endif #if defined(Q_OS_WIN) - + static QFileSystemEntry junctionTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); static bool uncListSharesOnServer(const QString &server, QStringList *list); //Used also by QFSFileEngineIterator::hasNext() static bool fillMetaData(int fd, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what); diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index d587e2bfd0..b2e1870b3d 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -536,6 +536,26 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, } //static +QFileSystemEntry QFileSystemEngine::junctionTarget(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ + Q_CHECK_FILE_NAME(link, link); + + if (data.missingFlags(QFileSystemMetaData::JunctionType)) + QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); + + QString target; + if (data.isJunction()) + target = readSymLink(link); + QFileSystemEntry ret(target); + if (!target.isEmpty() && ret.isRelative()) { + target.prepend(absoluteName(link).path() + QLatin1Char('/')); + ret = QFileSystemEntry(QDir::cleanPath(target)); + } + return ret; +} + +//static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { Q_CHECK_FILE_NAME(entry, entry); diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp index 9980359881..e85f81f267 100644 --- a/src/corelib/io/qfsfileengine_unix.cpp +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -476,6 +476,8 @@ QString QFSFileEngine::fileName(FileName file) const return entry.filePath(); } return QString(); + case JunctionName: + return QString(); case DefaultName: case NFileNames: break; diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index af7da61f9a..7ac1368ab3 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -647,6 +647,8 @@ QString QFSFileEngine::fileName(FileName file) const return QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData).filePath(); } else if (file == BundleName) { return QString(); + } else if (file == JunctionName) { + return QFileSystemEngine::getJunctionTarget(d->fileEntry, d->metaData).filePath(); } return d->fileEntry.filePath(); } diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index 916b084e24..4d74fe4d98 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -1782,14 +1782,36 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks() } } }); - const QString actualSymLinkTarget = isSymLink ? fi.symLinkTarget() : QString(); - const QString actualCanonicalFilePath = isSymLink ? fi.canonicalFilePath() : QString(); + const QString actualCanonicalFilePath = fi.canonicalFilePath(); QCOMPARE(fi.isJunction(), isJunction); QCOMPARE(fi.isSymbolicLink(), isSymLink); if (isSymLink) { - QCOMPARE(actualSymLinkTarget, linkTarget); + QCOMPARE(fi.symLinkTarget(), linkTarget); QCOMPARE(actualCanonicalFilePath, canonicalFilePath); } + + if (isJunction) { + if (creationResult.target.startsWith(uR"(\??\)")) + creationResult.target = creationResult.target.sliced(4); + + // resolve volume to drive letter + static const QRegularExpression matchVolumeRe(uR"(^Volume\{([a-z]|[0-9]|-)+\}\\)"_qs, + QRegularExpression::CaseInsensitiveOption); + auto matchVolume = matchVolumeRe.match(creationResult.target); + if (matchVolume.hasMatch()) { + Q_ASSERT(matchVolume.capturedStart() == 0); + DWORD len; + wchar_t buffer[MAX_PATH]; + const QString volumeName = uR"(\\?\)"_qs + matchVolume.captured(); + if (GetVolumePathNamesForVolumeName(reinterpret_cast<LPCWSTR>(volumeName.utf16()), + buffer, MAX_PATH, &len) != 0) { + creationResult.target.replace(0, matchVolume.capturedLength(), + QString::fromWCharArray(buffer)); + } + } + QCOMPARE(fi.junctionTarget(), QDir::fromNativeSeparators(creationResult.target)); + QCOMPARE(actualCanonicalFilePath, QFileInfo(creationResult.link).canonicalFilePath()); + } } void tst_QFileInfo::brokenShortcut() diff --git a/tests/benchmarks/corelib/io/qfileinfo/main.cpp b/tests/benchmarks/corelib/io/qfileinfo/main.cpp index 65b712898b..2cd06eef27 100644 --- a/tests/benchmarks/corelib/io/qfileinfo/main.cpp +++ b/tests/benchmarks/corelib/io/qfileinfo/main.cpp @@ -43,7 +43,7 @@ private slots: void existsStatic(); #if defined(Q_OS_WIN) void symLinkTargetPerformanceLNK(); - void symLinkTargetPerformanceMounpoint(); + void junctionTargetPerformanceMountpoint(); #endif void initTestCase(); void cleanupTestCase(); @@ -86,7 +86,7 @@ void qfileinfo::symLinkTargetPerformanceLNK() QVERIFY(QFile::remove("link.lnk")); } -void qfileinfo::symLinkTargetPerformanceMounpoint() +void qfileinfo::junctionTargetPerformanceMountpoint() { wchar_t buffer[MAX_PATH]; QString rootPath = QDir::toNativeSeparators(QDir::rootPath()); @@ -99,11 +99,11 @@ void qfileinfo::symLinkTargetPerformanceMounpoint() QFileInfo info(mountpoint); info.setCaching(false); - QVERIFY(info.isSymLink()); - QString linkTarget; + QVERIFY(info.isJunction()); + QString junctionTarget; QBENCHMARK { for(int i=0; i<100; i++) - linkTarget = info.symLinkTarget(); + junctionTarget = info.junctionTarget(); } QVERIFY(QDir().rmdir(mountpoint)); } |