Warning, file /system/kio-fuse/kiofusevfs.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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