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