File indexing completed on 2024-04-14 05:38:55
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 <fcntl.h> 0008 #include <sys/stat.h> 0009 #include <unistd.h> 0010 #include <errno.h> 0011 #include <dirent.h> 0012 0013 #include <QProcess> 0014 #include <QStandardPaths> 0015 #include <QTemporaryDir> 0016 #include <QTemporaryFile> 0017 #include <QTest> 0018 #include <QtDBus/QDBusConnection> 0019 #include <QtDBus/QDBusReply> 0020 #include <QDebug> 0021 0022 #include <KProtocolInfo> 0023 0024 #include "kiofuse_interface.h" 0025 #include "kiofuseprivate_interface.h" 0026 0027 class FileOpsTest : public QObject 0028 { 0029 Q_OBJECT 0030 0031 private Q_SLOTS: 0032 void initTestCase(); 0033 void cleanupTestCase(); 0034 0035 void testDBusErrorReply(); 0036 void testLocalPathToRemoteUrl(); 0037 void testLocalFileOps(); 0038 void testLocalDirOps(); 0039 void testReaddirOps(); 0040 void testCreationOps(); 0041 void testRenameOps(); 0042 void testDeletionOps(); 0043 void testArchiveOps(); 0044 void testManWorkaround(); 0045 void testKioErrorMapping(); 0046 void testRootLookup(); 0047 void testFilenameEscaping(); 0048 void testDirRefresh(); 0049 void testFileRefresh(); 0050 void testSymlinkRefresh(); 0051 void testTypeRefresh(); 0052 void testDirSymlink(); 0053 void testSymlinkRewrite(); 0054 #ifdef WASTE_DISK_SPACE 0055 void testReadWrite4GBFile(); 0056 #endif // WASTE_DISK_SPACE 0057 0058 private: 0059 QDateTime roundDownToSecond(const QDateTime &dt); 0060 bool forceNodeTimeout(); 0061 /** Unlike QFileInfo::symLinkTarget, which returns absolute paths only, 0062 * this returns the raw link content. On failure or truncation, a null 0063 * QString is returned instead. */ 0064 QString readlink(const QString &symlink); 0065 0066 org::kde::KIOFuse::VFS m_kiofuse_iface{QStringLiteral("org.kde.KIOFuse"), 0067 QStringLiteral("/org/kde/KIOFuse"), 0068 QDBusConnection::sessionBus()}; 0069 org::kde::KIOFuse::Private m_kiofuseprivate_iface{QStringLiteral("org.kde.KIOFuse"), 0070 QStringLiteral("/org/kde/KIOFuse"), 0071 QDBusConnection::sessionBus()}; 0072 QTemporaryDir m_mountDir; 0073 }; 0074 0075 void FileOpsTest::initTestCase() 0076 { 0077 // QTemporaryDir would otherwise rm -rf on destruction, 0078 // which is fatal if umount fails while something is mounted inside 0079 m_mountDir.setAutoRemove(false); 0080 QString programpath = QFINDTESTDATA("kio-fuse"); 0081 0082 QProcess kiofuseProcess; 0083 kiofuseProcess.setProgram(programpath); 0084 #ifdef TEST_CACHE_BASED_IO 0085 kiofuseProcess.setArguments(QStringList() << m_mountDir.path() << QStringLiteral("--disable-filejob-io")); 0086 #else 0087 kiofuseProcess.setArguments({m_mountDir.path()}); 0088 #endif 0089 kiofuseProcess.setProcessChannelMode(QProcess::ForwardedChannels); 0090 0091 kiofuseProcess.start(); 0092 QVERIFY(kiofuseProcess.waitForFinished()); 0093 QCOMPARE(kiofuseProcess.exitStatus(), QProcess::NormalExit); 0094 QCOMPARE(kiofuseProcess.exitCode(), 0); 0095 } 0096 0097 void FileOpsTest::cleanupTestCase() 0098 { 0099 QProcess unmountProcess; 0100 #ifdef Q_OS_FREEBSD 0101 // No fusermount on FreeBSD, use umount directly instead 0102 unmountProcess.start(QStringLiteral("umount"), {m_mountDir.path()}); 0103 #else 0104 unmountProcess.start(QStringLiteral("fusermount3"), {QStringLiteral("-u"), m_mountDir.path()}); 0105 #endif 0106 0107 QVERIFY(unmountProcess.waitForFinished()); 0108 QCOMPARE(unmountProcess.exitStatus(), QProcess::NormalExit); 0109 QCOMPARE(unmountProcess.exitCode(), 0); 0110 0111 // Remove only after umounting suceeded 0112 m_mountDir.remove(); 0113 } 0114 0115 void FileOpsTest::testDBusErrorReply() 0116 { 0117 QDBusPendingReply<QString> reply = m_kiofuse_iface.mountUrl(QStringLiteral("invalid URL")); 0118 reply.waitForFinished(); 0119 QVERIFY(reply.isError()); 0120 QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.CannotMount")); 0121 0122 reply = m_kiofuse_iface.mountUrl(QStringLiteral("http://www.kde.org")); 0123 reply.waitForFinished(); 0124 QVERIFY(reply.isError()); 0125 QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.SchemeNotSupported")); 0126 } 0127 0128 void FileOpsTest::testLocalPathToRemoteUrl() 0129 { 0130 QDBusPendingReply<QString> errorReply; 0131 // mtp:/ -> Remote URL can't possibly be location of KIOFuse mount. 0132 // / -> Root can't possibly be location of KIOFuse mount. 0133 // m_mountDir -> Whilst this is in the KIOFuse mount, no remote URL exists for it 0134 for(const auto &url : {QStringLiteral("mtp:/"), QStringLiteral("/"), m_mountDir.path()}) 0135 { 0136 errorReply = m_kiofuse_iface.remoteUrl(url); 0137 errorReply.waitForFinished(); 0138 QVERIFY2(errorReply.isError(), qPrintable(url)); 0139 QCOMPARE(errorReply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.RemoteURLNotFound")); 0140 } 0141 0142 QTemporaryFile localFile; 0143 QVERIFY(localFile.open()); 0144 localFile.close(); // Force creation of file to avoid empty fileName() 0145 QString remoteUrl = QStringLiteral("file://%1").arg(localFile.fileName()); 0146 QString reply = m_kiofuse_iface.mountUrl(remoteUrl).value(); 0147 QVERIFY(!reply.isEmpty()); 0148 QString calculatedRemoteUrl = m_kiofuse_iface.remoteUrl(reply).value(); 0149 QCOMPARE(remoteUrl, calculatedRemoteUrl); 0150 } 0151 0152 void FileOpsTest::testLocalFileOps() 0153 { 0154 QTemporaryFile localFile; 0155 QVERIFY(localFile.open()); 0156 0157 QCOMPARE(localFile.write("teststring"), 10); 0158 QVERIFY(localFile.flush()); 0159 0160 // Mount the temporary file 0161 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localFile.fileName())).value(); 0162 QVERIFY(!reply.isEmpty()); 0163 0164 // Doing the same again should work just fine 0165 reply = m_kiofuse_iface.mountUrl(localFile.fileName()).value(); 0166 QVERIFY(!reply.isEmpty()); 0167 0168 QFile mirroredFile(reply); 0169 QVERIFY(mirroredFile.exists()); 0170 QCOMPARE(mirroredFile.size(), localFile.size()); 0171 0172 // Compare file metadata 0173 QFileInfo localFileInfo(localFile), 0174 mirroredFileInfo(mirroredFile); 0175 0176 QCOMPARE(mirroredFileInfo.size(), localFileInfo.size()); 0177 QCOMPARE(mirroredFileInfo.ownerId(), localFileInfo.ownerId()); 0178 QCOMPARE(mirroredFileInfo.groupId(), localFileInfo.groupId()); 0179 // Not supported by KIO 0180 // QCOMPARE(mirroredFileInfo.metadataChangeTime(), localFileInfo.metadataChangeTime()); 0181 // KIO does not expose times with sub-second precision 0182 QCOMPARE(mirroredFileInfo.lastModified(), roundDownToSecond(localFileInfo.lastModified())); 0183 QCOMPARE(mirroredFileInfo.lastRead(), roundDownToSecond(localFileInfo.lastRead())); 0184 0185 QVERIFY(mirroredFile.open(QIODevice::ReadWrite)); 0186 // Test touching the file 0187 struct timespec times[2] = {{time_t(localFileInfo.lastModified().toSecsSinceEpoch()) + 42, 0}, 0188 {time_t(localFileInfo.lastRead().toSecsSinceEpoch()) + 1, 0}}; 0189 QCOMPARE(futimens(mirroredFile.handle(), times), 0); 0190 localFileInfo.refresh(); 0191 mirroredFileInfo.refresh(); 0192 QCOMPARE(mirroredFileInfo.lastModified().toSecsSinceEpoch(), times[1].tv_sec); 0193 QCOMPARE(localFileInfo.lastModified().toSecsSinceEpoch(), times[1].tv_sec); 0194 // Access time not supported on the remote side, so only check in the mirror 0195 QCOMPARE(mirroredFileInfo.lastRead().toSecsSinceEpoch(), times[0].tv_sec); 0196 //QCOMPARE(localFileInfo.lastRead().toSecsSinceEpoch(), times[0].tv_sec); 0197 0198 // Compare the content 0199 QVERIFY(localFile.seek(0)); 0200 QCOMPARE(localFile.readAll(), mirroredFile.readAll()); 0201 0202 // Try again 0203 QVERIFY(localFile.seek(0)); 0204 QVERIFY(mirroredFile.seek(0)); 0205 QCOMPARE(localFile.readAll(), mirroredFile.readAll()); 0206 0207 // Again, but at an offset 0208 QVERIFY(localFile.seek(1)); 0209 QVERIFY(mirroredFile.seek(1)); 0210 QCOMPARE(localFile.readAll(), mirroredFile.readAll()); 0211 0212 // Write new data 0213 QVERIFY(mirroredFile.seek(0)); 0214 QCOMPARE(mirroredFile.write(QStringLiteral("newteststring!").toUtf8()), 14); 0215 QVERIFY(mirroredFile.flush()); 0216 // Flush the written contents into the backend 0217 QCOMPARE(fsync(mirroredFile.handle()), 0); 0218 0219 // Currently, kio-fuse uses KIO::put and not KIO::write, so the file was replaced 0220 // instead of changed. So reopen the file. 0221 QFile localFile2(localFile.fileName()); 0222 QVERIFY(localFile2.open(QIODevice::ReadOnly)); 0223 0224 // Compare the content 0225 QVERIFY(localFile2.seek(0)); 0226 QVERIFY(mirroredFile.seek(0)); 0227 QCOMPARE(localFile2.readAll(), mirroredFile.readAll()); 0228 0229 // Write new data, but close the file instead of flushing 0230 QVERIFY(mirroredFile.seek(0)); 0231 QCOMPARE(mirroredFile.write(QStringLiteral("differentteststring").toUtf8()), 19); 0232 mirroredFile.close(); 0233 localFile2.close(); 0234 QVERIFY(localFile2.open(QIODevice::ReadOnly)); 0235 QVERIFY(localFile2.seek(0)); 0236 QVERIFY(mirroredFile.open(QIODevice::ReadWrite)); 0237 QVERIFY(mirroredFile.seek(0)); 0238 QCOMPARE(localFile2.readAll(), QStringLiteral("differentteststring").toUtf8()); 0239 0240 // Test truncation at open 0241 mirroredFile.close(); 0242 QVERIFY(mirroredFile.open(QIODevice::WriteOnly | QIODevice::Truncate)); 0243 QCOMPARE(mirroredFile.write(QStringLiteral("tststrng").toUtf8()), 8); 0244 QVERIFY(mirroredFile.flush()); 0245 QCOMPARE(fsync(mirroredFile.handle()), 0); // Flush the written contents into the backend 0246 0247 localFile2.close(); // Reopen the file, see above. 0248 QVERIFY(localFile2.open(QIODevice::ReadOnly)); 0249 QCOMPARE(localFile2.readAll(), QStringLiteral("tststrng").toUtf8()); // Compare the content 0250 0251 // Test manual truncation 0252 QCOMPARE(ftruncate(mirroredFile.handle(), 3), 0); 0253 QCOMPARE(fsync(mirroredFile.handle()), 0); // Flush the written contents into the backend 0254 0255 localFile2.close(); // Reopen the file, see above. 0256 QVERIFY(localFile2.open(QIODevice::ReadOnly)); 0257 QCOMPARE(localFile2.readAll(), QStringLiteral("tst").toUtf8()); // Compare the content 0258 0259 // Test chown by not changing anything (no CAP_CHOWN...) 0260 QCOMPARE(chown(mirroredFile.fileName().toUtf8().data(), getuid(), getgid()), 0); 0261 localFileInfo.refresh(); 0262 QCOMPARE(localFileInfo.ownerId(), getuid()); 0263 QCOMPARE(localFileInfo.groupId(), getgid()); 0264 // Should not be allowed 0265 QCOMPARE(chown(mirroredFile.fileName().toUtf8().data(), getuid(), 0), -1); 0266 QCOMPARE(chown(mirroredFile.fileName().toUtf8().data(), 0, getgid()), -1); 0267 0268 // Test chmod 0269 QCOMPARE(chmod(mirroredFile.fileName().toUtf8().data(), 0054), 0); 0270 struct stat attr; 0271 QCOMPARE(stat(localFile.fileName().toUtf8().data(), &attr), 0); 0272 QCOMPARE(attr.st_mode, S_IFREG | 0054); 0273 QCOMPARE(chmod(mirroredFile.fileName().toUtf8().data(), 0600), 0); 0274 QCOMPARE(stat(localFile.fileName().toUtf8().data(), &attr), 0); 0275 QCOMPARE(attr.st_mode, S_IFREG | 0600); 0276 0277 // Mount the data path 0278 QString dataPath = QFINDTESTDATA(QStringLiteral("data")); 0279 QVERIFY(!dataPath.isEmpty()); 0280 reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(dataPath)).value(); 0281 QVERIFY(!reply.isEmpty()); 0282 QString mirrordataPath = reply; 0283 0284 // Verify the symlink inside is correct 0285 QFile symlink(QDir(mirrordataPath).filePath(QStringLiteral("symlink"))); 0286 0287 QVERIFY(symlink.open(QIODevice::ReadOnly)); 0288 QCOMPARE(symlink.readAll(), QStringLiteral("symlinktargetcontent").toUtf8()); 0289 QCOMPARE(symlink.symLinkTarget(), QDir(mirrordataPath).filePath(QStringLiteral("symlinktarget"))); 0290 0291 // Verify that we adhere to O_APPEND flag as kernel doesn't handle this for us. 0292 QTemporaryFile appendFile; 0293 QVERIFY(appendFile.open()); 0294 QCOMPARE(appendFile.write("teststring"), 10); 0295 QVERIFY(appendFile.flush()); 0296 appendFile.close(); 0297 // Mount the temp file 0298 reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(appendFile.fileName())).value(); 0299 QVERIFY(!reply.isEmpty()); 0300 0301 QFile appendMirror(reply); 0302 QVERIFY(appendMirror.exists()); 0303 QVERIFY(appendMirror.open(QIODevice::Append | QIODevice::ReadWrite)); 0304 // even if we set seek to 0 kio-fuse should change it back to the end of the file. 0305 QVERIFY(appendMirror.seek(0)); 0306 QCOMPARE(appendMirror.write("APPENDME"), 8); 0307 // Pass changes from mirror to local. 0308 QVERIFY(appendMirror.flush()); 0309 QCOMPARE(fsync(appendMirror.handle()), 0); 0310 0311 // Currently, kio-fuse uses KIO::put and not KIO::write, so the file was replaced 0312 // instead of changed. So reopen the file. 0313 QFile appendFile2(appendFile.fileName()); 0314 QVERIFY(appendFile2.open(QIODevice::ReadOnly)); 0315 QVERIFY(appendMirror.seek(0)); 0316 QVERIFY(appendFile2.seek(0)); 0317 // If we don't adhere to O_APPEND flag we'd get "APPENDMEng" instead... 0318 QCOMPARE(appendMirror.readAll(), QStringLiteral("teststringAPPENDME").toUtf8()); 0319 QVERIFY(appendMirror.seek(0)); 0320 QVERIFY(appendFile2.seek(0)); 0321 QCOMPARE(appendMirror.readAll(), appendFile2.readAll()); 0322 } 0323 0324 void FileOpsTest::testLocalDirOps() 0325 { 0326 QTemporaryDir localDir; 0327 QVERIFY(localDir.isValid()); 0328 0329 // Mount the temporary dir 0330 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0331 QVERIFY(!reply.isEmpty()); 0332 0333 QDir mirrorDir(reply); 0334 QVERIFY(mirrorDir.exists()); 0335 0336 // Create a folder inside 0337 QVERIFY(mirrorDir.mkdir(QStringLiteral("directory"))); 0338 QVERIFY(QFile::exists(localDir.filePath(QStringLiteral("directory")))); 0339 0340 // Compare file metadata 0341 QFileInfo localDirInfo(localDir.path()), 0342 mirrorDirInfo(mirrorDir.path()); 0343 0344 QCOMPARE(mirrorDirInfo.ownerId(), localDirInfo.ownerId()); 0345 QCOMPARE(mirrorDirInfo.groupId(), localDirInfo.groupId()); 0346 // Not supported by KIO 0347 // QCOMPARE(mirroredFileInfo.metadataChangeTime(), localFileInfo.metadataChangeTime()); 0348 // KIO does not expose times with sub-second precision 0349 QCOMPARE(mirrorDirInfo.lastModified(), roundDownToSecond(localDirInfo.lastModified())); 0350 QCOMPARE(mirrorDirInfo.lastRead(), roundDownToSecond(localDirInfo.lastRead())); 0351 0352 // Test touching the file 0353 struct timespec times[2] = {{time_t(localDirInfo.lastModified().toSecsSinceEpoch()) + 42, 0}, 0354 {time_t(localDirInfo.lastRead().toSecsSinceEpoch()) + 1, 0}}; 0355 QCOMPARE(utimensat(AT_FDCWD, mirrorDir.path().toUtf8().data(), times, 0), 0); 0356 localDirInfo.refresh(); 0357 mirrorDirInfo.refresh(); 0358 QCOMPARE(mirrorDirInfo.lastModified().toSecsSinceEpoch(), times[1].tv_sec); 0359 QCOMPARE(localDirInfo.lastModified().toSecsSinceEpoch(), times[1].tv_sec); 0360 // Access time not supported on the remote side, so only check in the mirror 0361 QCOMPARE(mirrorDirInfo.lastRead().toSecsSinceEpoch(), times[0].tv_sec); 0362 //QCOMPARE(localDirInfo.lastRead().toSecsSinceEpoch(), times[0].tv_sec); 0363 0364 // Test chown by not changing anything (no CAP_CHOWN...) 0365 QCOMPARE(chown(mirrorDir.path().toUtf8().data(), getuid(), getgid()), 0); 0366 localDirInfo.refresh(); 0367 QCOMPARE(localDirInfo.ownerId(), getuid()); 0368 QCOMPARE(localDirInfo.groupId(), getgid()); 0369 // Should not be allowed 0370 QCOMPARE(chown(mirrorDir.path().toUtf8().data(), getuid(), 0), -1); 0371 QCOMPARE(chown(mirrorDir.path().toUtf8().data(), 0, getgid()), -1); 0372 0373 // Test chmod 0374 QCOMPARE(chmod(mirrorDir.path().toUtf8().data(), 0054), 0); 0375 struct stat attr; 0376 QCOMPARE(stat(localDir.path().toUtf8().data(), &attr), 0); 0377 QCOMPARE(attr.st_mode, S_IFDIR | 0054); 0378 QCOMPARE(chmod(mirrorDir.path().toUtf8().data(), 0700), 0); 0379 QCOMPARE(stat(localDir.path().toUtf8().data(), &attr), 0); 0380 QCOMPARE(attr.st_mode, S_IFDIR | 0700); 0381 0382 // Mount the data path and compare the directory content 0383 QString dataPath = QFINDTESTDATA(QStringLiteral("data")); 0384 QVERIFY(!dataPath.isEmpty()); 0385 reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(dataPath)).value(); 0386 QVERIFY(!reply.isEmpty()); 0387 QString mirrordataPath = reply; 0388 0389 auto sourceEntryList = QDir(dataPath).entryList(QDir::NoFilter, QDir::Name); 0390 auto mirrorEntryList = QDir(mirrordataPath).entryList(QDir::NoFilter, QDir::Name); 0391 0392 QCOMPARE(mirrorEntryList, sourceEntryList); 0393 0394 // Make sure dirlisting file:/// works 0395 sourceEntryList = QDir(QStringLiteral("/")).entryList(QDir::NoFilter, QDir::Name); 0396 reply = m_kiofuse_iface.mountUrl(QStringLiteral("file:///")).value(); 0397 QVERIFY(!reply.isEmpty()); 0398 mirrorEntryList = QDir(reply).entryList(QDir::NoFilter, QDir::Name); 0399 0400 QCOMPARE(mirrorEntryList, sourceEntryList); 0401 } 0402 0403 void FileOpsTest::testReaddirOps() 0404 { 0405 QTemporaryDir localDir; 0406 QVERIFY(localDir.isValid()); 0407 0408 // Mount the temporary dir 0409 QString testDirPath = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0410 QVERIFY(!testDirPath.isEmpty()); 0411 0412 // Fill the directory with some files 0413 for(unsigned int i=0; i<=10; i++) 0414 { 0415 QVERIFY(QFile(QStringLiteral("%1/tmpFile%2").arg(testDirPath).arg(i)).open(QIODevice::WriteOnly)); 0416 } 0417 0418 DIR *testDir = opendir(qPrintable(testDirPath)); 0419 QVERIFY(testDir); 0420 0421 auto opendirCleanup = qScopeGuard([&](){ closedir(testDir); }); 0422 0423 QStringList testDirEntryList; 0424 QStringList testDirEntryListUpdated; 0425 0426 // Get the initial entry list to compare with 0427 struct dirent *pDirent = nullptr; 0428 while((pDirent = readdir(testDir)) != nullptr) 0429 testDirEntryList.push_back(QString::fromUtf8(pDirent->d_name)); 0430 testDirEntryList.sort(); 0431 0432 // Verify that entries remain same even if we add new or remove existing entries 0433 QVERIFY(QFile::remove(testDirPath + QStringLiteral("/tmpFile1"))); 0434 QVERIFY(QFile(testDirPath + QStringLiteral("/addCaseFile")).open(QIODevice::WriteOnly)); 0435 0436 rewinddir(testDir); 0437 while((pDirent = readdir(testDir)) != nullptr) 0438 testDirEntryListUpdated.push_back(QString::fromUtf8(pDirent->d_name)); 0439 testDirEntryListUpdated.sort(); 0440 0441 QCOMPARE(testDirEntryListUpdated, testDirEntryList); 0442 0443 // Verify that entries remain same even if entries are modified while iterating 0444 testDirEntryListUpdated.clear(); 0445 rewinddir(testDir); 0446 0447 unsigned int count = 1; 0448 while((pDirent = readdir(testDir)) != nullptr) 0449 { 0450 QVERIFY(QFile(QStringLiteral("%1/iterCaseFile%2").arg(testDirPath).arg(count)).open(QIODevice::WriteOnly)); 0451 0452 testDirEntryListUpdated.push_back(QString::fromUtf8(pDirent->d_name)); 0453 count++; 0454 } 0455 testDirEntryListUpdated.sort(); 0456 0457 QCOMPARE(testDirEntryListUpdated, testDirEntryList); 0458 } 0459 0460 void FileOpsTest::testCreationOps() 0461 { 0462 QTemporaryDir localDir; 0463 QVERIFY(localDir.isValid()); 0464 0465 // Mount the temporary dir 0466 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0467 QVERIFY(!reply.isEmpty()); 0468 0469 QDir mirrorDir(reply); 0470 QVERIFY(mirrorDir.exists()); 0471 0472 // Create a symlink 0473 QCOMPARE(symlink("target", mirrorDir.filePath(QStringLiteral("symlink")).toUtf8().data()), 0); 0474 QCOMPARE(QFileInfo(localDir.filePath(QStringLiteral("symlink"))).symLinkTarget(), localDir.filePath(QStringLiteral("target"))); 0475 0476 // Create a regular file 0477 QFile newFile(mirrorDir.filePath(QStringLiteral("newFile"))); 0478 QVERIFY(newFile.open(QIODevice::ReadWrite)); 0479 0480 QFile newFileLocal(localDir.filePath(QStringLiteral("newFile"))); 0481 QVERIFY(newFileLocal.exists()); 0482 QCOMPARE(newFileLocal.size(), 0); 0483 0484 QVERIFY(newFile.write(QStringLiteral("someweirdstring").toUtf8())); 0485 QVERIFY(newFile.flush()); 0486 QCOMPARE(fsync(newFile.handle()), 0); 0487 0488 // Reopen the file (see above in testLocalFileOps) 0489 newFileLocal.close(); 0490 QVERIFY(newFileLocal.open(QIODevice::ReadOnly)); 0491 QCOMPARE(newFileLocal.readAll(), QStringLiteral("someweirdstring").toUtf8()); 0492 } 0493 0494 void FileOpsTest::testRenameOps() 0495 { 0496 QTemporaryDir localDir; 0497 QVERIFY(localDir.isValid()); 0498 0499 // Mount the temporary dir 0500 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0501 QVERIFY(!reply.isEmpty()); 0502 0503 QDir mirrorDir(reply); 0504 QVERIFY(mirrorDir.exists()); 0505 0506 // Create a directory 0507 QVERIFY(QDir(mirrorDir.path()).mkdir(QStringLiteral("dira"))); 0508 QDir dir(mirrorDir.filePath(QStringLiteral("dira"))); 0509 0510 // And a file inside 0511 QFile file(dir.filePath(QStringLiteral("filea"))); 0512 QVERIFY(file.open(QIODevice::ReadWrite)); 0513 QVERIFY(file.write(QStringLiteral("someweirdstring").toUtf8())); 0514 0515 // Note: QFile::rename copies and unlinks if the rename syscall fails, 0516 // so use the libc function directly 0517 0518 // Rename the file 0519 QCOMPARE(rename(dir.filePath(QStringLiteral("filea")).toUtf8().data(), 0520 dir.filePath(QStringLiteral("fileb")).toUtf8().data()), 0); 0521 QVERIFY(!QFile::exists(dir.filePath(QStringLiteral("filea")))); 0522 QVERIFY(QFile::exists(dir.filePath(QStringLiteral("fileb")))); 0523 QVERIFY(!QFile::exists(localDir.filePath(QStringLiteral("dira/filea")))); 0524 QVERIFY(QFile::exists(localDir.filePath(QStringLiteral("dira/fileb")))); 0525 0526 // Rename the directory 0527 QCOMPARE(rename(mirrorDir.filePath(QStringLiteral("dira")).toUtf8().data(), 0528 mirrorDir.filePath(QStringLiteral("dirb")).toUtf8().data()), 0); 0529 QVERIFY(!QFile::exists(mirrorDir.filePath(QStringLiteral("dira")))); 0530 QVERIFY(QFile::exists(mirrorDir.filePath(QStringLiteral("dirb")))); 0531 QVERIFY(!QFile::exists(mirrorDir.filePath(QStringLiteral("dira")))); 0532 QVERIFY(QFile::exists(mirrorDir.filePath(QStringLiteral("dirb")))); 0533 QVERIFY(!QFile::exists(mirrorDir.filePath(QStringLiteral("dirb/filea")))); 0534 QVERIFY(QFile::exists(mirrorDir.filePath(QStringLiteral("dirb/fileb")))); 0535 0536 // Verify that the file is still open and "connected" 0537 QVERIFY(file.write(QStringLiteral("!").toUtf8())); 0538 QVERIFY(file.flush()); 0539 QCOMPARE(fsync(file.handle()), 0); 0540 QFile localFile(localDir.filePath(QStringLiteral("dirb/fileb"))); 0541 QVERIFY(localFile.open(QIODevice::ReadOnly)); 0542 QCOMPARE(localFile.readAll(), QStringLiteral("someweirdstring!").toUtf8()); 0543 0544 // Try the same, but overwriting an existing file 0545 QFile overwrittenFile(mirrorDir.filePath(QStringLiteral("dirb/filec"))); 0546 QVERIFY(overwrittenFile.open(QIODevice::ReadWrite)); 0547 QCOMPARE(overwrittenFile.write(QStringLiteral("data").toUtf8()), 4); 0548 QVERIFY(overwrittenFile.flush()); 0549 #ifdef RENAME_NOREPLACE 0550 QCOMPARE(renameat2(AT_FDCWD, mirrorDir.filePath(QStringLiteral("dirb/fileb")).toUtf8().data(), 0551 AT_FDCWD, mirrorDir.filePath(QStringLiteral("dirb/filec")).toUtf8().data(), 0552 RENAME_NOREPLACE), -1); 0553 QCOMPARE(errno, EEXIST); 0554 #endif 0555 0556 QCOMPARE(rename(mirrorDir.filePath(QStringLiteral("dirb/fileb")).toUtf8().data(), 0557 mirrorDir.filePath(QStringLiteral("dirb/filec")).toUtf8().data()), 0); 0558 QVERIFY(!QFile::exists(localDir.filePath(QStringLiteral("dirb/fileb")))); 0559 QVERIFY(QFile::exists(localDir.filePath(QStringLiteral("dirb/filec")))); 0560 QVERIFY(!QFile::exists(mirrorDir.filePath(QStringLiteral("dirb/fileb")))); 0561 QVERIFY(QFile::exists(mirrorDir.filePath(QStringLiteral("dirb/filec")))); 0562 0563 QVERIFY(overwrittenFile.seek(0)); 0564 #ifdef TEST_CACHE_BASED_IO 0565 // Both handles must still be valid 0566 QCOMPARE(overwrittenFile.readAll(), QStringLiteral("data").toUtf8()); 0567 #else 0568 // Doesn't apply to FileJob (KIO::open) I/O 0569 #endif 0570 0571 localFile.close(); 0572 localFile.setFileName(localDir.filePath(QStringLiteral("dirb/filec"))); 0573 QVERIFY(localFile.open(QIODevice::ReadOnly)); 0574 QCOMPARE(localFile.readAll(), QStringLiteral("someweirdstring!").toUtf8()); 0575 } 0576 0577 void FileOpsTest::testDeletionOps() 0578 { 0579 QTemporaryDir localDir; 0580 QVERIFY(localDir.isValid()); 0581 0582 // Mount the temporary dir 0583 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0584 QVERIFY(!reply.isEmpty()); 0585 0586 QDir mirrorDir(reply); 0587 QVERIFY(mirrorDir.exists()); 0588 0589 // Create a directory 0590 QVERIFY(QDir(mirrorDir.path()).mkdir(QStringLiteral("dir"))); 0591 QDir dir(mirrorDir.filePath(QStringLiteral("dir"))); 0592 0593 // And a file inside 0594 QFile file(dir.filePath(QStringLiteral("file"))); 0595 QVERIFY(file.open(QIODevice::ReadWrite)); 0596 QVERIFY(file.write(QStringLiteral("someweirdstring").toUtf8())); 0597 QVERIFY(file.flush()); 0598 0599 // Try to delete the directory 0600 QCOMPARE(unlink(dir.path().toUtf8().data()), -1); 0601 #ifdef Q_OS_LINUX 0602 QCOMPARE(errno, EISDIR); 0603 #else 0604 QCOMPARE(errno, EPERM); 0605 #endif 0606 QCOMPARE(rmdir(dir.path().toUtf8().data()), -1); 0607 QCOMPARE(errno, ENOTEMPTY); 0608 0609 #ifdef TEST_CACHE_BASED_IO 0610 // Delete the file 0611 QCOMPARE(rmdir(file.fileName().toUtf8().data()), -1); 0612 QCOMPARE(errno, ENOTDIR); 0613 QCOMPARE(unlink(file.fileName().toUtf8().data()), 0); 0614 QVERIFY(!file.exists()); 0615 QVERIFY(!QFile::exists(localDir.filePath(QStringLiteral("dir/file")))); 0616 0617 // Make sure it's still open 0618 QVERIFY(file.seek(0)); 0619 QCOMPARE(file.readAll(), QStringLiteral("someweirdstring").toUtf8()); 0620 0621 // Delete the now empty directory 0622 QCOMPARE(rmdir(dir.path().toUtf8().data()), 0); 0623 QVERIFY(!dir.exists()); 0624 QVERIFY(!QFile::exists(localDir.filePath(QStringLiteral("dir")))); 0625 0626 // Make sure the file is still open 0627 QVERIFY(file.seek(0)); 0628 QCOMPARE(file.readAll(), QStringLiteral("someweirdstring").toUtf8()); 0629 #else 0630 // FileJob-based nodes only unlink if the file isn't open 0631 QCOMPARE(rmdir(file.fileName().toUtf8().data()), -1); 0632 QCOMPARE(errno, ENOTDIR); 0633 QCOMPARE(unlink(file.fileName().toUtf8().data()), -1); 0634 file.close(); 0635 QCOMPARE(unlink(file.fileName().toUtf8().data()), 0); 0636 QVERIFY(!file.exists()); 0637 QVERIFY(!QFile::exists(localDir.filePath(QStringLiteral("dir/file")))); 0638 #endif 0639 0640 0641 // Not implemented: Link the file back into the tree, if possible 0642 // QCOMPARE(link(QStringLiteral("/proc/self/fd/%1").arg(file.handle()).toUtf8().data(), 0643 // mirrorDir.filePath(QStringLiteral("deletedFile")).toUtf8().data()), 0); 0644 // ... test that the file is still open and connected. 0645 } 0646 0647 void FileOpsTest::testArchiveOps() 0648 { 0649 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("tar"))) 0650 QSKIP("Test requires tar protocol to be supported. See README for packages required."); 0651 0652 QString outerpath = QFINDTESTDATA(QStringLiteral("data/outerarchive.tar.gz")); 0653 0654 // Mount a file inside the archive 0655 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("tar://%1/outerarchive/outerfile").arg(outerpath)).value(); 0656 QVERIFY(!reply.isEmpty()); 0657 0658 // And verify its content 0659 QString outerfilepath = reply; 0660 QFile outerfile(outerfilepath); 0661 QVERIFY(outerfile.open(QIODevice::ReadOnly)); 0662 QCOMPARE(outerfile.readAll(), QStringLiteral("outercontent").toUtf8()); 0663 0664 reply = m_kiofuse_iface.mountUrl(QStringLiteral("tar://%1/outerarchive/innerarchive.tar.gz").arg(outerpath)).value(); 0665 QVERIFY(!reply.isEmpty()); 0666 QString innerpath = reply; 0667 0668 // Unfortunately kio_archive is not reentrant, so a direct access would deadlock. 0669 // As a workaround, cache the file to avoid a 2nd call into kio_archive. 0670 QFile innerarchiveFile(innerpath); 0671 QVERIFY(innerarchiveFile.open(QIODevice::ReadOnly)); 0672 QVERIFY(!innerarchiveFile.readAll().isEmpty()); 0673 0674 // Next, mount an archive inside - this uses kio-fuse recursively 0675 reply = m_kiofuse_iface.mountUrl(QStringLiteral("tar://%1").arg(innerpath)).value(); 0676 QVERIFY(!reply.isEmpty()); 0677 0678 QFile innerfile(QStringLiteral("%1/innerarchive/innerfile").arg(reply)); 0679 QVERIFY(innerfile.open(QIODevice::ReadOnly)); 0680 QCOMPARE(innerfile.readAll(), QStringLiteral("innercontent").toUtf8()); 0681 innerfile.close(); 0682 } 0683 0684 void FileOpsTest::testManWorkaround() 0685 { 0686 // The man ioworker has "hybrid" directories which stat as regular files but also support 0687 // listDir. This behaviour is not supported and mounting has to fail. 0688 0689 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("man"))) 0690 QSKIP("Test requires man protocol to be supported. See README for packages required."); 0691 0692 QDBusPendingReply<QString> reply = m_kiofuse_iface.mountUrl(QStringLiteral("man:foo")); 0693 reply.waitForFinished(); 0694 QVERIFY(reply.isError()); 0695 QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.CannotMount")); 0696 0697 reply = m_kiofuse_iface.mountUrl(QStringLiteral("man:/")); 0698 reply.waitForFinished(); 0699 QVERIFY(reply.isError()); 0700 QCOMPARE(reply.error().name(), QStringLiteral("org.kde.KIOFuse.VFS.Error.CannotMount")); 0701 } 0702 0703 void FileOpsTest::testKioErrorMapping() 0704 { 0705 QTemporaryFile localFile; 0706 QVERIFY(localFile.open()); 0707 0708 // Mount the temporary file 0709 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localFile.fileName())).value(); 0710 QVERIFY(!reply.isEmpty()); 0711 0712 QFile mirroredFile(reply); 0713 QVERIFY(mirroredFile.exists()); 0714 QVERIFY(mirroredFile.open(QIODevice::ReadWrite)); 0715 QCOMPARE(mirroredFile.size(), localFile.size()); 0716 // No permission to chown to root/root (unless running with CAP_CHOWN or being root) 0717 QCOMPARE(chown(mirroredFile.fileName().toUtf8().data(), 0, 0), -1); 0718 QCOMPARE(errno, EPERM); 0719 } 0720 0721 void FileOpsTest::testRootLookup() 0722 { 0723 struct stat st; 0724 // Verify that it does not exist... 0725 QCOMPARE(stat(qPrintable(QStringLiteral("%1/invalid").arg(m_mountDir.path())), &st), -1); 0726 // ... and set errno correctly 0727 QCOMPARE(errno, ENOENT); 0728 } 0729 0730 void FileOpsTest::testFilenameEscaping() 0731 { 0732 QTemporaryDir localDir; 0733 QVERIFY(localDir.isValid()); 0734 0735 // Mount the temporary dir 0736 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0737 QVERIFY(!reply.isEmpty()); 0738 0739 QDir mirrorDir(reply); 0740 QVERIFY(mirrorDir.exists()); 0741 0742 // Create a file in localDir with an "unusual" filename 0743 for(const QString &name : {QStringLiteral("file0?name"), 0744 QStringLiteral("file1#name"), QStringLiteral("file2%20name"), 0745 QStringLiteral("file2 \nname?asdf&foo#bar")}) 0746 { 0747 QFile localFile(localDir.filePath(name)); 0748 QVERIFY(localFile.open(QFile::WriteOnly)); 0749 QCOMPARE(localFile.write("teststring", 10), 10); 0750 localFile.close(); 0751 0752 QFile mirrorFile(mirrorDir.filePath(name)); 0753 QVERIFY2(mirrorFile.open(QFile::ReadOnly), name.toUtf8().data()); 0754 QCOMPARE(mirrorFile.readAll(), QStringLiteral("teststring").toUtf8()); 0755 } 0756 } 0757 0758 void FileOpsTest::testDirRefresh() 0759 { 0760 QTemporaryDir localDir; 0761 QVERIFY(localDir.isValid()); 0762 0763 // Mount the temporary dir 0764 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0765 QVERIFY(!reply.isEmpty()); 0766 0767 QDir mirrorDir(reply); 0768 QVERIFY(mirrorDir.exists()); 0769 0770 // readdir must not have any content yet 0771 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(), 0); 0772 0773 QFile newFile(localDir.filePath(QStringLiteral("newFile"))); 0774 QVERIFY(newFile.open(QFile::ReadWrite)); 0775 0776 // Verify that the file is part of a dirlist after refresh 0777 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(), 0); 0778 QVERIFY(forceNodeTimeout()); 0779 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot), 0780 QStringList{QStringLiteral("newFile")}); 0781 0782 // Delete the file 0783 newFile.close(); 0784 QVERIFY(newFile.remove()); 0785 0786 // Verify that it disappears from the dirlist after refresh 0787 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot), QStringList{QStringLiteral("newFile")}); 0788 QVERIFY(forceNodeTimeout()); 0789 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(), 0); 0790 0791 // Recreate the file 0792 QVERIFY(newFile.open(QFile::ReadWrite)); 0793 0794 // Verify that access is immediately possible again (lookup is "optimistic") 0795 QVERIFY(QFile::exists(mirrorDir.filePath(QStringLiteral("newFile")))); 0796 0797 // Delete the file again 0798 newFile.close(); 0799 QVERIFY(newFile.remove()); 0800 0801 // Verify that after a refresh it's dropped 0802 QVERIFY(forceNodeTimeout()); 0803 QVERIFY(!QFile::exists(mirrorDir.filePath(QStringLiteral("newFile")))); 0804 } 0805 0806 void FileOpsTest::testFileRefresh() 0807 { 0808 QTemporaryDir localDir; 0809 QVERIFY(localDir.isValid()); 0810 0811 // Mount the temporary dir 0812 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0813 QVERIFY(!reply.isEmpty()); 0814 0815 QDir mirrorDir(reply); 0816 QVERIFY(mirrorDir.exists()); 0817 0818 // readdir must not have any content yet 0819 QCOMPARE(mirrorDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(), 0); 0820 0821 QFile localFile(localDir.filePath(QStringLiteral("newFile"))); 0822 QVERIFY(localFile.open(QFile::ReadWrite)); 0823 0824 QFile mirrorFile(mirrorDir.filePath(QStringLiteral("newFile"))); 0825 QVERIFY(mirrorFile.open(QFile::ReadOnly)); 0826 QCOMPARE(mirrorFile.size(), 0); // File is empty 0827 QCOMPARE(mirrorFile.readAll(), QByteArray{}); 0828 QVERIFY(mirrorFile.permissions() & QFile::ReadOther); // Has default perms 0829 0830 QCOMPARE(localFile.write("teststring", 10), 10); // Write some data 0831 QVERIFY(localFile.flush()); 0832 QVERIFY(localFile.setPermissions(localFile.permissions() & ~QFile::ReadOther)); // Change perms 0833 QCOMPARE(mirrorFile.size(), 0); // File is still empty 0834 QVERIFY(forceNodeTimeout()); 0835 0836 // Without reopening, it has the new content and perms now 0837 QCOMPARE(mirrorFile.size(), 10); 0838 QCOMPARE(mirrorFile.readAll(), QStringLiteral("teststring").toUtf8()); 0839 QCOMPARE(mirrorFile.permissions() & QFile::ReadOther, 0); // Has changed perms 0840 } 0841 0842 void FileOpsTest::testSymlinkRefresh() 0843 { 0844 QTemporaryDir localDir; 0845 QVERIFY(localDir.isValid()); 0846 0847 // Mount the temporary dir 0848 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0849 QVERIFY(!reply.isEmpty()); 0850 0851 QDir mirrorDir(reply); 0852 QVERIFY(mirrorDir.exists()); 0853 0854 // Create a symlink 0855 QCOMPARE(symlink("oldtarget", localDir.filePath(QStringLiteral("symlink")).toUtf8().data()), 0); 0856 QCOMPARE(readlink(mirrorDir.filePath(QStringLiteral("symlink"))), QStringLiteral("oldtarget")); 0857 0858 // Change the symlink 0859 QVERIFY(QFile::remove(localDir.filePath((QStringLiteral("symlink"))))); 0860 QCOMPARE(symlink("newtarget", localDir.filePath(QStringLiteral("symlink")).toUtf8().data()), 0); 0861 0862 QVERIFY(forceNodeTimeout()); 0863 0864 QCOMPARE(readlink(mirrorDir.filePath(QStringLiteral("symlink"))), QStringLiteral("newtarget")); 0865 } 0866 0867 void FileOpsTest::testTypeRefresh() 0868 { 0869 QTemporaryDir localDir; 0870 QVERIFY(localDir.isValid()); 0871 0872 // Mount the temporary dir 0873 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0874 QVERIFY(!reply.isEmpty()); 0875 0876 QDir mirrorDir(reply); 0877 QVERIFY(mirrorDir.exists()); 0878 0879 // Create a file and directory 0880 QFile localFile(localDir.filePath(QStringLiteral("changingtodir"))); 0881 QVERIFY(localFile.open(QFile::ReadWrite)); 0882 QVERIFY(QDir(localDir.path()).mkdir(QStringLiteral("changingtofile"))); 0883 0884 // Open it on the mirror 0885 QFile changingMirrorFile(mirrorDir.filePath(QStringLiteral("changingtodir"))); 0886 QVERIFY(changingMirrorFile.open(QFile::ReadOnly)); 0887 0888 // Replace the file locally with a directory 0889 QVERIFY(localFile.remove()); 0890 QVERIFY(QDir(localDir.path()).mkdir(QStringLiteral("changingtodir"))); 0891 0892 QVERIFY(forceNodeTimeout()); 0893 0894 // Verify that it's a directory now 0895 struct stat st; 0896 QCOMPARE(stat(qPrintable(changingMirrorFile.fileName()), &st), 0); 0897 QCOMPARE(st.st_mode & S_IFMT, S_IFDIR); 0898 0899 // The opened file still refers to the (now deleted) file 0900 QCOMPARE(fstat(changingMirrorFile.handle(), &st), 0); 0901 QCOMPARE(st.st_mode & S_IFMT, S_IFREG); 0902 } 0903 0904 void FileOpsTest::testDirSymlink() 0905 { 0906 QTemporaryDir localTmpDir; 0907 QVERIFY(localTmpDir.isValid()); 0908 QDir localDir(localTmpDir.path()); 0909 0910 // Mount the temporary dir 0911 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0912 QVERIFY(!reply.isEmpty()); 0913 0914 QDir mirrorDir(reply); 0915 QVERIFY(mirrorDir.exists()); 0916 0917 // Create a directory (with a dir inside) and a symlink to the parent 0918 QVERIFY(mirrorDir.mkpath(QStringLiteral("realdir/child"))); 0919 QCOMPARE(symlink("realdir/../realdir", 0920 qPrintable(mirrorDir.filePath(QStringLiteral("linktodir")))), 0); 0921 // Verify that the link is saved as-is 0922 QCOMPARE(readlink(mirrorDir.filePath(QStringLiteral("linktodir"))), 0923 QStringLiteral("realdir/../realdir")); 0924 0925 // Verify that it was correctly created everywhere 0926 QVERIFY(mirrorDir.exists(QStringLiteral("linktodir/child"))); 0927 QVERIFY(localDir.exists(QStringLiteral("linktodir/child"))); 0928 QVERIFY(localDir.exists(QStringLiteral("realdir/child"))); 0929 0930 // Verify that remoteUrl contains the exact path 0931 auto remoteUrlReply = m_kiofuse_iface.remoteUrl(mirrorDir.filePath(QStringLiteral("linktodir/child"))); 0932 remoteUrlReply.waitForFinished(); 0933 QVERIFY(!remoteUrlReply.isError()); 0934 QCOMPARE(QUrl{remoteUrlReply.value()}, QUrl::fromLocalFile(localDir.filePath(QStringLiteral("linktodir/child")))); 0935 0936 // Verify that the child can be mounted through linktodir 0937 auto mountReply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.filePath(QStringLiteral("linktodir/child")))); 0938 mountReply.waitForFinished(); 0939 QVERIFY(!mountReply.isError()); 0940 } 0941 0942 void FileOpsTest::testSymlinkRewrite() 0943 { 0944 QTemporaryDir localTmpDir; 0945 QVERIFY(localTmpDir.isValid()); 0946 QDir localDir(localTmpDir.path()); 0947 0948 // Mount the temporary dir 0949 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localDir.path())).value(); 0950 QVERIFY(!reply.isEmpty()); 0951 0952 QDir mirrorDir(reply); 0953 QVERIFY(mirrorDir.exists()); 0954 0955 // Create a symlink /mnt/file/.../symlink -> /mnt/file/.../somedir/../somefile. 0956 // This is to test that even if the target does not exist and is some convoluted 0957 // path, it is still rewritten correctly. 0958 QCOMPARE(symlink(qPrintable(mirrorDir.filePath(QStringLiteral("somedir/../somefile"))), 0959 qPrintable(mirrorDir.filePath(QStringLiteral("symlink")))), 0); 0960 // Verify that it can be read back as-is on the mount 0961 QCOMPARE(readlink(mirrorDir.filePath(QStringLiteral("symlink"))), 0962 mirrorDir.filePath(QStringLiteral("somedir/../somefile"))); 0963 0964 // Verify that it's absolute on the local side 0965 QCOMPARE(readlink(localDir.filePath(QStringLiteral("symlink"))), 0966 localDir.filePath(QStringLiteral("somedir/../somefile"))); 0967 0968 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("tar"))) 0969 QSKIP("Test requires tar protocol to be supported. See README for packages required."); 0970 0971 // Mount something with a different origin 0972 QString outerpath = QFINDTESTDATA(QStringLiteral("data/outerarchive.tar.gz")); 0973 reply = m_kiofuse_iface.mountUrl(QStringLiteral("tar://%1/outerarchive/").arg(outerpath)).value(); 0974 QVERIFY(!reply.isEmpty()); 0975 QDir archiveDir(reply); 0976 QVERIFY(archiveDir.exists()); 0977 0978 // Create a symlink /mnt/file/.../symlink -> /mnt/tar/.../outerarchive/somewhere 0979 // which can't be rewritten properly 0980 QCOMPARE(symlink(qPrintable(archiveDir.filePath(QStringLiteral("somewhere"))), 0981 qPrintable(mirrorDir.filePath(QStringLiteral("symlink2")))), 0); 0982 // Verify that it didn't get rewritten during write 0983 QCOMPARE(readlink(localDir.filePath(QStringLiteral("symlink2"))), 0984 archiveDir.filePath(QStringLiteral("somewhere"))); 0985 // If rewriting fails, it'll keep it as-is, but the next read will rewrite 0986 // it in the other direction again, i.e. /mnt/file/mnt/tar/... 0987 reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(archiveDir.path())).value(); 0988 QVERIFY(!reply.isEmpty()); 0989 0990 QCOMPARE(readlink(mirrorDir.filePath(QStringLiteral("symlink2"))), 0991 QDir(reply).filePath(QStringLiteral("somewhere"))); 0992 } 0993 0994 #ifdef WASTE_DISK_SPACE 0995 void FileOpsTest::testReadWrite4GBFile() 0996 { 0997 QTemporaryFile localFile; 0998 QVERIFY(localFile.open()); 0999 1000 // Mount the temporary file 1001 QString reply = m_kiofuse_iface.mountUrl(QStringLiteral("file://%1").arg(localFile.fileName())).value(); 1002 QVERIFY(!reply.isEmpty()); 1003 1004 QFile mirroredFile(reply); 1005 QVERIFY(mirroredFile.exists()); 1006 QVERIFY(mirroredFile.open(QIODevice::ReadWrite)); 1007 1008 // Write new data at a 2^32 offset 1009 QVERIFY(mirroredFile.seek(qint64(4096)*1024*1024)); 1010 QCOMPARE(mirroredFile.write(QStringLiteral("newteststring!").toUtf8()), 14); 1011 1012 QVERIFY(mirroredFile.flush()); 1013 // Flush the written contents into the backend 1014 QCOMPARE(fsync(mirroredFile.handle()), 0); 1015 1016 // Currently, kio-fuse uses KIO::put and not KIO::write, so the file was replaced 1017 // instead of changed. So reopen the file. 1018 QFile localFile2(localFile.fileName()); 1019 QVERIFY(localFile2.open(QIODevice::ReadOnly));; 1020 1021 // Compare the content 1022 QVERIFY(localFile2.seek(qint64(4096)*1024*1024-6)); 1023 QCOMPARE(localFile2.read(20), QByteArray("\x00\x00\x00\x00\x00\x00newteststring!", 20)); 1024 QVERIFY(localFile2.seek(qint64(4096)*4096*1024-6)); 1025 QCOMPARE(localFile2.read(20), QByteArray()); 1026 QVERIFY(localFile2.seek(qint64(4096)*1024*1024-6)); 1027 QVERIFY(mirroredFile.seek(qint64(4096)*1024*1024-6)); 1028 QCOMPARE(localFile2.read(20), mirroredFile.read(20)); 1029 } 1030 #endif // WASTE_DISK_SPACE 1031 1032 QDateTime FileOpsTest::roundDownToSecond(const QDateTime &dt) 1033 { 1034 return dt.addMSecs(-dt.time().msec()); 1035 } 1036 1037 bool FileOpsTest::forceNodeTimeout() 1038 { 1039 auto reply = m_kiofuseprivate_iface.forceNodeTimeout(); 1040 reply.waitForFinished(); 1041 return !reply.isError(); 1042 } 1043 1044 QString FileOpsTest::readlink(const QString &symlink) 1045 { 1046 char buf[PATH_MAX]; 1047 int len = ::readlink(qPrintable(symlink), buf, sizeof(buf)); 1048 if(len < 0 || len >= int(sizeof(buf))) // Failed or truncated? 1049 return {}; // Return a null QString 1050 1051 return QString::fromLocal8Bit(buf, len); 1052 } 1053 1054 QTEST_GUILESS_MAIN(FileOpsTest) 1055 1056 #include "fileopstest.moc"