From 936cae6b53e8982fb087458745e06721d35341ef Mon Sep 17 00:00:00 2001 From: Wang Fei Date: Tue, 16 Nov 2021 13:17:39 +0800 Subject: [PATCH] Add QFileInfo::readSymLink() to read the raw link path The existing symLinkTarget() always resolves the symlink target to an absolute path; readSymLink() provides access to the relative path when that is how the symlink references its target. [ChangeLog][QtCore][QFileInfo] Added readSymLink() to read the symlink's raw target, without resolving to an absolute path. Fixes: QTBUG-96761 Change-Id: I360e55f1a3bdb00e2966229ea8de78cf29a29417 Reviewed-by: Thiago Macieira --- src/corelib/io/qabstractfileengine.cpp | 2 ++ src/corelib/io/qabstractfileengine_p.h | 1 + src/corelib/io/qfileinfo.cpp | 22 +++++++++++++++++++ src/corelib/io/qfileinfo.h | 4 ++++ src/corelib/io/qfilesystemengine_p.h | 2 ++ src/corelib/io/qfilesystemengine_unix.cpp | 10 +++++++++ src/corelib/io/qfilesystemengine_win.cpp | 19 +++++++++++----- src/corelib/io/qfsfileengine_unix.cpp | 6 +++++ src/corelib/io/qfsfileengine_win.cpp | 2 ++ src/corelib/io/qtemporaryfile.cpp | 2 +- .../tst_qabstractfileengine.cpp | 2 ++ .../corelib/io/qfileinfo/tst_qfileinfo.cpp | 16 ++++++++++++++ 12 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/corelib/io/qabstractfileengine.cpp b/src/corelib/io/qabstractfileengine.cpp index 401eae8aa26..25dfe79d644 100644 --- a/src/corelib/io/qabstractfileengine.cpp +++ b/src/corelib/io/qabstractfileengine.cpp @@ -225,6 +225,8 @@ QAbstractFileEngine *QAbstractFileEngine::create(const QString &fileName) the base name). \value AbsoluteLinkTarget The full file name of the file that this file is a link to. (This will be empty if this file is not a link.) + \value RawLinkPath The raw link path of the file that this file is a + link to. (This will be empty if this file is not a link.) \value CanonicalName Often very similar to AbsoluteLinkTarget. 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. diff --git a/src/corelib/io/qabstractfileengine_p.h b/src/corelib/io/qabstractfileengine_p.h index 04ad782763e..28f07bf8f95 100644 --- a/src/corelib/io/qabstractfileengine_p.h +++ b/src/corelib/io/qabstractfileengine_p.h @@ -73,6 +73,7 @@ public: CanonicalPathName, BundleName, JunctionName, + RawLinkPath, NFileNames // Must be last. }; enum FileOwner { diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index 351fbc3d861..a9b35b78d4f 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -38,6 +38,9 @@ QString QFileInfoPrivate::getFileName(QAbstractFileEngine::FileName name) const case QAbstractFileEngine::AbsoluteLinkTarget: ret = QFileSystemEngine::getLinkTarget(fileEntry, metaData).filePath(); break; + case QAbstractFileEngine::RawLinkPath: + ret = QFileSystemEngine::getRawLinkPath(fileEntry, metaData).filePath(); + break; case QAbstractFileEngine::JunctionName: ret = QFileSystemEngine::getJunctionTarget(fileEntry, metaData).filePath(); break; @@ -1221,6 +1224,25 @@ QString QFileInfo::symLinkTarget() const return d->getFileName(QAbstractFileEngine::AbsoluteLinkTarget); } +/*! + \since 6.6 + Read the path the symlink references. + + Returns the raw path referenced by the symbolic link, without resolving a relative + path relative to the directory containing the symbolic link. The returned string will + only be an absolute path if the symbolic link actually references it as such. Returns + an empty string if the object is not a symbolic link. + + \sa symLinkTarget(), exists(), isSymLink(), isDir(), isFile() +*/ +QString QFileInfo::readSymLink() const +{ + Q_D(const QFileInfo); + if (d->isDefaultConstructed) + return {}; + return d->getFileName(QAbstractFileEngine::RawLinkPath); +} + /*! \since 6.2 diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index d0ec231f89b..815659a7fed 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -126,12 +126,16 @@ public: bool isBundle() const; QString symLinkTarget() const; + QString readSymLink() const; QString junctionTarget() const; #if QT_CONFIG(cxx17_filesystem) || defined(Q_QDOC) std::filesystem::path filesystemSymLinkTarget() const { return QtPrivate::toFilesystemPath(symLinkTarget()); } + std::filesystem::path filesystemReadSymLink() const + { return QtPrivate::toFilesystemPath(readSymLink()); } + std::filesystem::path filesystemJunctionTarget() const { return QtPrivate::toFilesystemPath(junctionTarget()); } #endif // QT_CONFIG(cxx17_filesystem) diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index c54efa7f635..c1dddb2e1ea 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -67,6 +67,8 @@ public: } static QFileSystemEntry getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data); + static QFileSystemEntry getRawLinkPath(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); diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index daa11ff6b26..f420a0086ea 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -628,6 +628,16 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, return QFileSystemEntry(); } +//static +QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data) +{ + Q_UNUSED(data) + const QByteArray path = qt_readlink(link.nativeFilePath().constData()); + const QString ret = QFile::decodeName(path); + return QFileSystemEntry(ret); +} + //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 6d085d98c99..9cfa1d7b7d4 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -855,6 +855,18 @@ void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) //static QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) +{ + QFileSystemEntry ret = getRawLinkPath(link, data); + if (!ret.isEmpty() && ret.isRelative()) { + QString target = absoluteName(link).path() + u'/' + ret.filePath(); + ret = QFileSystemEntry(QDir::cleanPath(target)); + } + return ret; +} + +//static +QFileSystemEntry QFileSystemEngine::getRawLinkPath(const QFileSystemEntry &link, + QFileSystemMetaData &data) { Q_CHECK_FILE_NAME(link, link); @@ -866,12 +878,7 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, target = readLink(link); else if (data.isLink()) target = readSymLink(link); - QFileSystemEntry ret(target); - if (!target.isEmpty() && ret.isRelative()) { - target.prepend(absoluteName(link).path() + u'/'); - ret = QFileSystemEntry(QDir::cleanPath(target)); - } - return ret; + return QFileSystemEntry(target); } //static diff --git a/src/corelib/io/qfsfileengine_unix.cpp b/src/corelib/io/qfsfileengine_unix.cpp index a08feb73fb4..d5a0a731d6f 100644 --- a/src/corelib/io/qfsfileengine_unix.cpp +++ b/src/corelib/io/qfsfileengine_unix.cpp @@ -450,6 +450,12 @@ QString QFSFileEngine::fileName(FileName file) const return entry.filePath(); } return QString(); + case RawLinkPath: + if (d->isSymlink()) { + QFileSystemEntry entry = QFileSystemEngine::getRawLinkPath(d->fileEntry, d->metaData); + return entry.filePath(); + } + return QString(); case JunctionName: return QString(); case DefaultName: diff --git a/src/corelib/io/qfsfileengine_win.cpp b/src/corelib/io/qfsfileengine_win.cpp index 1030c559374..daa43262128 100644 --- a/src/corelib/io/qfsfileengine_win.cpp +++ b/src/corelib/io/qfsfileengine_win.cpp @@ -619,6 +619,8 @@ QString QFSFileEngine::fileName(FileName file) const } case AbsoluteLinkTarget: return QFileSystemEngine::getLinkTarget(d->fileEntry, d->metaData).filePath(); + case RawLinkPath: + return QFileSystemEngine::getRawLinkPath(d->fileEntry, d->metaData).filePath(); case BundleName: return QString(); case JunctionName: diff --git a/src/corelib/io/qtemporaryfile.cpp b/src/corelib/io/qtemporaryfile.cpp index 86ad1439e31..13f2e447e76 100644 --- a/src/corelib/io/qtemporaryfile.cpp +++ b/src/corelib/io/qtemporaryfile.cpp @@ -406,7 +406,7 @@ bool QTemporaryFileEngine::close() QString QTemporaryFileEngine::fileName(QAbstractFileEngine::FileName file) const { if (isUnnamedFile()) { - if (file == AbsoluteLinkTarget) { + if (file == AbsoluteLinkTarget || file == RawLinkPath) { // we know our file isn't (won't be) a symlink return QString(); } diff --git a/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp b/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp index 8f91ebe1360..b0936440137 100644 --- a/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp +++ b/tests/auto/corelib/io/qabstractfileengine/tst_qabstractfileengine.cpp @@ -215,6 +215,8 @@ public: return QLatin1String("AbsolutePathName"); case AbsoluteLinkTarget: return QLatin1String("AbsoluteLinkTarget"); + case RawLinkPath: + return QLatin1String("RawLinkPath"); case CanonicalName: return QLatin1String("CanonicalName"); case CanonicalPathName: diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index 4b383c0c53b..4a2e5cb5e3b 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -242,6 +242,7 @@ private slots: void nonExistingFile(); void stdfilesystem(); + void readSymLink(); private: const QString m_currentDir; @@ -2391,5 +2392,20 @@ void tst_QFileInfo::stdfilesystem() #endif } +void tst_QFileInfo::readSymLink() +{ + QString symLinkName("./a.link"); + const auto tidier = qScopeGuard([symLinkName]() { QFile::remove(symLinkName); }); + +#ifdef Q_OS_WIN + QVERIFY2(CreateSymbolicLink(L"a.link", L"..\\..\\a", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) + != 0, + "Failed to create symlink for test"); +#else + QVERIFY2(QFile::link("../../a", symLinkName), "Failed to create symlink for test"); +#endif + QFileInfo info(symLinkName); + QCOMPARE(info.readSymLink(), QString("../../a")); +} QTEST_MAIN(tst_QFileInfo) #include "tst_qfileinfo.moc"