Make Qt aware of NTFS Junctions on Windows

On NTFS, a junction point can be created and deleted by the mklink and
rmdir commands, respectively. If a directory is not identified
correctly as a junction, then applications will likely try to remove
it using recursive methods, leading to fatal data loss.

With this change, Qt can identify file system entries as junctions,
allowing applications to use the correct file system operation to
remove it.

The test needs to delay the cleaning up of junctions and files it
creates until the checks are complete; since they might fail and make
the test function return prematurely, use a scope guard.

[ChangeLog][QtCore][QFileInfo] Add QFileInfo::isJunction so that
applications can recognize NTFS file system entries as junctions

Task-number: QTBUG-75869
Change-Id: I3c208245afbd9fb7555515fb776ff63b133ca858
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
bb10
Ryan Chu 2019-08-21 16:18:07 +02:00 committed by Volker Hilsheimer
parent b81863cfad
commit 004e3e0dc2
4 changed files with 47 additions and 14 deletions

View File

@ -1145,6 +1145,25 @@ bool QFileInfo::isShortcut() const
[d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); });
}
/*!
Returns \c true if the object points to a junction;
otherwise returns \c false.
Junctions only exist on Windows' NTFS file system, and are typically
created by the \c{mklink} command. They can be thought of as symlinks for
directories, and can only be created for absolute paths on the local
volume.
*/
bool QFileInfo::isJunction() const
{
Q_D(const QFileInfo);
return d->checkAttribute<bool>(
QFileSystemMetaData::LegacyLinkType,
[d]() { return d->metaData.isJunction(); },
[d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); });
}
/*!
Returns \c true if the object points to a directory or to a symbolic
link to a directory, and that directory is the root directory; otherwise

View File

@ -113,6 +113,7 @@ public:
bool isSymLink() const;
bool isSymbolicLink() const;
bool isShortcut() const;
bool isJunction() const;
bool isRoot() const;
bool isBundle() const;

View File

@ -111,8 +111,10 @@ public:
AliasType = 0x0,
#endif
#if defined(Q_OS_WIN)
JunctionType = 0x04000000,
WinLnkType = 0x08000000, // Note: Uses the same position for AliasType on Mac
#else
JunctionType = 0x0,
WinLnkType = 0x0,
#endif
SequentialType = 0x00800000, // Note: overlaps with QAbstractFileEngine::RootFlag
@ -205,8 +207,10 @@ public:
bool wasDeleted() const { return (entryFlags & WasDeletedAttribute); }
#if defined(Q_OS_WIN)
bool isLnkFile() const { return (entryFlags & WinLnkType); }
bool isJunction() const { return (entryFlags & JunctionType); }
#else
bool isLnkFile() const { return false; }
bool isJunction() const { return false; }
#endif
qint64 size() const { return size_; }
@ -356,9 +360,15 @@ inline void QFileSystemMetaData::fillFromFindData(WIN32_FIND_DATA &findData, boo
if (setLinkType) {
knownFlagsMask |= LinkType;
entryFlags &= ~LinkType;
if ((fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT)
&& (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
entryFlags |= LinkType;
if (fileAttribute_ & FILE_ATTRIBUTE_REPARSE_POINT) {
if (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK) {
entryFlags |= LinkType;
#if defined(IO_REPARSE_TAG_MOUNT_POINT)
} else if ((fileAttribute_ & FILE_ATTRIBUTE_DIRECTORY)
&& (findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) {
entryFlags |= JunctionType;
#endif
}
}
}
}

View File

@ -1813,19 +1813,22 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks()
QVERIFY2(creationResult == ERROR_SUCCESS, qPrintable(errorMessage));
QFileInfo fi(path);
const bool actualIsSymLink = fi.isSymbolicLink();
auto guard = qScopeGuard([&fi, this]() {
// Ensure that junctions, mountpoints are removed. If this fails, do not remove
// temporary directory to prevent it from trashing the system.
if (fi.isDir()) {
if (!QDir().rmdir(fi.filePath())) {
qWarning("Unable to remove NTFS junction '%ls', keeping '%ls'.",
qUtf16Printable(fi.fileName()),
qUtf16Printable(QDir::toNativeSeparators(m_dir.path())));
m_dir.setAutoRemove(false);
}
}
});
const QString actualSymLinkTarget = isSymLink ? fi.symLinkTarget() : QString();
const QString actualCanonicalFilePath = isSymLink ? fi.canonicalFilePath() : QString();
// Ensure that junctions, mountpoints are removed. If this fails, do not remove
// temporary directory to prevent it from trashing the system.
if (fi.isDir()) {
if (!QDir().rmdir(fi.filePath())) {
qWarning("Unable to remove NTFS junction '%s'', keeping '%s'.",
qPrintable(fi.fileName()), qPrintable(QDir::toNativeSeparators(m_dir.path())));
m_dir.setAutoRemove(false);
}
}
QCOMPARE(actualIsSymLink, isSymLink);
QCOMPARE(fi.isJunction(), resource.type == NtfsTestResource::Junction);
QCOMPARE(fi.isSymbolicLink(), isSymLink);
if (isSymLink) {
QCOMPARE(actualSymLinkTarget, linkTarget);
QCOMPARE(actualCanonicalFilePath, canonicalFilePath);