1 /*
2 SPDX-FileCopyrightText: 2019-2020 Fabian Vogt <fabian@ritter-vogt.de>
3 SPDX-FileCopyrightText: 2019-2020 Alexander Saoutkin <a.saoutkin@gmail.com>
4 SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7 #include <qglobal.h>
8
9 #include <sys/types.h>
10 #include <errno.h>
11 #include <unistd.h>
12 #include <signal.h>
13 #include <chrono>
14
15 #ifdef Q_OS_LINUX
16 #include <linux/fs.h>
17 #include <sys/utsname.h>
18 #endif
19
20 #include <QDateTime>
21 #include <QDebug>
22 #include <QDir>
23 #include <QVersionNumber>
24
25 #include <KIO/ListJob>
26 #include <KIO/MkdirJob>
27 #include <KIO/StatJob>
28 #include <KIO/TransferJob>
29 #include <KIO/DeleteJob>
30 #include <KIO/FileJob>
31 #include <KProtocolManager>
32
33 #include "debug.h"
34 #include "kiofusevfs.h"
35
36 // Flags that don't exist on FreeBSD; since these are used as
37 // bit(masks), setting them to 0 effectively means they're always unset.
38 #ifndef O_NOATIME
39 #define O_NOATIME 0
40 #endif
41 #ifndef RENAME_NOREPLACE
42 #define RENAME_NOREPLACE 0
43 #endif
44
45 // The libfuse macros make this necessary
46 #pragma GCC diagnostic ignored "-Wpedantic"
47 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
48
49 struct KIOFuseVFS::FuseLLOps : public fuse_lowlevel_ops
50 {
FuseLLOpsKIOFuseVFS::FuseLLOps51 FuseLLOps()
52 {
53 init = &KIOFuseVFS::init;
54 lookup = &KIOFuseVFS::lookup;
55 forget = &KIOFuseVFS::forget;
56 getattr = &KIOFuseVFS::getattr;
57 setattr = &KIOFuseVFS::setattr;
58 readlink = &KIOFuseVFS::readlink;
59 mknod = &KIOFuseVFS::mknod;
60 mkdir = &KIOFuseVFS::mkdir;
61 unlink = &KIOFuseVFS::unlink;
62 rmdir = &KIOFuseVFS::rmdir;
63 symlink = &KIOFuseVFS::symlink;
64 rename = &KIOFuseVFS::rename;
65 open = &KIOFuseVFS::open;
66 read = &KIOFuseVFS::read;
67 write = &KIOFuseVFS::write;
68 flush = &KIOFuseVFS::flush;
69 release = &KIOFuseVFS::release;
70 fsync = &KIOFuseVFS::fsync;
71 opendir = &KIOFuseVFS::opendir;
72 readdir = &KIOFuseVFS::readdir;
73 releasedir = &KIOFuseVFS::releasedir;
74 }
75 };
76
77 const struct KIOFuseVFS::FuseLLOps KIOFuseVFS::fuse_ll_ops;
78
79 const std::chrono::steady_clock::duration KIOFuseRemoteNodeInfo::ATTR_TIMEOUT = std::chrono::seconds(30);
80 std::chrono::steady_clock::time_point g_timeoutEpoch = {};
81
82 /* Handles partial writes and EINTR.
83 * Returns true only if count bytes were written successfully. */
sane_write(int fd,const void * buf,size_t count)84 static bool sane_write(int fd, const void *buf, size_t count)
85 {
86 size_t bytes_left = count;
87 const char *buf_left = (const char*)buf;
88 while(bytes_left)
89 {
90 ssize_t step = write(fd, buf_left, bytes_left);
91 if(step == -1)
92 {
93 if(errno == EINTR)
94 continue;
95 else
96 return false;
97 }
98 else if(step == 0)
99 return false;
100
101 bytes_left -= step;
102 buf_left += step;
103 }
104
105 return true;
106 }
107
108 /* Handles partial reads and EINTR.
109 * Returns true only if count bytes were read successfully. */
sane_read(int fd,void * buf,size_t count)110 static bool sane_read(int fd, void *buf, size_t count)
111 {
112 size_t bytes_left = count;
113 char *buf_left = (char*)buf;
114 while(bytes_left)
115 {
116 ssize_t step = read(fd, buf_left, bytes_left);
117 if(step == -1)
118 {
119 if(errno == EINTR)
120 continue;
121 else
122 return false;
123 }
124 else if(step == 0)
125 return false;
126
127 bytes_left -= step;
128 buf_left += step;
129 }
130
131 return true;
132 }
133
operator <(const struct timespec & a,const struct timespec & b)134 static bool operator <(const struct timespec &a, const struct timespec &b)
135 {
136 return (a.tv_sec == b.tv_sec) ? (a.tv_nsec < b.tv_nsec) : (a.tv_sec < b.tv_sec);
137 }
138
139 /** Returns whether the given name is valid for a file. */
isValidFilename(const QString & name)140 static bool isValidFilename(const QString &name)
141 {
142 return !name.isEmpty() && !name.contains(QLatin1Char('/'))
143 && name != QStringLiteral(".") && name != QStringLiteral("..");
144 }
145
146 /** Returns url with members of pathElements appended to its path,
147 * unless url is empty, then it's returned as-is. */
addPathElements(QUrl url,QStringList pathElements)148 static QUrl addPathElements(QUrl url, QStringList pathElements)
149 {
150 if(url.isEmpty())
151 return url;
152
153 if(!url.path().endsWith(QLatin1Char('/')) && !pathElements.isEmpty())
154 pathElements.prepend({});
155
156 url.setPath(url.path() + pathElements.join(QLatin1Char('/')));
157 return url;
158 }
159
160 int KIOFuseVFS::signalFd[2];
161
KIOFuseVFS(QObject * parent)162 KIOFuseVFS::KIOFuseVFS(QObject *parent)
163 : QObject(parent)
164 {
165 struct stat attr = {};
166 fillStatForFile(attr);
167 attr.st_mode = S_IFDIR | 0755;
168
169 auto root = std::make_shared<KIOFuseDirNode>(KIOFuseIno::Invalid, QString(), attr);
170 insertNode(root, KIOFuseIno::Root);
171 incrementLookupCount(root, 1); // Implicitly referenced by mounting
172
173 auto deletedRoot = std::make_shared<KIOFuseDirNode>(KIOFuseIno::Invalid, QString(), attr);
174 insertNode(deletedRoot, KIOFuseIno::DeletedRoot);
175 }
176
~KIOFuseVFS()177 KIOFuseVFS::~KIOFuseVFS()
178 {
179 stop();
180 }
181
start(struct fuse_args & args,const QString & mountpoint)182 bool KIOFuseVFS::start(struct fuse_args &args, const QString& mountpoint)
183 {
184 if(!isEnvironmentValid())
185 return false;
186
187 stop();
188
189 m_fuseConnInfoOpts.reset(fuse_parse_conn_info_opts(&args));
190 m_fuseSession = fuse_session_new(&args, &fuse_ll_ops, sizeof(fuse_ll_ops), this);
191 m_mountpoint = QDir::cleanPath(mountpoint); // Remove redundant slashes
192 if(!m_mountpoint.endsWith(QLatin1Char('/')))
193 m_mountpoint += QLatin1Char('/');
194
195 if(!m_fuseSession)
196 return false;
197
198 if(!setupSignalHandlers()
199 || fuse_session_mount(m_fuseSession, m_mountpoint.toUtf8().data()) != 0)
200 {
201 stop();
202 return false;
203 }
204
205 // Setup a notifier on the FUSE FD
206 int fusefd = fuse_session_fd(m_fuseSession);
207
208 // Set the FD to O_NONBLOCK so that it can be read in a loop until empty
209 int flags = fcntl(fusefd, F_GETFL);
210 fcntl(fusefd, F_SETFL, flags | O_NONBLOCK);
211
212 m_fuseNotifier = std::make_unique<QSocketNotifier>(fusefd, QSocketNotifier::Read, this);
213 m_fuseNotifier->connect(m_fuseNotifier.get(), &QSocketNotifier::activated, this, &KIOFuseVFS::fuseRequestPending);
214
215 // Arm the QEventLoopLocker
216 m_eventLoopLocker = std::make_unique<QEventLoopLocker>();
217
218 return true;
219 }
220
stop()221 void KIOFuseVFS::stop()
222 {
223 if(!m_fuseSession) // Not started or already (being) stopped. Avoids reentrancy.
224 return;
225
226 // Disable the QSocketNotifier
227 m_fuseNotifier.reset();
228
229 // Disarm the QEventLoopLocker
230 m_eventLoopLocker.reset();
231
232 fuse_session_unmount(m_fuseSession);
233 fuse_session_destroy(m_fuseSession);
234 m_fuseSession = nullptr;
235
236 // Flush all dirty nodes
237 QEventLoop loop;
238 bool needEventLoop = false;
239
240 for(auto it = m_dirtyNodes.begin(); it != m_dirtyNodes.end();)
241 {
242 auto node = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(nodeForIno(*it));
243
244 ++it; // Increment now as awaitNodeFlushed invalidates the iterator
245
246 if(!node || (!node->m_cacheDirty && !node->m_flushRunning))
247 {
248 qWarning(KIOFUSE_LOG) << "Broken inode in dirty set";
249 continue;
250 }
251
252 auto lockerPointer = std::make_shared<QEventLoopLocker>(&loop);
253 // Trigger or wait until flush done.
254 awaitNodeFlushed(node, [lp = std::move(lockerPointer)](int) {});
255
256 needEventLoop = true;
257 }
258
259 if(needEventLoop)
260 loop.exec(); // Wait until all QEventLoopLockers got destroyed
261
262 // FIXME: If a signal arrives after this, it would be fatal.
263 removeSignalHandlers();
264 }
265
fuseRequestPending()266 void KIOFuseVFS::fuseRequestPending()
267 {
268 // Never deallocated, just reused
269 static struct fuse_buf fbuf = {};
270
271 // Read requests until empty (-EAGAIN) or error
272 for(;;)
273 {
274 int res = fuse_session_receive_buf(m_fuseSession, &fbuf);
275
276 if (res == -EINTR || res == -EAGAIN)
277 break;
278
279 if (res <= 0)
280 {
281 if(res < 0) // Error
282 qWarning(KIOFUSE_LOG) << "Error reading FUSE request:" << strerror(errno);
283
284 // Error or umounted -> quit
285 stop();
286 break;
287 }
288
289 fuse_session_process_buf(m_fuseSession, &fbuf);
290 }
291 }
292
setUseFileJob(bool useFileJob)293 void KIOFuseVFS::setUseFileJob(bool useFileJob)
294 {
295 m_useFileJob = useFileJob;
296 }
297
mountUrl(QUrl url,std::function<void (const QString &,int)> callback)298 void KIOFuseVFS::mountUrl(QUrl url, std::function<void (const QString &, int)> callback)
299 {
300 // First make sure it actually exists
301 qDebug(KIOFUSE_LOG) << "Stating" << url.toDisplayString() << "for mount";
302 auto statJob = KIO::stat(url);
303 statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
304 // files over plain HTTP
305 connect(statJob, &KIO::StatJob::result, [=] {
306 if(statJob->error())
307 {
308 qDebug(KIOFUSE_LOG) << statJob->errorString();
309 return callback({}, kioErrorToFuseError(statJob->error()));
310 }
311
312 // The file exists, try to mount it
313 auto pathElements = url.path().split(QLatin1Char('/'));
314 pathElements.removeAll({});
315
316 return findAndCreateOrigin(originOfUrl(url), pathElements, callback);
317 });
318 }
319
mapUrlToVfs(QUrl url)320 QStringList KIOFuseVFS::mapUrlToVfs(QUrl url)
321 {
322 // Build the path where it will appear in the VFS
323 auto targetPathComponents = QStringList{url.scheme(), url.authority()};
324 targetPathComponents.append(url.path().split(QLatin1Char('/')));
325
326 // Strip empty path elements, for instance in
327 // "file:///home/foo"
328 // ^ V
329 // "ftp://user@host/dir/ectory/"
330 targetPathComponents.removeAll({});
331
332 return targetPathComponents;
333 }
334
findAndCreateOrigin(QUrl url,QStringList pathElements,std::function<void (const QString &,int)> callback)335 void KIOFuseVFS::findAndCreateOrigin(QUrl url, QStringList pathElements, std::function<void (const QString &, int)> callback)
336 {
337 qDebug(KIOFUSE_LOG) << "Trying origin" << url.toDisplayString();
338 auto statJob = KIO::stat(url);
339 statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
340 // files over plain HTTP
341 connect(statJob, &KIO::StatJob::result, [=] {
342 if(statJob->error())
343 {
344 qDebug(KIOFUSE_LOG) << statJob->errorString();
345
346 if(!pathElements.isEmpty())
347 return findAndCreateOrigin(addPathElements(url, pathElements.mid(0, 1)), pathElements.mid(1), callback);
348
349 qDebug(KIOFUSE_LOG) << "Creating origin failed";
350 return callback({}, kioErrorToFuseError(statJob->error()));
351 }
352
353 qDebug(KIOFUSE_LOG) << "Origin found at" << url.toDisplayString();
354
355 auto targetPathComponents = mapUrlToVfs(url);
356
357 if(std::any_of(targetPathComponents.begin(), targetPathComponents.end(),
358 [](const QString &s) { return !isValidFilename(s); }))
359 return callback({}, EINVAL); // Invalid path (contains '.' or '..')
360
361 auto currentNode = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(KIOFuseIno::Root));
362
363 // Traverse/create all components until the last
364 for(int pathIdx = 0; pathIdx < targetPathComponents.size() - 1; ++pathIdx)
365 {
366 auto name = targetPathComponents[pathIdx];
367 auto nextNode = nodeByName(currentNode, name);
368 auto nextDirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(nextNode);
369
370 if(!nextNode)
371 {
372 struct stat attr = {};
373 fillStatForFile(attr);
374 attr.st_mode = S_IFDIR | 0755;
375
376 nextDirNode = std::make_shared<KIOFuseDirNode>(currentNode->m_stat.st_ino, name, attr);
377 insertNode(nextDirNode);
378 }
379 else if(!nextDirNode)
380 {
381 qWarning(KIOFUSE_LOG) << "Node" << nextNode->m_nodeName << "not a dir?";
382 return callback({}, EIO);
383 }
384
385 currentNode = nextDirNode;
386 }
387
388 auto finalNode = nodeByName(currentNode, targetPathComponents.last());
389 if(!finalNode)
390 {
391 finalNode = createNodeFromUDSEntry(statJob->statResult(), currentNode->m_stat.st_ino, targetPathComponents.last());
392 if(!finalNode)
393 {
394 qWarning(KIOFUSE_LOG) << "Unable to create a valid final node for" << url.toDisplayString() << "from its UDS Entry";
395 return callback({}, EIO);
396 }
397
398 // Some ioslaves like man:/ implement "index files" for folders (including /) by making
399 // them look as regular file when stating, but they also support listDir for directory
400 // functionality. This behaviour is not compatible, so just reject it outright.
401 if((url.path().isEmpty() || url.path() == QStringLiteral("/"))
402 && !S_ISDIR(finalNode->m_stat.st_mode))
403 {
404 qWarning(KIOFUSE_LOG) << "Root of mount at" << url.toDisplayString() << "not a directory";
405 return callback({}, ENOTDIR);
406 }
407
408 insertNode(finalNode);
409 }
410
411 auto originNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(finalNode);
412 if(!originNode)
413 {
414 qWarning(KIOFUSE_LOG) << "Origin" << finalNode->m_nodeName << "exists but not a remote node?";
415 return callback({}, EIO);
416 }
417
418 originNode->m_overrideUrl = url; // Allow the user to change the password
419 return callback((targetPathComponents + pathElements).join(QLatin1Char('/')), 0);
420 });
421 }
422
init(void * userdata,fuse_conn_info * conn)423 void KIOFuseVFS::init(void *userdata, fuse_conn_info *conn)
424 {
425 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(userdata);
426
427 if(that->m_fuseConnInfoOpts)
428 {
429 fuse_apply_conn_info_opts(that->m_fuseConnInfoOpts.get(), conn);
430 that->m_fuseConnInfoOpts.reset();
431 }
432
433 conn->want &= ~FUSE_CAP_HANDLE_KILLPRIV; // Don't care about resetting setuid/setgid flags
434 conn->want &= ~FUSE_CAP_ATOMIC_O_TRUNC; // Use setattr with st_size = 0 instead of open with O_TRUNC
435 // Writeback caching needs fuse_notify calls for shared filesystems, but those are broken by design
436 conn->want &= ~FUSE_CAP_WRITEBACK_CACHE;
437 conn->time_gran = 1000000000; // Only second resolution for mtime
438 }
439
getattr(fuse_req_t req,fuse_ino_t ino,fuse_file_info * fi)440 void KIOFuseVFS::getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
441 {
442 Q_UNUSED(fi);
443 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
444 auto node = that->nodeForIno(ino);
445 if(!node)
446 {
447 fuse_reply_err(req, EIO);
448 return;
449 }
450
451 return that->awaitAttrRefreshed(node, [=] (int error) {
452 Q_UNUSED(error); // Just send the old attr...
453 replyAttr(req, node);
454 });
455 }
456
setattr(fuse_req_t req,fuse_ino_t ino,struct stat * attr,int to_set,fuse_file_info * fi)457 void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, fuse_file_info *fi)
458 {
459 Q_UNUSED(fi);
460 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
461 auto node = that->nodeForIno(ino);
462 if(!node)
463 {
464 fuse_reply_err(req, EIO);
465 return;
466 }
467
468 auto remoteDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
469 auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileNode>(node);
470
471 if(!remoteDirNode && !remoteFileNode)
472 {
473 fuse_reply_err(req, EOPNOTSUPP);
474 return;
475 }
476
477 if((to_set & ~(FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID
478 | FUSE_SET_ATTR_MODE
479 | FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW
480 | FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW
481 | FUSE_SET_ATTR_CTIME))
482 || (!remoteFileNode && (to_set & FUSE_SET_ATTR_SIZE))) // Unsupported operation requested?
483 {
484 // Don't do anything
485 fuse_reply_err(req, EOPNOTSUPP);
486 return;
487 }
488
489 auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
490 auto fileJobBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
491
492 // To have equal atim and mtim
493 struct timespec tsNow;
494 clock_gettime(CLOCK_REALTIME, &tsNow);
495
496 // Can anything be done directly?
497
498 // This is a hack: Access and change time are not actually passed through to KIO.
499 // The kernel sends request for those if writeback caching is enabled, so it's not
500 // possible to ignore them. So just save them in the local cache.
501 if(to_set & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW))
502 {
503 if(to_set & FUSE_SET_ATTR_ATIME_NOW)
504 attr->st_atim = tsNow;
505
506 node->m_stat.st_atim = attr->st_atim;
507 to_set &= ~(FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_ATIME_NOW);
508 }
509 if(to_set & FUSE_SET_ATTR_CTIME)
510 {
511 node->m_stat.st_ctim = attr->st_ctim;
512 to_set &= ~FUSE_SET_ATTR_CTIME;
513 }
514 if((to_set & FUSE_SET_ATTR_SIZE) && cacheBasedFileNode)
515 {
516 // Can be done directly if the new size is zero (and there is no get going on).
517 // This is an optimization to avoid fetching the entire file just to ignore its content.
518 if(!cacheBasedFileNode->m_localCache && attr->st_size == 0)
519 {
520 // Just create an empty file
521 cacheBasedFileNode->m_localCache = tmpfile();
522 if(!cacheBasedFileNode->m_localCache)
523 {
524 fuse_reply_err(req, EIO);
525 return;
526 }
527
528 cacheBasedFileNode->m_cacheComplete = true;
529 cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = 0;
530 cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
531 that->markCacheDirty(cacheBasedFileNode);
532
533 to_set &= ~FUSE_SET_ATTR_SIZE; // Done already!
534 }
535 }
536
537 if(!to_set) // Done already?
538 {
539 replyAttr(req, node);
540 return;
541 }
542
543 // Everything else has to be done async - but there might be multiple ops that
544 // need to be coordinated. If an operation completes and clearing its value(s)
545 // in to_set_remaining leaves a zero value, it replies with fuse_reply_attr if
546 // error is zero and fuse_reply_err(error) otherwise.
547 struct SetattrState {
548 int to_set_remaining;
549 int error;
550 struct stat value;
551 };
552
553 auto sharedState = std::make_shared<SetattrState>((SetattrState){to_set, 0, *attr});
554
555 auto markOperationCompleted = [=] (int to_set_done) {
556 sharedState->to_set_remaining &= ~to_set_done;
557 if(!sharedState->to_set_remaining)
558 {
559 if(sharedState->error)
560 fuse_reply_err(req, sharedState->error);
561 else
562 replyAttr(req, node);
563 }
564 };
565
566 if((to_set & FUSE_SET_ATTR_SIZE) && cacheBasedFileNode)
567 {
568 // Have to wait until the cache is complete to truncate.
569 // Waiting until all bytes up to the truncation point are available won't work,
570 // as the fetch function would just overwrite the cache.
571 that->awaitCacheComplete(cacheBasedFileNode, [=] (int error) {
572 if(error)
573 sharedState->error = error;
574 else // Cache complete!
575 {
576 // Truncate the cache file
577 if(fflush(cacheBasedFileNode->m_localCache) != 0
578 || ftruncate(fileno(cacheBasedFileNode->m_localCache), sharedState->value.st_size) == -1)
579 sharedState->error = errno;
580 else
581 {
582 cacheBasedFileNode->m_cacheSize = cacheBasedFileNode->m_stat.st_size = sharedState->value.st_size;
583 cacheBasedFileNode->m_stat.st_mtim = cacheBasedFileNode->m_stat.st_ctim = tsNow;
584 that->markCacheDirty(cacheBasedFileNode);
585 }
586 }
587 markOperationCompleted(FUSE_SET_ATTR_SIZE);
588 });
589 }
590 else if ((to_set & FUSE_SET_ATTR_SIZE) && fileJobBasedFileNode)
591 {
592 auto *fileJob = KIO::open(that->remoteUrl(fileJobBasedFileNode), QIODevice::ReadWrite);
593 connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
594 // All errors come through this signal, so error-handling is done here
595 if(job->error())
596 {
597 sharedState->error = kioErrorToFuseError(job->error());
598 markOperationCompleted(FUSE_SET_ATTR_SIZE);
599 }
600 });
601 connect(fileJob, &KIO::FileJob::open, [=] {
602 fileJob->truncate(sharedState->value.st_size);
603 connect(fileJob, &KIO::FileJob::truncated, [=] {
604 fileJob->close();
605 connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
606 fileJobBasedFileNode->m_stat.st_size = sharedState->value.st_size;
607 markOperationCompleted(FUSE_SET_ATTR_SIZE);
608 });
609 });
610 });
611 }
612
613 if(to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))
614 {
615 // KIO uses strings for passing user and group, but the VFS uses IDs exclusively.
616 // So this needs a roundtrip.
617
618 uid_t newUid = (to_set & FUSE_SET_ATTR_UID) ? attr->st_uid : node->m_stat.st_uid;
619 gid_t newGid = (to_set & FUSE_SET_ATTR_GID) ? attr->st_gid : node->m_stat.st_gid;
620 auto *pw = getpwuid(newUid);
621 auto *gr = getgrgid(newGid);
622
623 if(!pw || !gr)
624 {
625 sharedState->error = ENOENT;
626 markOperationCompleted(FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID);
627 }
628 else
629 {
630 QString newOwner = QString::fromUtf8(pw->pw_name),
631 newGroup = QString::fromUtf8(gr->gr_name);
632
633 auto *job = KIO::chown(that->remoteUrl(node), newOwner, newGroup);
634 that->connect(job, &KIO::SimpleJob::finished, [=] {
635 if(job->error())
636 sharedState->error = kioErrorToFuseError(job->error());
637 else
638 {
639 node->m_stat.st_uid = newUid;
640 node->m_stat.st_gid = newGid;
641 node->m_stat.st_ctim = tsNow;
642 }
643
644 markOperationCompleted(FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID);
645 });
646 }
647 }
648
649 if(to_set & (FUSE_SET_ATTR_MODE))
650 {
651 auto newMode = attr->st_mode & ~S_IFMT;
652 auto *job = KIO::chmod(that->remoteUrl(node), newMode);
653 that->connect(job, &KIO::SimpleJob::finished, [=] {
654 if(job->error())
655 sharedState->error = kioErrorToFuseError(job->error());
656 else
657 {
658 node->m_stat.st_mode = (node->m_stat.st_mode & S_IFMT) | newMode;
659 node->m_stat.st_ctim = tsNow;
660 }
661
662 markOperationCompleted(FUSE_SET_ATTR_MODE);
663 });
664 }
665
666 if(to_set & (FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW))
667 {
668 if(to_set & FUSE_SET_ATTR_MTIME_NOW)
669 sharedState->value.st_mtim = tsNow;
670
671 auto time = QDateTime::fromMSecsSinceEpoch(qint64(sharedState->value.st_mtim.tv_sec) * 1000
672 + sharedState->value.st_mtim.tv_nsec / 1000000);
673 auto *job = KIO::setModificationTime(that->remoteUrl(node), time);
674 that->connect(job, &KIO::SimpleJob::finished, [=] {
675 if(job->error())
676 sharedState->error = kioErrorToFuseError(job->error());
677 else // This is not quite correct, as KIO rounded the value down to a second
678 node->m_stat.st_mtim = sharedState->value.st_mtim;
679
680 markOperationCompleted(FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_MTIME_NOW);
681 });
682 }
683 }
684
readlink(fuse_req_t req,fuse_ino_t ino)685 void KIOFuseVFS::readlink(fuse_req_t req, fuse_ino_t ino)
686 {
687 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
688 auto node = that->nodeForIno(ino);
689 if(!node)
690 {
691 fuse_reply_err(req, EIO);
692 return;
693 }
694
695 auto symlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node);
696 if(!symlinkNode)
697 {
698 fuse_reply_err(req, EINVAL);
699 return;
700 }
701
702 that->awaitAttrRefreshed(node, [=](int error) {
703 Q_UNUSED(error); // Just send the old target...
704
705 QString target = symlinkNode->m_target;
706
707 // Convert an absolute link to be absolute within its origin
708 if(QDir::isAbsolutePath(target))
709 {
710 target = target.mid(1); // Strip the initial /
711 QUrl origin = that->originOfUrl(that->remoteUrl(symlinkNode));
712 origin = addPathElements(origin, target.split(QLatin1Char('/')));
713 target = that->m_mountpoint + that->mapUrlToVfs(origin).join(QLatin1Char('/'));
714 qCDebug(KIOFUSE_LOG) << "Detected reading of absolute symlink" << symlinkNode->m_target << "at" << that->virtualPath(symlinkNode) << ", rewritten to" << target;
715 }
716
717 fuse_reply_readlink(req, target.toUtf8().data());
718 });
719 }
720
mknod(fuse_req_t req,fuse_ino_t parent,const char * name,mode_t mode,dev_t rdev)721 void KIOFuseVFS::mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
722 {
723 Q_UNUSED(rdev);
724
725 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
726 auto node = that->nodeForIno(parent);
727 if(!node)
728 {
729 fuse_reply_err(req, EIO);
730 return;
731 }
732
733 auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
734 if(!remote)
735 {
736 fuse_reply_err(req, EINVAL);
737 return;
738 }
739
740 // No type means regular file as well
741 if((mode & S_IFMT) != S_IFREG && (mode & S_IFMT) != 0)
742 {
743 fuse_reply_err(req, EOPNOTSUPP);
744 return;
745 }
746
747 auto nameStr = QString::fromUtf8(name);
748 auto url = addPathElements(that->remoteUrl(node), {nameStr});
749 auto *job = KIO::put(url, mode & ~S_IFMT);
750 // Not connecting to the dataReq signal at all results in an empty file
751 that->connect(job, &KIO::SimpleJob::finished, [=] {
752 if(job->error())
753 {
754 fuse_reply_err(req, kioErrorToFuseError(job->error()));
755 return;
756 }
757
758 that->awaitChildMounted(remote, nameStr, [=](auto node, int error) {
759 if(error)
760 fuse_reply_err(req, error);
761 else
762 that->replyEntry(req, node);
763 });
764 });
765 }
766
mkdir(fuse_req_t req,fuse_ino_t parent,const char * name,mode_t mode)767 void KIOFuseVFS::mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
768 {
769 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
770 auto node = that->nodeForIno(parent);
771 if(!node)
772 {
773 fuse_reply_err(req, EIO);
774 return;
775 }
776
777 auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
778 if(!remote)
779 {
780 fuse_reply_err(req, EINVAL);
781 return;
782 }
783
784 auto namestr = QString::fromUtf8(name);
785 auto url = addPathElements(that->remoteUrl(node), {namestr});
786 auto *job = KIO::mkdir(url, mode & ~S_IFMT);
787 that->connect(job, &KIO::SimpleJob::finished, [=] {
788 if(job->error())
789 {
790 fuse_reply_err(req, kioErrorToFuseError(job->error()));
791 return;
792 }
793
794 that->awaitChildMounted(remote, namestr, [=](auto node, int error) {
795 if(error)
796 fuse_reply_err(req, error);
797 else
798 that->replyEntry(req, node);
799 });
800 });
801 }
802
unlinkHelper(fuse_req_t req,fuse_ino_t parent,const char * name,bool isDirectory)803 void KIOFuseVFS::unlinkHelper(fuse_req_t req, fuse_ino_t parent, const char *name, bool isDirectory)
804 {
805 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
806 auto parentNode = that->nodeForIno(parent);
807 if(!parentNode)
808 {
809 fuse_reply_err(req, EIO);
810 return;
811 }
812
813 // Make sure the to-be deleted node is in a remote dir
814 auto parentDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode);
815 if(!parentDirNode)
816 {
817 fuse_reply_err(req, EINVAL);
818 return;
819 }
820
821 auto node = that->nodeByName(parentDirNode, QString::fromUtf8(name));
822 if(!node)
823 {
824 fuse_reply_err(req, ENOENT);
825 return;
826 }
827
828 auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
829
830 if(!isDirectory && dirNode != nullptr)
831 {
832 fuse_reply_err(req, EISDIR);
833 return;
834 }
835
836 if(isDirectory && dirNode == nullptr)
837 {
838 fuse_reply_err(req, ENOTDIR);
839 return;
840 }
841 else if(dirNode && !dirNode->m_childrenInos.empty())
842 {
843 // If node is a dir, it must be empty
844 fuse_reply_err(req, ENOTEMPTY);
845 return;
846 }
847
848 if(auto fileJobNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
849 {
850 // After deleting a file, the contents become inaccessible immediately,
851 // so avoid creating nameless inodes. tmpfile() semantics aren't possible with FileJob.
852 if(fileJobNode->m_openCount)
853 {
854 fuse_reply_err(req, EBUSY);
855 return;
856 }
857 }
858
859 auto *job = KIO::del(that->remoteUrl(node));
860 that->connect(job, &KIO::SimpleJob::finished, [=] {
861 if(job->error())
862 {
863 fuse_reply_err(req, kioErrorToFuseError(job->error()));
864 return;
865 }
866
867 that->markNodeDeleted(node);
868 fuse_reply_err(req, 0);
869 });
870 }
871
unlink(fuse_req_t req,fuse_ino_t parent,const char * name)872 void KIOFuseVFS::unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
873 {
874 unlinkHelper(req, parent, name, false);
875 }
876
rmdir(fuse_req_t req,fuse_ino_t parent,const char * name)877 void KIOFuseVFS::rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
878 {
879 unlinkHelper(req, parent, name, true);
880 }
881
symlink(fuse_req_t req,const char * link,fuse_ino_t parent,const char * name)882 void KIOFuseVFS::symlink(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
883 {
884 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
885 auto node = that->nodeForIno(parent);
886 if(!node)
887 {
888 fuse_reply_err(req, EIO);
889 return;
890 }
891
892 auto remote = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
893 if(!remote)
894 {
895 fuse_reply_err(req, EINVAL);
896 return;
897 }
898
899 QString target = QString::fromUtf8(link);
900
901 // Convert an absolute link within the VFS to an absolute link on the origin
902 // (inverse of readlink)
903 if(QDir::isAbsolutePath(target) && target.startsWith(that->m_mountpoint))
904 {
905 QUrl remoteSourceUrl = that->remoteUrl(remote),
906 // QDir::relativePath would mangle the path, we want to keep it as-is
907 remoteTargetUrl = that->localPathToRemoteUrl(target.mid(that->m_mountpoint.length()));
908
909 if(remoteTargetUrl.isValid()
910 && remoteSourceUrl.scheme() == remoteTargetUrl.scheme()
911 && remoteSourceUrl.authority() == remoteTargetUrl.authority())
912 {
913 target = remoteTargetUrl.path();
914 if(!target.startsWith(QLatin1Char('/')))
915 target.prepend(QLatin1Char('/'));
916
917 qCDebug(KIOFUSE_LOG) << "Detected creation of absolute symlink, rewritten to" << target;
918 }
919 else
920 qCWarning(KIOFUSE_LOG) << "Creation of absolute symlink to other origin";
921 }
922
923 auto namestr = QString::fromUtf8(name);
924 auto url = addPathElements(that->remoteUrl(node), {namestr});
925 auto *job = KIO::symlink(target, url);
926 that->connect(job, &KIO::SimpleJob::finished, [=] {
927 if(job->error())
928 {
929 fuse_reply_err(req, kioErrorToFuseError(job->error()));
930 return;
931 }
932
933 that->awaitChildMounted(remote, namestr, [=](auto node, int error) {
934 if(error)
935 fuse_reply_err(req, error);
936 else
937 that->replyEntry(req, node);
938 });
939 });
940 }
941
open(fuse_req_t req,fuse_ino_t ino,fuse_file_info * fi)942 void KIOFuseVFS::open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
943 {
944 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
945 auto node = that->nodeForIno(ino);
946 if(!node)
947 {
948 fuse_reply_err(req, EIO);
949 return;
950 }
951
952 node->m_openCount += 1;
953
954 if (!(fi->flags & O_NOATIME))
955 clock_gettime(CLOCK_REALTIME, &node->m_stat.st_atim);
956
957 fuse_reply_open(req, fi);
958 }
959
rename(fuse_req_t req,fuse_ino_t parent,const char * name,fuse_ino_t newparent,const char * newname,unsigned int flags)960 void KIOFuseVFS::rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
961 {
962 if(flags & ~(RENAME_NOREPLACE))
963 {
964 // RENAME_EXCHANGE could be emulated locally, but not with the same guarantees
965 fuse_reply_err(req, EOPNOTSUPP);
966 return;
967 }
968
969 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
970 auto parentNode = that->nodeForIno(parent), newParentNode = that->nodeForIno(newparent);
971 if(!parentNode || !newParentNode)
972 {
973 fuse_reply_err(req, EIO);
974 return;
975 }
976
977 auto remoteParent = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode),
978 remoteNewParent = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(newParentNode);
979 if(!remoteParent || !remoteNewParent)
980 {
981 fuse_reply_err(req, EINVAL);
982 return;
983 }
984
985 auto node = that->nodeByName(remoteParent, QString::fromUtf8(name));
986 if(!node)
987 {
988 fuse_reply_err(req, ENOENT);
989 return;
990 }
991
992 QString newNameStr = QString::fromUtf8(newname);
993
994 auto replacedNode = that->nodeByName(remoteNewParent, newNameStr);
995
996 // Ensure that if node is a directory, replacedNode either does not exist or is an empty directory.
997 if(std::dynamic_pointer_cast<KIOFuseDirNode>(node) && replacedNode)
998 {
999 auto replacedDir = std::dynamic_pointer_cast<KIOFuseDirNode>(replacedNode);
1000 if(!replacedDir)
1001 {
1002 fuse_reply_err(req, ENOTDIR);
1003 return;
1004 }
1005 if(replacedDir && !replacedDir->m_childrenInos.empty())
1006 {
1007 fuse_reply_err(req, ENOTEMPTY);
1008 return;
1009 }
1010 }
1011
1012 auto url = addPathElements(that->remoteUrl(remoteParent), {QString::fromUtf8(name)}),
1013 newUrl = addPathElements(that->remoteUrl(remoteNewParent), {newNameStr});
1014
1015 auto *job = KIO::rename(url, newUrl, (flags & RENAME_NOREPLACE) ? KIO::DefaultFlags : KIO::Overwrite);
1016 that->connect(job, &KIO::SimpleJob::finished, [=] {
1017 if(job->error())
1018 fuse_reply_err(req, kioErrorToFuseError(job->error()));
1019 else
1020 {
1021 if(replacedNode)
1022 that->markNodeDeleted(replacedNode);
1023
1024 that->reparentNode(node, newParentNode->m_stat.st_ino);
1025 node->m_nodeName = newNameStr;
1026
1027 clock_gettime(CLOCK_REALTIME, &node->m_stat.st_ctim);
1028 fuse_reply_err(req, 0);
1029 }
1030 });
1031 }
1032
appendDirentry(std::vector<char> & dirbuf,fuse_req_t req,const char * name,const struct stat * stbuf)1033 static void appendDirentry(std::vector<char> &dirbuf, fuse_req_t req, const char *name, const struct stat *stbuf)
1034 {
1035 size_t oldsize = dirbuf.size();
1036 dirbuf.resize(oldsize + fuse_add_direntry(req, nullptr, 0, name, nullptr, 0));
1037 fuse_add_direntry(req, dirbuf.data() + oldsize, dirbuf.size() + oldsize, name, stbuf, dirbuf.size());
1038 }
1039
opendir(fuse_req_t req,fuse_ino_t ino,fuse_file_info * fi)1040 void KIOFuseVFS::opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1041 {
1042 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1043 auto node = that->nodeForIno(ino);
1044 if(!node)
1045 {
1046 fuse_reply_err(req, EIO);
1047 return;
1048 }
1049
1050 auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1051
1052 if(!dirNode)
1053 {
1054 fuse_reply_err(req, ENOTDIR);
1055 return;
1056 }
1057
1058 node->m_openCount += 1;
1059
1060 that->awaitChildrenComplete(dirNode, [=, myfi=*fi](int error) mutable {
1061 if(error)
1062 {
1063 fuse_reply_err(req, error);
1064 return;
1065 }
1066
1067 auto dirbuf = std::make_unique<std::vector<char>>();
1068
1069 appendDirentry(*dirbuf, req, ".", &node->m_stat);
1070
1071 std::shared_ptr<KIOFuseNode> parentNode;
1072 if(node->m_parentIno != KIOFuseIno::DeletedRoot)
1073 parentNode = that->nodeForIno(node->m_parentIno);
1074 if(!parentNode)
1075 parentNode = that->nodeForIno(KIOFuseIno::Root);
1076 if(parentNode)
1077 appendDirentry(*dirbuf, req, "..", &parentNode->m_stat);
1078
1079 for(auto ino : dirNode->m_childrenInos)
1080 {
1081 auto child = that->nodeForIno(ino);
1082 if(!child)
1083 {
1084 qWarning(KIOFUSE_LOG) << "Node" << node->m_nodeName << "references nonexistent child";
1085 continue;
1086 }
1087
1088 appendDirentry(*dirbuf, req, qPrintable(child->m_nodeName), &child->m_stat);
1089 }
1090
1091 myfi.fh = reinterpret_cast<uint64_t>(dirbuf.release());
1092
1093 if (!(myfi.flags & O_NOATIME))
1094 clock_gettime(CLOCK_REALTIME, &node->m_stat.st_atim);
1095
1096 fuse_reply_open(req, &myfi);
1097 });
1098 }
1099
readdir(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)1100 void KIOFuseVFS::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
1101 {
1102 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1103 auto node = that->nodeForIno(ino);
1104 if(!node)
1105 {
1106 fuse_reply_err(req, EIO);
1107 return;
1108 }
1109
1110 auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1111 if(!dirNode)
1112 {
1113 fuse_reply_err(req, ENOTDIR);
1114 return;
1115 }
1116
1117 std::vector<char>* dirbuf = reinterpret_cast<std::vector<char>*>(fi->fh);
1118 if(!dirbuf)
1119 {
1120 fuse_reply_err(req, EIO);
1121 return;
1122 }
1123
1124 if(off < off_t(dirbuf->size()))
1125 fuse_reply_buf(req, dirbuf->data() + off, std::min(off_t(size), off_t(dirbuf->size()) - off));
1126 else
1127 fuse_reply_buf(req, nullptr, 0);
1128 }
1129
releasedir(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)1130 void KIOFuseVFS::releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
1131 {
1132 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1133 auto node = that->nodeForIno(ino);
1134 if(!node)
1135 {
1136 fuse_reply_err(req, EIO);
1137 return;
1138 }
1139
1140 auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1141 if(!dirNode)
1142 {
1143 fuse_reply_err(req, ENOTDIR);
1144 return;
1145 }
1146
1147 node->m_openCount -= 1;
1148 if(std::vector<char> *ptr = reinterpret_cast<std::vector<char>*>(fi->fh))
1149 {
1150 delete ptr;
1151 }
1152 else{
1153 qWarning(KIOFUSE_LOG) << "File handler of node" << node->m_nodeName << "already null";
1154 }
1155
1156 fuse_reply_err(req, 0);
1157 }
1158
read(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,fuse_file_info * fi)1159 void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fuse_file_info *fi)
1160 {
1161 Q_UNUSED(fi);
1162 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1163 auto node = that->nodeForIno(ino);
1164 if(!node)
1165 {
1166 fuse_reply_err(req, EIO);
1167 return;
1168 }
1169
1170 if(std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1171 {
1172 fuse_reply_err(req, EISDIR);
1173 return;
1174 }
1175
1176 if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1177 {
1178 qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
1179 that->awaitBytesAvailable(remoteNode, off + size, [=] (int error) {
1180 if(error != 0 && error != ESPIPE)
1181 {
1182 fuse_reply_err(req, error);
1183 return;
1184 }
1185
1186 auto actualSize = size;
1187
1188 if(error == ESPIPE)
1189 {
1190 // Reading over the end
1191 if(off >= remoteNode->m_cacheSize)
1192 actualSize = 0;
1193 else
1194 actualSize = std::min(remoteNode->m_cacheSize - off, off_t(size));
1195 }
1196
1197 // Make sure that the kernel has the data
1198 fflush(remoteNode->m_localCache);
1199
1200 // Construct a buf pointing to the cache file
1201 fuse_bufvec buf = FUSE_BUFVEC_INIT(actualSize);
1202 buf.buf[0].fd = fileno(remoteNode->m_localCache);
1203 buf.buf[0].flags = static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
1204 buf.buf[0].pos = off;
1205
1206 fuse_reply_data(req, &buf, fuse_buf_copy_flags{});
1207 });
1208
1209 return;
1210 }
1211 else if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
1212 {
1213 qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
1214
1215 if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1216 {
1217 fuse_reply_err(req, ENOENT);
1218 return;
1219 }
1220
1221 auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadOnly);
1222 connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
1223 // All errors come through this signal, so error-handling is done here
1224 if(job->error())
1225 fuse_reply_err(req, kioErrorToFuseError(job->error()));
1226 });
1227 connect(fileJob, &KIO::FileJob::open, [=] {
1228 fileJob->seek(off);
1229 connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
1230 Q_UNUSED(job);
1231 if(off_t(offset) != off)
1232 {
1233 fileJob->close();
1234 fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
1235 fuse_reply_err(req, EIO);
1236 });
1237 return;
1238 }
1239 auto actualSize = remoteNode->m_stat.st_size = fileJob->size();
1240 // Reading over the end
1241 if(off >= off_t(actualSize))
1242 actualSize = 0;
1243 else
1244 actualSize = std::min(off_t(actualSize) - off, off_t(size));
1245 fileJob->read(actualSize);
1246 QByteArray buffer;
1247 fileJob->connect(fileJob, &KIO::FileJob::data, [=] (auto *readJob, const QByteArray &data) mutable {
1248 Q_UNUSED(readJob);
1249 QByteArray truncatedData = data.left(actualSize);
1250 buffer.append(truncatedData);
1251 actualSize -= truncatedData.size();
1252
1253 if(actualSize > 0)
1254 {
1255 // Keep reading until we get all the data we need.
1256 fileJob->read(actualSize);
1257 return;
1258 }
1259 fileJob->close();
1260 fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
1261 fuse_reply_buf(req, buffer.constData(), buffer.size());
1262 });
1263 });
1264 });
1265 });
1266
1267 return;
1268 }
1269
1270 fuse_reply_err(req, EIO);
1271 }
1272
write(fuse_req_t req,fuse_ino_t ino,const char * buf,size_t size,off_t off,fuse_file_info * fi)1273 void KIOFuseVFS::write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, fuse_file_info *fi)
1274 {
1275 Q_UNUSED(fi);
1276 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1277 auto node = that->nodeForIno(ino);
1278 if(!node)
1279 {
1280 fuse_reply_err(req, EIO);
1281 return;
1282 }
1283
1284 if(std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1285 {
1286 fuse_reply_err(req, EISDIR);
1287 return;
1288 }
1289
1290 if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1291 {
1292 qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (cache-based) node" << node->m_nodeName;
1293 QByteArray data(buf, size); // Copy data
1294 // fi lives on the caller's stack make a copy.
1295 auto cacheBasedWriteCallback = [=, fi_flags=fi->flags] (int error) {
1296 if(error && error != ESPIPE)
1297 {
1298 fuse_reply_err(req, error);
1299 return;
1300 }
1301
1302 off_t offset = (fi_flags & O_APPEND) ? remoteNode->m_cacheSize : off;
1303
1304 int cacheFd = fileno(remoteNode->m_localCache);
1305 if(lseek(cacheFd, offset, SEEK_SET) == -1
1306 || !sane_write(cacheFd, data.data(), data.size()))
1307 {
1308 fuse_reply_err(req, errno);
1309 return;
1310 }
1311
1312 remoteNode->m_cacheSize = std::max(remoteNode->m_cacheSize, off_t(offset + size));
1313 // Don't set it to a lower value, in case the cache is incomplete
1314 remoteNode->m_stat.st_size = std::max(remoteNode->m_stat.st_size, remoteNode->m_cacheSize);
1315 // Update [cm] time as without writeback caching,
1316 // the kernel doesn't do this for us.
1317 clock_gettime(CLOCK_REALTIME, &remoteNode->m_stat.st_mtim);
1318 remoteNode->m_stat.st_ctim = remoteNode->m_stat.st_mtim;
1319 that->markCacheDirty(remoteNode);
1320
1321 fuse_reply_write(req, data.size());
1322 };
1323
1324 if(fi->flags & O_APPEND)
1325 // Wait for cache to be complete to ensure valid m_cacheSize
1326 that->awaitCacheComplete(remoteNode, cacheBasedWriteCallback);
1327 else
1328 that->awaitBytesAvailable(remoteNode, off + size, cacheBasedWriteCallback);
1329
1330 return;
1331 }
1332 else if(auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node))
1333 {
1334 qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
1335
1336 if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1337 {
1338 fuse_reply_err(req, ENOENT);
1339 return;
1340 }
1341
1342 QByteArray data(buf, size); // Copy data
1343 auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadWrite);
1344 connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
1345 // All errors come through this signal, so error-handling is done here
1346 if(job->error())
1347 fuse_reply_err(req, kioErrorToFuseError(job->error()));
1348 });
1349 connect(fileJob, &KIO::FileJob::open, [=, fi_flags=fi->flags] {
1350 off_t offset = (fi_flags & O_APPEND) ? fileJob->size() : off;
1351 fileJob->seek(offset);
1352 connect(fileJob, &KIO::FileJob::position, [=] (auto *job, KIO::filesize_t offset) {
1353 Q_UNUSED(job);
1354 if (off_t(offset) != off) {
1355 fileJob->close();
1356 fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
1357 fuse_reply_err(req, EIO);
1358 });
1359 return;
1360 }
1361 // Limit write to avoid killing the slave.
1362 // @see https://phabricator.kde.org/D15448
1363 fileJob->write(data.left(0xFFFFFF));
1364 off_t bytesLeft = size;
1365 fileJob->connect(fileJob, &KIO::FileJob::written, [=] (auto *writeJob, KIO::filesize_t written) mutable {
1366 Q_UNUSED(writeJob);
1367 bytesLeft -= written;
1368 if (bytesLeft > 0)
1369 {
1370 // Keep writing until we write all the data we need.
1371 fileJob->write(data.mid(size - bytesLeft, 0xFFFFFF));
1372 return;
1373 }
1374 fileJob->close();
1375 fileJob->connect(fileJob, qOverload<KIO::Job*>(&KIO::FileJob::close), [=] {
1376 // Wait till we've flushed first...
1377 remoteNode->m_stat.st_size = std::max(off_t(offset + data.size()), remoteNode->m_stat.st_size);
1378 fuse_reply_write(req, data.size());
1379 });
1380 });
1381 });
1382 });
1383
1384 return;
1385 }
1386
1387 fuse_reply_err(req, EIO);
1388 }
1389
flush(fuse_req_t req,fuse_ino_t ino,fuse_file_info * fi)1390 void KIOFuseVFS::flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1391 {
1392 // This is called on each close of a FD, so it might be a bit overzealous
1393 // to do writeback here. I can't think of a better alternative though -
1394 // doing it only on fsync and the final forget seems like a bit too late.
1395
1396 return KIOFuseVFS::fsync(req, ino, 1, fi);
1397 }
1398
release(fuse_req_t req,fuse_ino_t ino,fuse_file_info * fi)1399 void KIOFuseVFS::release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
1400 {
1401 Q_UNUSED(fi);
1402 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1403 auto node = that->nodeForIno(ino);
1404 if(!node)
1405 {
1406 fuse_reply_err(req, EIO);
1407 return;
1408 }
1409
1410 node->m_openCount -= 1;
1411
1412 fuse_reply_err(req, 0); // Ignored anyway
1413
1414 auto remoteFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node);
1415 if(node->m_openCount || !remoteFileNode || !remoteFileNode->m_localCache)
1416 return; // Nothing to do
1417
1418 // When the cache is not dirty, remove the cache file.
1419 that->awaitNodeFlushed(remoteFileNode, [=](int error) {
1420 if(error != 0 || node->m_openCount != 0)
1421 return; // Better not remove the cache
1422 if(remoteFileNode->m_localCache == nullptr)
1423 return; // Already removed (happens if the file was reopened and closed while flushing)
1424 if(!remoteFileNode->cacheIsComplete())
1425 return; // Currently filling
1426
1427 if(remoteFileNode->m_cacheDirty || remoteFileNode->m_flushRunning)
1428 {
1429 if(remoteFileNode->m_parentIno == KIOFuseIno::DeletedRoot)
1430 return; // Closed a deleted dirty file, keep the cache as it could be reopened
1431
1432 // Can't happen, but if it does, avoid data loss and potential crashing later by keeping
1433 // the cache.
1434 qWarning(KIOFUSE_LOG) << "Node" << remoteFileNode->m_nodeName << "turned dirty in flush callback";
1435 return;
1436 }
1437
1438 qDebug(KIOFUSE_LOG) << "Removing cache of" << remoteFileNode->m_nodeName;
1439 fclose(remoteFileNode->m_localCache);
1440 remoteFileNode->m_cacheSize = 0;
1441 remoteFileNode->m_localCache = nullptr;
1442 remoteFileNode->m_cacheComplete = false;
1443 });
1444 }
1445
fsync(fuse_req_t req,fuse_ino_t ino,int datasync,fuse_file_info * fi)1446 void KIOFuseVFS::fsync(fuse_req_t req, fuse_ino_t ino, int datasync, fuse_file_info *fi)
1447 {
1448 Q_UNUSED(datasync); Q_UNUSED(fi);
1449 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1450 auto node = that->nodeForIno(ino);
1451 if(!node)
1452 {
1453 fuse_reply_err(req, EIO);
1454 return;
1455 }
1456
1457 if(auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1458 that->awaitNodeFlushed(cacheBasedFileNode, [=](int error) {
1459 fuse_reply_err(req, error);
1460 });
1461 else
1462 fuse_reply_err(req, 0);
1463 }
1464
isEnvironmentValid()1465 bool KIOFuseVFS::isEnvironmentValid()
1466 {
1467 static_assert(sizeof(off_t) >= 8, "Please compile with -D_FILE_OFFSET_BITS=64 to allow working with large (>4GB) files");
1468
1469 #ifdef Q_OS_LINUX
1470 // On 32bit Linux before "fuse: fix writepages on 32bit", writes past 4GiB were silently discarded.
1471 // Technically this would have to check the kernel's bitness, but that's not easily possible.
1472 if(sizeof(size_t) != sizeof(off_t))
1473 {
1474 struct utsname uts;
1475 if(uname(&uts) != 0)
1476 return false;
1477
1478 auto kernelversion = QVersionNumber::fromString(QLatin1String(uts.release));
1479 if(kernelversion < QVersionNumber(5, 2))
1480 {
1481 qCritical(KIOFUSE_LOG) << "You're running kio-fuse on an older 32-bit kernel, which can lead to data loss.\n"
1482 "Please use a newer one or make sure that the 'fuse: fix writepages on 32bit' commit "
1483 "is part of the kernel and then build kio-fuse with this check adjusted.\n"
1484 "If you don't know how to do that, please file a bug at your distro.";
1485 return false;
1486 }
1487 }
1488 #endif
1489
1490 return true;
1491 }
1492
nodeByName(const std::shared_ptr<KIOFuseDirNode> & parent,const QString name) const1493 std::shared_ptr<KIOFuseNode> KIOFuseVFS::nodeByName(const std::shared_ptr<KIOFuseDirNode> &parent, const QString name) const
1494 {
1495 for(auto ino : parent->m_childrenInos)
1496 {
1497 auto child = nodeForIno(ino);
1498 if(!child)
1499 {
1500 qWarning(KIOFUSE_LOG) << "Node" << parent->m_nodeName << "references nonexistent child";
1501 continue;
1502 }
1503
1504 if(child->m_nodeName == name)
1505 return child;
1506 }
1507
1508 return nullptr;
1509 }
1510
lookup(fuse_req_t req,fuse_ino_t parent,const char * name)1511 void KIOFuseVFS::lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
1512 {
1513 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1514 auto parentNode = that->nodeForIno(parent);
1515 if(!parentNode)
1516 {
1517 fuse_reply_err(req, EIO);
1518 return;
1519 }
1520
1521 auto parentDirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(parentNode);
1522 if(!parentDirNode)
1523 {
1524 fuse_reply_err(req, ENOTDIR);
1525 return;
1526 }
1527
1528 QString nodeName = QString::fromUtf8(name);
1529
1530 if(auto child = that->nodeByName(parentDirNode, nodeName)) // Part of the tree?
1531 {
1532 return that->awaitAttrRefreshed(child, [=](int error) {
1533 Q_UNUSED(error);
1534 // Lookup again, node might've been replaced or deleted
1535 auto child = that->nodeByName(parentDirNode, nodeName);
1536 that->replyEntry(req, child);
1537 });
1538 }
1539
1540 auto remoteDirNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentDirNode);
1541 if(!remoteDirNode)
1542 {
1543 // Directory not remote, so definitely does not exist
1544 fuse_reply_err(req, ENOENT);
1545 return;
1546 }
1547
1548 // Not in the local tree, but remote - try again
1549 that->awaitChildMounted(remoteDirNode, nodeName, [=](auto node, int error) {
1550 if(error && error != ENOENT)
1551 fuse_reply_err(req, error);
1552 else
1553 that->replyEntry(req, node);
1554 });
1555 }
1556
forget(fuse_req_t req,fuse_ino_t ino,uint64_t nlookup)1557 void KIOFuseVFS::forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
1558 {
1559 KIOFuseVFS *that = reinterpret_cast<KIOFuseVFS*>(fuse_req_userdata(req));
1560 if(auto node = that->nodeForIno(ino))
1561 that->decrementLookupCount(node, nlookup);
1562
1563 fuse_reply_none(req);
1564 }
1565
nodeForIno(const fuse_ino_t ino) const1566 std::shared_ptr<KIOFuseNode> KIOFuseVFS::nodeForIno(const fuse_ino_t ino) const
1567 {
1568 auto it = m_nodes.find(ino);
1569 if(it == m_nodes.end())
1570 return nullptr;
1571
1572 return it->second;
1573 }
1574
reparentNode(const std::shared_ptr<KIOFuseNode> & node,fuse_ino_t newParentIno)1575 void KIOFuseVFS::reparentNode(const std::shared_ptr<KIOFuseNode> &node, fuse_ino_t newParentIno)
1576 {
1577 if(node->m_parentIno == newParentIno)
1578 return;
1579
1580 if(node->m_parentIno != KIOFuseIno::Invalid)
1581 {
1582 // Remove from old parent's children list
1583 if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1584 {
1585 auto &childrenList = parentDir->m_childrenInos;
1586 auto it = std::find(begin(childrenList), end(childrenList), node->m_stat.st_ino);
1587 if(it != childrenList.end())
1588 childrenList.erase(it);
1589 else
1590 qWarning(KIOFUSE_LOG) << "Tried to reparent node with broken parent link";
1591 }
1592 else
1593 qWarning(KIOFUSE_LOG) << "Tried to reparent node with invalid parent";
1594 }
1595
1596 node->m_parentIno = newParentIno;
1597
1598 if(node->m_parentIno != KIOFuseIno::Invalid)
1599 {
1600 // Add to new parent's children list
1601 if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1602 parentDir->m_childrenInos.push_back(node->m_stat.st_ino);
1603 else
1604 qWarning(KIOFUSE_LOG) << "Tried to insert node with invalid parent";
1605 }
1606 }
1607
insertNode(const std::shared_ptr<KIOFuseNode> & node,fuse_ino_t ino)1608 fuse_ino_t KIOFuseVFS::insertNode(const std::shared_ptr<KIOFuseNode> &node, fuse_ino_t ino)
1609 {
1610 if(ino == KIOFuseIno::Invalid)
1611 {
1612 // Allocate a free inode number
1613 ino = m_nextIno;
1614 while(ino < KIOFuseIno::DynamicStart || m_nodes.find(ino) != m_nodes.end())
1615 ino++;
1616
1617 m_nextIno = ino + 1;
1618 }
1619
1620 m_nodes[ino] = node;
1621
1622 // Adjust internal ino
1623 node->m_stat.st_ino = ino;
1624
1625 if(node->m_parentIno != KIOFuseIno::Invalid)
1626 {
1627 // Add to parent's child
1628 if(auto parentDir = std::dynamic_pointer_cast<KIOFuseDirNode>(nodeForIno(node->m_parentIno)))
1629 parentDir->m_childrenInos.push_back(ino);
1630 else
1631 qWarning(KIOFUSE_LOG) << "Tried to insert node with invalid parent";
1632 }
1633
1634 return ino;
1635 }
1636
localPathToRemoteUrl(const QString & localPath) const1637 QUrl KIOFuseVFS::localPathToRemoteUrl(const QString& localPath) const
1638 {
1639 auto node = nodeForIno(KIOFuseIno::Root);
1640 auto pathSegments = localPath.split(QStringLiteral("/"));
1641 for(int i = 0; i < pathSegments.size(); ++i)
1642 {
1643 auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node);
1644 if(!dirNode || !(node = nodeByName(dirNode, pathSegments[i])))
1645 break;
1646
1647 auto url = remoteUrl(node);
1648 if(!url.isEmpty())
1649 return addPathElements(url, pathSegments.mid(i + 1));
1650 }
1651 return {};
1652 }
1653
remoteUrl(const std::shared_ptr<const KIOFuseNode> & node) const1654 QUrl KIOFuseVFS::remoteUrl(const std::shared_ptr<const KIOFuseNode> &node) const
1655 {
1656 QStringList path;
1657 for(const KIOFuseNode *currentNode = node.get(); currentNode != nullptr; currentNode = nodeForIno(currentNode->m_parentIno).get())
1658 {
1659 auto remoteDirNode = dynamic_cast<const KIOFuseRemoteNodeInfo*>(currentNode);
1660 if(remoteDirNode && !remoteDirNode->m_overrideUrl.isEmpty())
1661 // Origin found - add path and return
1662 return addPathElements(remoteDirNode->m_overrideUrl, path);
1663
1664 path.prepend(currentNode->m_nodeName);
1665 }
1666
1667 // No origin found until the root - return an invalid URL
1668 return {};
1669 }
1670
virtualPath(const std::shared_ptr<KIOFuseNode> & node) const1671 QString KIOFuseVFS::virtualPath(const std::shared_ptr<KIOFuseNode> &node) const
1672 {
1673 QStringList path;
1674 for(const KIOFuseNode *currentNode = node.get(); currentNode != nullptr; currentNode = nodeForIno(currentNode->m_parentIno).get())
1675 path.prepend(currentNode->m_nodeName);
1676
1677 return path.join(QLatin1Char('/'));
1678 }
1679
fillStatForFile(struct stat & attr)1680 void KIOFuseVFS::fillStatForFile(struct stat &attr)
1681 {
1682 static uid_t uid = getuid();
1683 static gid_t gid = getgid();
1684
1685 attr.st_nlink = 1;
1686 attr.st_mode = S_IFREG | 0755;
1687 attr.st_uid = uid;
1688 attr.st_gid = gid;
1689 attr.st_size = 0;
1690 attr.st_blksize = 512;
1691 // This is set to match st_size by replyAttr
1692 attr.st_blocks = 0;
1693
1694 clock_gettime(CLOCK_REALTIME, &attr.st_atim);
1695 attr.st_mtim = attr.st_atim;
1696 attr.st_ctim = attr.st_atim;
1697 }
1698
incrementLookupCount(const std::shared_ptr<KIOFuseNode> & node,uint64_t delta)1699 void KIOFuseVFS::incrementLookupCount(const std::shared_ptr<KIOFuseNode> &node, uint64_t delta)
1700 {
1701 if(node->m_lookupCount + delta < node->m_lookupCount)
1702 qWarning(KIOFUSE_LOG) << "Lookup count overflow!";
1703 else
1704 node->m_lookupCount += delta;
1705 }
1706
decrementLookupCount(const std::shared_ptr<KIOFuseNode> node,uint64_t delta)1707 void KIOFuseVFS::decrementLookupCount(const std::shared_ptr<KIOFuseNode> node, uint64_t delta)
1708 {
1709 if(node->m_lookupCount < delta)
1710 qWarning(KIOFUSE_LOG) << "Tried to set lookup count negative!";
1711 else
1712 node->m_lookupCount -= delta;
1713
1714 if(node->m_parentIno == KIOFuseIno::DeletedRoot && node->m_lookupCount == 0)
1715 {
1716 // Delete the node
1717 m_dirtyNodes.extract(node->m_stat.st_ino);
1718 reparentNode(node, KIOFuseIno::Invalid);
1719 m_nodes.erase(m_nodes.find(node->m_stat.st_ino));
1720 }
1721 }
1722
markNodeDeleted(const std::shared_ptr<KIOFuseNode> & node)1723 void KIOFuseVFS::markNodeDeleted(const std::shared_ptr<KIOFuseNode> &node)
1724 {
1725 if(auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node))
1726 {
1727 // Mark all children as deleted first (flatten the hierarchy)
1728 for (auto childIno : std::vector<fuse_ino_t>(dirNode->m_childrenInos))
1729 if(auto childNode = nodeForIno(childIno))
1730 markNodeDeleted(childNode);
1731 }
1732
1733 qDebug(KIOFUSE_LOG) << "Marking node" << node->m_nodeName << "as deleted";
1734
1735 reparentNode(node, KIOFuseIno::DeletedRoot);
1736 decrementLookupCount(node, 0); // Trigger reevaluation
1737 }
1738
replyAttr(fuse_req_t req,std::shared_ptr<KIOFuseNode> node)1739 void KIOFuseVFS::replyAttr(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
1740 {
1741 // Set st_blocks accordingly
1742 node->m_stat.st_blocks = (node->m_stat.st_size + node->m_stat.st_blksize - 1) / node->m_stat.st_blksize;
1743
1744 // TODO: Validity timeout?
1745 fuse_reply_attr(req, &node->m_stat, 0);
1746 }
1747
replyEntry(fuse_req_t req,std::shared_ptr<KIOFuseNode> node)1748 void KIOFuseVFS::replyEntry(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
1749 {
1750 // Zero means invalid entry. Compared to an ENOENT reply, the kernel can cache this.
1751 struct fuse_entry_param entry {};
1752
1753 if(node)
1754 {
1755 incrementLookupCount(node);
1756
1757 entry.ino = node->m_stat.st_ino;
1758 entry.attr_timeout = 0.0;
1759 entry.entry_timeout = 0.0;
1760 entry.attr = node->m_stat;
1761 }
1762
1763 fuse_reply_entry(req, &entry);
1764 }
1765
createNodeFromUDSEntry(const KIO::UDSEntry & entry,const fuse_ino_t parentIno,QString nameOverride)1766 std::shared_ptr<KIOFuseNode> KIOFuseVFS::createNodeFromUDSEntry(const KIO::UDSEntry &entry, const fuse_ino_t parentIno, QString nameOverride)
1767 {
1768 QString name = nameOverride;
1769 if(name.isEmpty())
1770 name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1771 if(!isValidFilename(name))
1772 return nullptr; // Reject invalid names
1773
1774 // Create a stat struct with default values
1775 struct stat attr = {};
1776 fillStatForFile(attr);
1777 attr.st_size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 1);
1778 attr.st_mode = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, entry.isDir() ? 0755 : 0644);
1779 attr.st_mode &= ~S_IFMT; // Set by us further down
1780 if(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME))
1781 {
1782 attr.st_mtim.tv_sec = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
1783 attr.st_mtim.tv_nsec = 0;
1784 }
1785 if(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME))
1786 {
1787 attr.st_atim.tv_sec = entry.numberValue(KIO::UDSEntry::UDS_ACCESS_TIME);
1788 attr.st_atim.tv_nsec = 0;
1789 }
1790 // No support for ctim/btim in KIO...
1791
1792 // Setting UID and GID here to UDS_USER/UDS_GROUP respectively does not lead to the expected
1793 // results as those values might only be meaningful on the remote side.
1794 // As access checks are only performed by the remote side, it shouldn't matter much though.
1795 // It's necessary to make chown/chmod meaningful.
1796 if(entry.contains(KIO::UDSEntry::UDS_USER))
1797 {
1798 QString user = entry.stringValue(KIO::UDSEntry::UDS_USER);
1799 if(auto *pw = getpwnam(user.toUtf8().data()))
1800 attr.st_uid = pw->pw_uid;
1801 }
1802 if(entry.contains(KIO::UDSEntry::UDS_GROUP))
1803 {
1804 QString group = entry.stringValue(KIO::UDSEntry::UDS_GROUP);
1805 if(auto *gr = getgrnam(group.toUtf8().data()))
1806 attr.st_gid = gr->gr_gid;
1807 }
1808
1809 if(entry.contains(KIO::UDSEntry::UDS_LOCAL_PATH) || entry.contains(KIO::UDSEntry::UDS_TARGET_URL))
1810 {
1811 // Create as symlink if possible
1812 QString target = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
1813 if(target.isEmpty())
1814 target = QUrl(entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)).toLocalFile();
1815
1816 if(!target.isEmpty())
1817 {
1818 // Symlink to local file/folder
1819 attr.st_mode |= S_IFLNK;
1820 auto ret = std::make_shared<KIOFuseSymLinkNode>(parentIno, name, attr);
1821 ret->m_target = target;
1822 ret->m_stat.st_size = ret->m_target.toUtf8().length();
1823 return ret;
1824 }
1825 else if(entry.isLink())
1826 return nullptr; // Does this even happen?
1827 else if(entry.isDir())
1828 return nullptr; // Maybe create a mountpoint (remote dir with override URL) here?
1829 else // Regular file pointing to URL
1830 {
1831 attr.st_mode |= S_IFREG;
1832 std::shared_ptr<KIOFuseRemoteFileNode> ret = nullptr;
1833 const QUrl nodeUrl = QUrl{entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)};
1834 if(nodeUrl.isEmpty())
1835 return nullptr;
1836 if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
1837 ret = std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
1838 else
1839 ret = std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
1840 ret->m_overrideUrl = nodeUrl;
1841 return ret;
1842 }
1843 }
1844 else if(entry.isLink()) // Check for link first as isDir can also be a link
1845 {
1846 attr.st_mode |= S_IFLNK;
1847 auto ret = std::make_shared<KIOFuseSymLinkNode>(parentIno, name, attr);
1848 ret->m_target = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
1849 attr.st_size = ret->m_target.size();
1850 return ret;
1851 }
1852 else if(entry.isDir())
1853 {
1854 attr.st_mode |= S_IFDIR;
1855 return std::make_shared<KIOFuseRemoteDirNode>(parentIno, name, attr);
1856 }
1857 else // it's a regular file
1858 {
1859 attr.st_mode |= S_IFREG;
1860 const QUrl nodeUrl = remoteUrl(nodeForIno(parentIno));
1861 if(m_useFileJob && KProtocolManager::supportsOpening(nodeUrl) && KProtocolManager::supportsTruncating(nodeUrl))
1862 return std::make_shared<KIOFuseRemoteFileJobBasedFileNode>(parentIno, name, attr);
1863 else
1864 return std::make_shared<KIOFuseRemoteCacheBasedFileNode>(parentIno, name, attr);
1865 }
1866 }
1867
updateNodeFromUDSEntry(const std::shared_ptr<KIOFuseNode> & node,const KIO::UDSEntry & entry)1868 std::shared_ptr<KIOFuseNode> KIOFuseVFS::updateNodeFromUDSEntry(const std::shared_ptr<KIOFuseNode> &node, const KIO::UDSEntry &entry)
1869 {
1870 qDebug(KIOFUSE_LOG) << "Updating attributes of" << node->m_nodeName;
1871
1872 // Updating a node works by creating a new node and merging their attributes
1873 // (both m_stat and certain other class attributes) together.
1874 // This is broadly done with node->m_stat = newNode->m_stat;
1875 // However, there are some things we may not want to copy straight from the
1876 // new node into the node to be updated and so we update newNode as
1877 // appropriate before merging.
1878 auto newNode = createNodeFromUDSEntry(entry, node->m_parentIno, node->m_nodeName);
1879 if (!newNode)
1880 {
1881 qWarning(KIOFUSE_LOG) << "Could not create new node for" << node->m_nodeName;
1882 return node;
1883 }
1884
1885 #pragma GCC diagnostic push
1886 #pragma GCC diagnostic ignored "-Wpragmas" // GCC does not know the diagnostic below and warns m(
1887 #pragma GCC diagnostic ignored "-Wpotentially-evaluated-expression"
1888 if (typeid(*newNode.get()) != typeid(*node.get()))
1889 #pragma GCC diagnostic pop
1890 {
1891 if(node->m_parentIno != KIOFuseIno::DeletedRoot)
1892 {
1893 markNodeDeleted(node);
1894 insertNode(newNode);
1895 return newNode;
1896 }
1897 return node;
1898 }
1899
1900 const bool newMtimNewer = node->m_stat.st_mtim < newNode->m_stat.st_mtim;
1901 // Take the newer time value
1902 if (!newMtimNewer)
1903 newNode->m_stat.st_mtim = node->m_stat.st_mtim;
1904
1905 if (newNode->m_stat.st_atim < node->m_stat.st_atim)
1906 newNode->m_stat.st_atim = node->m_stat.st_atim;
1907
1908 if (auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
1909 {
1910 if(newMtimNewer || newNode->m_stat.st_size != node->m_stat.st_size)
1911 {
1912 // Someone has changed something server side, lets get those changes.
1913 if(cacheBasedFileNode->m_cacheDirty || cacheBasedFileNode->m_flushRunning)
1914 {
1915 if(newMtimNewer)
1916 qWarning(KIOFUSE_LOG) << cacheBasedFileNode->m_nodeName << "cache is dirty but file has also been changed by something else";
1917
1918 newNode->m_stat.st_size = cacheBasedFileNode->m_stat.st_size;
1919 }
1920 else if(cacheBasedFileNode->m_localCache && cacheBasedFileNode->cacheIsComplete())
1921 {
1922 // Our cache isn't dirty but the file has changed, our current cache is invalid
1923 // and we can safely get rid of it.
1924 fclose(cacheBasedFileNode->m_localCache);
1925 cacheBasedFileNode->m_cacheSize = 0;
1926 cacheBasedFileNode->m_cacheComplete = false;
1927 cacheBasedFileNode->m_localCache = nullptr;
1928 }
1929 }
1930 }
1931 else if (auto oldSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node))
1932 {
1933 auto newSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(newNode);
1934 oldSymlinkNode->m_target = newSymlinkNode->m_target;
1935 }
1936
1937 auto oldRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
1938 auto newRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(newNode);
1939
1940 oldRemoteNode->m_lastStatRefresh = std::chrono::steady_clock::now();
1941
1942 // oldRemoteNode->m_overrideUrl could've been set by mountUrl, don't clear it
1943 if (!newRemoteNode->m_overrideUrl.isEmpty())
1944 oldRemoteNode->m_overrideUrl = newRemoteNode->m_overrideUrl;
1945
1946 // Preserve the inode number
1947 newNode->m_stat.st_ino = node->m_stat.st_ino;
1948 node->m_stat = newNode->m_stat;
1949 return node;
1950 }
1951
awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> & node,off_t bytes,std::function<void (int error)> callback)1952 void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, off_t bytes, std::function<void(int error)> callback)
1953 {
1954 if(bytes < 0)
1955 {
1956 qWarning(KIOFUSE_LOG) << "Negative size passed to awaitBytesAvailable";
1957 return callback(EINVAL);
1958 }
1959
1960 if(node->m_localCache && node->m_cacheSize >= bytes)
1961 return callback(0); // Already available
1962 else if(node->cacheIsComplete()) // Full cache is available...
1963 return callback(ESPIPE); // ...but less than requested.
1964
1965 if(!node->m_localCache)
1966 {
1967 if(node->m_parentIno == KIOFuseIno::DeletedRoot)
1968 return callback(ENOENT);
1969
1970 // Create a temporary file
1971 node->m_localCache = tmpfile();
1972
1973 if(!node->m_localCache)
1974 return callback(errno);
1975
1976 // Request the file
1977 qDebug(KIOFUSE_LOG) << "Fetching cache for" << node->m_nodeName;
1978 auto *job = KIO::get(remoteUrl(node));
1979 connect(job, &KIO::TransferJob::data, [=](auto *job, const QByteArray &data) {
1980 // Nobody needs the data anymore? Drop the cache.
1981 if(node->m_openCount == 0 && !node->m_cacheDirty && !node->m_flushRunning)
1982 {
1983 // KJob::Quietly would break the cache in the result handler while
1984 // the error handler sets up the node state just right.
1985 job->kill(KJob::EmitResult);
1986 qDebug(KIOFUSE_LOG) << "Stopped filling the cache of" << node->m_nodeName;
1987 return;
1988 }
1989
1990 int cacheFd = fileno(node->m_localCache);
1991 if(lseek(cacheFd, 0, SEEK_END) == -1
1992 || !sane_write(cacheFd, data.data(), data.size()))
1993 emit node->localCacheChanged(errno);
1994 else
1995 {
1996 node->m_cacheSize += data.size();
1997 emit node->localCacheChanged(0);
1998 }
1999 });
2000 connect(job, &KIO::TransferJob::result, [=] {
2001 if(job->error())
2002 {
2003 // It's possible that the cache was written to meanwhile - that's bad.
2004 // TODO: Is it possible to recover?
2005 node->m_cacheDirty = false;
2006
2007 fclose(node->m_localCache);
2008 node->m_cacheSize = 0;
2009 node->m_cacheComplete = false;
2010 node->m_localCache = nullptr;
2011 emit node->localCacheChanged(kioErrorToFuseError(job->error()));
2012 }
2013 else
2014 {
2015 // Might be different from the attr size meanwhile, use the more recent value.
2016 // This also ensures that the cache is seen as complete.
2017 node->m_stat.st_size = node->m_cacheSize;
2018 node->m_cacheComplete = true;
2019 emit node->localCacheChanged(0);
2020 }
2021 });
2022 }
2023
2024 // Using a unique_ptr here to let the lambda disconnect the connection itself
2025 auto connection = std::make_unique<QMetaObject::Connection>();
2026 auto &conn = *connection;
2027 conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::localCacheChanged,
2028 [=, connection = std::move(connection)](int error) {
2029 if(error)
2030 {
2031 callback(error);
2032 node->disconnect(*connection);
2033 }
2034 else if(node->m_cacheSize >= bytes) // Requested data available
2035 {
2036 callback(0);
2037 node->disconnect(*connection);
2038 }
2039 else if(node->cacheIsComplete()) // Full cache is available...
2040 {
2041 // ...but less than requested.
2042 callback(ESPIPE);
2043 node->disconnect(*connection);
2044 }
2045 // else continue waiting until the above happens
2046 }
2047 );
2048 }
2049
awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> & node,std::function<void (int)> callback)2050 void KIOFuseVFS::awaitCacheComplete(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void (int)> callback)
2051 {
2052 return awaitBytesAvailable(node, std::numeric_limits<off_t>::max(), [callback](int error) {
2053 // ESPIPE == cache complete, but less than the requested size, which is expected.
2054 return callback(error == ESPIPE ? 0 : error);
2055 });
2056 }
2057
awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> & node,std::function<void (int)> callback)2058 void KIOFuseVFS::awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> &node, std::function<void (int)> callback)
2059 {
2060 auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(node);
2061 if(!remoteNode)
2062 return callback(0); // Not a remote node
2063
2064 if(!remoteNode->haveChildrenTimedOut())
2065 return callback(0); // Children complete and up to date
2066
2067 if(!remoteNode->m_childrenRequested)
2068 {
2069 if(node->m_parentIno == KIOFuseIno::DeletedRoot)
2070 return callback(0);
2071
2072 auto childrenNotSeen = std::make_shared<std::vector<fuse_ino_t>>(remoteNode->m_childrenInos);
2073
2074 // List the remote dir
2075 auto refreshTime = std::chrono::steady_clock::now();
2076 auto *job = KIO::listDir(remoteUrl(remoteNode));
2077 connect(job, &KIO::ListJob::entries, [=](auto *job, const KIO::UDSEntryList &entries) {
2078 for(auto &entry : entries)
2079 {
2080 // Inside the loop because refreshing "." might drop it
2081 if(remoteNode->m_parentIno == KIOFuseIno::DeletedRoot)
2082 {
2083 job->kill(KJob::EmitResult);
2084 return;
2085 }
2086
2087 QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
2088
2089 // Refresh "." and ignore ".."
2090 if(name == QStringLiteral("."))
2091 {
2092 updateNodeFromUDSEntry(remoteNode, entry);
2093 continue;
2094 }
2095 else if(name == QStringLiteral(".."))
2096 continue;
2097
2098 auto childrenNode = nodeByName(remoteNode, name);
2099 if(childrenNode)
2100 {
2101 auto it = std::find(begin(*childrenNotSeen), end(*childrenNotSeen),
2102 childrenNode->m_stat.st_ino);
2103 if(it != end(*childrenNotSeen))
2104 childrenNotSeen->erase(it);
2105
2106 // Try to update existing node
2107 updateNodeFromUDSEntry(childrenNode, entry);
2108 continue;
2109 }
2110
2111 childrenNode = createNodeFromUDSEntry(entry, remoteNode->m_stat.st_ino, name);
2112 if(!childrenNode)
2113 {
2114 qWarning(KIOFUSE_LOG) << "Could not create node for" << name;
2115 continue;
2116 }
2117
2118 insertNode(childrenNode);
2119 }
2120 });
2121 connect(job, &KIO::ListJob::result, [=] {
2122 remoteNode->m_childrenRequested = false;
2123
2124 if(job->error() && job->error() != KJob::KilledJobError)
2125 {
2126 emit remoteNode->gotChildren(kioErrorToFuseError(job->error()));
2127 return;
2128 }
2129
2130 for(auto ino : *childrenNotSeen)
2131 {
2132 auto childNode = nodeForIno(ino);
2133 // Was not refreshed as part of this listDir operation, drop it
2134 if(childNode && childNode->m_parentIno == node->m_stat.st_ino)
2135 markNodeDeleted(childNode);
2136 }
2137
2138 remoteNode->m_lastChildrenRefresh = refreshTime;
2139 emit remoteNode->gotChildren(0);
2140 });
2141
2142 remoteNode->m_childrenRequested = true;
2143 }
2144
2145 // Using a unique_ptr here to let the lambda disconnect the connection itself
2146 auto connection = std::make_unique<QMetaObject::Connection>();
2147 auto &conn = *connection;
2148 conn = connect(remoteNode.get(), &KIOFuseRemoteDirNode::gotChildren,
2149 [=, connection = std::move(connection)](int error) {
2150 callback(error);
2151 remoteNode->disconnect(*connection);
2152 }
2153 );
2154 }
2155
markCacheDirty(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> & node)2156 void KIOFuseVFS::markCacheDirty(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node)
2157 {
2158 if(node->m_cacheDirty)
2159 return; // Already dirty, nothing to do
2160
2161 node->m_cacheDirty = true;
2162 m_dirtyNodes.insert(node->m_stat.st_ino);
2163 }
2164
awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> & node,std::function<void (int)> callback)2165 void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, std::function<void (int)> callback)
2166 {
2167 if(!node->m_cacheDirty && !node->m_flushRunning)
2168 return callback(0); // Nothing to flush/wait for
2169
2170 if(node->m_parentIno == KIOFuseIno::DeletedRoot)
2171 {
2172 // Important: This is before marking it as flushed as it can be linked back.
2173 qDebug(KIOFUSE_LOG) << "Not flushing unlinked node" << node->m_nodeName;
2174 return callback(0);
2175 }
2176
2177 // Don't send incomplete data
2178 if(!node->cacheIsComplete())
2179 {
2180 qDebug(KIOFUSE_LOG) << "Deferring flushing of node" << node->m_nodeName << "until cache complete";
2181 return awaitCacheComplete(node, [=](int error) {
2182 if(error)
2183 callback(error);
2184 else
2185 awaitNodeFlushed(node, callback);
2186 });
2187 }
2188
2189 if(!node->m_flushRunning)
2190 {
2191 qDebug(KIOFUSE_LOG) << "Flushing node" << node->m_nodeName;
2192
2193 // Clear the flag now to not lose any writes that happen while sending data
2194 node->m_cacheDirty = false;
2195 node->m_flushRunning = true;
2196
2197 auto *job = KIO::put(remoteUrl(node), -1, KIO::Overwrite);
2198 job->setTotalSize(node->m_cacheSize);
2199
2200 off_t bytesSent = 0; // Modified inside the lambda
2201 connect(job, &KIO::TransferJob::dataReq, [=](auto *job, QByteArray &data) mutable {
2202 Q_UNUSED(job);
2203
2204 // Someone truncated the file?
2205 if(node->m_cacheSize <= bytesSent)
2206 return;
2207
2208 // Somebody wrote to the cache whilst sending data.
2209 // Kill the job to save time and try again.
2210 // However, set a limit to how many times we do this consecutively.
2211 if(node->m_cacheDirty && node->m_numKilledJobs < 2 && job->percent() < 85)
2212 {
2213 job->kill(KJob::Quietly);
2214 node->m_numKilledJobs++;
2215 node->m_flushRunning = false;
2216 awaitNodeFlushed(node, [](int){});
2217 return;
2218 }
2219
2220 off_t toSend = std::min(node->m_cacheSize - bytesSent, off_t(14*1024*1024ul)); // 14MiB max
2221 data.resize(toSend);
2222
2223 // Read the cache file into the buffer
2224 int cacheFd = fileno(node->m_localCache);
2225 if(lseek(cacheFd, bytesSent, SEEK_SET) == -1
2226 || !sane_read(cacheFd, data.data(), toSend))
2227 {
2228 qWarning(KIOFUSE_LOG) << "Failed to read cache:" << strerror(errno);
2229 job->kill(KJob::EmitResult);
2230 return;
2231 }
2232
2233 bytesSent += toSend;
2234 });
2235 connect(job, &KIO::TransferJob::result, [=] {
2236 node->m_flushRunning = false;
2237
2238 if(job->error())
2239 {
2240 qWarning(KIOFUSE_LOG) << "Failed to send data:" << job->errorString();
2241 markCacheDirty(node); // Try again
2242 emit node->cacheFlushed(kioErrorToFuseError(job->error()));
2243 return;
2244 }
2245
2246 if(!node->m_cacheDirty)
2247 {
2248 // Nobody wrote to the cache while sending data
2249 m_dirtyNodes.extract(node->m_stat.st_ino);
2250 node->m_numKilledJobs = 0;
2251 emit node->cacheFlushed(0);
2252 }
2253 else
2254 awaitNodeFlushed(node, [](int){});
2255 });
2256 }
2257
2258 // Using a unique_ptr here to let the lambda disconnect the connection itself
2259 auto connection = std::make_unique<QMetaObject::Connection>();
2260 auto &conn = *connection;
2261 conn = connect(node.get(), &KIOFuseRemoteCacheBasedFileNode::cacheFlushed,
2262 [=, connection = std::move(connection)](int error) {
2263 callback(error);
2264 node->disconnect(*connection);
2265 }
2266 );
2267 }
2268
awaitAttrRefreshed(const std::shared_ptr<KIOFuseNode> & node,std::function<void (int)> callback)2269 void KIOFuseVFS::awaitAttrRefreshed(const std::shared_ptr<KIOFuseNode> &node, std::function<void (int)> callback)
2270 {
2271 auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
2272 if(!remoteNode || !remoteNode->hasStatTimedOut())
2273 return callback(0); // Node not remote, or it hasn't timed out yet
2274
2275 // To do the same request as the initial mount or lookup, build the URL from the parent
2276 auto parentNode = nodeForIno(node->m_parentIno);
2277 auto remoteParentNode = std::dynamic_pointer_cast<KIOFuseRemoteDirNode>(parentNode);
2278 QUrl url;
2279 if(!remoteParentNode || (url = remoteUrl(parentNode)).isEmpty())
2280 return callback(0); // Parent not remote
2281
2282 if(!remoteNode->m_statRequested)
2283 {
2284 qDebug(KIOFUSE_LOG) << "Refreshing attributes of node" << node->m_nodeName;
2285 remoteNode->m_statRequested = true;
2286 awaitChildMounted(remoteParentNode, node->m_nodeName, [=] (auto mountedNode, int error) {
2287 if(error == ENOENT)
2288 markNodeDeleted(node);
2289
2290 Q_UNUSED(mountedNode);
2291 remoteNode->m_statRequested = false;
2292 emit remoteNode->statRefreshed(error);
2293 });
2294 }
2295
2296 // Using a unique_ptr here to let the lambda disconnect the connection itself
2297 auto connection = std::make_unique<QMetaObject::Connection>();
2298 auto &conn = *connection;
2299 conn = connect(remoteNode.get(), &KIOFuseRemoteNodeInfo::statRefreshed,
2300 [=, connection = std::move(connection)](int error) {
2301 callback(error);
2302 remoteNode->disconnect(*connection);
2303 }
2304 );
2305 }
2306
awaitChildMounted(const std::shared_ptr<KIOFuseRemoteDirNode> & parent,const QString name,std::function<void (const std::shared_ptr<KIOFuseNode> &,int)> callback)2307 void KIOFuseVFS::awaitChildMounted(const std::shared_ptr<KIOFuseRemoteDirNode> &parent, const QString name, std::function<void (const std::shared_ptr<KIOFuseNode> &, int)> callback)
2308 {
2309 auto url = addPathElements(remoteUrl(parent), {name});
2310 if(url.isEmpty()) // Not remote?
2311 {
2312 if(auto node = nodeByName(parent, name))
2313 return callback(node, 0);
2314 else
2315 return callback({}, ENOENT);
2316 }
2317
2318 qDebug(KIOFUSE_LOG) << "Mounting" << url.toDisplayString();
2319 auto statJob = KIO::stat(url);
2320 statJob->setSide(KIO::StatJob::SourceSide); // Be "optimistic" to allow accessing
2321 // files over plain HTTP
2322 connect(statJob, &KIO::StatJob::result, [=] {
2323 if(statJob->error())
2324 {
2325 qDebug(KIOFUSE_LOG) << statJob->errorString();
2326 callback(nullptr, kioErrorToFuseError(statJob->error()));
2327 return;
2328 }
2329
2330 // Finally create the last component
2331 auto finalNode = nodeByName(parent, name);
2332 if(finalNode)
2333 // Note that node may fail to update, but this type of error is ignored.
2334 finalNode = updateNodeFromUDSEntry(finalNode, statJob->statResult());
2335 else
2336 {
2337 // The remote name (statJob->statResult().stringValue(KIO::UDSEntry::UDS_NAME)) has to be
2338 // ignored as it can be different from the path. e.g. tar:/foo.tar/ is "/"
2339 finalNode = createNodeFromUDSEntry(statJob->statResult(), parent->m_stat.st_ino, name);
2340 if(!finalNode)
2341 return callback(nullptr, EIO);
2342
2343 insertNode(finalNode);
2344 }
2345
2346 callback(finalNode, 0);
2347 });
2348 }
2349
originOfUrl(QUrl url)2350 QUrl KIOFuseVFS::originOfUrl(QUrl url)
2351 {
2352 QUrl originUrl = url;
2353 if(originUrl.path().startsWith(QLatin1Char('/')))
2354 originUrl.setPath(QStringLiteral("/"));
2355 else
2356 originUrl.setPath({});
2357
2358 return originUrl;
2359 }
2360
setupSignalHandlers()2361 bool KIOFuseVFS::setupSignalHandlers()
2362 {
2363 // Create required socketpair for custom signal handling
2364 if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) {
2365 return false;
2366 }
2367 m_signalNotifier = std::make_unique<QSocketNotifier>(signalFd[1], QSocketNotifier::Read, this);
2368 m_signalNotifier->connect(m_signalNotifier.get(), &QSocketNotifier::activated, this, &KIOFuseVFS::exitHandler);
2369
2370 struct sigaction sig;
2371
2372 sig.sa_handler = KIOFuseVFS::signalHandler;
2373 sigemptyset(&sig.sa_mask);
2374 sig.sa_flags = SA_RESTART;
2375
2376 if (sigaction(SIGHUP, &sig, 0))
2377 return false;
2378 if (sigaction(SIGTERM, &sig, 0))
2379 return false;
2380 if (sigaction(SIGINT, &sig, 0))
2381 return false;
2382
2383 return true;
2384 }
2385
removeSignalHandlers()2386 bool KIOFuseVFS::removeSignalHandlers()
2387 {
2388 m_signalNotifier.reset();
2389 ::close(signalFd[0]);
2390 ::close(signalFd[1]);
2391
2392 struct sigaction sig;
2393
2394 sig.sa_handler = SIG_DFL;
2395 sigemptyset(&sig.sa_mask);
2396 sig.sa_flags = SA_RESTART;
2397
2398 if (sigaction(SIGHUP, &sig, 0))
2399 return false;
2400 if (sigaction(SIGTERM, &sig, 0))
2401 return false;
2402 if (sigaction(SIGINT, &sig, 0))
2403 return false;
2404
2405 return true;
2406 }
2407
exitHandler()2408 void KIOFuseVFS::exitHandler()
2409 {
2410 m_signalNotifier->setEnabled(false);
2411 int tmp;
2412 ::read(signalFd[1], &tmp, sizeof(tmp));
2413 stop();
2414 }
2415
signalHandler(int signal)2416 void KIOFuseVFS::signalHandler(int signal)
2417 {
2418 ::write(signalFd[0], &signal, sizeof(signal));
2419 }
2420
kioErrorToFuseError(const int kioError)2421 int KIOFuseVFS::kioErrorToFuseError(const int kioError) {
2422 switch (kioError) {
2423 case 0 : return 0; // No error
2424 case KIO::ERR_CANNOT_OPEN_FOR_READING : return EIO;
2425 case KIO::ERR_CANNOT_OPEN_FOR_WRITING : return EIO;
2426 case KIO::ERR_CANNOT_LAUNCH_PROCESS : return EIO;
2427 case KIO::ERR_INTERNAL : return EIO;
2428 case KIO::ERR_MALFORMED_URL : return EIO;
2429 case KIO::ERR_UNSUPPORTED_PROTOCOL : return ENOPROTOOPT;
2430 case KIO::ERR_NO_SOURCE_PROTOCOL : return ENOPROTOOPT;
2431 case KIO::ERR_UNSUPPORTED_ACTION : return ENOTTY;
2432 case KIO::ERR_IS_DIRECTORY : return EISDIR;
2433 case KIO::ERR_IS_FILE : return EEXIST;
2434 case KIO::ERR_DOES_NOT_EXIST : return ENOENT;
2435 case KIO::ERR_FILE_ALREADY_EXIST : return EEXIST;
2436 case KIO::ERR_DIR_ALREADY_EXIST : return EEXIST;
2437 case KIO::ERR_UNKNOWN_HOST : return EHOSTUNREACH;
2438 case KIO::ERR_ACCESS_DENIED : return EPERM;
2439 case KIO::ERR_WRITE_ACCESS_DENIED : return EPERM;
2440 case KIO::ERR_CANNOT_ENTER_DIRECTORY : return EIO;
2441 case KIO::ERR_PROTOCOL_IS_NOT_A_FILESYSTEM : return EPROTOTYPE;
2442 case KIO::ERR_CYCLIC_LINK : return ELOOP;
2443 case KIO::ERR_USER_CANCELED : return ECANCELED;
2444 case KIO::ERR_CYCLIC_COPY : return ELOOP;
2445 case KIO::ERR_CANNOT_CREATE_SOCKET : return ENOTCONN;
2446 case KIO::ERR_CANNOT_CONNECT : return ENOTCONN;
2447 case KIO::ERR_CONNECTION_BROKEN : return ENOTCONN;
2448 case KIO::ERR_NOT_FILTER_PROTOCOL : return EPROTOTYPE;
2449 case KIO::ERR_CANNOT_MOUNT : return EIO;
2450 case KIO::ERR_CANNOT_READ : return EIO;
2451 case KIO::ERR_CANNOT_WRITE : return EIO;
2452 case KIO::ERR_CANNOT_BIND : return EPERM;
2453 case KIO::ERR_CANNOT_LISTEN : return EPERM;
2454 case KIO::ERR_CANNOT_ACCEPT : return EPERM;
2455 case KIO::ERR_CANNOT_LOGIN : return ECONNREFUSED;
2456 case KIO::ERR_CANNOT_STAT : return EIO;
2457 case KIO::ERR_CANNOT_CLOSEDIR : return EIO;
2458 case KIO::ERR_CANNOT_MKDIR : return EIO;
2459 case KIO::ERR_CANNOT_RMDIR : return EIO;
2460 case KIO::ERR_CANNOT_RESUME : return ECONNABORTED;
2461 case KIO::ERR_CANNOT_RENAME : return EIO;
2462 case KIO::ERR_CANNOT_CHMOD : return EIO;
2463 case KIO::ERR_CANNOT_DELETE : return EIO;
2464 case KIO::ERR_SLAVE_DIED : return EIO;
2465 case KIO::ERR_OUT_OF_MEMORY : return ENOMEM;
2466 case KIO::ERR_UNKNOWN_PROXY_HOST : return EHOSTUNREACH;
2467 case KIO::ERR_CANNOT_AUTHENTICATE : return EACCES;
2468 case KIO::ERR_ABORTED : return ECONNABORTED;
2469 case KIO::ERR_INTERNAL_SERVER : return EPROTO;
2470 case KIO::ERR_SERVER_TIMEOUT : return ETIMEDOUT;
2471 case KIO::ERR_SERVICE_NOT_AVAILABLE : return ENOPROTOOPT;
2472 case KIO::ERR_UNKNOWN : return EIO;
2473 case KIO::ERR_UNKNOWN_INTERRUPT : return EIO;
2474 case KIO::ERR_CANNOT_DELETE_ORIGINAL : return EIO;
2475 case KIO::ERR_CANNOT_DELETE_PARTIAL : return EIO;
2476 case KIO::ERR_CANNOT_RENAME_ORIGINAL : return EIO;
2477 case KIO::ERR_CANNOT_RENAME_PARTIAL : return EIO;
2478 case KIO::ERR_NEED_PASSWD : return EACCES;
2479 case KIO::ERR_CANNOT_SYMLINK : return EIO;
2480 case KIO::ERR_NO_CONTENT :
2481 #ifdef ENODATA
2482 /* ENODATA is defined by GNU libc, and C++ tr1 seems to have
2483 * it as well, as does Boost: these consistently define it
2484 * to the value 9919. There is no guarantee the underlying
2485 * FUSE implementation understands that if ENODATA doesn't
2486 * exist in libc, though -- in that case, fall back to
2487 * the more generic EIO.
2488 */
2489 return ENODATA;
2490 #else
2491 return EIO;
2492 #endif
2493 case KIO::ERR_DISK_FULL : return ENOSPC;
2494 case KIO::ERR_IDENTICAL_FILES : return EEXIST;
2495 case KIO::ERR_SLAVE_DEFINED : return EIO;
2496 case KIO::ERR_UPGRADE_REQUIRED : return EPROTOTYPE;
2497 case KIO::ERR_POST_DENIED : return EACCES;
2498 case KIO::ERR_CANNOT_SEEK : return EIO;
2499 case KIO::ERR_CANNOT_SETTIME : return EIO;
2500 case KIO::ERR_CANNOT_CHOWN : return EIO;
2501 case KIO::ERR_POST_NO_SIZE : return EIO;
2502 case KIO::ERR_DROP_ON_ITSELF : return EINVAL;
2503 case KIO::ERR_CANNOT_MOVE_INTO_ITSELF : return EINVAL;
2504 case KIO::ERR_PASSWD_SERVER : return EIO;
2505 case KIO::ERR_CANNOT_CREATE_SLAVE : return EIO;
2506 case KIO::ERR_FILE_TOO_LARGE_FOR_FAT32 : return EFBIG;
2507 case KIO::ERR_OWNER_DIED : return EIO;
2508 default : return EIO;
2509 }
2510 }
2511