Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kio-fuse for openSUSE:Factory checked in at 2021-03-25 14:52:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kio-fuse (Old) and /work/SRC/openSUSE:Factory/.kio-fuse.new.2401 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kio-fuse" Thu Mar 25 14:52:37 2021 rev:4 rq:881270 version:5.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/kio-fuse/kio-fuse.changes 2020-12-30 17:13:55.416417541 +0100 +++ /work/SRC/openSUSE:Factory/.kio-fuse.new.2401/kio-fuse.changes 2021-03-25 14:52:39.188505469 +0100 @@ -1,0 +2,14 @@ +Sun Mar 21 14:37:43 UTC 2021 - Fabian Vogt <[email protected]> + +- Update to version 5.0.1: + * Changes to a directory while iterating it (opendir/readdir) don't break the + iteration anymore + * Some ioslaves with incompatible behaviour (they claim "/" is a regular file) + are rejected now. Previously, mounting those succeeded, but accessing files + failed. Now mounting fails immediately and the fallback code is triggered. + * Mixup of UDS_URL/UDS_TARGET_URL got fixed. Now the baloosearch:/ protocol + works and doesn't cause a crash anymore. Additionally, the relevant code got + hardened. +- Install by default: supplement dolphin + +------------------------------------------------------------------- Old: ---- kio-fuse-5.0.0.tar.xz kio-fuse-5.0.0.tar.xz.sig New: ---- kio-fuse-5.0.1.tar.xz kio-fuse-5.0.1.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kio-fuse.spec ++++++ --- /var/tmp/diff_new_pack.4xzCj3/_old 2021-03-25 14:52:39.984506279 +0100 +++ /var/tmp/diff_new_pack.4xzCj3/_new 2021-03-25 14:52:39.984506279 +0100 @@ -1,7 +1,7 @@ # # spec file for package kio-fuse # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %bcond_without lang Name: kio-fuse -Version: 5.0.0 +Version: 5.0.1 Release: 0 Summary: Access KIO over the regular filesystem License: GPL-3.0-or-later @@ -38,6 +38,8 @@ Requires: fuse3 # For %%check BuildRequires: kio-extras5 +# While kio itself can make use of this, it's most likely used through dolphin +Supplements: dolphin %description kio-fuse is a daemon which makes KIO URLs accessible to KIO unaware ++++++ kio-fuse-5.0.0.tar.xz -> kio-fuse-5.0.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/README new/kio-fuse-5.0.1/README --- old/kio-fuse-5.0.0/README 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/README 2021-03-21 15:14:52.000000000 +0100 @@ -10,16 +10,29 @@ pacman -S base-devel fuse3 cmake extra-cmake-modules qt5base kio + (and kio-extras for running certain tests) + +To install build dependencies on Fedora 32: + + dnf install cmake extra-cmake-modules kf5-kio-devel fuse3-devel + qt5-qtbase-devel pkg-config + + (and kio-extras for running certain tests) + To install build dependencies on openSUSE Tumbleweed: zypper install extra-cmake-modules 'cmake(KF5KIO)' 'pkgconfig(fuse3)' kio-devel 'cmake(Qt5Test)' 'cmake(Qt5Dbus)' + (and kio-extras5 for running certain tests) + To install build dependencies on Ubuntu 19.04: apt install fuse3 libfuse3-dev build-essential cmake extra-cmake-modules pkg-config libkf5kio-dev + (and kio-extras for running certain tests) + To run the tests, run make test. To install, run make install. Using diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/TODO new/kio-fuse-5.0.1/TODO --- old/kio-fuse-5.0.0/TODO 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/TODO 2021-03-21 15:14:52.000000000 +0100 @@ -1,6 +1,4 @@ Filesystem features: -- opendir/readdir/releasedir could be more POSIX compliant by increasing the lookup count of - children and storing a copy of childrenInos in fuse_file_info. - Handle more FUSE ops: * link (to allow relinking of deleted nodes only, like O_TMPFILE) * statvfs (?) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/kiofusevfs.cpp new/kio-fuse-5.0.1/kiofusevfs.cpp --- old/kio-fuse-5.0.0/kiofusevfs.cpp 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/kiofusevfs.cpp 2021-03-21 15:14:52.000000000 +0100 @@ -68,7 +68,9 @@ flush = &KIOFuseVFS::flush; release = &KIOFuseVFS::release; fsync = &KIOFuseVFS::fsync; + opendir = &KIOFuseVFS::opendir; readdir = &KIOFuseVFS::readdir; + releasedir = &KIOFuseVFS::releasedir; } }; @@ -296,10 +298,7 @@ void KIOFuseVFS::mountUrl(QUrl url, std::function<void (const QString &, int)> callback) { // First make sure it actually exists - QUrl urlWithoutPassword = url; - urlWithoutPassword.setPassword({}); - - qDebug(KIOFUSE_LOG) << "Stating" << urlWithoutPassword << "for mount"; + qDebug(KIOFUSE_LOG) << "Stating" << url.toDisplayString() << "for mount"; auto statJob = KIO::stat(url); statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing // files over plain HTTP @@ -335,10 +334,7 @@ void KIOFuseVFS::findAndCreateOrigin(QUrl url, QStringList pathElements, std::function<void (const QString &, int)> callback) { - QUrl urlWithoutPassword = url; - urlWithoutPassword.setPassword({}); - - qDebug(KIOFUSE_LOG) << "Trying origin" << urlWithoutPassword; + qDebug(KIOFUSE_LOG) << "Trying origin" << url.toDisplayString(); auto statJob = KIO::stat(url); statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing // files over plain HTTP @@ -354,7 +350,7 @@ return callback({}, kioErrorToFuseError(statJob->error())); } - qDebug(KIOFUSE_LOG) << "Origin found at" << urlWithoutPassword; + qDebug(KIOFUSE_LOG) << "Origin found at" << url.toDisplayString(); auto targetPathComponents = mapUrlToVfs(url); @@ -393,6 +389,22 @@ if(!finalNode) { finalNode = createNodeFromUDSEntry(statJob->statResult(), currentNode->m_stat.st_ino, targetPathComponents.last()); + if(!finalNode) + { + qWarning(KIOFUSE_LOG) << "Unable to create a valid final node for" << url.toDisplayString() << "from its UDS Entry"; + return callback({}, EIO); + } + + // Some ioslaves like man:/ implement "index files" for folders (including /) by making + // them look as regular file when stating, but they also support listDir for directory + // functionality. This behaviour is not compatible, so just reject it outright. + if((url.path().isEmpty() || url.path() == QStringLiteral("/")) + && !S_ISDIR(finalNode->m_stat.st_mode)) + { + qWarning(KIOFUSE_LOG) << "Root of mount at" << url.toDisplayString() << "not a directory"; + return callback({}, ENOTDIR); + } + insertNode(finalNode); } @@ -1025,9 +1037,8 @@ fuse_add_direntry(req, dirbuf.data() + oldsize, dirbuf.size() + oldsize, name, stbuf, dirbuf.size()); } -void KIOFuseVFS::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fuse_file_info *fi) +void KIOFuseVFS::opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - Q_UNUSED(fi); KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req)); auto node = that->nodeForIno(ino); if(!node) @@ -1044,15 +1055,18 @@ return; } - that->awaitChildrenComplete(dirNode, [=](int error){ + node->m_openCount += 1; + + that->awaitChildrenComplete(dirNode, [=, myfi=*fi](int error) mutable { if(error) { fuse_reply_err(req, error); return; } - std::vector<char> dirbuf; - appendDirentry(dirbuf, req, ".", &node->m_stat); + auto dirbuf = std::make_unique<std::vector<char>>(); + + appendDirentry(*dirbuf, req, ".", &node->m_stat); std::shared_ptr<KIOFuseNode> parentNode; if(node->m_parentIno != KIOFuseIno::DeletedRoot) @@ -1060,27 +1074,88 @@ if(!parentNode) parentNode = that->nodeForIno(KIOFuseIno::Root); if(parentNode) - appendDirentry(dirbuf, req, "..", &parentNode->m_stat); + appendDirentry(*dirbuf, req, "..", &parentNode->m_stat); for(auto ino : dirNode->m_childrenInos) { auto child = that->nodeForIno(ino); if(!child) { - qWarning(KIOFUSE_LOG) << "Node" << node->m_nodeName << "references nonexistant child"; + qWarning(KIOFUSE_LOG) << "Node" << node->m_nodeName << "references nonexistent child"; continue; } - appendDirentry(dirbuf, req, qPrintable(child->m_nodeName), &child->m_stat); + appendDirentry(*dirbuf, req, qPrintable(child->m_nodeName), &child->m_stat); } - if(off < off_t(dirbuf.size())) - fuse_reply_buf(req, dirbuf.data() + off, std::min(off_t(size), off_t(dirbuf.size()) - off)); - else - fuse_reply_buf(req, nullptr, 0); + myfi.fh = reinterpret_cast<uint64_t>(dirbuf.release()); + + if (!(myfi.flags & O_NOATIME)) + clock_gettime(CLOCK_REALTIME, &node->m_stat.st_atim); + + fuse_reply_open(req, &myfi); }); } +void KIOFuseVFS::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) +{ + KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req)); + auto node = that->nodeForIno(ino); + if(!node) + { + fuse_reply_err(req, EIO); + return; + } + + auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node); + if(!dirNode) + { + fuse_reply_err(req, ENOTDIR); + return; + } + + std::vector<char>* dirbuf = reinterpret_cast<std::vector<char>*>(fi->fh); + if(!dirbuf) + { + fuse_reply_err(req, EIO); + return; + } + + if(off < off_t(dirbuf->size())) + fuse_reply_buf(req, dirbuf->data() + off, std::min(off_t(size), off_t(dirbuf->size()) - off)); + else + fuse_reply_buf(req, nullptr, 0); +} + +void KIOFuseVFS::releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req)); + auto node = that->nodeForIno(ino); + if(!node) + { + fuse_reply_err(req, EIO); + return; + } + + auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node); + if(!dirNode) + { + fuse_reply_err(req, ENOTDIR); + return; + } + + node->m_openCount -= 1; + if(std::vector<char> *ptr = reinterpret_cast<std::vector<char>*>(fi->fh)) + { + delete ptr; + } + else{ + qWarning(KIOFUSE_LOG) << "File handler of node" << node->m_nodeName << "already null"; + } + + fuse_reply_err(req, 0); +} + void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fuse_file_info *fi) { Q_UNUSED(fi); @@ -1422,7 +1497,7 @@ auto child = nodeForIno(ino); if(!child) { - qWarning(KIOFUSE_LOG) << "Node" << parent->m_nodeName << "references nonexistant child"; + qWarning(KIOFUSE_LOG) << "Node" << parent->m_nodeName << "references nonexistent child"; continue; } @@ -1731,12 +1806,12 @@ attr.st_gid = gr->gr_gid; } - if(entry.contains(KIO::UDSEntry::UDS_LOCAL_PATH) || entry.contains(KIO::UDSEntry::UDS_URL)) + if(entry.contains(KIO::UDSEntry::UDS_LOCAL_PATH) || entry.contains(KIO::UDSEntry::UDS_TARGET_URL)) { // Create as symlink if possible QString target = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if(target.isEmpty()) - target = QUrl(entry.stringValue(KIO::UDSEntry::UDS_URL)).toLocalFile(); + target = QUrl(entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)).toLocalFile(); if(!target.isEmpty()) { @@ -1755,7 +1830,9 @@ { attr.st_mode |= S_IFREG; std::shared_ptr<KIOFuseRemoteFileNode> ret = nullptr; - const QUrl nodeUrl = QUrl{entry.stringValue(KIO::UDSEntry::UDS_URL)}; + const QUrl nodeUrl = QUrl{entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)}; + if(nodeUrl.isEmpty()) + return nullptr; if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl)) ret = std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr); else @@ -2238,10 +2315,7 @@ return callback({}, ENOENT); } - QUrl urlWithoutPassword = url; - urlWithoutPassword.setPassword({}); - - qDebug(KIOFUSE_LOG) << "Mounting" << urlWithoutPassword; + qDebug(KIOFUSE_LOG) << "Mounting" << url.toDisplayString(); auto statJob = KIO::stat(url); statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing // files over plain HTTP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/kiofusevfs.h new/kio-fuse-5.0.1/kiofusevfs.h --- old/kio-fuse-5.0.0/kiofusevfs.h 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/kiofusevfs.h 2021-03-21 15:14:52.000000000 +0100 @@ -78,8 +78,10 @@ static void open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi); static void rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags); + static void opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi); static void readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); + static void releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); static void read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); static void write(fuse_req_t req, fuse_ino_t ino, const char *buf, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/tests/CMakeLists.txt new/kio-fuse-5.0.1/tests/CMakeLists.txt --- old/kio-fuse-5.0.0/tests/CMakeLists.txt 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/tests/CMakeLists.txt 2021-03-21 15:14:52.000000000 +0100 @@ -9,12 +9,12 @@ qt5_add_dbus_interface(KIOFUSE_TEST_SOURCES org.kde.KIOFuse.Private.xml kiofuseprivate_interface) add_executable(fileopstest-cache ${KIOFUSE_TEST_SOURCES}) -target_link_libraries(fileopstest-cache PRIVATE Qt5::Test Qt5::DBus) +target_link_libraries(fileopstest-cache PRIVATE Qt5::Test Qt5::DBus KF5::KIOCore) target_compile_definitions(fileopstest-cache PRIVATE -DTEST_CACHE_BASED_IO) add_test(NAME fileopstest-cache COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/fileopstest-cache) set_tests_properties(fileopstest-cache PROPERTIES ENVIRONMENT KDE_FORK_SLAVES=1) add_executable(fileopstest-filejob ${KIOFUSE_TEST_SOURCES}) -target_link_libraries(fileopstest-filejob PRIVATE Qt5::Test Qt5::DBus) +target_link_libraries(fileopstest-filejob PRIVATE Qt5::Test Qt5::DBus KF5::KIOCore) add_test(NAME fileopstest-filejob COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/fileopstest-filejob) set_tests_properties(fileopstest-filejob PROPERTIES ENVIRONMENT KDE_FORK_SLAVES=1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kio-fuse-5.0.0/tests/fileopstest.cpp new/kio-fuse-5.0.1/tests/fileopstest.cpp --- old/kio-fuse-5.0.0/tests/fileopstest.cpp 2020-12-27 12:52:28.000000000 +0100 +++ new/kio-fuse-5.0.1/tests/fileopstest.cpp 2021-03-21 15:14:52.000000000 +0100 @@ -8,6 +8,7 @@ #include <sys/stat.h> #include <unistd.h> #include <errno.h> +#include <dirent.h> #include <QProcess> #include <QStandardPaths> @@ -17,6 +18,9 @@ #include <QtDBus/QDBusConnection> #include <QtDBus/QDBusReply> #include <QDebug> + +#include <KProtocolInfo> + #include "kiofuse_interface.h" #include "kiofuseprivate_interface.h" @@ -32,10 +36,12 @@ void testLocalPathToRemoteUrl(); void testLocalFileOps(); void testLocalDirOps(); + void testReaddirOps(); void testCreationOps(); void testRenameOps(); void testDeletionOps(); void testArchiveOps(); + void testManWorkaround(); void testKioErrorMapping(); void testRootLookup(); void testFilenameEscaping(); @@ -394,6 +400,63 @@ QCOMPARE(mirrorEntryList, sourceEntryList); } +void FileOpsTest::testReaddirOps() +{ + QTemporaryDir localDir; + QVERIFY(localDir.isValid()); + + // Mount the temporary dir + QString testDirPath = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); + QVERIFY(!testDirPath.isEmpty()); + + // Fill the directory with some files + for(unsigned int i=0; i<=10; i++) + { + QVERIFY(QFile(QStringLiteral("%1/tmpFile%2").arg(testDirPath).arg(i)).open(QIODevice::WriteOnly)); + } + + DIR *testDir = opendir(qPrintable(testDirPath)); + QVERIFY(testDir); + + auto opendirCleanup = qScopeGuard([&](){ closedir(testDir); }); + + QStringList testDirEntryList; + QStringList testDirEntryListUpdated; + + // Get the initial entry list to compare with + struct dirent *pDirent = nullptr; + while((pDirent = readdir(testDir)) != nullptr) + testDirEntryList.push_back(QString::fromUtf8(pDirent->d_name)); + testDirEntryList.sort(); + + // Verify that entries remain same even if we add new or remove existing entries + QVERIFY(QFile::remove(testDirPath + QStringLiteral("/tmpFile1"))); + QVERIFY(QFile(testDirPath + QStringLiteral("/addCaseFile")).open(QIODevice::WriteOnly)); + + rewinddir(testDir); + while((pDirent = readdir(testDir)) != nullptr) + testDirEntryListUpdated.push_back(QString::fromUtf8(pDirent->d_name)); + testDirEntryListUpdated.sort(); + + QCOMPARE(testDirEntryListUpdated, testDirEntryList); + + // Verify that entries remain same even if entries are modified while iterating + testDirEntryListUpdated.clear(); + rewinddir(testDir); + + unsigned int count = 1; + while((pDirent = readdir(testDir)) != nullptr) + { + QVERIFY(QFile(QStringLiteral("%1/iterCaseFile%2").arg(testDirPath).arg(count)).open(QIODevice::WriteOnly)); + + testDirEntryListUpdated.push_back(QString::fromUtf8(pDirent->d_name)); + count++; + } + testDirEntryListUpdated.sort(); + + QCOMPARE(testDirEntryListUpdated, testDirEntryList); +} + void FileOpsTest::testCreationOps() { QTemporaryDir localDir; @@ -583,6 +646,9 @@ void FileOpsTest::testArchiveOps() { + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("tar"))) + QSKIP("Test requires tar protocol to be supported. See README for packages required."); + QString outerpath = QFINDTESTDATA(QStringLiteral("data/outerarchive.tar.gz")); // Mount a file inside the archive @@ -615,6 +681,25 @@ innerfile.close(); } +void FileOpsTest::testManWorkaround() +{ + // The man ioslave has "hybrid" directories which stat as regular files but also support + // listDir. This behaviour is not supported and mounting has to fail. + + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("man"))) + QSKIP("Test requires man protocol to be supported. See README for packages required."); + + QDBusPendingReply<QString> reply = m_kiofuse_iface.mountUrl(QStringLiteral("man:foo")); + reply.waitForFinished(); + QVERIFY(reply.isError()); + QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.CannotMount")); + + reply = m_kiofuse_iface.mountUrl(QStringLiteral("man:/")); + reply.waitForFinished(); + QVERIFY(reply.isError()); + QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.CannotMount")); +} + void FileOpsTest::testKioErrorMapping() { QTemporaryFile localFile; @@ -880,6 +965,9 @@ QCOMPARE(readlink(localDir.filePath(QStringLiteral("symlink"))), localDir.filePath(QStringLiteral("somedir/../somefile"))); + if (!KProtocolInfo::isKnownProtocol(QStringLiteral("tar"))) + QSKIP("Test requires tar protocol to be supported. See README for packages required."); + // Mount something with a different origin QString outerpath = QFINDTESTDATA(QStringLiteral("data/outerarchive.tar.gz")); reply = m_kiofuse_iface.mountUrl(QStringLiteral("tar://%1/outerarchive/").arg(outerpath)).value();
