diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index a3e140f2094..2337cfc6c84 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -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(mdFlags, std::move(fsLambda), std::move(engineLambda)); +} + /*! Returns \c true if this object points to a shortcut; otherwise returns \c false. diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index 732182049fb..6e4a0297c85 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -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; diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index e7ab9bad401..7cee0b1b661 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -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 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("path"); + QTest::addColumn("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("path");