Implement QFileInfo::junctionTarget(), adjust auto-test

The change in 004e3e0dc2 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>
bb10
Karsten Heimrich 2021-05-31 16:25:21 +02:00
parent 0564ebdb36
commit ded82d1b07
11 changed files with 110 additions and 10 deletions

View File

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

View File

@ -106,7 +106,8 @@ public:
CanonicalName,
CanonicalPathName,
BundleName,
NFileNames = 9
JunctionName,
NFileNames // Must be last.
};
enum FileOwner {
OwnerUser,

View File

@ -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;
@ -1224,6 +1227,28 @@ QString QFileInfo::symLinkTarget() const
return d->getFileName(QAbstractFileEngine::LinkName);
}
/*!
\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
@ -1630,6 +1655,13 @@ QDebug operator<<(QDebug dbg, const QFileInfo &fi)
Returns symLinkTarget() as a \c{std::filesystem::path}.
\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

View File

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

View File

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

View File

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

View File

@ -535,6 +535,26 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link,
return ret;
}
//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)
{

View File

@ -476,6 +476,8 @@ QString QFSFileEngine::fileName(FileName file) const
return entry.filePath();
}
return QString();
case JunctionName:
return QString();
case DefaultName:
case NFileNames:
break;

View File

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

View File

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

View File

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