Commit 78d89b72 authored by Alexander Saoutkin's avatar Alexander Saoutkin Committed by Fabian Vogt

Adjusting behaviour during unlink, and fixing tests accordingly

Unlink now only unlinks files if openCount == 0 as we cannot
read from unlinked files when using KIO::FileJob
parent 2af7cd07
......@@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(Qt5_MIN_VERSION 5.9)
set(KF5_MIN_VERSION 5.52)
set(KF5_MIN_VERSION 5.66)
find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
......
......@@ -101,6 +101,11 @@ KIOFuseRootNode ----> KIOFuseRemoteFileNode
The root node with inode number 1 represents the root of the VFS.
Only files below are visible in the VFS hierarchy.
Note that RemoteFileNode is an abstract class. At runtime, it will actually be
instantiated as one of its subclasses (KIOFuseRemoteCacheBasedNode or
KIOFuseRemoteFileJobBasedNode). The type of class instantiated will depend on
the URL of the file. Please see the "File I/O" section to learn more.
Both kinds of remote nodes (KIOFuseRemoteDirNode, KIOFuseRemoteFileNode) have
an m_overrideUrl member which is used to implement URL mountpoints and
redirections. To get the remote URL of a node, the tree is traversed upwards
......@@ -175,7 +180,19 @@ awaitChildrenComplete method in KIOFuseVFS.
File IO
-------
File IO is implemented completely on top of a file based cache.
File IO is done in either of two ways, depending on what the protocol supports.
If the protocol supports KIO::open (and all of its operations, which at the time
of writing is read/write/seek/truncate) then IO will be based upon KIO's FileJob.
KIO's FileJob interface allows random-access IO, and hence all read, write and
truncate requests are simply forwarded to the corresponding FileJob functions.
Whilst improving performance for larger files compared to the cache-based IO
described below, the performance of individual read/write/truncate requests
if significantly reduced.
The protocols that currently support KIO::open are file/sftp/smb.
Otherwise, file IO is implemented completely on top of a file based cache.
On the first read or write access to a non truncated file, the whole file is
downloaded into a new temporary file and all readers are notified on cache
completeness changes (see awaitBytesAvailable).
......
......@@ -20,13 +20,15 @@ Performance/usability improvements:
- Better error reporting:
* Flushing happens on close(), which can't report errors, so it might be a good idea to show
a notification to the user if it fails
- Look into using KIO::read/KIO::write if possible:
* Needs ::open to succeed, has state per slave (?)
* Does not support truncation, so either a fallback to ::put or support in KIO is necessary
- Improve usability with large files over a slow connection (e.g. VM disk images):
* Don't cache too large files - might DoS the system. Determining the mime type of a single VM disk
fills /tmp (or wherever nodes created with tmpfile() reside in). Those files are impractical
to handle without KIO::read/KIO::write.
- Re-introduce writeback-caching, which would improve performance of FileJob-based IO.
Currently disabled due to design issues upstream (fuse/libfuse). See discussion:
* https://sourceforge.net/p/fuse/mailman/message/36524459/
* https://sourceforge.net/p/fuse/mailman/message/36878595/
* https://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git/tree/Documentation/filesystems/fuse-io.txt?id=5ba24197b
Bugfixes:
- Check whether write access works before writing into the cache:
Currently write access it not explicitly checked at all, possibly leading to data loss
......@@ -29,6 +29,8 @@
#include <QUrl>
#include <QString>
#include <KIO/FileJob>
class KIOFuseNode {
public:
// Creates a new node. Make sure to set the node's m_stat.st_ino once inserted.
......@@ -48,8 +50,9 @@ public:
LastDirType = RemoteDirNode,
// File types
RemoteFileNode,
RemoteSymlinkNode,
RemoteCacheBasedFileNode,
RemoteFileJobBasedFileNode,
};
// By having this as a virtual method instead of a class member
......@@ -106,15 +109,22 @@ Q_SIGNALS:
void gotChildren(int error);
};
class KIOFuseRemoteFileNode : public QObject, public KIOFuseNode {
Q_OBJECT
class KIOFuseRemoteFileNode : public KIOFuseNode {
public:
using KIOFuseNode::KIOFuseNode;
~KIOFuseRemoteFileNode() {
// Override the URL (used for UDS_URL)
QUrl m_overrideUrl;
};
class KIOFuseRemoteCacheBasedFileNode : public QObject, public KIOFuseRemoteFileNode {
Q_OBJECT
public:
using KIOFuseRemoteFileNode::KIOFuseRemoteFileNode;
~KIOFuseRemoteCacheBasedFileNode() {
if(m_localCache)
fclose(m_localCache);
}
static const NodeType Type = NodeType::RemoteFileNode;
static const NodeType Type = NodeType::RemoteCacheBasedFileNode;
NodeType type() const override { return Type; }
// Cache information
bool cacheIsComplete() { return m_cacheComplete; }
......@@ -125,8 +135,6 @@ public:
m_flushRunning = false; // If a flush is currently running
int m_numKilledJobs = 0; // reset on successful flush, incremented every time job is killed because cache is dirty (among other factors)
// Override the URL (used for UDS_URL)
QUrl m_overrideUrl;
Q_SIGNALS:
// Emitted when a download operation on this node made progress, finished or failed.
void localCacheChanged(int error);
......@@ -134,6 +142,15 @@ Q_SIGNALS:
void cacheFlushed(int error);
};
class KIOFuseRemoteFileJobBasedFileNode : public QObject, public KIOFuseRemoteFileNode {
Q_OBJECT
public:
using KIOFuseRemoteFileNode::KIOFuseRemoteFileNode;
static const NodeType Type = NodeType::RemoteFileJobBasedFileNode;
NodeType type() const override { return Type; }
};
class KIOFuseSymLinkNode : public QObject, public KIOFuseNode {
Q_OBJECT
public:
......
......@@ -39,6 +39,7 @@ public:
/** Attempts to register the service and start kiofusevfs. If both succeed,
* returns true, false otherwise. */
bool start(struct fuse_args &args, QString mountpoint, bool foreground);
KIOFuseVFS kiofusevfs;
public Q_SLOTS:
/** Mounts a URL onto the filesystem, and returns the local path back. */
......@@ -51,7 +52,6 @@ private:
/** Daemonizes the kio-fuse process, whilst also managing the registration of the org.kde.KIOFuse service.
* Derived from fuse_daemonize() in libfuse. */
bool registerServiceDaemonized();
KIOFuseVFS kiofusevfs;
/** where kiofusevfs is mounted */
QString m_mountpoint;
/** tempdir created if user does not specify mountpoint */
......
......@@ -39,6 +39,8 @@
#include <KIO/StatJob>
#include <KIO/TransferJob>
#include <KIO/DeleteJob>
#include <KIO/FileJob>
#include <KProtocolManager>
#include "debug.h"
#include "kiofusevfs.h"
......@@ -214,7 +216,7 @@ void KIOFuseVFS::stop()
for(auto it = m_dirtyNodes.begin(); it != m_dirtyNodes.end();)
{
auto node = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(nodeForIno(*it));
auto node = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(nodeForIno(*it));
++it; // Increment now as awaitNodeFlushed invalidates the iterator
......@@ -262,6 +264,11 @@ void KIOFuseVFS::fuseRequestPending()
}
}
void KIOFuseVFS::setUseFileJob(bool useFileJob)
{
m_useFileJob = useFileJob;
}
void KIOFuseVFS::init(void *userdata, fuse_conn_info *conn)
{
Q_UNUSED(userdata);
......@@ -304,7 +311,8 @@ void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int
fuse_reply_err(req, EOPNOTSUPP);
return;
case KIOFuseNode::NodeType::RemoteDirNode:
case KIOFuseNode::NodeType::RemoteFileNode:
case KIOFuseNode::NodeType::RemoteCacheBasedFileNode:
case KIOFuseNode::NodeType::RemoteFileJobBasedFileNode:
{
auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
if((to_set & ~(FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID
......@@ -341,25 +349,25 @@ void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int
node->m_stat.st_ctim = attr->st_ctim;
to_set &= ~FUSE_SET_ATTR_CTIME;
}
if(to_set & FUSE_SET_ATTR_SIZE)
if((to_set & FUSE_SET_ATTR_SIZE) && remoteFileNode->type() == KIOFuseNode::NodeType::RemoteCacheBasedFileNode)
{
auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(remoteFileNode);
// Can be done directly if the new size is zero (and there is no get going on).
// This is an optimization to avoid fetching the entire file just to ignore its content.
if(!remoteFileNode->m_localCache && attr->st_size == 0)
if(!cacheBasedFileNode->m_localCache && attr->st_size == 0)
{
// Just create an empty file
remoteFileNode->m_localCache = tmpfile();
if(remoteFileNode->m_localCache == nullptr)
cacheBasedFileNode->m_localCache = tmpfile();
if(cacheBasedFileNode->m_localCache == nullptr)
{
fuse_reply_err(req, EIO);
return;
}
remoteFileNode->m_cacheComplete = true;
remoteFileNode->m_cacheSize = remoteFileNode->m_stat.st_size = 0;
remoteFileNode->m_stat.st_mtim = remoteFileNode->m_stat.st_ctim = tsNow;
that->markCacheDirty(remoteFileNode);
cacheBasedFileNode->m_cacheComplete = true;
cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = 0;
cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
that->markCacheDirty(cacheBasedFileNode);
to_set &= ~FUSE_SET_ATTR_SIZE; // Done already!
}
......@@ -396,31 +404,54 @@ void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int
}
};
if(to_set & FUSE_SET_ATTR_SIZE)
if((to_set & FUSE_SET_ATTR_SIZE) && remoteFileNode->type() == KIOFuseNode::NodeType::RemoteCacheBasedFileNode)
{
auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(remoteFileNode);
// Have to wait until the cache is complete to truncate.
// Waiting until all bytes up to the truncation point are available won't work,
// as the fetch function would just overwrite the cache.
that->awaitCacheComplete(remoteFileNode, [=](int error) {
that->awaitCacheComplete(cacheBasedFileNode, [=] (int error) {
if(error)
sharedState->error = error;
else // Cache complete!
{
// Truncate the cache file
if(fflush(remoteFileNode->m_localCache) != 0
|| ftruncate(fileno(remoteFileNode->m_localCache), sharedState->value.st_size) == -1)
if(fflush(cacheBasedFileNode->m_localCache) != 0
|| ftruncate(fileno(cacheBasedFileNode->m_localCache), sharedState->value.st_size) == -1)
sharedState->error = errno;
else
{
remoteFileNode->m_cacheSize = remoteFileNode->m_stat.st_size = sharedState->value.st_size;
remoteFileNode->m_stat.st_mtim = remoteFileNode->m_stat.st_ctim = tsNow;
that->markCacheDirty(remoteFileNode);
cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = sharedState->value.st_size;
cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
that->markCacheDirty(cacheBasedFileNode);
}
}
markOperationCompleted(FUSE_SET_ATTR_SIZE);
});
}
else if ((to_set & FUSE_SET_ATTR_SIZE) && remoteFileNode->type() == KIOFuseNode::NodeType::RemoteFileJobBasedFileNode)
{
auto fileJobBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(remoteFileNode);
auto *fileJob = KIO::open(that->remoteUrl(fileJobBasedFileNode), QIODevice::ReadWrite);
connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
// All errors come through this signal, so error-handling is done here
if(job->error())
{
sharedState->error = kioErrorToFuseError(job->error());
markOperationCompleted(FUSE_SET_ATTR_SIZE);
}
});
connect(fileJob, &KIO::FileJob::open, [=] {
fileJob->truncate(sharedState->value.st_size);
connect(fileJob, &KIO::FileJob::truncated, [=] {
fileJob->close();
connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
fileJobBasedFileNode->m_stat.st_size = sharedState->value.st_size;
markOperationCompleted(FUSE_SET_ATTR_SIZE);
});
});
});
}
if(to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))
{
......@@ -641,6 +672,17 @@ void KIOFuseVFS::unlinkHelper(fuse_req_t req, fuse_ino_t parent, const char *nam
return;
}
if(auto fileJobNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
{
// After deleting a file, the contents become inaccessible immediately,
// so avoid creating nameless inodes. tmpfile() semantics aren't possible with FileJob.
if(fileJobNode->m_openCount)
{
fuse_reply_err(req, EBUSY);
return;
}
}
auto *job = KIO::del(that->remoteUrl(node));
that->connect(job, &KIO::SimpleJob::finished, [=] {
if(job->error())
......@@ -876,10 +918,11 @@ void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fu
default:
fuse_reply_err(req, EIO);
return;
case KIOFuseNode::NodeType::RemoteFileNode:
case KIOFuseNode::NodeType::RemoteCacheBasedFileNode:
{
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
that->awaitBytesAvailable(remoteNode, off + size, [=](int error) {
qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
that->awaitBytesAvailable(remoteNode, off + size, [=] (int error) {
if(error != 0 && error != ESPIPE)
{
fuse_reply_err(req, error);
......@@ -907,6 +950,58 @@ void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fu
buf.buf[0].pos = off;
fuse_reply_data(req, &buf, fuse_buf_copy_flags{});
});
break;
}
case KIOFuseNode::NodeType::RemoteFileJobBasedFileNode:
{
qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadOnly);
connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
// All errors come through this signal, so error-handling is done here
if(job->error())
fuse_reply_err(req, kioErrorToFuseError(job->error()));
});
connect(fileJob, &KIO::FileJob::open, [=] {
fileJob->seek(off);
connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
Q_UNUSED(job);
if(off_t(offset) != off)
{
fileJob->close();
connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
fuse_reply_err(req, EIO);
});
return;
}
auto actualSize = remoteNode->m_stat.st_size = fileJob->size();
// Reading over the end
if(off >= off_t(actualSize))
actualSize = 0;
else
actualSize = std::min(off_t(actualSize) - off, off_t(size));
fileJob->read(actualSize);
QByteArray buffer;
connect(fileJob, &KIO::FileJob::data, [=] (auto *readJob, const QByteArray &data) mutable {
Q_UNUSED(readJob);
QByteArray truncatedData = data.left(actualSize);
buffer.append(truncatedData);
actualSize -= truncatedData.size();
if(actualSize > 0)
{
// Keep reading until we get all the data we need.
fileJob->read(actualSize);
return;
}
fileJob->close();
connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
fuse_reply_buf(req, buffer.constData(), buffer.size());
});
});
});
});
break;
}
......@@ -935,14 +1030,13 @@ void KIOFuseVFS::write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t s
default:
fuse_reply_err(req, EIO);
return;
case KIOFuseNode::NodeType::RemoteFileNode:
case KIOFuseNode::NodeType::RemoteCacheBasedFileNode:
{
qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
QByteArray data(buf, size); // Copy data
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
// fi lives on the caller's stack, make a copy
auto fuseReplyCallback = [=, fi_flags=fi->flags] (int error) {
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
// fi lives on the caller's stack make a copy.
auto cacheBasedWriteCallback = [=, fi_flags=fi->flags] (int error) {
if(error && error != ESPIPE)
{
fuse_reply_err(req, error);
......@@ -972,9 +1066,57 @@ void KIOFuseVFS::write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t s
if(fi->flags & O_APPEND)
// Wait for cache to be complete to ensure valid m_cacheSize
that->awaitCacheComplete(remoteNode, fuseReplyCallback);
that->awaitCacheComplete(remoteNode, cacheBasedWriteCallback);
else
that->awaitBytesAvailable(remoteNode, off + size, fuseReplyCallback);
that->awaitBytesAvailable(remoteNode, off + size, cacheBasedWriteCallback);
break;
}
case KIOFuseNode::NodeType::RemoteFileJobBasedFileNode:
{
qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
QByteArray data(buf, size); // Copy data
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadWrite);
connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
// All errors come through this signal, so error-handling is done here
if(job->error())
fuse_reply_err(req, kioErrorToFuseError(job->error()));
});
connect(fileJob, &KIO::FileJob::open, [=, fi_flags=fi->flags] {
off_t offset = (fi_flags & O_APPEND) ? fileJob->size() : off;
fileJob->seek(offset);
connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
Q_UNUSED(job);
if (off_t(offset) != off) {
fileJob->close();
connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
fuse_reply_err(req, EIO);
});
return;
}
// Limit write to avoid killing the slave.
// @see https://phabricator.kde.org/D15448
fileJob->write(data.left(0xFFFFFF));
off_t bytesLeft = size;
connect(fileJob, &KIO::FileJob::written, [=] (auto *writeJob, KIO::filesize_t written) mutable {
Q_UNUSED(writeJob);
bytesLeft -= written;
if (bytesLeft > 0)
{
// Keep writing until we write all the data we need.
fileJob->write(data.mid(size - bytesLeft, 0xFFFFFF));
return;
}
fileJob->close();
connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
// Wait till we've flushed first...
remoteNode->m_stat.st_size = std::max(off_t(offset + data.size()), remoteNode->m_stat.st_size);
fuse_reply_write(req, data.size());
});
});
});
});
break;
}
}
}
......@@ -1003,7 +1145,7 @@ void KIOFuseVFS::release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
fuse_reply_err(req, 0); // Ignored anyway
auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
if(node->m_openCount || !remoteFileNode || !remoteFileNode->m_localCache)
return; // Nothing to do
......@@ -1046,16 +1188,12 @@ void KIOFuseVFS::fsync(fuse_req_t req, fuse_ino_t ino, int datasync, fuse_file_i
return;
}
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
if(!remoteNode)
{
if(auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
that->awaitNodeFlushed(cacheBasedFileNode, [=](int error) {
fuse_reply_err(req, error);
});
else
fuse_reply_err(req, 0);
return;
}
that->awaitNodeFlushed(remoteNode, [=](int error) {
fuse_reply_err(req, error);
});
}
bool KIOFuseVFS::isEnvironmentValid()
......@@ -1403,8 +1541,13 @@ std::shared_ptr<KIOFuseNode> KIOFuseVFS::createNodeFromUDSEntry(const KIO::UDSEn
else // Regular file pointing to URL
{
attr.st_mode |= S_IFREG;
auto ret = std::make_shared<KIOFuseRemoteFileNode>(parentIno, name, attr);
ret->m_overrideUrl = QUrl{entry.stringValue(KIO::UDSEntry::UDS_URL)};
std::shared_ptr<KIOFuseRemoteFileNode> ret = nullptr;
const QUrl nodeUrl = QUrl::fromLocalFile(entry.stringValue(KIO::UDSEntry::UDS_URL));
if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
ret = std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
else
ret = std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
ret->m_overrideUrl = nodeUrl;
return ret;
}
}
......@@ -1424,11 +1567,15 @@ std::shared_ptr<KIOFuseNode> KIOFuseVFS::createNodeFromUDSEntry(const KIO::UDSEn
else // it's a regular file
{
attr.st_mode |= S_IFREG;
return std::make_shared<KIOFuseRemoteFileNode>(parentIno, name, attr);
const QUrl nodeUrl = remoteUrl(nodeForIno(parentIno));
if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
return std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
else
return std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
}
}
void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteFileNode> &node, off_t bytes, std::function<void(int error)> callback)
void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, off_t bytes, std::function<void(int error)> callback)
{
if(bytes < 0)
{
......@@ -1500,7 +1647,7 @@ void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteFileNode
// Using a unique_ptr here to let the lambda disconnect the connection itself
auto connection = std::make_unique<QMetaObject::Connection>();
auto &conn = *connection;
conn = connect(node.get(), &KIOFuseRemoteFileNode::localCacheChanged,
conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::localCacheChanged,
[=, connection = std::move(connection)](int error) {
if(error)
{
......@@ -1523,7 +1670,7 @@ void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteFileNode
);
}
void KIOFuseVFS::awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteFileNode> &node, std::function<void (int)> callback)
void KIOFuseVFS::awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void (int)> callback)
{
return awaitBytesAvailable(node, std::numeric_limits<off_t>::max(), [callback](int error) {
// ESPIPE == cache complete, but less than the requested size, which is expected.
......@@ -1731,7 +1878,7 @@ QUrl KIOFuseVFS::makeOriginUrl(QUrl url)
return url;
}
void KIOFuseVFS::markCacheDirty(const std::shared_ptr<KIOFuseRemoteFileNode> &node)
void KIOFuseVFS::markCacheDirty(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node)
{
if(node->m_cacheDirty)
return; // Already dirty, nothing to do
......@@ -1740,7 +1887,7 @@ void KIOFuseVFS::markCacheDirty(const std::shared_ptr<KIOFuseRemoteFileNode> &no
m_dirtyNodes.insert(node->m_stat.st_ino);
}
void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteFileNode> &node, std::function<void (int)> callback)
void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void (int)> callback)
{
if(!node->m_cacheDirty && !node->m_flushRunning)
return callback(0); // Nothing to flush/wait for
......@@ -1836,7 +1983,7 @@ void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteFileNode> &
// Using a unique_ptr here to let the lambda disconnect the connection itself
auto connection = std::make_unique<QMetaObject::Connection>();
auto &conn = *connection;
conn = connect(node.get(), &KIOFuseRemoteFileNode::cacheFlushed,
conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::cacheFlushed,
[=, connection = std::move(connection)](int error) {
callback(error);
node->disconnect(*connection);
......
......@@ -61,6 +61,8 @@ public:
bool start(fuse_args &args, const QString& mountpoint);
/** Umounts the filesystem (if necessary) and flushes dirty nodes. */
void stop();
/** Designates whether KIOFuse should perform FileJob-based (KIO::open) I/O where possible. */
void setUseFileJob(bool useFileJob);
/** Runs KIO::stat on url and adds a node to the tree if successful. Calls the callback at the end. */
void mountUrl(QUrl url, std::function<void(const std::shared_ptr<KIOFuseNode>&, int)> callback);
/** Returns the path upwards until a root node. */
......@@ -140,16 +142,16 @@ private:
/** Invokes callback on error or when the bytes are available for reading/writing.
* If the file is smaller than bytes, it sets error = ESPIPE. */
void awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteFileNode> &node, off_t bytes, std::function<void(int error)> callback);
void awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, off_t bytes, std::function<void(int error)> callback);
/** Invokes callback on error or when the cache is marked as complete. */
void awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteFileNode> &node, std::function<void(int error)> callback);
void awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void(int error)> callback);
/** Invokes callback on error or when all children nodes are available */
void awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> &node, std::function<void(int error)> callback);
/** Marks a node's cache as dirty and add it to m_dirtyNodes. */
void markCacheDirty(const std::shared_ptr<KIOFuseRemoteFileNode> &node);
void markCacheDirty(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node);
/** Calls the callback once the cache is not dirty anymore (no cache counts as clean as well).
* If writes happen while a flush is sending data, a flush will be retriggered. */
void awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteFileNode> &node, std::function<void(int error)> callback);
void awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void(int error)> callback);
/** Returns the override URL for an origin node */
QUrl makeOriginUrl(QUrl url);
......@@ -182,4 +184,7 @@ private:
std::unordered_map<fuse_ino_t, std::shared_ptr<KIOFuseNode>> m_nodes;
/** Set of all nodes with a dirty cache. */
std::set<fuse_ino_t> m_dirtyNodes;
/** @see setUseFileJob() */
bool m_useFileJob;
};
......@@ -17,7 +17,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fuse_lowlevel.h>
#include <QCoreApplication>
......@@ -27,11 +26,28 @@
#include "kiofuseservice.h"
#include "kiofuseversion.h"