File indexing completed on 2024-12-01 13:48:43

0001 /*
0002    SPDX-FileCopyrightText: 2019-2020 Fabian Vogt <fabian@ritter-vogt.de>
0003    SPDX-FileCopyrightText: 2019-2020 Alexander Saoutkin <a.saoutkin@gmail.com>
0004    SPDX-License-Identifier: GPL-3.0-or-later
0005 */
0006 
0007 #include <qglobal.h>
0008 
0009 #include <sys/types.h>
0010 #include <errno.h>
0011 #include <unistd.h>
0012 #include <signal.h>
0013 #include <chrono>
0014 
0015 #ifdef Q_OS_LINUX
0016 #include <linux/fs.h>
0017 #include <sys/utsname.h>
0018 #endif
0019 
0020 #include <QDateTime>
0021 #include <QDebug>
0022 #include <QDir>
0023 #include <QVersionNumber>
0024 
0025 #include <KIO/ListJob>
0026 #include <KIO/MkdirJob>
0027 #include <KIO/StatJob>
0028 #include <KIO/TransferJob>
0029 #include <KIO/DeleteJob>
0030 #include <KIO/FileJob>
0031 #include <KProtocolManager>
0032 
0033 #include "debug.h"
0034 #include "kiofusevfs.h"
0035 
0036 // Flags that don't exist on FreeBSD; since these are used as
0037 // bit(masks), setting them to 0 effectively means they're always unset.
0038 #ifndef O_NOATIME
0039 #define O_NOATIME 0
0040 #endif
0041 #ifndef RENAME_NOREPLACE
0042 #define RENAME_NOREPLACE 0
0043 #endif
0044 
0045 // The libfuse macros make this necessary
0046 #pragma GCC diagnostic ignored "-Wpedantic"
0047 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
0048 
0049 struct KIOFuseVFS::FuseLLOps : public fuse_lowlevel_ops
0050 {
0051     FuseLLOps()
0052     {
0053         init = &KIOFuseVFS::init;
0054         lookup = &KIOFuseVFS::lookup;
0055         forget = &KIOFuseVFS::forget;
0056         getattr = &KIOFuseVFS::getattr;
0057         setattr = &KIOFuseVFS::setattr;
0058         readlink = &KIOFuseVFS::readlink;
0059         mknod = &KIOFuseVFS::mknod;
0060         mkdir = &KIOFuseVFS::mkdir;
0061         unlink = &KIOFuseVFS::unlink;
0062         rmdir = &KIOFuseVFS::rmdir;
0063         symlink = &KIOFuseVFS::symlink;
0064         rename = &KIOFuseVFS::rename;
0065         open = &KIOFuseVFS::open;
0066         read = &KIOFuseVFS::read;
0067         write = &KIOFuseVFS::write;
0068         flush = &KIOFuseVFS::flush;
0069         release = &KIOFuseVFS::release;
0070         fsync = &KIOFuseVFS::fsync;
0071         opendir = &KIOFuseVFS::opendir;
0072         readdir = &KIOFuseVFS::readdir;
0073         releasedir = &KIOFuseVFS::releasedir;
0074     }
0075 };
0076 
0077 const struct KIOFuseVFS::FuseLLOps KIOFuseVFS::fuse_ll_ops;
0078 
0079 const std::chrono::steady_clock::duration KIOFuseRemoteNodeInfo::ATTR_TIMEOUT = std::chrono::seconds(30);
0080 std::chrono::steady_clock::time_point g_timeoutEpoch = {};
0081 
0082 /* Handles partial writes and EINTR.
0083  * Returns true only if count bytes were written successfully. */
0084 static bool sane_write(int fd, const void *buf, size_t count)
0085 {
0086     size_t bytes_left = count;
0087     const char *buf_left = (const char*)buf;
0088     while(bytes_left)
0089     {
0090         ssize_t step = write(fd, buf_left, bytes_left);
0091         if(step == -1)
0092         {
0093             if(errno == EINTR)
0094                 continue;
0095             else
0096                 return false;
0097         }
0098         else if(step == 0)
0099             return false;
0100 
0101         bytes_left -= step;
0102         buf_left += step;
0103     }
0104 
0105     return true;
0106 }
0107 
0108 /* Handles partial reads and EINTR.
0109  * Returns true only if count bytes were read successfully. */
0110 static bool sane_read(int fd, void *buf, size_t count)
0111 {
0112     size_t bytes_left = count;
0113     char *buf_left = (char*)buf;
0114     while(bytes_left)
0115     {
0116         ssize_t step = read(fd, buf_left, bytes_left);
0117         if(step == -1)
0118         {
0119             if(errno == EINTR)
0120                 continue;
0121             else
0122                 return false;
0123         }
0124         else if(step == 0)
0125             return false;
0126 
0127         bytes_left -= step;
0128         buf_left += step;
0129     }
0130 
0131     return true;
0132 }
0133 
0134 static bool operator <(const struct timespec &a, const struct timespec &b)
0135 {
0136     return (a.tv_sec == b.tv_sec) ? (a.tv_nsec < b.tv_nsec) : (a.tv_sec < b.tv_sec);
0137 }
0138 
0139 /** Returns whether the given name is valid for a file. */
0140 static bool isValidFilename(const QString &name)
0141 {
0142     return !name.isEmpty() && !name.contains(QLatin1Char('/'))
0143        && name != QStringLiteral(".") && name != QStringLiteral("..");
0144 }
0145 
0146 /** Returns url with members of pathElements appended to its path,
0147   * unless url is empty, then it's returned as-is. */
0148 static QUrl addPathElements(QUrl url, QStringList pathElements)
0149 {
0150     if(url.isEmpty())
0151         return url;
0152 
0153     if(!url.path().endsWith(QLatin1Char('/')) && !pathElements.isEmpty())
0154         pathElements.prepend({});
0155 
0156     url.setPath(url.path() + pathElements.join(QLatin1Char('/')));
0157     return url;
0158 }
0159 
0160 int KIOFuseVFS::signalFd[2];
0161 
0162 KIOFuseVFS::KIOFuseVFS(QObject *parent)
0163     : QObject(parent)
0164 {
0165     struct stat attr = {};
0166     fillStatForFile(attr);
0167     attr.st_mode = S_IFDIR | 0755;
0168 
0169     auto root = std::make_shared<KIOFuseDirNode>(KIOFuseIno::Invalid, QString(), attr);
0170     insertNode(root, KIOFuseIno::Root);
0171     incrementLookupCount(root, 1); // Implicitly referenced by mounting
0172 
0173     auto deletedRoot = std::make_shared<KIOFuseDirNode>(KIOFuseIno::Invalid, QString(), attr);
0174     insertNode(deletedRoot, KIOFuseIno::DeletedRoot);
0175 }
0176 
0177 KIOFuseVFS::~KIOFuseVFS()
0178 {
0179     stop();
0180 }
0181 
0182 bool KIOFuseVFS::start(struct fuse_args &args, const QString& mountpoint)
0183 {
0184     if(!isEnvironmentValid())
0185        return false;
0186 
0187     stop();
0188 
0189     m_fuseConnInfoOpts.reset(fuse_parse_conn_info_opts(&args));
0190     m_fuseSession = fuse_session_new(&args, &fuse_ll_ops, sizeof(fuse_ll_ops), this);
0191     m_mountpoint = QDir::cleanPath(mountpoint); // Remove redundant slashes
0192     if(!m_mountpoint.endsWith(QLatin1Char('/')))
0193         m_mountpoint += QLatin1Char('/');
0194 
0195     if(!m_fuseSession)
0196         return false;
0197 
0198     if(!setupSignalHandlers()
0199        || fuse_session_mount(m_fuseSession, m_mountpoint.toUtf8().data()) != 0)
0200     {
0201         stop();
0202         return false;
0203     }
0204 
0205     // Setup a notifier on the FUSE FD
0206     int fusefd = fuse_session_fd(m_fuseSession);
0207 
0208     // Set the FD to O_NONBLOCK so that it can be read in a loop until empty
0209     int flags = fcntl(fusefd, F_GETFL);
0210     fcntl(fusefd, F_SETFL, flags | O_NONBLOCK);
0211 
0212     m_fuseNotifier = std::make_unique<QSocketNotifier>(fusefd, QSocketNotifier::Read, this);
0213     m_fuseNotifier->connect(m_fuseNotifier.get(), &QSocketNotifier::activated, this, &KIOFuseVFS::fuseRequestPending);
0214 
0215     // Arm the QEventLoopLocker
0216     m_eventLoopLocker = std::make_unique<QEventLoopLocker>();
0217 
0218     return true;
0219 }
0220 
0221 void KIOFuseVFS::stop()
0222 {
0223     if(!m_fuseSession) // Not started or already (being) stopped. Avoids reentrancy.
0224         return;
0225 
0226     // Disable the QSocketNotifier
0227     m_fuseNotifier.reset();
0228 
0229     // Disarm the QEventLoopLocker
0230     m_eventLoopLocker.reset();
0231 
0232     fuse_session_unmount(m_fuseSession);
0233     fuse_session_destroy(m_fuseSession);
0234     m_fuseSession = nullptr;
0235 
0236     // Flush all dirty nodes
0237     QEventLoop loop;
0238     bool needEventLoop = false;
0239 
0240     for(auto it = m_dirtyNodes.begin(); it != m_dirtyNodes.end();)
0241     {
0242         auto node = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(nodeForIno(*it));
0243 
0244         ++it; // Increment now as awaitNodeFlushed invalidates the iterator
0245 
0246         if(!node || (!node->m_cacheDirty && !node->m_flushRunning))
0247         {
0248             qWarning(KIOFUSE_LOG) << "Broken inode in dirty set";
0249             continue;
0250         }
0251 
0252         auto lockerPointer = std::make_shared<QEventLoopLocker>(&loop);
0253         // Trigger or wait until flush done.
0254         awaitNodeFlushed(node, [lp = std::move(lockerPointer)](int) {});
0255 
0256         needEventLoop = true;
0257     }
0258 
0259     if(needEventLoop)
0260         loop.exec(); // Wait until all QEventLoopLockers got destroyed
0261 
0262     // FIXME: If a signal arrives after this, it would be fatal.
0263     removeSignalHandlers();
0264 }
0265 
0266 void KIOFuseVFS::fuseRequestPending()
0267 {
0268     // Never deallocated, just reused
0269     static struct fuse_buf fbuf = {};
0270 
0271     // Read requests until empty (-EAGAIN) or error
0272     for(;;)
0273     {
0274         int res = fuse_session_receive_buf(m_fuseSession, &fbuf);
0275 
0276         if (res == -EINTR || res == -EAGAIN)
0277             break;
0278 
0279         if (res <= 0)
0280         {
0281             if(res < 0) // Error
0282                 qWarning(KIOFUSE_LOG) << "Error reading FUSE request:" << strerror(errno);
0283 
0284             // Error or umounted -> quit
0285             stop();
0286             break;
0287         }
0288 
0289         fuse_session_process_buf(m_fuseSession, &fbuf);
0290     }
0291 }
0292 
0293 void KIOFuseVFS::setUseFileJob(bool useFileJob)
0294 {
0295     m_useFileJob = useFileJob;
0296 }
0297 
0298 void KIOFuseVFS::mountUrl(const QUrl &url, const std::function<void (const QString &, int)> &callback)
0299 {
0300     qDebug(KIOFUSE_LOG) << "Trying to mount" << url;
0301 
0302     if(!url.isValid()) {
0303         qDebug(KIOFUSE_LOG) << "Got invalid URL";
0304         return callback({}, EINVAL);
0305     }
0306 
0307     const auto vfsPath = mapUrlToVfs(url).join(QLatin1Char('/'));
0308     // If it's not mounted, this returns an empty QUrl.
0309     // Checking with the target URL is done to detect password changes.
0310     if(localPathToRemoteUrl(vfsPath) == url) {
0311         qDebug(KIOFUSE_LOG) << "Already mounted";
0312         return callback(vfsPath, 0);
0313     }
0314 
0315     // First make sure it actually exists
0316     qDebug(KIOFUSE_LOG) << "Stating" << url.toDisplayString() << "for mount";
0317     auto statJob = KIO::stat(url);
0318     statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
0319                                                 // files over plain HTTP
0320     connect(statJob, &KIO::StatJob::result, this, [=] {
0321         if(statJob->error())
0322         {
0323             qDebug(KIOFUSE_LOG) << statJob->errorString();
0324             return callback({}, kioErrorToFuseError(statJob->error()));
0325         }
0326 
0327         // The file exists, try to mount it
0328         auto pathElements = url.path().split(QLatin1Char('/'));
0329         pathElements.removeAll({});
0330 
0331         return findAndCreateOrigin(originOfUrl(url), pathElements, callback);
0332     });
0333 }
0334 
0335 QStringList KIOFuseVFS::mapUrlToVfs(const QUrl &url)
0336 {
0337     // Build the path where it will appear in the VFS
0338     auto urlWithoutPassword = url;
0339     urlWithoutPassword.setPassword({});
0340     auto targetPathComponents = QStringList{urlWithoutPassword.scheme(), urlWithoutPassword.authority()};
0341     targetPathComponents.append(url.path().split(QLatin1Char('/')));
0342 
0343     // Strip empty path elements, for instance in
0344     // "file:///home/foo"
0345     //         ^                   V
0346     // "ftp://user@host/dir/ectory/"
0347     targetPathComponents.removeAll({});
0348 
0349     return targetPathComponents;
0350 }
0351 
0352 void KIOFuseVFS::findAndCreateOrigin(const QUrl &url, const QStringList &pathElements, const std::function<void (const QString &, int)> &callback)
0353 {
0354     qDebug(KIOFUSE_LOG) << "Trying origin" << url.toDisplayString();
0355     auto statJob = KIO::stat(url);
0356     statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
0357                                                 // files over plain HTTP
0358     connect(statJob, &KIO::StatJob::result, this, [=] {
0359         if(statJob->error())
0360         {
0361             qDebug(KIOFUSE_LOG) << statJob->errorString();
0362 
0363             if(!pathElements.isEmpty())
0364                 return findAndCreateOrigin(addPathElements(url, pathElements.mid(0, 1)), pathElements.mid(1), callback);
0365 
0366             qDebug(KIOFUSE_LOG) << "Creating origin failed";
0367             return callback({}, kioErrorToFuseError(statJob->error()));
0368         }
0369 
0370         qDebug(KIOFUSE_LOG) << "Origin found at" << url.toDisplayString();
0371 
0372         auto targetPathComponents = mapUrlToVfs(url);
0373 
0374         if(std::any_of(targetPathComponents.begin(), targetPathComponents.end(),
0375                        [](const QString &s) { return !isValidFilename(s); }))
0376             return callback({}, EINVAL); // Invalid path (contains '.' or '..')
0377 
0378         auto currentNode = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(KIOFuseIno::Root));
0379 
0380         // Traverse/create all components until the last
0381         for(int pathIdx = 0; pathIdx < targetPathComponents.size() - 1; ++pathIdx)
0382         {
0383             auto name = targetPathComponents[pathIdx];
0384             auto nextNode = nodeByName(currentNode, name);
0385             auto nextDirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(nextNode);
0386 
0387             if(!nextNode)
0388             {
0389                 struct stat attr = {};
0390                 fillStatForFile(attr);
0391                 attr.st_mode = S_IFDIR | 0755;
0392 
0393                 nextDirNode = std::make_shared<KIOFuseDirNode>(currentNode->m_stat.st_ino, name, attr);
0394                 insertNode(nextDirNode);
0395             }
0396             else if(!nextDirNode)
0397             {
0398                 qWarning(KIOFUSE_LOG) << "Node" << nextNode->m_nodeName << "not a dir?";
0399                 return callback({}, EIO);
0400             }
0401 
0402             currentNode = nextDirNode;
0403         }
0404 
0405         auto finalNode = nodeByName(currentNode, targetPathComponents.last());
0406         if(!finalNode)
0407         {
0408             finalNode = createNodeFromUDSEntry(statJob->statResult(), currentNode->m_stat.st_ino, targetPathComponents.last());
0409             if(!finalNode)
0410             {
0411                 qWarning(KIOFUSE_LOG) << "Unable to create a valid final node for" << url.toDisplayString() << "from its UDS Entry";
0412                 return callback({}, EIO);
0413             }
0414 
0415             // Some ioworkers like man:/ implement "index files" for folders (including /) by making
0416             // them look as regular file when stating, but they also support listDir for directory
0417             // functionality. This behaviour is not compatible, so just reject it outright.
0418             if((url.path().isEmpty() || url.path() == QStringLiteral("/"))
0419                && !S_ISDIR(finalNode->m_stat.st_mode))
0420             {
0421                 qWarning(KIOFUSE_LOG) << "Root of mount at" << url.toDisplayString() << "not a directory";
0422                 return callback({}, ENOTDIR);
0423             }
0424 
0425             insertNode(finalNode);
0426         }
0427 
0428         auto originNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(finalNode);
0429         if(!originNode)
0430         {
0431             qWarning(KIOFUSE_LOG) << "Origin" << finalNode->m_nodeName << "exists but not a remote node?";
0432             return callback({}, EIO);
0433         }
0434 
0435         originNode->m_overrideUrl = url; // Allow the user to change the password
0436         return callback((targetPathComponents + pathElements).join(QLatin1Char('/')), 0);
0437     });
0438 }
0439 
0440 void KIOFuseVFS::init(void *userdata, fuse_conn_info *conn)
0441 {
0442     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(userdata);
0443 
0444     if(that->m_fuseConnInfoOpts)
0445     {
0446         fuse_apply_conn_info_opts(that->m_fuseConnInfoOpts.get(), conn);
0447         that->m_fuseConnInfoOpts.reset();
0448     }
0449 
0450     conn->want &= ~FUSE_CAP_HANDLE_KILLPRIV; // Don't care about resetting setuid/setgid flags
0451     conn->want &= ~FUSE_CAP_ATOMIC_O_TRUNC; // Use setattr with st_size = 0 instead of open with O_TRUNC
0452     // Writeback caching needs fuse_notify calls for shared filesystems, but those are broken by design
0453     conn->want &= ~FUSE_CAP_WRITEBACK_CACHE;
0454     conn->time_gran = 1000000000; // Only second resolution for mtime
0455 }
0456 
0457 void KIOFuseVFS::getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
0458 {
0459     Q_UNUSED(fi);
0460     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0461     auto node = that->nodeForIno(ino);
0462     if(!node)
0463     {
0464         fuse_reply_err(req, EIO);
0465         return;
0466     }
0467 
0468     return that->awaitAttrRefreshed(node, [=] (int error) {
0469         Q_UNUSED(error); // Just send the old attr...
0470         replyAttr(req, node);
0471     });
0472 }
0473 
0474 void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, fuse_file_info *fi)
0475 {
0476     Q_UNUSED(fi);
0477     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0478     auto node = that->nodeForIno(ino);
0479     if(!node)
0480     {
0481         fuse_reply_err(req, EIO);
0482         return;
0483     }
0484 
0485     auto remoteDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
0486     auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
0487 
0488     if(!remoteDirNode && !remoteFileNode)
0489     {
0490         fuse_reply_err(req, EOPNOTSUPP);
0491         return;
0492     }
0493 
0494     if((to_set & ~(FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID
0495                   | FUSE_SET_ATTR_MODE
0496                   | FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW
0497                   | FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW
0498                   | FUSE_SET_ATTR_CTIME))
0499        || (!remoteFileNode && (to_set & FUSE_SET_ATTR_SIZE))) // Unsupported operation requested?
0500     {
0501         // Don't do anything
0502         fuse_reply_err(req, EOPNOTSUPP);
0503         return;
0504     }
0505 
0506     auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
0507     auto fileJobBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
0508 
0509     // To have equal atim and mtim
0510     struct timespec tsNow;
0511     clock_gettime(CLOCK_REALTIME, &tsNow);
0512 
0513     // Can anything be done directly?
0514 
0515     // This is a hack: Access and change time are not actually passed through to KIO.
0516     // The kernel sends request for those if writeback caching is enabled, so it's not
0517     // possible to ignore them. So just save them in the local cache.
0518     if(to_set & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW))
0519     {
0520         if(to_set & FUSE_SET_ATTR_ATIME_NOW)
0521             attr->st_atim = tsNow;
0522 
0523         node->m_stat.st_atim = attr->st_atim;
0524         to_set &= ~(FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW);
0525     }
0526     if(to_set & FUSE_SET_ATTR_CTIME)
0527     {
0528         node->m_stat.st_ctim = attr->st_ctim;
0529         to_set &= ~FUSE_SET_ATTR_CTIME;
0530     }
0531     if((to_set & FUSE_SET_ATTR_SIZE) && cacheBasedFileNode)
0532     {
0533         // Can be done directly if the new size is zero (and there is no get going on).
0534         // This is an optimization to avoid fetching the entire file just to ignore its content.
0535         if(!cacheBasedFileNode->m_localCache && attr->st_size == 0)
0536         {
0537             // Just create an empty file
0538             cacheBasedFileNode->m_localCache = tmpfile();
0539             if(!cacheBasedFileNode->m_localCache)
0540             {
0541                 fuse_reply_err(req, EIO);
0542                 return;
0543             }
0544 
0545             cacheBasedFileNode->m_cacheComplete = true;
0546             cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = 0;
0547             cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
0548             that->markCacheDirty(cacheBasedFileNode);
0549 
0550             to_set &= ~FUSE_SET_ATTR_SIZE; // Done already!
0551         }
0552     }
0553 
0554     if(!to_set) // Done already?
0555     {
0556         replyAttr(req, node);
0557         return;
0558     }
0559 
0560     // Everything else has to be done async - but there might be multiple ops that
0561     // need to be coordinated. If an operation completes and clearing its value(s)
0562     // in to_set_remaining leaves a zero value, it replies with fuse_reply_attr if
0563     // error is zero and fuse_reply_err(error) otherwise.
0564     struct SetattrState {
0565         int to_set_remaining;
0566         int error;
0567         struct stat value;
0568     };
0569 
0570     auto sharedState = std::make_shared<SetattrState>((SetattrState){to_set, 0, *attr});
0571 
0572     auto markOperationCompleted = [=] (int to_set_done) {
0573         sharedState->to_set_remaining &= ~to_set_done;
0574         if(!sharedState->to_set_remaining)
0575         {
0576             if(sharedState->error)
0577                 fuse_reply_err(req, sharedState->error);
0578             else
0579                 replyAttr(req, node);
0580         }
0581     };
0582 
0583     if((to_set & FUSE_SET_ATTR_SIZE) && cacheBasedFileNode)
0584     {
0585         // Have to wait until the cache is complete to truncate.
0586         // Waiting until all bytes up to the truncation point are available won't work,
0587         // as the fetch function would just overwrite the cache.
0588         that->awaitCacheComplete(cacheBasedFileNode, [=] (int error) {
0589             if(error)
0590                 sharedState->error = error;
0591             else // Cache complete!
0592             {
0593                 // Truncate the cache file
0594                 if(fflush(cacheBasedFileNode->m_localCache) != 0
0595                     || ftruncate(fileno(cacheBasedFileNode->m_localCache), sharedState->value.st_size) == -1)
0596                     sharedState->error = errno;
0597                 else
0598                 {
0599                     cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = sharedState->value.st_size;
0600                     cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
0601                     that->markCacheDirty(cacheBasedFileNode);
0602                 }
0603             }
0604             markOperationCompleted(FUSE_SET_ATTR_SIZE);
0605         });
0606     }
0607     else if ((to_set & FUSE_SET_ATTR_SIZE) && fileJobBasedFileNode)
0608     {
0609         auto *fileJob = KIO::open(that->remoteUrl(fileJobBasedFileNode), QIODevice::ReadWrite);
0610         connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
0611             // All errors come through this signal, so error-handling is done here
0612             if(job->error())
0613             {
0614                 sharedState->error = kioErrorToFuseError(job->error());
0615                 markOperationCompleted(FUSE_SET_ATTR_SIZE);
0616             }
0617         });
0618         connect(fileJob, &KIO::FileJob::open, [=] {
0619             fileJob->truncate(sharedState->value.st_size);
0620             connect(fileJob, &KIO::FileJob::truncated, [=] {
0621                 fileJob->close();
0622                 connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::fileClosed), [=] {
0623                     fileJobBasedFileNode->m_stat.st_size = sharedState->value.st_size;
0624                     markOperationCompleted(FUSE_SET_ATTR_SIZE);
0625                 });
0626             });
0627         });
0628     }
0629 
0630     if(to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))
0631     {
0632         // KIO uses strings for passing user and group, but the VFS uses IDs exclusively.
0633         // So this needs a roundtrip.
0634 
0635         uid_t newUid = (to_set & FUSE_SET_ATTR_UID) ? attr->st_uid : node->m_stat.st_uid;
0636         gid_t newGid = (to_set & FUSE_SET_ATTR_GID) ? attr->st_gid : node->m_stat.st_gid;
0637         auto *pw = getpwuid(newUid);
0638         auto *gr = getgrgid(newGid);
0639 
0640         if(!pw || !gr)
0641         {
0642             sharedState->error = ENOENT;
0643             markOperationCompleted(FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID);
0644         }
0645         else
0646         {
0647             QString newOwner = QString::fromUtf8(pw->pw_name),
0648                     newGroup = QString::fromUtf8(gr->gr_name);
0649 
0650             auto *job = KIO::chown(that->remoteUrl(node), newOwner, newGroup);
0651             that->connect(job, &KIO::SimpleJob::finished, [=] {
0652                 if(job->error())
0653                     sharedState->error = kioErrorToFuseError(job->error());
0654                 else
0655                 {
0656                     node->m_stat.st_uid = newUid;
0657                     node->m_stat.st_gid = newGid;
0658                     node->m_stat.st_ctim = tsNow;
0659                 }
0660 
0661                 markOperationCompleted(FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID);
0662             });
0663         }
0664     }
0665 
0666     if(to_set & (FUSE_SET_ATTR_MODE))
0667     {
0668         auto newMode = attr->st_mode & ~S_IFMT;
0669         auto *job = KIO::chmod(that->remoteUrl(node), newMode);
0670         that->connect(job, &KIO::SimpleJob::finished, [=] {
0671             if(job->error())
0672                 sharedState->error = kioErrorToFuseError(job->error());
0673             else
0674             {
0675                 node->m_stat.st_mode = (node->m_stat.st_mode & S_IFMT) | newMode;
0676                 node->m_stat.st_ctim = tsNow;
0677             }
0678 
0679             markOperationCompleted(FUSE_SET_ATTR_MODE);
0680         });
0681     }
0682 
0683     if(to_set & (FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW))
0684     {
0685         if(to_set & FUSE_SET_ATTR_MTIME_NOW)
0686             sharedState->value.st_mtim = tsNow;
0687 
0688         auto time = QDateTime::fromMSecsSinceEpoch(qint64(sharedState->value.st_mtim.tv_sec) * 1000
0689                                                    + sharedState->value.st_mtim.tv_nsec / 1000000);
0690         auto *job = KIO::setModificationTime(that->remoteUrl(node), time);
0691         that->connect(job, &KIO::SimpleJob::finished, [=] {
0692             if(job->error())
0693                 sharedState->error = kioErrorToFuseError(job->error());
0694             else // This is not quite correct, as KIO rounded the value down to a second
0695                 node->m_stat.st_mtim = sharedState->value.st_mtim;
0696 
0697             markOperationCompleted(FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW);
0698         });
0699     }
0700 }
0701 
0702 void KIOFuseVFS::readlink(fuse_req_t req, fuse_ino_t ino)
0703 {
0704     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0705     auto node = that->nodeForIno(ino);
0706     if(!node)
0707     {
0708         fuse_reply_err(req, EIO);
0709         return;
0710     }
0711 
0712     auto symlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node);
0713     if(!symlinkNode)
0714     {
0715         fuse_reply_err(req, EINVAL);
0716         return;
0717     }
0718 
0719     that->awaitAttrRefreshed(node, [=](int error) {
0720         Q_UNUSED(error); // Just send the old target...
0721 
0722         QString target = symlinkNode->m_target;
0723 
0724         // Convert an absolute link to be absolute within its origin
0725         if(QDir::isAbsolutePath(target))
0726         {
0727             target = target.mid(1); // Strip the initial /
0728             QUrl origin = that->originOfUrl(that->remoteUrl(symlinkNode));
0729             origin = addPathElements(origin, target.split(QLatin1Char('/')));
0730             target = that->m_mountpoint + that->mapUrlToVfs(origin).join(QLatin1Char('/'));
0731             qCDebug(KIOFUSE_LOG) << "Detected reading of absolute symlink" << symlinkNode->m_target << "at" << that->virtualPath(symlinkNode) << ", rewritten to" << target;
0732         }
0733 
0734         fuse_reply_readlink(req, target.toUtf8().data());
0735     });
0736 }
0737 
0738 void KIOFuseVFS::mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
0739 {
0740     Q_UNUSED(rdev);
0741 
0742     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0743     auto node = that->nodeForIno(parent);
0744     if(!node)
0745     {
0746         fuse_reply_err(req, EIO);
0747         return;
0748     }
0749 
0750     auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
0751     if(!remote)
0752     {
0753         fuse_reply_err(req, EINVAL);
0754         return;
0755     }
0756 
0757     // No type means regular file as well
0758     if((mode & S_IFMT) != S_IFREG && (mode & S_IFMT) != 0)
0759     {
0760         fuse_reply_err(req, EOPNOTSUPP);
0761         return;
0762     }
0763 
0764     auto nameStr = QString::fromUtf8(name);
0765     auto url = addPathElements(that->remoteUrl(node), {nameStr});
0766     auto *job = KIO::put(url, mode & ~S_IFMT);
0767     // Not connecting to the dataReq signal at all results in an empty file
0768     that->connect(job, &KIO::SimpleJob::finished, that, [=] {
0769         if(job->error())
0770         {
0771             fuse_reply_err(req, kioErrorToFuseError(job->error()));
0772             return;
0773         }
0774 
0775         that->awaitChildMounted(remote, nameStr, [=](auto node, int error) {
0776             if(error)
0777                 fuse_reply_err(req, error);
0778             else
0779                 that->replyEntry(req, node);
0780         });
0781     });
0782 }
0783 
0784 void KIOFuseVFS::mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
0785 {
0786     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0787     auto node = that->nodeForIno(parent);
0788     if(!node)
0789     {
0790         fuse_reply_err(req, EIO);
0791         return;
0792     }
0793 
0794     auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
0795     if(!remote)
0796     {
0797         fuse_reply_err(req, EINVAL);
0798         return;
0799     }
0800 
0801     auto namestr = QString::fromUtf8(name);
0802     auto url = addPathElements(that->remoteUrl(node), {namestr});
0803     auto *job = KIO::mkdir(url, mode & ~S_IFMT);
0804     that->connect(job, &KIO::SimpleJob::finished, that, [=] {
0805         if(job->error())
0806         {
0807             fuse_reply_err(req, kioErrorToFuseError(job->error()));
0808             return;
0809         }
0810 
0811         that->awaitChildMounted(remote, namestr, [=](auto node, int error) {
0812             if(error)
0813                 fuse_reply_err(req, error);
0814             else
0815                 that->replyEntry(req, node);
0816         });
0817     });
0818 }
0819 
0820 void KIOFuseVFS::unlinkHelper(fuse_req_t req, fuse_ino_t parent, const char *name, bool isDirectory)
0821 {
0822     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0823     auto parentNode = that->nodeForIno(parent);
0824     if(!parentNode)
0825     {
0826         fuse_reply_err(req, EIO);
0827         return;
0828     }
0829 
0830     // Make sure the to-be deleted node is in a remote dir
0831     auto parentDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode);
0832     if(!parentDirNode)
0833     {
0834         fuse_reply_err(req, EINVAL);
0835         return;
0836     }
0837 
0838     auto node = that->nodeByName(parentDirNode, QString::fromUtf8(name));
0839     if(!node)
0840     {
0841         fuse_reply_err(req, ENOENT);
0842         return;
0843     }
0844 
0845     auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
0846 
0847     if(!isDirectory && dirNode != nullptr)
0848     {
0849         fuse_reply_err(req, EISDIR);
0850         return;
0851     }
0852 
0853     if(isDirectory && dirNode == nullptr)
0854     {
0855         fuse_reply_err(req, ENOTDIR);
0856         return;
0857     }
0858     else if(dirNode && !dirNode->m_childrenInos.empty())
0859     {
0860         // If node is a dir, it must be empty
0861         fuse_reply_err(req, ENOTEMPTY);
0862         return;
0863     }
0864 
0865     if(auto fileJobNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
0866     {
0867         // After deleting a file, the contents become inaccessible immediately,
0868         // so avoid creating nameless inodes. tmpfile() semantics aren't possible with FileJob.
0869         if(fileJobNode->m_openCount)
0870         {
0871             fuse_reply_err(req, EBUSY);
0872             return;
0873         }
0874     }
0875 
0876     auto *job = KIO::del(that->remoteUrl(node));
0877     that->connect(job, &KIO::SimpleJob::finished, that, [=] {
0878         if(job->error())
0879         {
0880             fuse_reply_err(req, kioErrorToFuseError(job->error()));
0881             return;
0882         }
0883 
0884         that->markNodeDeleted(node);
0885         fuse_reply_err(req, 0);
0886     });
0887 }
0888 
0889 void KIOFuseVFS::unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
0890 {
0891     unlinkHelper(req, parent, name, false);
0892 }
0893 
0894 void KIOFuseVFS::rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
0895 {
0896     unlinkHelper(req, parent, name, true);
0897 }
0898 
0899 void KIOFuseVFS::symlink(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
0900 {
0901     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0902     auto node = that->nodeForIno(parent);
0903     if(!node)
0904     {
0905         fuse_reply_err(req, EIO);
0906         return;
0907     }
0908 
0909     auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
0910     if(!remote)
0911     {
0912         fuse_reply_err(req, EINVAL);
0913         return;
0914     }
0915 
0916     QString target = QString::fromUtf8(link);
0917 
0918     // Convert an absolute link within the VFS to an absolute link on the origin
0919     // (inverse of readlink)
0920     if(QDir::isAbsolutePath(target) && target.startsWith(that->m_mountpoint))
0921     {
0922         QUrl remoteSourceUrl = that->remoteUrl(remote),
0923              // QDir::relativePath would mangle the path, we want to keep it as-is
0924              remoteTargetUrl = that->localPathToRemoteUrl(target.mid(that->m_mountpoint.length()));
0925 
0926         if(remoteTargetUrl.isValid()
0927            && remoteSourceUrl.scheme() == remoteTargetUrl.scheme()
0928            && remoteSourceUrl.authority() == remoteTargetUrl.authority())
0929         {
0930             target = remoteTargetUrl.path();
0931             if(!target.startsWith(QLatin1Char('/')))
0932                 target.prepend(QLatin1Char('/'));
0933 
0934             qCDebug(KIOFUSE_LOG) << "Detected creation of absolute symlink, rewritten to" << target;
0935         }
0936         else
0937             qCWarning(KIOFUSE_LOG) << "Creation of absolute symlink to other origin";
0938     }
0939 
0940     auto namestr = QString::fromUtf8(name);
0941     auto url = addPathElements(that->remoteUrl(node), {namestr});
0942     auto *job = KIO::symlink(target, url);
0943     that->connect(job, &KIO::SimpleJob::finished, that, [=] {
0944         if(job->error())
0945         {
0946             fuse_reply_err(req, kioErrorToFuseError(job->error()));
0947             return;
0948         }
0949 
0950         that->awaitChildMounted(remote, namestr, [=](auto node, int error) {
0951             if(error)
0952                 fuse_reply_err(req, error);
0953             else
0954                 that->replyEntry(req, node);
0955         });
0956     });
0957 }
0958 
0959 void KIOFuseVFS::open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
0960 {
0961     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0962     auto node = that->nodeForIno(ino);
0963     if(!node)
0964     {
0965         fuse_reply_err(req, EIO);
0966         return;
0967     }
0968 
0969     node->m_openCount += 1;
0970 
0971     if (!(fi->flags & O_NOATIME))
0972         clock_gettime(CLOCK_REALTIME, &node->m_stat.st_atim);
0973 
0974     fuse_reply_open(req, fi);
0975 }
0976 
0977 void KIOFuseVFS::rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
0978 {
0979     if(flags & ~(RENAME_NOREPLACE))
0980     {
0981         // RENAME_EXCHANGE could be emulated locally, but not with the same guarantees
0982         fuse_reply_err(req, EOPNOTSUPP);
0983         return;
0984     }
0985 
0986     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
0987     auto parentNode = that->nodeForIno(parent), newParentNode = that->nodeForIno(newparent);
0988     if(!parentNode || !newParentNode)
0989     {
0990         fuse_reply_err(req, EIO);
0991         return;
0992     }
0993 
0994     auto remoteParent = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode),
0995          remoteNewParent = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(newParentNode);
0996     if(!remoteParent || !remoteNewParent)
0997     {
0998         fuse_reply_err(req, EINVAL);
0999         return;
1000     }
1001 
1002     auto node = that->nodeByName(remoteParent, QString::fromUtf8(name));
1003     if(!node)
1004     {
1005         fuse_reply_err(req, ENOENT);
1006         return;
1007     }
1008 
1009     QString newNameStr = QString::fromUtf8(newname);
1010 
1011     auto replacedNode = that->nodeByName(remoteNewParent, newNameStr);
1012 
1013     // Ensure that if node is a directory, replacedNode either does not exist or is an empty directory.
1014     if(std::dynamic_pointer_cast<KIOFuseDirNode>(node) && replacedNode)
1015     {
1016         auto replacedDir = std::dynamic_pointer_cast<KIOFuseDirNode>(replacedNode);
1017         if(!replacedDir)
1018         {
1019             fuse_reply_err(req, ENOTDIR);
1020             return;
1021         }
1022         if(replacedDir && !replacedDir->m_childrenInos.empty())
1023         {
1024             fuse_reply_err(req, ENOTEMPTY);
1025             return;
1026         }
1027     }
1028 
1029     auto url = addPathElements(that->remoteUrl(remoteParent), {QString::fromUtf8(name)}),
1030          newUrl = addPathElements(that->remoteUrl(remoteNewParent), {newNameStr});
1031 
1032     auto *job = KIO::rename(url, newUrl, (flags & RENAME_NOREPLACE) ? KIO::DefaultFlags : KIO::Overwrite);
1033     that->connect(job, &KIO::SimpleJob::finished, that, [=] {
1034         if(job->error())
1035             fuse_reply_err(req, kioErrorToFuseError(job->error()));
1036         else
1037         {
1038             if(replacedNode)
1039                 that->markNodeDeleted(replacedNode);
1040 
1041             that->reparentNode(node, newParentNode->m_stat.st_ino);
1042             node->m_nodeName = newNameStr;
1043 
1044             clock_gettime(CLOCK_REALTIME, &node->m_stat.st_ctim);
1045             fuse_reply_err(req, 0);
1046         }
1047     });
1048 }
1049 
1050 static void appendDirentry(std::vector<char> &dirbuf, fuse_req_t req, const char *name, const struct stat *stbuf)
1051 {
1052     size_t oldsize = dirbuf.size();
1053     dirbuf.resize(oldsize + fuse_add_direntry(req, nullptr, 0, name, nullptr, 0));
1054     fuse_add_direntry(req, dirbuf.data() + oldsize, dirbuf.size() + oldsize, name, stbuf, dirbuf.size());
1055 }
1056 
1057 void KIOFuseVFS::opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1058 {
1059     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1060     auto node = that->nodeForIno(ino);
1061     if(!node)
1062     {
1063         fuse_reply_err(req, EIO);
1064         return;
1065     }
1066 
1067     auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1068 
1069     if(!dirNode)
1070     {
1071         fuse_reply_err(req, ENOTDIR);
1072         return;
1073     }
1074 
1075     node->m_openCount += 1;
1076 
1077     that->awaitChildrenComplete(dirNode, [=, myfi=*fi](int error) mutable {
1078         if(error)
1079         {
1080             fuse_reply_err(req, error);
1081             return;
1082         }
1083 
1084         auto dirbuf = std::make_unique<std::vector<char>>();
1085 
1086         appendDirentry(*dirbuf, req, ".", &node->m_stat);
1087 
1088         std::shared_ptr<KIOFuseNode> parentNode;
1089         if(node->m_parentIno != KIOFuseIno::DeletedRoot)
1090             parentNode = that->nodeForIno(node->m_parentIno);
1091         if(!parentNode)
1092             parentNode = that->nodeForIno(KIOFuseIno::Root);
1093         if(parentNode)
1094             appendDirentry(*dirbuf, req, "..", &parentNode->m_stat);
1095 
1096         for(auto ino : dirNode->m_childrenInos)
1097         {
1098             auto child = that->nodeForIno(ino);
1099             if(!child)
1100             {
1101                 qWarning(KIOFUSE_LOG) << "Node" << node->m_nodeName << "references nonexistent child";
1102                 continue;
1103             }
1104 
1105             appendDirentry(*dirbuf, req, qPrintable(child->m_nodeName), &child->m_stat);
1106         }
1107 
1108         myfi.fh = reinterpret_cast<uint64_t>(dirbuf.release());
1109 
1110         if (!(myfi.flags & O_NOATIME))
1111             clock_gettime(CLOCK_REALTIME, &node->m_stat.st_atim);
1112 
1113         fuse_reply_open(req, &myfi);
1114     });
1115 }
1116 
1117 void KIOFuseVFS::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
1118 {
1119     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1120     auto node = that->nodeForIno(ino);
1121     if(!node)
1122     {
1123         fuse_reply_err(req, EIO);
1124         return;
1125     }
1126 
1127     auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1128     if(!dirNode)
1129     {
1130         fuse_reply_err(req, ENOTDIR);
1131         return;
1132     }
1133 
1134     std::vector<char>* dirbuf = reinterpret_cast<std::vector<char>*>(fi->fh);
1135     if(!dirbuf)
1136     {
1137         fuse_reply_err(req, EIO);
1138         return;
1139     }
1140 
1141     if(off < off_t(dirbuf->size()))
1142         fuse_reply_buf(req, dirbuf->data() + off, std::min(off_t(size), off_t(dirbuf->size()) - off));
1143     else
1144         fuse_reply_buf(req, nullptr, 0);
1145 }
1146 
1147 void KIOFuseVFS::releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
1148 {
1149     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1150     auto node = that->nodeForIno(ino);
1151     if(!node)
1152     {
1153         fuse_reply_err(req, EIO);
1154         return;
1155     }
1156 
1157     auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1158     if(!dirNode)
1159     {
1160         fuse_reply_err(req, ENOTDIR);
1161         return;
1162     }
1163 
1164     node->m_openCount -= 1;
1165     if(std::vector<char> *ptr = reinterpret_cast<std::vector<char>*>(fi->fh))
1166     {
1167         delete ptr;
1168     }
1169     else{
1170         qWarning(KIOFUSE_LOG) << "File handler of node" << node->m_nodeName << "already null";
1171     }
1172       
1173     fuse_reply_err(req, 0);
1174 }
1175 
1176 void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fuse_file_info *fi)
1177 {
1178     Q_UNUSED(fi);
1179     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1180     auto node = that->nodeForIno(ino);
1181     if(!node)
1182     {
1183         fuse_reply_err(req, EIO);
1184         return;
1185     }
1186 
1187     if(std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1188     {
1189         fuse_reply_err(req, EISDIR);
1190         return;
1191     }
1192 
1193     if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1194     {
1195         qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
1196         that->awaitBytesAvailable(remoteNode, off + size, [=] (int error) {
1197             if(error != 0 && error != ESPIPE)
1198             {
1199                 fuse_reply_err(req, error);
1200                 return;
1201             }
1202 
1203             auto actualSize = size;
1204 
1205             if(error == ESPIPE)
1206             {
1207                 // Reading over the end
1208                 if(off >= remoteNode->m_cacheSize)
1209                     actualSize = 0;
1210                 else
1211                     actualSize = std::min(remoteNode->m_cacheSize - off, off_t(size));
1212             }
1213 
1214             // Make sure that the kernel has the data
1215             fflush(remoteNode->m_localCache);
1216 
1217             // Construct a buf pointing to the cache file
1218             fuse_bufvec buf = FUSE_BUFVEC_INIT(actualSize);
1219             buf.buf[0].fd = fileno(remoteNode->m_localCache);
1220             buf.buf[0].flags = static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
1221             buf.buf[0].pos = off;
1222 
1223             fuse_reply_data(req, &buf, fuse_buf_copy_flags{});
1224         });
1225 
1226         return;
1227     }
1228     else if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
1229     {
1230         qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
1231 
1232         if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1233         {
1234             fuse_reply_err(req, ENOENT);
1235             return;
1236         }
1237 
1238         auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadOnly);
1239         connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
1240             // All errors come through this signal, so error-handling is done here
1241             if(job->error())
1242                 fuse_reply_err(req, kioErrorToFuseError(job->error()));
1243         });
1244         connect(fileJob, &KIO::FileJob::open, [=] {
1245             fileJob->seek(off);
1246             connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
1247                 Q_UNUSED(job);
1248                 if(off_t(offset) != off)
1249                 {
1250                     fileJob->close();
1251                     fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::fileClosed), [=] {
1252                         fuse_reply_err(req, EIO);
1253                     });
1254                     return;
1255                 }
1256                 auto actualSize = remoteNode->m_stat.st_size = fileJob->size();
1257                 // Reading over the end
1258                 if(off >= off_t(actualSize))
1259                     actualSize = 0;
1260                 else
1261                     actualSize = std::min(off_t(actualSize) - off, off_t(size));
1262                 fileJob->read(actualSize);
1263                 QByteArray buffer;
1264                 fileJob->connect(fileJob, &KIO::FileJob::data, [=] (auto *readJob, const QByteArray &data) mutable {
1265                     Q_UNUSED(readJob);
1266                     QByteArray truncatedData = data.left(actualSize);
1267                     buffer.append(truncatedData);
1268                     actualSize -= truncatedData.size();
1269 
1270                     if(actualSize > 0)
1271                     {
1272                         // Keep reading until we get all the data we need.
1273                         fileJob->read(actualSize);
1274                         return;
1275                     }
1276                     fileJob->close();
1277                     fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::fileClosed), [=] {
1278                         fuse_reply_buf(req, buffer.constData(), buffer.size());
1279                     });
1280                 });
1281             });
1282         });
1283 
1284         return;
1285     }
1286 
1287     fuse_reply_err(req, EIO);
1288 }
1289 
1290 void KIOFuseVFS::write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, fuse_file_info *fi)
1291 {
1292     Q_UNUSED(fi);
1293     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1294     auto node = that->nodeForIno(ino);
1295     if(!node)
1296     {
1297         fuse_reply_err(req, EIO);
1298         return;
1299     }
1300 
1301     if(std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1302     {
1303         fuse_reply_err(req, EISDIR);
1304         return;
1305     }
1306 
1307     if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1308     {
1309         qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
1310         QByteArray data(buf, size); // Copy data
1311         // fi lives on the caller's stack make a copy.
1312         auto cacheBasedWriteCallback = [=, fi_flags=fi->flags] (int error) {
1313             if(error && error != ESPIPE)
1314             {
1315                 fuse_reply_err(req, error);
1316                 return;
1317             }
1318 
1319             off_t offset = (fi_flags & O_APPEND) ? remoteNode->m_cacheSize : off;
1320 
1321             int cacheFd = fileno(remoteNode->m_localCache);
1322             if(lseek(cacheFd, offset, SEEK_SET) == -1
1323                || !sane_write(cacheFd, data.data(), data.size()))
1324             {
1325                 fuse_reply_err(req, errno);
1326                 return;
1327             }
1328 
1329             remoteNode->m_cacheSize = std::max(remoteNode->m_cacheSize, off_t(offset + size));
1330             // Don't set it to a lower value, in case the cache is incomplete
1331             remoteNode->m_stat.st_size = std::max(remoteNode->m_stat.st_size, remoteNode->m_cacheSize);
1332             // Update [cm] time as without writeback caching,
1333             // the kernel doesn't do this for us.
1334             clock_gettime(CLOCK_REALTIME, &remoteNode->m_stat.st_mtim);
1335             remoteNode->m_stat.st_ctim = remoteNode->m_stat.st_mtim;
1336             that->markCacheDirty(remoteNode);
1337 
1338             fuse_reply_write(req, data.size());
1339         };
1340 
1341         if(fi->flags & O_APPEND)
1342             // Wait for cache to be complete to ensure valid m_cacheSize
1343             that->awaitCacheComplete(remoteNode, cacheBasedWriteCallback);
1344         else
1345             that->awaitBytesAvailable(remoteNode, off + size, cacheBasedWriteCallback);
1346 
1347         return;
1348     }
1349     else if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
1350     {
1351         qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
1352 
1353         if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1354         {
1355             fuse_reply_err(req, ENOENT);
1356             return;
1357         }
1358 
1359         QByteArray data(buf, size); // Copy data
1360         auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadWrite);
1361         connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
1362             // All errors come through this signal, so error-handling is done here
1363             if(job->error())
1364                 fuse_reply_err(req, kioErrorToFuseError(job->error()));
1365         });
1366         connect(fileJob, &KIO::FileJob::open, [=, fi_flags=fi->flags] {
1367             off_t offset = (fi_flags & O_APPEND) ? fileJob->size() : off;
1368             fileJob->seek(offset);
1369             connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
1370                 Q_UNUSED(job);
1371                 if (off_t(offset) != off) {
1372                     fileJob->close();
1373                     fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::fileClosed), [=] {
1374                         fuse_reply_err(req, EIO);
1375                     });
1376                     return;
1377                 }
1378                 // Limit write to avoid killing the worker.
1379                 // @see https://phabricator.kde.org/D15448
1380                 fileJob->write(data.left(0xFFFFFF));
1381                 off_t bytesLeft = size;
1382                 fileJob->connect(fileJob, &KIO::FileJob::written, [=] (auto *writeJob, KIO::filesize_t written) mutable {
1383                     Q_UNUSED(writeJob);
1384                     bytesLeft -= written;
1385                     if (bytesLeft > 0)
1386                     {
1387                         // Keep writing until we write all the data we need.
1388                         fileJob->write(data.mid(size - bytesLeft, 0xFFFFFF));
1389                         return;
1390                     }
1391                     fileJob->close();
1392                     fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::fileClosed), [=] {
1393                         // Wait till we've flushed first...
1394                         remoteNode->m_stat.st_size = std::max(off_t(offset + data.size()), remoteNode->m_stat.st_size);
1395                         fuse_reply_write(req, data.size());
1396                     });
1397                 });
1398             });
1399         });
1400 
1401         return;
1402     }
1403 
1404     fuse_reply_err(req, EIO);
1405 }
1406 
1407 void KIOFuseVFS::flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1408 {
1409     // This is called on each close of a FD, so it might be a bit overzealous
1410     // to do writeback here. I can't think of a better alternative though -
1411     // doing it only on fsync and the final forget seems like a bit too late.
1412 
1413     return KIOFuseVFS::fsync(req, ino, 1, fi);
1414 }
1415 
1416 void KIOFuseVFS::release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1417 {
1418     Q_UNUSED(fi);
1419     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1420     auto node = that->nodeForIno(ino);
1421     if(!node)
1422     {
1423         fuse_reply_err(req, EIO);
1424         return;
1425     }
1426 
1427     node->m_openCount -= 1;
1428 
1429     fuse_reply_err(req, 0); // Ignored anyway
1430 
1431     auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
1432     if(node->m_openCount || !remoteFileNode || !remoteFileNode->m_localCache)
1433         return; // Nothing to do
1434 
1435     // When the cache is not dirty, remove the cache file.
1436     that->awaitNodeFlushed(remoteFileNode, [=](int error) {
1437         if(error != 0 || node->m_openCount != 0)
1438             return; // Better not remove the cache
1439         if(remoteFileNode->m_localCache == nullptr)
1440             return; // Already removed (happens if the file was reopened and closed while flushing)
1441         if(!remoteFileNode->cacheIsComplete())
1442             return; // Currently filling
1443 
1444         if(remoteFileNode->m_cacheDirty || remoteFileNode->m_flushRunning)
1445         {
1446             if(remoteFileNode->m_parentIno == KIOFuseIno::DeletedRoot)
1447                 return; // Closed a deleted dirty file, keep the cache as it could be reopened
1448 
1449             // Can't happen, but if it does, avoid data loss and potential crashing later by keeping
1450             // the cache.
1451             qWarning(KIOFUSE_LOG) << "Node" << remoteFileNode->m_nodeName << "turned dirty in flush callback";
1452             return;
1453         }
1454 
1455         qDebug(KIOFUSE_LOG) << "Removing cache of" << remoteFileNode->m_nodeName;
1456         fclose(remoteFileNode->m_localCache);
1457         remoteFileNode->m_cacheSize = 0;
1458         remoteFileNode->m_localCache = nullptr;
1459         remoteFileNode->m_cacheComplete = false;
1460     });
1461 }
1462 
1463 void KIOFuseVFS::fsync(fuse_req_t req, fuse_ino_t ino, int datasync, fuse_file_info *fi)
1464 {
1465     Q_UNUSED(datasync); Q_UNUSED(fi);
1466     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1467     auto node = that->nodeForIno(ino);
1468     if(!node)
1469     {
1470         fuse_reply_err(req, EIO);
1471         return;
1472     }
1473 
1474     if(auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1475         that->awaitNodeFlushed(cacheBasedFileNode, [=](int error) {
1476             fuse_reply_err(req, error);
1477         });
1478     else
1479         fuse_reply_err(req, 0);
1480 }
1481 
1482 bool KIOFuseVFS::isEnvironmentValid()
1483 {
1484     static_assert(sizeof(off_t) >= 8, "Please compile with -D_FILE_OFFSET_BITS=64 to allow working with large (>4GB) files");
1485 
1486 #ifdef Q_OS_LINUX
1487     // On 32bit Linux before "fuse: fix writepages on 32bit", writes past 4GiB were silently discarded.
1488     // Technically this would have to check the kernel's bitness, but that's not easily possible.
1489     if(sizeof(size_t) != sizeof(off_t))
1490     {
1491         struct utsname uts;
1492         if(uname(&uts) != 0)
1493             return false;
1494 
1495         auto kernelversion = QVersionNumber::fromString(QLatin1String(uts.release));
1496         if(kernelversion < QVersionNumber(5, 2))
1497         {
1498             qCritical(KIOFUSE_LOG) << "You're running kio-fuse on an older 32-bit kernel, which can lead to data loss.\n"
1499                                       "Please use a newer one or make sure that the 'fuse: fix writepages on 32bit' commit "
1500                                       "is part of the kernel and then build kio-fuse with this check adjusted.\n"
1501                                       "If you don't know how to do that, please file a bug at your distro.";
1502             return false;
1503         }
1504     }
1505 #endif
1506 
1507     return true;
1508 }
1509 
1510 std::shared_ptr<KIOFuseNode> KIOFuseVFS::nodeByName(const std::shared_ptr<KIOFuseDirNode> &parent, const QString &name) const
1511 {
1512     for(auto ino : parent->m_childrenInos)
1513     {
1514         auto child = nodeForIno(ino);
1515         if(!child)
1516         {
1517             qWarning(KIOFUSE_LOG) << "Node" << parent->m_nodeName << "references nonexistent child";
1518             continue;
1519         }
1520 
1521         if(child->m_nodeName == name)
1522             return child;
1523     }
1524 
1525     return nullptr;
1526 }
1527 
1528 void KIOFuseVFS::lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
1529 {
1530     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1531     auto parentNode = that->nodeForIno(parent);
1532     if(!parentNode)
1533     {
1534         fuse_reply_err(req, EIO);
1535         return;
1536     }
1537 
1538     auto parentDirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(parentNode);
1539     if(!parentDirNode)
1540     {
1541         fuse_reply_err(req, ENOTDIR);
1542         return;
1543     }
1544 
1545     QString nodeName = QString::fromUtf8(name);
1546 
1547     if(auto child = that->nodeByName(parentDirNode, nodeName)) // Part of the tree?
1548     {
1549         return that->awaitAttrRefreshed(child, [=](int error) {
1550             Q_UNUSED(error);
1551             // Lookup again, node might've been replaced or deleted
1552             auto child = that->nodeByName(parentDirNode, nodeName);
1553             that->replyEntry(req, child);
1554         });
1555     }
1556 
1557     auto remoteDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentDirNode);
1558     if(!remoteDirNode)
1559     {
1560         // Directory not remote, so definitely does not exist
1561         fuse_reply_err(req, ENOENT);
1562         return;
1563     }
1564 
1565     // Not in the local tree, but remote - try again
1566     that->awaitChildMounted(remoteDirNode, nodeName, [=](auto node, int error) {
1567         if(error && error != ENOENT)
1568             fuse_reply_err(req, error);
1569         else
1570             that->replyEntry(req, node);
1571     });
1572 }
1573 
1574 void KIOFuseVFS::forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
1575 {
1576     KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1577     if(auto node = that->nodeForIno(ino))
1578         that->decrementLookupCount(node, nlookup);
1579 
1580     fuse_reply_none(req);
1581 }
1582 
1583 std::shared_ptr<KIOFuseNode> KIOFuseVFS::nodeForIno(const fuse_ino_t ino) const
1584 {
1585     auto it = m_nodes.find(ino);
1586     if(it == m_nodes.end())
1587         return nullptr;
1588 
1589     return it->second;
1590 }
1591 
1592 void KIOFuseVFS::reparentNode(const std::shared_ptr<KIOFuseNode> &node, fuse_ino_t newParentIno)
1593 {
1594     if(node->m_parentIno == newParentIno)
1595         return;
1596 
1597     if(node->m_parentIno != KIOFuseIno::Invalid)
1598     {
1599         // Remove from old parent's children list
1600         if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1601         {
1602             auto &childrenList = parentDir->m_childrenInos;
1603             auto it = std::find(begin(childrenList), end(childrenList), node->m_stat.st_ino);
1604             if(it != childrenList.end())
1605                 childrenList.erase(it);
1606             else
1607                 qWarning(KIOFUSE_LOG) << "Tried to reparent node with broken parent link";
1608         }
1609         else
1610             qWarning(KIOFUSE_LOG) << "Tried to reparent node with invalid parent";
1611     }
1612 
1613     node->m_parentIno = newParentIno;
1614 
1615     if(node->m_parentIno != KIOFuseIno::Invalid)
1616     {
1617         // Add to new parent's children list
1618         if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1619             parentDir->m_childrenInos.push_back(node->m_stat.st_ino);
1620         else
1621             qWarning(KIOFUSE_LOG) << "Tried to insert node with invalid parent";
1622     }
1623 }
1624 
1625 fuse_ino_t KIOFuseVFS::insertNode(const std::shared_ptr<KIOFuseNode> &node, fuse_ino_t ino)
1626 {
1627     if(ino == KIOFuseIno::Invalid)
1628     {
1629         // Allocate a free inode number
1630         ino = m_nextIno;
1631         while(ino < KIOFuseIno::DynamicStart || m_nodes.find(ino) != m_nodes.end())
1632             ino++;
1633 
1634         m_nextIno = ino + 1;
1635     }
1636 
1637     m_nodes[ino] = node;
1638 
1639     // Adjust internal ino
1640     node->m_stat.st_ino = ino;
1641 
1642     if(node->m_parentIno != KIOFuseIno::Invalid)
1643     {
1644         // Add to parent's child
1645         if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1646             parentDir->m_childrenInos.push_back(ino);
1647         else
1648             qWarning(KIOFUSE_LOG) << "Tried to insert node with invalid parent";
1649     }
1650 
1651     return ino;
1652 }
1653 
1654 QUrl KIOFuseVFS::localPathToRemoteUrl(const QString& localPath) const
1655 {
1656     auto node = nodeForIno(KIOFuseIno::Root);
1657     auto pathSegments = localPath.split(QStringLiteral("/"));
1658     for(int i = 0; i < pathSegments.size(); ++i)
1659     {
1660         auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1661         if(!dirNode || !(node = nodeByName(dirNode, pathSegments[i])))
1662             break;
1663 
1664         auto url = remoteUrl(node);
1665         if(!url.isEmpty())
1666             return addPathElements(url, pathSegments.mid(i + 1));
1667     }
1668     return {};
1669 }
1670 
1671 QUrl KIOFuseVFS::remoteUrl(const std::shared_ptr<const KIOFuseNode> &node) const
1672 {
1673     QStringList path;
1674     for(const KIOFuseNode *currentNode = node.get(); currentNode != nullptr; currentNode = nodeForIno(currentNode->m_parentIno).get())
1675     {
1676         auto remoteDirNode = dynamic_cast<const KIOFuseRemoteNodeInfo*>(currentNode);
1677         if(remoteDirNode && !remoteDirNode->m_overrideUrl.isEmpty())
1678             // Origin found - add path and return
1679             return addPathElements(remoteDirNode->m_overrideUrl, path);
1680 
1681         path.prepend(currentNode->m_nodeName);
1682     }
1683 
1684     // No origin found until the root - return an invalid URL
1685     return {};
1686 }
1687 
1688 QString KIOFuseVFS::virtualPath(const std::shared_ptr<KIOFuseNode> &node) const
1689 {
1690     QStringList path;
1691     for(const KIOFuseNode *currentNode = node.get(); currentNode != nullptr; currentNode = nodeForIno(currentNode->m_parentIno).get())
1692         path.prepend(currentNode->m_nodeName);
1693 
1694     return path.join(QLatin1Char('/'));
1695 }
1696 
1697 void KIOFuseVFS::fillStatForFile(struct stat &attr)
1698 {
1699     static uid_t uid = getuid();
1700     static gid_t gid = getgid();
1701 
1702     attr.st_nlink = 1;
1703     attr.st_mode = S_IFREG | 0755;
1704     attr.st_uid = uid;
1705     attr.st_gid = gid;
1706     attr.st_size = 0;
1707     attr.st_blksize = 512;
1708     // This is set to match st_size by replyAttr
1709     attr.st_blocks = 0;
1710 
1711     clock_gettime(CLOCK_REALTIME, &attr.st_atim);
1712     attr.st_mtim = attr.st_atim;
1713     attr.st_ctim = attr.st_atim;
1714 }
1715 
1716 void KIOFuseVFS::incrementLookupCount(const std::shared_ptr<KIOFuseNode> &node, uint64_t delta)
1717 {
1718     if(node->m_lookupCount + delta < node->m_lookupCount)
1719         qWarning(KIOFUSE_LOG) << "Lookup count overflow!";
1720     else
1721         node->m_lookupCount += delta;
1722 }
1723 
1724 void KIOFuseVFS::decrementLookupCount(const std::shared_ptr<KIOFuseNode> node, uint64_t delta)
1725 {
1726     if(node->m_lookupCount < delta)
1727         qWarning(KIOFUSE_LOG) << "Tried to set lookup count negative!";
1728     else
1729         node->m_lookupCount -= delta;
1730 
1731     if(node->m_parentIno == KIOFuseIno::DeletedRoot && node->m_lookupCount == 0)
1732     {
1733         // Delete the node
1734         m_dirtyNodes.extract(node->m_stat.st_ino);
1735         reparentNode(node, KIOFuseIno::Invalid);
1736         m_nodes.erase(m_nodes.find(node->m_stat.st_ino));
1737     }
1738 }
1739 
1740 void KIOFuseVFS::markNodeDeleted(const std::shared_ptr<KIOFuseNode> &node)
1741 {
1742     if(auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1743     {
1744         // Mark all children as deleted first (flatten the hierarchy)
1745         for (auto childIno : std::vector<fuse_ino_t>(dirNode->m_childrenInos))
1746             if(auto childNode = nodeForIno(childIno))
1747                 markNodeDeleted(childNode);
1748     }
1749 
1750     qDebug(KIOFUSE_LOG) << "Marking node" << node->m_nodeName << "as deleted";
1751 
1752     reparentNode(node, KIOFuseIno::DeletedRoot);
1753     decrementLookupCount(node, 0); // Trigger reevaluation
1754 }
1755 
1756 void KIOFuseVFS::replyAttr(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
1757 {
1758     // Set st_blocks accordingly
1759     node->m_stat.st_blocks = (node->m_stat.st_size + 512 - 1) / 512;
1760 
1761     // TODO: Validity timeout?
1762     fuse_reply_attr(req, &node->m_stat, 0);
1763 }
1764 
1765 void KIOFuseVFS::replyEntry(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
1766 {
1767     // Zero means invalid entry. Compared to an ENOENT reply, the kernel can cache this.
1768     struct fuse_entry_param entry {};
1769 
1770     if(node)
1771     {
1772         incrementLookupCount(node);
1773 
1774         entry.ino = node->m_stat.st_ino;
1775         entry.attr_timeout = 0.0;
1776         entry.entry_timeout = 0.0;
1777         entry.attr = node->m_stat;
1778     }
1779 
1780     fuse_reply_entry(req, &entry);
1781 }
1782 
1783 std::shared_ptr<KIOFuseNode> KIOFuseVFS::createNodeFromUDSEntry(const KIO::UDSEntry &entry, const fuse_ino_t parentIno, const QString &nameOverride)
1784 {
1785     QString name = nameOverride;
1786     if(name.isEmpty())
1787         name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1788     if(!isValidFilename(name))
1789         return nullptr; // Reject invalid names
1790 
1791     // Create a stat struct with default values
1792     struct stat attr = {};
1793     fillStatForFile(attr);
1794     attr.st_size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 1);
1795     attr.st_mode = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, entry.isDir() ? 0755 : 0644);
1796     attr.st_mode &= ~S_IFMT; // Set by us further down
1797     if(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME))
1798     {
1799         attr.st_mtim.tv_sec = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
1800         attr.st_mtim.tv_nsec = 0;
1801     }
1802     if(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME))
1803     {
1804         attr.st_atim.tv_sec = entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME);
1805         attr.st_atim.tv_nsec = 0;
1806     }
1807     // No support for ctim/btim in KIO...
1808 
1809     // Setting UID and GID here to UDS_USER/UDS_GROUP respectively does not lead to the expected
1810     // results as those values might only be meaningful on the remote side.
1811     // As access checks are only performed by the remote side, it shouldn't matter much though.
1812     // It's necessary to make chown/chmod meaningful.
1813     if(entry.contains(KIO::UDSEntry::UDS_USER))
1814     {
1815         QString user = entry.stringValue(KIO::UDSEntry::UDS_USER);
1816         if(auto *pw = getpwnam(user.toUtf8().data()))
1817             attr.st_uid = pw->pw_uid;
1818     }
1819     if(entry.contains(KIO::UDSEntry::UDS_GROUP))
1820     {
1821         QString group = entry.stringValue(KIO::UDSEntry::UDS_GROUP);
1822         if(auto *gr = getgrnam(group.toUtf8().data()))
1823             attr.st_gid = gr->gr_gid;
1824     }
1825 
1826     if(entry.contains(KIO::UDSEntry::UDS_LOCAL_PATH) || entry.contains(KIO::UDSEntry::UDS_TARGET_URL))
1827     {
1828         // Create as symlink if possible
1829         QString target = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
1830         if(target.isEmpty())
1831             target = QUrl(entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)).toLocalFile();
1832 
1833         if(!target.isEmpty())
1834         {
1835             // Symlink to local file/folder
1836             attr.st_mode |= S_IFLNK;
1837             auto ret = std::make_shared<KIOFuseSymLinkNode>(parentIno, name, attr);
1838             ret->m_target = target;
1839             ret->m_stat.st_size = ret->m_target.toUtf8().length();
1840             return ret;
1841         }
1842         else if(entry.isLink())
1843             return nullptr; // Does this even happen?
1844         else if(entry.isDir())
1845             return nullptr; // Maybe create a mountpoint (remote dir with override URL) here?
1846         else // Regular file pointing to URL
1847         {
1848             attr.st_mode |= S_IFREG;
1849             std::shared_ptr<KIOFuseRemoteFileNode> ret = nullptr;
1850             const QUrl nodeUrl = QUrl{entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)};
1851             if(nodeUrl.isEmpty())
1852                 return nullptr;
1853             if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
1854                 ret = std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
1855             else
1856                 ret = std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
1857             ret->m_overrideUrl = nodeUrl;
1858             return ret;
1859         }
1860     }
1861     else if(entry.isLink()) // Check for link first as isDir can also be a link
1862     {
1863         attr.st_mode |= S_IFLNK;
1864         auto ret = std::make_shared<KIOFuseSymLinkNode>(parentIno, name, attr);
1865         ret->m_target = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
1866         attr.st_size = ret->m_target.size();
1867         return ret;
1868     }
1869     else if(entry.isDir())
1870     {
1871         attr.st_mode |= S_IFDIR;
1872         return std::make_shared<KIOFuseRemoteDirNode>(parentIno, name, attr);
1873     }
1874     else // it's a regular file
1875     {
1876         attr.st_mode |= S_IFREG;
1877         const QUrl nodeUrl = remoteUrl(nodeForIno(parentIno));
1878         if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
1879             return std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
1880         else
1881             return std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
1882     }
1883 }
1884 
1885 std::shared_ptr<KIOFuseNode> KIOFuseVFS::updateNodeFromUDSEntry(const std::shared_ptr<KIOFuseNode> &node, const KIO::UDSEntry &entry)
1886 {
1887     qDebug(KIOFUSE_LOG) << "Updating attributes of" << node->m_nodeName;
1888 
1889     // Updating a node works by creating a new node and merging their attributes
1890     // (both m_stat and certain other class attributes) together.
1891     // This is broadly done with node->m_stat = newNode->m_stat;
1892     // However, there are some things we may not want to copy straight from the
1893     // new node into the node to be updated and so we update newNode as
1894     // appropriate before merging.
1895     auto newNode = createNodeFromUDSEntry(entry, node->m_parentIno, node->m_nodeName);
1896     if (!newNode)
1897     {
1898         qWarning(KIOFUSE_LOG) << "Could not create new node for" << node->m_nodeName;
1899         return node;
1900     }
1901 
1902 #pragma GCC diagnostic push
1903 #pragma GCC diagnostic ignored "-Wpragmas" // GCC does not know the diagnostic below and warns m(
1904 #pragma GCC diagnostic ignored "-Wpotentially-evaluated-expression"
1905     if (typeid(*newNode.get()) != typeid(*node.get()))
1906 #pragma GCC diagnostic pop
1907     {
1908         if(node->m_parentIno != KIOFuseIno::DeletedRoot)
1909         {
1910             markNodeDeleted(node);
1911             insertNode(newNode);
1912             return newNode;
1913         }
1914         return node;
1915     }
1916 
1917     const bool newMtimNewer = node->m_stat.st_mtim < newNode->m_stat.st_mtim;
1918     // Take the newer time value
1919     if (!newMtimNewer)
1920         newNode->m_stat.st_mtim = node->m_stat.st_mtim;
1921 
1922     if (newNode->m_stat.st_atim < node->m_stat.st_atim)
1923         newNode->m_stat.st_atim = node->m_stat.st_atim;
1924 
1925     if (auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1926     {
1927         if(newMtimNewer || newNode->m_stat.st_size != node->m_stat.st_size)
1928         {
1929             // Someone has changed something server side, lets get those changes.
1930             if(cacheBasedFileNode->m_cacheDirty || cacheBasedFileNode->m_flushRunning)
1931             {
1932                 if(newMtimNewer)
1933                     qWarning(KIOFUSE_LOG) << cacheBasedFileNode->m_nodeName << "cache is dirty but file has also been changed by something else";
1934 
1935                 newNode->m_stat.st_size = cacheBasedFileNode->m_stat.st_size;
1936             }
1937             else if(cacheBasedFileNode->m_localCache && cacheBasedFileNode->cacheIsComplete())
1938             {
1939                 // Our cache isn't dirty but the file has changed, our current cache is invalid
1940                 // and we can safely get rid of it.
1941                 fclose(cacheBasedFileNode->m_localCache);
1942                 cacheBasedFileNode->m_cacheSize = 0;
1943                 cacheBasedFileNode->m_cacheComplete = false;
1944                 cacheBasedFileNode->m_localCache = nullptr;
1945             }
1946         }
1947     }
1948     else if (auto oldSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node))
1949     {
1950         auto newSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(newNode);
1951         oldSymlinkNode->m_target = newSymlinkNode->m_target;
1952     }
1953 
1954     auto oldRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
1955     auto newRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(newNode);
1956 
1957     oldRemoteNode->m_lastStatRefresh = std::chrono::steady_clock::now();
1958 
1959     // oldRemoteNode->m_overrideUrl could've been set by mountUrl, don't clear it
1960     if (!newRemoteNode->m_overrideUrl.isEmpty())
1961         oldRemoteNode->m_overrideUrl = newRemoteNode->m_overrideUrl;
1962 
1963     // Preserve the inode number
1964     newNode->m_stat.st_ino = node->m_stat.st_ino;
1965     node->m_stat = newNode->m_stat;
1966     return node;
1967 }
1968 
1969 void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, off_t bytes, const std::function<void(int error)> &callback)
1970 {
1971     if(bytes < 0)
1972     {
1973         qWarning(KIOFUSE_LOG) << "Negative size passed to awaitBytesAvailable";
1974         return callback(EINVAL);
1975     }
1976 
1977     if(node->m_localCache && node->m_cacheSize >= bytes)
1978         return callback(0); // Already available
1979     else if(node->cacheIsComplete()) // Full cache is available...
1980         return callback(ESPIPE); // ...but less than requested.
1981 
1982     if(!node->m_localCache)
1983     {
1984         if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1985             return callback(ENOENT);
1986 
1987         // Create a temporary file
1988         node->m_localCache = tmpfile();
1989 
1990         if(!node->m_localCache)
1991             return callback(errno);
1992 
1993         // Request the file
1994         qDebug(KIOFUSE_LOG) << "Fetching cache for" << node->m_nodeName;
1995         auto *job = KIO::get(remoteUrl(node));
1996         connect(job, &KIO::TransferJob::data, [=](auto *job, const QByteArray &data) {
1997             // Nobody needs the data anymore? Drop the cache.
1998             if(node->m_openCount == 0 && !node->m_cacheDirty && !node->m_flushRunning)
1999             {
2000                 // KJob::Quietly would break the cache in the result handler while
2001                 // the error handler sets up the node state just right.
2002                 job->kill(KJob::EmitResult);
2003                 qDebug(KIOFUSE_LOG) << "Stopped filling the cache of" << node->m_nodeName;
2004                 return;
2005             }
2006 
2007             int cacheFd = fileno(node->m_localCache);
2008             if(lseek(cacheFd, 0, SEEK_END) == -1
2009                || !sane_write(cacheFd, data.data(), data.size()))
2010                 Q_EMIT node->localCacheChanged(errno);
2011             else
2012             {
2013                 node->m_cacheSize += data.size();
2014                 Q_EMIT node->localCacheChanged(0);
2015             }
2016         });
2017         connect(job, &KIO::TransferJob::result, [=] {
2018             if(job->error())
2019             {
2020                 // It's possible that the cache was written to meanwhile - that's bad.
2021                 // TODO: Is it possible to recover?
2022                 node->m_cacheDirty = false;
2023 
2024                 fclose(node->m_localCache);
2025                 node->m_cacheSize = 0;
2026                 node->m_cacheComplete = false;
2027                 node->m_localCache = nullptr;
2028                 Q_EMIT node->localCacheChanged(kioErrorToFuseError(job->error()));
2029             }
2030             else
2031             {
2032                 // Might be different from the attr size meanwhile, use the more recent value.
2033                 // This also ensures that the cache is seen as complete.
2034                 node->m_stat.st_size = node->m_cacheSize;
2035                 node->m_cacheComplete = true;
2036                 Q_EMIT node->localCacheChanged(0);
2037             }
2038         });
2039     }
2040 
2041     // Using a unique_ptr here to let the lambda disconnect the connection itself
2042     auto connection = std::make_unique<QMetaObject::Connection>();
2043     auto &conn = *connection;
2044     conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::localCacheChanged,
2045                    [=, connection = std::move(connection)](int error) {
2046         if(error)
2047         {
2048             callback(error);
2049             node->disconnect(*connection);
2050         }
2051         else if(node->m_cacheSize >= bytes) // Requested data available
2052         {
2053             callback(0);
2054             node->disconnect(*connection);
2055         }
2056         else if(node->cacheIsComplete()) // Full cache is available...
2057         {
2058             // ...but less than requested.
2059             callback(ESPIPE);
2060             node->disconnect(*connection);
2061         }
2062         // else continue waiting until the above happens
2063     }
2064     );
2065 }
2066 
2067 void KIOFuseVFS::awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, const std::function<void (int)> &callback)
2068 {
2069     return awaitBytesAvailable(node, std::numeric_limits<off_t>::max(), [callback](int error) {
2070         // ESPIPE == cache complete, but less than the requested size, which is expected.
2071         return callback(error == ESPIPE ? 0 : error);
2072     });
2073 }
2074 
2075 void KIOFuseVFS::awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> &node, const std::function<void (int)> &callback)
2076 {
2077     auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
2078     if(!remoteNode)
2079         return callback(0); // Not a remote node
2080 
2081     if(!remoteNode->haveChildrenTimedOut())
2082         return callback(0); // Children complete and up to date
2083 
2084     if(!remoteNode->m_childrenRequested)
2085     {
2086         if(node->m_parentIno == KIOFuseIno::DeletedRoot)
2087             return callback(0);
2088 
2089         auto childrenNotSeen = std::make_shared<std::vector<fuse_ino_t>>(remoteNode->m_childrenInos);
2090 
2091         // List the remote dir
2092         auto refreshTime = std::chrono::steady_clock::now();
2093         auto *job = KIO::listDir(remoteUrl(remoteNode));
2094         connect(job, &KIO::ListJob::entries, this, [=](auto *job, const KIO::UDSEntryList &entries) {
2095             for(auto &entry : entries)
2096             {
2097                 // Inside the loop because refreshing "." might drop it
2098                 if(remoteNode->m_parentIno == KIOFuseIno::DeletedRoot)
2099                 {
2100                     job->kill(KJob::EmitResult);
2101                     return;
2102                 }
2103 
2104                 QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
2105 
2106                 // Refresh "." and ignore ".."
2107                 if(name == QStringLiteral("."))
2108                 {
2109                     updateNodeFromUDSEntry(remoteNode, entry);
2110                     continue;
2111                 }
2112                 else if(name == QStringLiteral(".."))
2113                    continue;
2114 
2115                 auto childrenNode = nodeByName(remoteNode, name);
2116                 if(childrenNode)
2117                 {
2118                     auto it = std::find(begin(*childrenNotSeen), end(*childrenNotSeen),
2119                                         childrenNode->m_stat.st_ino);
2120                     if(it != end(*childrenNotSeen))
2121                         childrenNotSeen->erase(it);
2122 
2123                     // Try to update existing node
2124                     updateNodeFromUDSEntry(childrenNode, entry);
2125                     continue;
2126                 }
2127 
2128                 childrenNode = createNodeFromUDSEntry(entry, remoteNode->m_stat.st_ino, name);
2129                 if(!childrenNode)
2130                 {
2131                     qWarning(KIOFUSE_LOG) << "Could not create node for" << name;
2132                     continue;
2133                 }
2134 
2135                 insertNode(childrenNode);
2136             }
2137         });
2138         connect(job, &KIO::ListJob::result, this, [=] {
2139             remoteNode->m_childrenRequested = false;
2140 
2141             if(job->error() && job->error() != KJob::KilledJobError)
2142             {
2143                 Q_EMIT remoteNode->gotChildren(kioErrorToFuseError(job->error()));
2144                 return;
2145             }
2146 
2147             for(auto ino : *childrenNotSeen)
2148             {
2149                 auto childNode = nodeForIno(ino);
2150                 // Was not refreshed as part of this listDir operation, drop it
2151                 if(childNode && childNode->m_parentIno == node->m_stat.st_ino)
2152                     markNodeDeleted(childNode);
2153             }
2154 
2155             remoteNode->m_lastChildrenRefresh = refreshTime;
2156             Q_EMIT remoteNode->gotChildren(0);
2157         });
2158 
2159         remoteNode->m_childrenRequested = true;
2160     }
2161 
2162     // Using a unique_ptr here to let the lambda disconnect the connection itself
2163     auto connection = std::make_unique<QMetaObject::Connection>();
2164     auto &conn = *connection;
2165     conn = connect(remoteNode.get(), &KIOFuseRemoteDirNode::gotChildren,
2166                    [=, connection = std::move(connection)](int error) {
2167         callback(error);
2168         remoteNode->disconnect(*connection);
2169     }
2170     );
2171 }
2172 
2173 void KIOFuseVFS::markCacheDirty(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node)
2174 {
2175     if(node->m_cacheDirty)
2176         return; // Already dirty, nothing to do
2177 
2178     node->m_cacheDirty = true;
2179     m_dirtyNodes.insert(node->m_stat.st_ino);
2180 }
2181 
2182 void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, const std::function<void (int)> &callback)
2183 {
2184     if(!node->m_cacheDirty && !node->m_flushRunning)
2185         return callback(0); // Nothing to flush/wait for
2186 
2187     if(node->m_parentIno == KIOFuseIno::DeletedRoot)
2188     {
2189         // Important: This is before marking it as flushed as it can be linked back.
2190         qDebug(KIOFUSE_LOG) << "Not flushing unlinked node" << node->m_nodeName;
2191         return callback(0);
2192     }
2193 
2194     // Don't send incomplete data
2195     if(!node->cacheIsComplete())
2196     {
2197         qDebug(KIOFUSE_LOG) << "Deferring flushing of node" << node->m_nodeName << "until cache complete";
2198         return awaitCacheComplete(node, [=](int error) {
2199             if(error)
2200                 callback(error);
2201             else
2202                 awaitNodeFlushed(node, callback);
2203         });
2204     }
2205 
2206     if(!node->m_flushRunning)
2207     {
2208         qDebug(KIOFUSE_LOG) << "Flushing node" << node->m_nodeName;
2209 
2210         // Clear the flag now to not lose any writes that happen while sending data
2211         node->m_cacheDirty = false;
2212         node->m_flushRunning = true;
2213 
2214         auto *job = KIO::put(remoteUrl(node), -1, KIO::Overwrite);
2215         job->setTotalSize(node->m_cacheSize);
2216 
2217         off_t bytesSent = 0; // Modified inside the lambda
2218         connect(job, &KIO::TransferJob::dataReq, this, [=](auto *job, QByteArray &data) mutable {
2219             Q_UNUSED(job);
2220 
2221             // Someone truncated the file?
2222             if(node->m_cacheSize <= bytesSent)
2223                 return;
2224 
2225             // Somebody wrote to the cache whilst sending data.
2226             // Kill the job to save time and try again.
2227             // However, set a limit to how many times we do this consecutively.
2228             if(node->m_cacheDirty && node->m_numKilledJobs < 2 && job->percent() < 85)
2229             {
2230                 job->kill(KJob::Quietly);
2231                 node->m_numKilledJobs++;
2232                 node->m_flushRunning = false;
2233                 awaitNodeFlushed(node, [](int){});
2234                 return;
2235             }
2236 
2237             off_t toSend = std::min(node->m_cacheSize - bytesSent, off_t(14*1024*1024ul)); // 14MiB max
2238             data.resize(toSend);
2239 
2240             // Read the cache file into the buffer
2241             int cacheFd = fileno(node->m_localCache);
2242             if(lseek(cacheFd, bytesSent, SEEK_SET) == -1
2243                || !sane_read(cacheFd, data.data(), toSend))
2244             {
2245                 qWarning(KIOFUSE_LOG) << "Failed to read cache:" << strerror(errno);
2246                 job->kill(KJob::EmitResult);
2247                 return;
2248             }
2249 
2250             bytesSent += toSend;
2251         });
2252         connect(job, &KIO::TransferJob::result, this, [=] {
2253             node->m_flushRunning = false;
2254 
2255             if(job->error())
2256             {
2257                 qWarning(KIOFUSE_LOG) << "Failed to send data:" << job->errorString();
2258                 markCacheDirty(node); // Try again
2259                 Q_EMIT node->cacheFlushed(kioErrorToFuseError(job->error()));
2260                 return;
2261             }
2262 
2263             if(!node->m_cacheDirty)
2264             {
2265                 // Nobody wrote to the cache while sending data
2266                 m_dirtyNodes.extract(node->m_stat.st_ino);
2267                 node->m_numKilledJobs = 0;
2268                 Q_EMIT node->cacheFlushed(0);
2269             }
2270             else
2271                 awaitNodeFlushed(node, [](int){});
2272         });
2273     }
2274 
2275     // Using a unique_ptr here to let the lambda disconnect the connection itself
2276     auto connection = std::make_unique<QMetaObject::Connection>();
2277     auto &conn = *connection;
2278     conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::cacheFlushed,
2279                    [=, connection = std::move(connection)](int error) {
2280         callback(error);
2281         node->disconnect(*connection);
2282     }
2283     );
2284 }
2285 
2286 void KIOFuseVFS::awaitAttrRefreshed(const std::shared_ptr<KIOFuseNode> &node, const std::function<void (int)> &callback)
2287 {
2288     auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
2289     if(!remoteNode || !remoteNode->hasStatTimedOut())
2290         return callback(0); // Node not remote, or it hasn't timed out yet
2291 
2292     // To do the same request as the initial mount or lookup, build the URL from the parent
2293     auto parentNode = nodeForIno(node->m_parentIno);
2294     auto remoteParentNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode);
2295     QUrl url;
2296     if(!remoteParentNode || (url = remoteUrl(parentNode)).isEmpty())
2297         return callback(0); // Parent not remote
2298 
2299     if(!remoteNode->m_statRequested)
2300     {
2301         qDebug(KIOFUSE_LOG) << "Refreshing attributes of node" << node->m_nodeName;
2302         remoteNode->m_statRequested = true;
2303         awaitChildMounted(remoteParentNode, node->m_nodeName, [=] (auto mountedNode, int error) {
2304             if(error == ENOENT)
2305                 markNodeDeleted(node);
2306 
2307             Q_UNUSED(mountedNode);
2308             remoteNode->m_statRequested = false;
2309             Q_EMIT remoteNode->statRefreshed(error);
2310         });
2311     }
2312 
2313     // Using a unique_ptr here to let the lambda disconnect the connection itself
2314     auto connection = std::make_unique<QMetaObject::Connection>();
2315     auto &conn = *connection;
2316     conn = connect(remoteNode.get(), &KIOFuseRemoteNodeInfo::statRefreshed,
2317                    [=, connection = std::move(connection)](int error) {
2318         callback(error);
2319         remoteNode->disconnect(*connection);
2320     }
2321     );
2322 }
2323 
2324 void KIOFuseVFS::awaitChildMounted(const std::shared_ptr<KIOFuseRemoteDirNode> &parent, const QString &name, const std::function<void (const std::shared_ptr<KIOFuseNode> &, int)> &callback)
2325 {
2326     auto url = addPathElements(remoteUrl(parent), {name});
2327     if(url.isEmpty()) // Not remote?
2328     {
2329         if(auto node = nodeByName(parent, name))
2330             return callback(node, 0);
2331         else
2332             return callback({}, ENOENT);
2333     }
2334 
2335     qDebug(KIOFUSE_LOG) << "Mounting" << url.toDisplayString();
2336     auto statJob = KIO::stat(url);
2337     statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
2338                                                 // files over plain HTTP
2339     connect(statJob, &KIO::StatJob::result, this, [=] {
2340         if(statJob->error())
2341         {
2342             qDebug(KIOFUSE_LOG) << statJob->errorString();
2343             callback(nullptr, kioErrorToFuseError(statJob->error()));
2344             return;
2345         }
2346 
2347         // Finally create the last component
2348         auto finalNode = nodeByName(parent, name);
2349         if(finalNode)
2350             // Note that node may fail to update, but this type of error is ignored.
2351             finalNode = updateNodeFromUDSEntry(finalNode, statJob->statResult());
2352         else
2353         {
2354             // The remote name (statJob->statResult().stringValue(KIO::UDSEntry::UDS_NAME)) has to be
2355             // ignored as it can be different from the path. e.g. tar:/foo.tar/ is "/"
2356             finalNode = createNodeFromUDSEntry(statJob->statResult(), parent->m_stat.st_ino, name);
2357             if(!finalNode)
2358                 return callback(nullptr, EIO);
2359 
2360             insertNode(finalNode);
2361         }
2362 
2363         callback(finalNode, 0);
2364     });
2365 }
2366 
2367 QUrl KIOFuseVFS::originOfUrl(const QUrl &url)
2368 {
2369     QUrl originUrl = url;
2370     if(originUrl.path().startsWith(QLatin1Char('/')))
2371         originUrl.setPath(QStringLiteral("/"));
2372     else
2373         originUrl.setPath({});
2374 
2375     return originUrl;
2376 }
2377 
2378 bool KIOFuseVFS::setupSignalHandlers()
2379 {
2380     // Create required socketpair for custom signal handling
2381     if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) {
2382         return false;
2383     }
2384     m_signalNotifier = std::make_unique<QSocketNotifier>(signalFd[1], QSocketNotifier::Read, this);
2385     m_signalNotifier->connect(m_signalNotifier.get(), &QSocketNotifier::activated, this, &KIOFuseVFS::exitHandler);
2386     
2387     struct sigaction sig;
2388 
2389     sig.sa_handler = KIOFuseVFS::signalHandler;
2390     sigemptyset(&sig.sa_mask);
2391     sig.sa_flags = SA_RESTART;
2392 
2393     if (sigaction(SIGHUP, &sig, 0))
2394         return false;
2395     if (sigaction(SIGTERM, &sig, 0))
2396         return false;
2397     if (sigaction(SIGINT, &sig, 0))
2398         return false;
2399 
2400     return true;
2401 }
2402 
2403 bool KIOFuseVFS::removeSignalHandlers() 
2404 {
2405     m_signalNotifier.reset();
2406     ::close(signalFd[0]);
2407     ::close(signalFd[1]);
2408 
2409     struct sigaction sig;
2410 
2411     sig.sa_handler = SIG_DFL;
2412     sigemptyset(&sig.sa_mask);
2413     sig.sa_flags = SA_RESTART;
2414 
2415     if (sigaction(SIGHUP, &sig, 0))
2416         return false;
2417     if (sigaction(SIGTERM, &sig, 0))
2418         return false;
2419     if (sigaction(SIGINT, &sig, 0))
2420         return false;
2421 
2422     return true;
2423 }
2424 
2425 void KIOFuseVFS::exitHandler() 
2426 {
2427     m_signalNotifier->setEnabled(false);
2428     int tmp;
2429     ::read(signalFd[1], &tmp, sizeof(tmp));
2430     stop();
2431 }
2432 
2433 void KIOFuseVFS::signalHandler(int signal) 
2434 {
2435     ::write(signalFd[0], &signal, sizeof(signal));
2436 }
2437 
2438 int KIOFuseVFS::kioErrorToFuseError(const int kioError) {
2439     switch (kioError) {
2440         case 0                                     : return 0; // No error
2441         case KIO::ERR_CANNOT_OPEN_FOR_READING      : return EIO;
2442         case KIO::ERR_CANNOT_OPEN_FOR_WRITING      : return EIO;
2443         case KIO::ERR_CANNOT_LAUNCH_PROCESS        : return EIO;
2444         case KIO::ERR_INTERNAL                     : return EIO;
2445         case KIO::ERR_MALFORMED_URL                : return EIO;
2446         case KIO::ERR_UNSUPPORTED_PROTOCOL         : return ENOPROTOOPT;
2447         case KIO::ERR_NO_SOURCE_PROTOCOL           : return ENOPROTOOPT;
2448         case KIO::ERR_UNSUPPORTED_ACTION           : return ENOTTY;
2449         case KIO::ERR_IS_DIRECTORY                 : return EISDIR;
2450         case KIO::ERR_IS_FILE                      : return EEXIST;
2451         case KIO::ERR_DOES_NOT_EXIST               : return ENOENT;
2452         case KIO::ERR_FILE_ALREADY_EXIST           : return EEXIST;
2453         case KIO::ERR_DIR_ALREADY_EXIST            : return EEXIST;
2454         case KIO::ERR_UNKNOWN_HOST                 : return EHOSTUNREACH;
2455         case KIO::ERR_ACCESS_DENIED                : return EPERM;
2456         case KIO::ERR_WRITE_ACCESS_DENIED          : return EPERM;
2457         case KIO::ERR_CANNOT_ENTER_DIRECTORY       : return EIO;
2458         case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM : return EPROTOTYPE;
2459         case KIO::ERR_CYCLIC_LINK                  : return ELOOP;
2460         case KIO::ERR_USER_CANCELED                : return ECANCELED;
2461         case KIO::ERR_CYCLIC_COPY                  : return ELOOP;
2462         case KIO::ERR_CANNOT_CREATE_SOCKET         : return ENOTCONN;
2463         case KIO::ERR_CANNOT_CONNECT               : return ENOTCONN;
2464         case KIO::ERR_CONNECTION_BROKEN            : return ENOTCONN;
2465         case KIO::ERR_NOT_FILTER_PROTOCOL          : return EPROTOTYPE;
2466         case KIO::ERR_CANNOT_MOUNT                 : return EIO;
2467         case KIO::ERR_CANNOT_READ                  : return EIO;
2468         case KIO::ERR_CANNOT_WRITE                 : return EIO;
2469         case KIO::ERR_CANNOT_BIND                  : return EPERM;
2470         case KIO::ERR_CANNOT_LISTEN                : return EPERM;
2471         case KIO::ERR_CANNOT_ACCEPT                : return EPERM;
2472         case KIO::ERR_CANNOT_LOGIN                 : return ECONNREFUSED;
2473         case KIO::ERR_CANNOT_STAT                  : return EIO;
2474         case KIO::ERR_CANNOT_CLOSEDIR              : return EIO;
2475         case KIO::ERR_CANNOT_MKDIR                 : return EIO;
2476         case KIO::ERR_CANNOT_RMDIR                 : return EIO;
2477         case KIO::ERR_CANNOT_RESUME                : return ECONNABORTED;
2478         case KIO::ERR_CANNOT_RENAME                : return EIO;
2479         case KIO::ERR_CANNOT_CHMOD                 : return EIO;
2480         case KIO::ERR_CANNOT_DELETE                : return EIO;
2481         case KIO::ERR_WORKER_DIED                  : return EIO;
2482         case KIO::ERR_OUT_OF_MEMORY                : return ENOMEM;
2483         case KIO::ERR_UNKNOWN_PROXY_HOST           : return EHOSTUNREACH;
2484         case KIO::ERR_CANNOT_AUTHENTICATE          : return EACCES;
2485         case KIO::ERR_ABORTED                      : return ECONNABORTED;
2486         case KIO::ERR_INTERNAL_SERVER              : return EPROTO;
2487         case KIO::ERR_SERVER_TIMEOUT               : return ETIMEDOUT;
2488         case KIO::ERR_SERVICE_NOT_AVAILABLE        : return ENOPROTOOPT;
2489         case KIO::ERR_UNKNOWN                      : return EIO;
2490         case KIO::ERR_UNKNOWN_INTERRUPT            : return EIO;
2491         case KIO::ERR_CANNOT_DELETE_ORIGINAL       : return EIO;
2492         case KIO::ERR_CANNOT_DELETE_PARTIAL        : return EIO;
2493         case KIO::ERR_CANNOT_RENAME_ORIGINAL       : return EIO;
2494         case KIO::ERR_CANNOT_RENAME_PARTIAL        : return EIO;
2495         case KIO::ERR_NEED_PASSWD                  : return EACCES;
2496         case KIO::ERR_CANNOT_SYMLINK               : return EIO;
2497         case KIO::ERR_NO_CONTENT                   : return EIO;
2498         case KIO::ERR_DISK_FULL                    : return ENOSPC;
2499         case KIO::ERR_IDENTICAL_FILES              : return EEXIST;
2500         case KIO::ERR_WORKER_DEFINED               : return EIO;
2501         case KIO::ERR_UPGRADE_REQUIRED             : return EPROTOTYPE;
2502         case KIO::ERR_POST_DENIED                  : return EACCES;
2503         case KIO::ERR_CANNOT_SEEK                  : return EIO;
2504         case KIO::ERR_CANNOT_SETTIME               : return EIO;
2505         case KIO::ERR_CANNOT_CHOWN                 : return EIO;
2506         case KIO::ERR_POST_NO_SIZE                 : return EIO;
2507         case KIO::ERR_DROP_ON_ITSELF               : return EINVAL;
2508         case KIO::ERR_CANNOT_MOVE_INTO_ITSELF      : return EINVAL;
2509         case KIO::ERR_PASSWD_SERVER                : return EIO;
2510         case KIO::ERR_CANNOT_CREATE_WORKER         : return EIO;
2511         case KIO::ERR_FILE_TOO_LARGE_FOR_FAT32     : return EFBIG;
2512         case KIO::ERR_OWNER_DIED                   : return EIO;
2513         default                                    : return EIO;
2514     }
2515 }