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
parent
0564ebdb36
commit
ded82d1b07
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@ public:
|
|||
CanonicalName,
|
||||
CanonicalPathName,
|
||||
BundleName,
|
||||
NFileNames = 9
|
||||
JunctionName,
|
||||
NFileNames // Must be last.
|
||||
};
|
||||
enum FileOwner {
|
||||
OwnerUser,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -476,6 +476,8 @@ QString QFSFileEngine::fileName(FileName file) const
|
|||
return entry.filePath();
|
||||
}
|
||||
return QString();
|
||||
case JunctionName:
|
||||
return QString();
|
||||
case DefaultName:
|
||||
case NFileNames:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue