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