File indexing completed on 2024-05-12 05:48:50
0001 // SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com> 0002 // 0003 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "bupvfs.h" 0006 0007 #include <QCoreApplication> 0008 #include <QFile> 0009 #include <QVarLengthArray> 0010 #include <QUrl> 0011 0012 #include <KIO/WorkerBase> 0013 using namespace KIO; 0014 #include <KLocalizedString> 0015 #include <KProcess> 0016 0017 #include <grp.h> 0018 #include <pwd.h> 0019 0020 // Pseudo plugin class to embed meta data 0021 class KIOPluginForMetaData : public QObject 0022 { 0023 Q_OBJECT 0024 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.bup" FILE "bup.json") 0025 }; 0026 0027 class BupWorker : public WorkerBase 0028 { 0029 public: 0030 BupWorker(const QByteArray &pPoolSocket, const QByteArray &pAppSocket); 0031 ~BupWorker() override; 0032 KIO::WorkerResult close() override; 0033 KIO::WorkerResult get(const QUrl &pUrl) override; 0034 KIO::WorkerResult listDir(const QUrl &pUrl) override ; 0035 KIO::WorkerResult open(const QUrl &pUrl, QIODevice::OpenMode pMode) override; 0036 KIO::WorkerResult read(filesize_t pSize) override; 0037 KIO::WorkerResult seek(filesize_t pOffset) override; 0038 KIO::WorkerResult stat(const QUrl &pUrl) override; 0039 KIO::WorkerResult mimetype(const QUrl &pUrl) override; 0040 0041 private: 0042 bool checkCorrectRepository(const QUrl &pUrl, QStringList &pPathInRepository); 0043 QString getUserName(uid_t pUid); 0044 QString getGroupName(gid_t pGid); 0045 void createUDSEntry(Node *pNode, KIO::UDSEntry & pUDSEntry, int pDetails); 0046 0047 QHash<uid_t, QString> mUsercache; 0048 QHash<gid_t, QString> mGroupcache; 0049 Repository *mRepository; 0050 File *mOpenFile; 0051 }; 0052 0053 BupWorker::BupWorker(const QByteArray &pPoolSocket, const QByteArray &pAppSocket) 0054 : WorkerBase("bup", pPoolSocket, pAppSocket) 0055 { 0056 mRepository = nullptr; 0057 mOpenFile = nullptr; 0058 git_libgit2_init(); 0059 } 0060 0061 BupWorker::~BupWorker() { 0062 delete mRepository; 0063 git_libgit2_shutdown(); 0064 } 0065 0066 KIO::WorkerResult BupWorker::close() { 0067 mOpenFile = nullptr; 0068 return KIO::WorkerResult::pass(); 0069 } 0070 0071 KIO::WorkerResult BupWorker::get(const QUrl &pUrl) { 0072 QStringList lPathInRepo; 0073 if(!checkCorrectRepository(pUrl, lPathInRepo)) { 0074 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("No bup repository found.\n%1", pUrl.toDisplayString())); 0075 } 0076 0077 // Assume that a symlink should be followed. 0078 // Kio will never call get() on a symlink if it actually wants to copy a 0079 // symlink, it would just create a symlink on the destination kioworker using the 0080 // target it already got from calling stat() on this one. 0081 Node *lNode = mRepository->resolve(lPathInRepo, true); 0082 if(lNode == nullptr) { 0083 return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, lPathInRepo.join(QStringLiteral("/"))); 0084 } 0085 File *lFile = qobject_cast<File *>(lNode); 0086 if(lFile == nullptr) { 0087 return KIO::WorkerResult::fail(KIO::ERR_IS_DIRECTORY, lPathInRepo.join(QStringLiteral("/"))); 0088 } 0089 0090 mimeType(lFile->mMimeType); 0091 // Emit total size AFTER mimetype 0092 totalSize(lFile->size()); 0093 0094 //make sure file is at the beginning 0095 lFile->seek(0); 0096 KIO::filesize_t lProcessedSize = 0; 0097 const QString lResumeOffset = metaData(QStringLiteral("resume")); 0098 if(!lResumeOffset.isEmpty()) { 0099 bool ok; 0100 quint64 lOffset = lResumeOffset.toULongLong(&ok); 0101 if (ok && lOffset < lFile->size()) { 0102 if(0 == lFile->seek(lOffset)) { 0103 canResume(); 0104 lProcessedSize = lOffset; 0105 } 0106 } 0107 } 0108 0109 QByteArray lResultArray; 0110 int lRetVal; 0111 while(0 == (lRetVal = lFile->read(lResultArray))) { 0112 data(lResultArray); 0113 lProcessedSize += static_cast<quint64>(lResultArray.length()); 0114 processedSize(lProcessedSize); 0115 } 0116 if(lRetVal == KIO::ERR_NO_CONTENT) { 0117 data(QByteArray()); 0118 processedSize(lProcessedSize); 0119 return KIO::WorkerResult::pass(); 0120 } else { 0121 return KIO::WorkerResult::fail(lRetVal, lPathInRepo.join(QStringLiteral("/"))); 0122 } 0123 } 0124 0125 KIO::WorkerResult BupWorker::listDir(const QUrl &pUrl) { 0126 QStringList lPathInRepo; 0127 if(!checkCorrectRepository(pUrl, lPathInRepo)) { 0128 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("No bup repository found.\n%1", pUrl.toDisplayString())); 0129 } 0130 Node *lNode = mRepository->resolve(lPathInRepo, true); 0131 if(lNode == nullptr) { 0132 return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, lPathInRepo.join(QStringLiteral("/"))); 0133 } 0134 auto lDir = qobject_cast<Directory *>(lNode); 0135 if(lDir == nullptr) { 0136 return KIO::WorkerResult::fail(KIO::ERR_IS_FILE, lPathInRepo.join(QStringLiteral("/"))); 0137 } 0138 0139 // give the directory a chance to reload if necessary. 0140 lDir->reload(); 0141 0142 const QString sDetails = metaData(QStringLiteral("details")); 0143 const int lDetails = sDetails.isEmpty() ? 2 : sDetails.toInt(); 0144 0145 NodeMapIterator i(lDir->subNodes()); 0146 UDSEntry lEntry; 0147 while(i.hasNext()) { 0148 createUDSEntry(i.next().value(), lEntry, lDetails); 0149 listEntry(lEntry); 0150 } 0151 return KIO::WorkerResult::pass(); 0152 } 0153 0154 KIO::WorkerResult BupWorker::open(const QUrl &pUrl, QIODevice::OpenMode pMode) { 0155 if(pMode & QIODevice::WriteOnly) { 0156 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_WRITING, pUrl.toDisplayString()); 0157 } 0158 0159 QStringList lPathInRepo; 0160 if(!checkCorrectRepository(pUrl, lPathInRepo)) { 0161 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("No bup repository found.\n%1", pUrl.toDisplayString())); 0162 } 0163 0164 Node *lNode = mRepository->resolve(lPathInRepo, true); 0165 if(lNode == nullptr) { 0166 return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, lPathInRepo.join(QStringLiteral("/"))); 0167 } 0168 0169 File *lFile = qobject_cast<File *>(lNode); 0170 if(lFile == nullptr) { 0171 return KIO::WorkerResult::fail(KIO::ERR_IS_DIRECTORY, lPathInRepo.join(QStringLiteral("/"))); 0172 } 0173 0174 if(0 != lFile->seek(0)) { 0175 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, pUrl.toDisplayString()); 0176 } 0177 0178 mOpenFile = lFile; 0179 mimeType(lFile->mMimeType); 0180 totalSize(lFile->size()); 0181 position(0); 0182 return KIO::WorkerResult::pass(); 0183 } 0184 0185 KIO::WorkerResult BupWorker::read(filesize_t pSize) { 0186 if(mOpenFile == nullptr) { 0187 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_READ, QString()); 0188 } 0189 QByteArray lResultArray; 0190 int lRetVal = 0; 0191 while(pSize > 0 && 0 == (lRetVal = mOpenFile->read(lResultArray, static_cast<int>(pSize)))) { 0192 pSize -= static_cast<quint64>(lResultArray.size()); 0193 data(lResultArray); 0194 } 0195 if(lRetVal == 0) { 0196 data(QByteArray()); 0197 return KIO::WorkerResult::pass(); 0198 } else { 0199 return KIO::WorkerResult::fail(lRetVal, mOpenFile->completePath()); 0200 } 0201 } 0202 0203 KIO::WorkerResult BupWorker::seek(filesize_t pOffset) { 0204 if(mOpenFile == nullptr) { 0205 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_SEEK, QString()); 0206 } 0207 0208 if(0 != mOpenFile->seek(pOffset)) { 0209 return KIO::WorkerResult::fail(KIO::ERR_CANNOT_SEEK, mOpenFile->completePath()); 0210 } 0211 position(pOffset); 0212 return KIO::WorkerResult::pass(); 0213 } 0214 0215 KIO::WorkerResult BupWorker::stat(const QUrl &pUrl) { 0216 QStringList lPathInRepo; 0217 if(!checkCorrectRepository(pUrl, lPathInRepo)) { 0218 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("No bup repository found.\n%1", pUrl.toDisplayString())); 0219 } 0220 0221 Node *lNode = mRepository->resolve(lPathInRepo); 0222 if(lNode == nullptr) { 0223 return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, lPathInRepo.join(QStringLiteral("/"))); 0224 } 0225 0226 const QString sDetails = metaData(QStringLiteral("details")); 0227 const int lDetails = sDetails.isEmpty() ? 2 : sDetails.toInt(); 0228 0229 UDSEntry lUDSEntry; 0230 createUDSEntry(lNode, lUDSEntry, lDetails); 0231 statEntry(lUDSEntry); 0232 return KIO::WorkerResult::pass(); 0233 } 0234 0235 KIO::WorkerResult BupWorker::mimetype(const QUrl &pUrl) { 0236 QStringList lPathInRepo; 0237 if(!checkCorrectRepository(pUrl, lPathInRepo)) { 0238 return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("No bup repository found.\n%1", pUrl.toDisplayString())); 0239 } 0240 0241 Node *lNode = mRepository->resolve(lPathInRepo); 0242 if(lNode == nullptr) { 0243 return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, lPathInRepo.join(QStringLiteral("/"))); 0244 } 0245 0246 mimeType(lNode->mMimeType); 0247 return KIO::WorkerResult::pass(); 0248 } 0249 0250 bool BupWorker::checkCorrectRepository(const QUrl &pUrl, QStringList &pPathInRepository) { 0251 // make this worker accept most URLs.. even incorrect ones. (no slash (wrong), 0252 // one slash (correct), two slashes (wrong), three slashes (correct)) 0253 QString lPath; 0254 if(!pUrl.host().isEmpty()) { 0255 lPath = QStringLiteral("/") + pUrl.host() + pUrl.adjusted(QUrl::StripTrailingSlash).path() + '/'; 0256 } else { 0257 lPath = pUrl.adjusted(QUrl::StripTrailingSlash).path() + '/'; 0258 if(!lPath.startsWith(QLatin1Char('/'))) { 0259 lPath.prepend(QLatin1Char('/')); 0260 } 0261 } 0262 0263 if(mRepository && mRepository->isValid()) { 0264 if(lPath.startsWith(mRepository->objectName())) { 0265 lPath.remove(0, mRepository->objectName().length()); 0266 pPathInRepository = lPath.split(QLatin1Char('/'), Qt::SkipEmptyParts); 0267 return true; 0268 } 0269 delete mRepository; 0270 mRepository = nullptr; 0271 } 0272 0273 pPathInRepository = lPath.split(QLatin1Char('/'), Qt::SkipEmptyParts); 0274 QString lRepoPath = QStringLiteral("/"); 0275 while(!pPathInRepository.isEmpty()) { 0276 // make sure the repo path will end with a slash 0277 lRepoPath += pPathInRepository.takeFirst(); 0278 lRepoPath += QStringLiteral("/"); 0279 if((QFile::exists(lRepoPath + QStringLiteral("objects")) && 0280 QFile::exists(lRepoPath + QStringLiteral("refs"))) || 0281 (QFile::exists(lRepoPath + QStringLiteral(".git/objects")) && 0282 QFile::exists(lRepoPath + QStringLiteral(".git/refs")))) { 0283 mRepository = new Repository(nullptr, lRepoPath); 0284 return mRepository->isValid(); 0285 } 0286 } 0287 return false; 0288 } 0289 0290 QString BupWorker::getUserName(uid_t pUid) { 0291 if(!mUsercache.contains(pUid)) { 0292 struct passwd *lUserInfo = getpwuid(pUid); 0293 if(lUserInfo) { 0294 mUsercache.insert(pUid, QString::fromLocal8Bit(lUserInfo->pw_name)); 0295 } 0296 else { 0297 return QString::number(pUid); 0298 } 0299 } 0300 return mUsercache.value(pUid); 0301 } 0302 0303 QString BupWorker::getGroupName(gid_t pGid) { 0304 if(!mGroupcache.contains(pGid)) { 0305 struct group *lGroupInfo = getgrgid(pGid); 0306 if(lGroupInfo) { 0307 mGroupcache.insert(pGid, QString::fromLocal8Bit(lGroupInfo->gr_name)); 0308 } 0309 else { 0310 return QString::number( pGid ); 0311 } 0312 } 0313 return mGroupcache.value(pGid); 0314 } 0315 0316 void BupWorker::createUDSEntry(Node *pNode, UDSEntry &pUDSEntry, int pDetails) { 0317 pUDSEntry.clear(); 0318 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_NAME, pNode->objectName()); 0319 if(!pNode->mSymlinkTarget.isEmpty()) { 0320 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, pNode->mSymlinkTarget); 0321 if(pDetails > 1) { 0322 Node *lNode = qobject_cast<Node *>(pNode->parent())->resolve(pNode->mSymlinkTarget, true); 0323 if(lNode != nullptr) { // follow symlink only if details > 1 and it leads to something 0324 pNode = lNode; 0325 } 0326 } 0327 } 0328 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, pNode->mMode & S_IFMT); 0329 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS, pNode->mMode & 07777); 0330 if(pDetails > 0) { 0331 quint64 lSize = 0; 0332 File *lFile = qobject_cast<File *>(pNode); 0333 if(lFile != nullptr) { 0334 lSize = lFile->size(); 0335 } 0336 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_SIZE, static_cast<qint64>(lSize)); 0337 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, pNode->mMimeType); 0338 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, pNode->mAtime); 0339 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, pNode->mMtime); 0340 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(static_cast<uint>(pNode->mUid))); 0341 pUDSEntry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(static_cast<uint>(pNode->mGid))); 0342 } 0343 } 0344 0345 extern "C" int Q_DECL_EXPORT kdemain(int pArgc, char **pArgv) { 0346 QCoreApplication lApp(pArgc, pArgv); 0347 QCoreApplication::setApplicationName(QStringLiteral("kio_bup")); 0348 KLocalizedString::setApplicationDomain("kup"); 0349 0350 if(pArgc != 4) { 0351 fprintf(stderr, "Usage: kio_bup protocol domain-socket1 domain-socket2\n"); 0352 exit(-1); 0353 } 0354 0355 BupWorker lWorker(pArgv[2], pArgv[3]); 0356 lWorker.dispatchLoop(); 0357 0358 return 0; 0359 } 0360 0361 #include "bupworker.moc"