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 <thiago.macieira@intel.com>
This commit is contained in:
Wang Fei 2021-11-16 13:17:39 +08:00 committed by Christian Ehrlicher
parent 8844c6ef37
commit 936cae6b53
12 changed files with 81 additions and 7 deletions

View File

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

View File

@ -73,6 +73,7 @@ public:
CanonicalPathName,
BundleName,
JunctionName,
RawLinkPath,
NFileNames // Must be last.
};
enum FileOwner {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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