QFileInfo: add isOther()

"Special" is interpreted similar to the logic used in
QDirListing::IteratorFlag::ExcludeOther. This also matches how
std::filesystem::is_other() is supposed to work except that in Qt .lnk
files on Windows are treated as "other".

[ChangeLog][QtCore][QFileInfo] Added isOther() method.

Fixes: QTBUG-133331
Change-Id: I2550fd37cd495b4593cacf704ce63da7d1c2addd
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2025-02-01 23:38:02 +02:00
parent c553f39e3d
commit 9a83c2b88c
3 changed files with 157 additions and 0 deletions

View File

@ -1178,6 +1178,57 @@ bool QFileInfo::isSymbolicLink() const
[d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); });
}
/*!
\since 6.10
Returns \c true if this QFileInfo refers to a file system entry that is
\e not a directory, regular file or symbolic link. Otherwise returns
\c false.
If this QFileInfo refers to a nonexistent entry, this method returns
\c false.
If the entry is a dangling symbolic link (the target doesn't exist), this
method returns \c false. For a non-dangling symbolic link, this function
returns information about the target, not the symbolic link.
On Unix a special (other) file system entry is a FIFO, socket, character
device, or block device. For more details, see the
\l{https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknod.html}{\c mknod}
manual page.
On Windows (for historical reasons, see \l{Symbolic Links and Shortcuts})
this method returns \c true for \c .lnk files.
\sa isDir(), isFile(), isSymLink(), QDirListing::IteratorFlag::ExcludeOther
*/
bool QFileInfo::isOther() const
{
Q_D(const QFileInfo);
using M = QFileSystemMetaData::MetaDataFlag;
// No M::LinkType to make QFileSystemEngine always call stat().
// M::WinLnkType is only relevant on Windows for '.lnk' files
constexpr auto mdFlags = M::ExistsAttribute | M::DirectoryType | M::FileType | M::WinLnkType;
auto fsLambda = [d]() {
// Check isLnkFile() first because currently exists() returns false for
// a broken '.lnk' where the target doesn't exist.
if (d->metaData.isLnkFile()) // Always false on non-Windows OSes
return true;
return d->metaData.exists() && !d->metaData.isDirectory() && !d->metaData.isFile();
};
auto engineLambda = [d]() {
using F = QAbstractFileEngine::FileFlag;
return d->getFileFlags(F::ExistsFlag)
&& !d->getFileFlags(F::LinkType) // QAFE doesn't have a separate type for ".lnk" file
&& !d->getFileFlags(F::DirectoryType)
&& !d->getFileFlags(F::FileType);
};
return d->checkAttribute<bool>(mdFlags, std::move(fsLambda), std::move(engineLambda));
}
/*!
Returns \c true if this object points to a shortcut;
otherwise returns \c false.

View File

@ -125,6 +125,7 @@ public:
bool isDir() const;
bool isSymLink() const;
bool isSymbolicLink() const;
bool isOther() const;
bool isShortcut() const;
bool isAlias() const;
bool isJunction() const;

View File

@ -133,6 +133,9 @@ private slots:
void isDir_data();
void isDir();
void isOther_data();
void isOther();
void isRoot_data();
void isRoot();
@ -258,6 +261,7 @@ private:
QString m_resourcesDir;
QTemporaryDir m_dir;
QSharedPointer<QTemporaryDir> m_dataDir;
QTemporaryDir m_tempSubDir;
};
void tst_QFileInfo::initTestCase()
@ -412,6 +416,107 @@ void tst_QFileInfo::isDir()
QVERIFY(!isDir);
}
void tst_QFileInfo::isOther_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<bool>("expected");
m_tempSubDir.remove();
m_tempSubDir = QTemporaryDir(m_dir.path() + "/isother_test_dir.XXXXXX"_L1);
const QString prefix = m_tempSubDir.path() + u'/';
const QString filePath = prefix + "file"_L1;
QFile file(filePath);
if (file.open(QFile::WriteOnly)) {
file.write("JAJAJAA");
file.close();
}
const QString linkToFile = prefix + "link-to-file"_L1;
file.link(linkToFile);
const QString dirPath = m_dir.path();
const QString linkToDir = prefix + "link-to-dir"_L1;
QFile::link(dirPath, linkToDir);
const QString brokenLink = prefix + "broken-symlink"_L1;
QFile dummy(prefix + "dummyfile"_L1);
(void)dummy.open(QIODevice::WriteOnly);
dummy.link(brokenLink);
dummy.remove();
QTest::newRow("regular-file") << filePath << false;
QTest::newRow("symlink-to-regular-file") << linkToFile << false;
QTest::newRow("dir") << dirPath << false;
QTest::newRow("symlink-to-dir") << linkToDir << false;
QTest::newRow("broken-symlink") << brokenLink << false;
QTest::newRow("qresources-file") << ":/tst_qfileinfo/resources/file1" << false;
QTest::newRow("qresources-broken-dir") << ":/I/do_not_expect_this_path_to_exist/" << false;
QTest::newRow("qresources-broken-file") << ":/tst_qfileinfo/resources/ghost-file" << false;
#ifdef Q_OS_UNIX
auto addSpecialRow = [&prefix](const QString &s, uint type) {
const QString name = prefix + s;
if (::mknod(name.toLatin1().constData(), 0777 | type, dev_t{}) == 0)
QTest::addRow("%s", qPrintable(name)) << name << true;
else
qDebug("mknod call failed: %s", strerror(errno));
};
addSpecialRow("fifo-test", S_IFIFO);
addSpecialRow("socket-test", S_IFSOCK);
const QString linkToSpecial = prefix + "link-to-dev-null"_L1;
QFile::link(u"/dev/null"_s, linkToSpecial);
QTest::newRow("character-device") << u"/dev/null"_s << true;
QTest::newRow("symlink-to-special") << linkToSpecial << true;
#elif defined(Q_OS_WIN)
const QString name = prefix + "win-CreateSymbolicLink-file"_L1;
const auto res = FileSystem::createSymbolicLink(name.toLatin1().constData(),
file.fileName().toLatin1().constData());
if (res.dwErr == ERROR_SUCCESS)
QTest::newRow("win-CreateSymbolicLink-file") << name << false;
else if (res.dwErr == ERROR_PRIVILEGE_NOT_HELD)
qDebug() << msgInsufficientPrivileges(res.errorMessage);
else
qDebug() << res.errorMessage;
const QString winLnk = prefix + "winLnk.lnk"_L1;
QFile lnkTarget(prefix + "lnkTarget"_L1);
(void)lnkTarget.open(QIODevice::WriteOnly);
lnkTarget.link(winLnk);
QTest::newRow("winLnk") << winLnk << true;
const QString brokenWinLnk = prefix + "broken_winLnk.lnk"_L1;
const QString dummyPath = prefix + "lnkDummy"_L1;
lnkTarget.copy(dummyPath);
QFile lnkDummy(dummyPath);
lnkDummy.link(brokenWinLnk);
lnkDummy.remove();
QTest::newRow("broken-winLnk") << brokenWinLnk << true;
#endif // Q_OS_UNIX
}
void tst_QFileInfo::isOther()
{
QFETCH(QString, path);
QFETCH(bool, expected);
QFileInfo info(path);
QCOMPARE(info.isOther(), expected);
if (!QByteArrayView{QTest::currentDataTag()}.contains("broken"))
QVERIFY(info.exists());
#if QT_CONFIG(cxx17_filesystem) // This code doesn't work in QNX on the CI
if (QByteArrayView{QTest::currentDataTag()}.contains("winLnk"))
QEXPECT_FAIL("", "QFileInfo::isOther() returns true for '.lnk' files on Windows, "
"std::filesystem::is_other() returns false", Continue);
QCOMPARE_EQ(info.isOther(), std::filesystem::is_other(path.toStdString()));
#endif
}
void tst_QFileInfo::isRoot_data()
{
QTest::addColumn<QString>("path");