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