File indexing completed on 2025-02-02 03:49:26
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "trashimpl.h" 0009 #include "discspaceutil.h" 0010 #include "kiotrashdebug.h" 0011 #include "trashsizecache.h" 0012 0013 #include "../utils_p.h" 0014 #include <kdirnotify.h> 0015 #include <kfileitem.h> 0016 #include <kio/chmodjob.h> 0017 #include <kio/copyjob.h> 0018 #include <kio/deletejob.h> 0019 #include <kmountpoint.h> 0020 0021 #include <KConfigGroup> 0022 #include <KFileUtils> 0023 #include <KJobUiDelegate> 0024 #include <KLocalizedString> 0025 #include <KSharedConfig> 0026 #include <solid/block.h> 0027 #include <solid/device.h> 0028 #include <solid/networkshare.h> 0029 #include <solid/storageaccess.h> 0030 0031 #include <QCoreApplication> 0032 #include <QDebug> 0033 #include <QDir> 0034 #include <QEventLoop> 0035 #include <QFile> 0036 #include <QLockFile> 0037 #include <QStandardPaths> 0038 #include <QUrl> 0039 0040 #include <cerrno> 0041 #include <dirent.h> 0042 #include <fcntl.h> 0043 #include <stdlib.h> 0044 #include <sys/param.h> 0045 #include <sys/stat.h> 0046 #include <sys/types.h> 0047 #include <unistd.h> 0048 0049 TrashImpl::TrashImpl() 0050 : QObject() 0051 , m_lastErrorCode(0) 0052 , m_initStatus(InitToBeDone) 0053 , m_homeDevice(0) 0054 , m_trashDirectoriesScanned(false) 0055 , 0056 // not using kio_trashrc since KIO uses that one already for kio_trash 0057 // so better have a separate one, for faster parsing by e.g. kmimetype.cpp 0058 m_config(QStringLiteral("trashrc"), KConfig::SimpleConfig) 0059 { 0060 QT_STATBUF buff; 0061 if (QT_LSTAT(QFile::encodeName(QDir::homePath()).constData(), &buff) == 0) { 0062 m_homeDevice = buff.st_dev; 0063 } else { 0064 qCWarning(KIO_TRASH) << "Should never happen: couldn't stat $HOME" << strerror(errno); 0065 } 0066 } 0067 0068 /** 0069 * Test if a directory exists, create otherwise 0070 * @param _name full path of the directory 0071 * @return errorcode, or 0 if the dir was created or existed already 0072 * Warning, don't use return value like a bool 0073 */ 0074 int TrashImpl::testDir(const QString &_name) const 0075 { 0076 DIR *dp = ::opendir(QFile::encodeName(_name).constData()); 0077 if (!dp) { 0078 QString name = Utils::trailingSlashRemoved(_name); 0079 0080 bool ok = QDir().mkdir(name); 0081 if (!ok && QFile::exists(name)) { 0082 QString new_name = name; 0083 name.append(QStringLiteral(".orig")); 0084 if (QFile::rename(name, new_name)) { 0085 ok = QDir().mkdir(name); 0086 } else { // foo.orig existed already. How likely is that? 0087 ok = false; 0088 } 0089 if (!ok) { 0090 return KIO::ERR_DIR_ALREADY_EXIST; 0091 } 0092 } 0093 if (!ok) { 0094 // KMessageBox::sorry( 0, i18n( "Could not create directory %1. Check for permissions." ).arg( name ) ); 0095 qCWarning(KIO_TRASH) << "could not create" << name; 0096 return KIO::ERR_CANNOT_MKDIR; 0097 } else { 0098 // qCDebug(KIO_TRASH) << name << "created."; 0099 } 0100 } else { // exists already 0101 closedir(dp); 0102 } 0103 return 0; // success 0104 } 0105 0106 void TrashImpl::deleteEmptyTrashInfrastructure() 0107 { 0108 #ifdef Q_OS_OSX 0109 // For each known trash directory... 0110 if (!m_trashDirectoriesScanned) { 0111 scanTrashDirectories(); 0112 } 0113 0114 for (auto it = m_trashDirectories.cbegin(); it != m_trashDirectories.cend(); ++it) { 0115 const QString trashPath = it.value(); 0116 QString infoPath = trashPath + QLatin1String("/info"); 0117 0118 // qCDebug(KIO_TRASH) << "empty Trash" << trashPath << "; removing infrastructure"; 0119 synchronousDel(infoPath, false, true); 0120 synchronousDel(trashPath + QLatin1String("/files"), false, true); 0121 if (trashPath.endsWith(QLatin1String("/KDE.trash"))) { 0122 synchronousDel(trashPath, false, true); 0123 } 0124 } 0125 #endif 0126 } 0127 0128 bool TrashImpl::createTrashInfrastructure(int trashId, const QString &path) 0129 { 0130 const QString trashDir = path.isEmpty() ? trashDirectoryPath(trashId) : path; 0131 if (const int err = testDir(trashDir)) { 0132 error(err, trashDir); 0133 return false; 0134 } 0135 0136 const QString infoDir = trashDir + QLatin1String("/info"); 0137 if (const int err = testDir(infoDir)) { 0138 error(err, infoDir); 0139 return false; 0140 } 0141 0142 const QString filesDir = trashDir + QLatin1String("/files"); 0143 if (const int err = testDir(filesDir)) { 0144 error(err, filesDir); 0145 return false; 0146 } 0147 0148 return true; 0149 } 0150 0151 bool TrashImpl::init() 0152 { 0153 if (m_initStatus == InitOK) { 0154 return true; 0155 } 0156 if (m_initStatus == InitError) { 0157 return false; 0158 } 0159 0160 // Check the trash directory and its info and files subdirs 0161 // see also kdesktop/init.cc for first time initialization 0162 m_initStatus = InitError; 0163 #ifndef Q_OS_OSX 0164 // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default. 0165 const QString xdgDataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/'); 0166 if (!QDir().mkpath(xdgDataDir)) { 0167 qCWarning(KIO_TRASH) << "failed to create" << xdgDataDir; 0168 return false; 0169 } 0170 0171 const QString trashDir = xdgDataDir + QLatin1String("Trash"); 0172 if (!createTrashInfrastructure(0, trashDir)) { 0173 return false; 0174 } 0175 #else 0176 // we DO NOT create ~/.Trash on OS X, that's the operating system's privilege 0177 QString trashDir = QDir::homePath() + QLatin1String("/.Trash"); 0178 if (!QFileInfo(trashDir).isDir()) { 0179 error(KIO::ERR_DOES_NOT_EXIST, trashDir); 0180 return false; 0181 } 0182 trashDir += QLatin1String("/KDE.trash"); 0183 // we don't have to call createTrashInfrastructure() here because it'll be called when needed. 0184 #endif 0185 m_trashDirectories.insert(0, trashDir); 0186 m_initStatus = InitOK; 0187 // qCDebug(KIO_TRASH) << "initialization OK, home trash dir:" << trashDir; 0188 return true; 0189 } 0190 0191 void TrashImpl::migrateOldTrash() 0192 { 0193 qCDebug(KIO_TRASH); 0194 0195 KConfigGroup g(KSharedConfig::openConfig(), QStringLiteral("Paths")); 0196 const QString oldTrashDir = g.readPathEntry("Trash", QString()); 0197 0198 if (oldTrashDir.isEmpty()) { 0199 return; 0200 } 0201 0202 const QStringList entries = listDir(oldTrashDir); 0203 bool allOK = true; 0204 for (QString srcPath : entries) { 0205 if (srcPath == QLatin1Char('.') || srcPath == QLatin1String("..") || srcPath == QLatin1String(".directory")) { 0206 continue; 0207 } 0208 srcPath.prepend(oldTrashDir); // make absolute 0209 int trashId; 0210 QString fileId; 0211 if (!createInfo(srcPath, trashId, fileId)) { 0212 qCWarning(KIO_TRASH) << "Trash migration: failed to create info for" << srcPath; 0213 allOK = false; 0214 } else { 0215 bool ok = moveToTrash(srcPath, trashId, fileId); 0216 if (!ok) { 0217 (void)deleteInfo(trashId, fileId); 0218 qCWarning(KIO_TRASH) << "Trash migration: failed to create info for" << srcPath; 0219 allOK = false; 0220 } else { 0221 qCDebug(KIO_TRASH) << "Trash migration: moved" << srcPath; 0222 } 0223 } 0224 } 0225 if (allOK) { 0226 // We need to remove the old one, otherwise the desktop will have two trashcans... 0227 qCDebug(KIO_TRASH) << "Trash migration: all OK, removing old trash directory"; 0228 synchronousDel(oldTrashDir, false, true); 0229 } 0230 } 0231 0232 bool TrashImpl::createInfo(const QString &origPath, int &trashId, QString &fileId) 0233 { 0234 // off_t should be 64bit on Unix systems to have large file support 0235 // FIXME: on windows this gets disabled until trash gets integrated 0236 // BUG: 165449 0237 #ifndef Q_OS_WIN 0238 Q_STATIC_ASSERT(sizeof(off_t) >= 8); 0239 #endif 0240 0241 // qCDebug(KIO_TRASH) << origPath; 0242 // Check source 0243 QT_STATBUF buff_src; 0244 if (QT_LSTAT(QFile::encodeName(origPath).constData(), &buff_src) == -1) { 0245 if (errno == EACCES) { 0246 error(KIO::ERR_ACCESS_DENIED, origPath); 0247 } else { 0248 error(KIO::ERR_DOES_NOT_EXIST, origPath); 0249 } 0250 return false; 0251 } 0252 0253 // Choose destination trash 0254 trashId = findTrashDirectory(origPath); 0255 if (trashId < 0) { 0256 qCWarning(KIO_TRASH) << "OUCH - internal error, TrashImpl::findTrashDirectory returned" << trashId; 0257 return false; // ### error() needed? 0258 } 0259 // qCDebug(KIO_TRASH) << "trashing to" << trashId; 0260 0261 // Grab original filename 0262 auto url = QUrl::fromLocalFile(origPath); 0263 url = url.adjusted(QUrl::StripTrailingSlash); 0264 const QString origFileName = url.fileName(); 0265 0266 // Make destination file in info/ 0267 #ifdef Q_OS_OSX 0268 createTrashInfrastructure(trashId); 0269 #endif 0270 url.setPath(infoPath(trashId, origFileName)); // we first try with origFileName 0271 QUrl baseDirectory = QUrl::fromLocalFile(url.path()); 0272 // Here we need to use O_EXCL to avoid race conditions with other kioworker processes 0273 int fd = 0; 0274 QString fileName; 0275 do { 0276 // qCDebug(KIO_TRASH) << "trying to create" << url.path(); 0277 fd = ::open(QFile::encodeName(url.path()).constData(), O_WRONLY | O_CREAT | O_EXCL, 0600); 0278 if (fd < 0) { 0279 if (errno == EEXIST) { 0280 fileName = url.fileName(); 0281 url = url.adjusted(QUrl::RemoveFilename); 0282 url.setPath(url.path() + KFileUtils::suggestName(baseDirectory, fileName)); 0283 // and try again on the next iteration 0284 } else { 0285 error(KIO::ERR_CANNOT_WRITE, url.path()); 0286 return false; 0287 } 0288 } 0289 } while (fd < 0); 0290 const QString infoPath = url.path(); 0291 fileId = url.fileName(); 0292 Q_ASSERT(fileId.endsWith(QLatin1String(".trashinfo"))); 0293 fileId.chop(10); // remove .trashinfo from fileId 0294 0295 FILE *file = ::fdopen(fd, "w"); 0296 if (!file) { // can't see how this would happen 0297 error(KIO::ERR_CANNOT_WRITE, infoPath); 0298 return false; 0299 } 0300 0301 // Contents of the info file. We could use KSimpleConfig, but that would 0302 // mean closing and reopening fd, i.e. opening a race condition... 0303 QByteArray info = "[Trash Info]\n"; 0304 info += "Path="; 0305 // Escape filenames according to the way they are encoded on the filesystem 0306 // All this to basically get back to the raw 8-bit representation of the filename... 0307 if (trashId == 0) { // home trash: absolute path 0308 info += QUrl::toPercentEncoding(origPath, "/"); 0309 } else { 0310 info += QUrl::toPercentEncoding(makeRelativePath(topDirectoryPath(trashId), origPath), "/"); 0311 } 0312 info += '\n'; 0313 info += "DeletionDate=" + QDateTime::currentDateTime().toString(Qt::ISODate).toLatin1() + '\n'; 0314 size_t sz = info.size(); 0315 0316 size_t written = ::fwrite(info.data(), 1, sz, file); 0317 if (written != sz) { 0318 ::fclose(file); 0319 QFile::remove(infoPath); 0320 error(KIO::ERR_DISK_FULL, infoPath); 0321 return false; 0322 } 0323 0324 ::fclose(file); 0325 0326 // qCDebug(KIO_TRASH) << "info file created in trashId=" << trashId << ":" << fileId; 0327 return true; 0328 } 0329 0330 QString TrashImpl::makeRelativePath(const QString &topdir, const QString &path) 0331 { 0332 QString realPath = QFileInfo(path).canonicalFilePath(); 0333 if (realPath.isEmpty()) { // shouldn't happen 0334 realPath = path; 0335 } 0336 // topdir ends with '/' 0337 #ifndef Q_OS_WIN 0338 if (realPath.startsWith(topdir)) { 0339 #else 0340 if (realPath.startsWith(topdir, Qt::CaseInsensitive)) { 0341 #endif 0342 const QString rel = realPath.mid(topdir.length()); 0343 Q_ASSERT(rel[0] != QLatin1Char('/')); 0344 return rel; 0345 } else { // shouldn't happen... 0346 qCWarning(KIO_TRASH) << "Couldn't make relative path for" << realPath << "(" << path << "), with topdir=" << topdir; 0347 return realPath; 0348 } 0349 } 0350 0351 void TrashImpl::enterLoop() 0352 { 0353 QEventLoop eventLoop; 0354 connect(this, &TrashImpl::leaveModality, &eventLoop, &QEventLoop::quit); 0355 eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 0356 } 0357 0358 QString TrashImpl::infoPath(int trashId, const QString &fileId) const 0359 { 0360 const QString trashPath = trashDirectoryPath(trashId) + QLatin1String("/info/") + fileId + QLatin1String(".trashinfo"); 0361 return trashPath; 0362 } 0363 0364 QString TrashImpl::filesPath(int trashId, const QString &fileId) const 0365 { 0366 const QString trashPath = trashDirectoryPath(trashId) + QLatin1String("/files/") + fileId; 0367 return trashPath; 0368 } 0369 0370 bool TrashImpl::deleteInfo(int trashId, const QString &fileId) 0371 { 0372 #ifdef Q_OS_OSX 0373 createTrashInfrastructure(trashId); 0374 #endif 0375 0376 if (QFile::remove(infoPath(trashId, fileId))) { 0377 fileRemoved(); 0378 return true; 0379 } 0380 0381 return false; 0382 } 0383 0384 bool TrashImpl::moveToTrash(const QString &origPath, int trashId, const QString &fileId) 0385 { 0386 // qCDebug(KIO_TRASH) << "Trashing" << origPath << trashId << fileId; 0387 if (!adaptTrashSize(origPath, trashId)) { 0388 return false; 0389 } 0390 0391 #ifdef Q_OS_OSX 0392 createTrashInfrastructure(trashId); 0393 #endif 0394 const QString dest = filesPath(trashId, fileId); 0395 if (!move(origPath, dest)) { 0396 // Maybe the move failed due to no permissions to delete source. 0397 // In that case, delete dest to keep things consistent, since KIO doesn't do it. 0398 if (QFileInfo(dest).isFile()) { 0399 QFile::remove(dest); 0400 } else { 0401 synchronousDel(dest, false, true); 0402 } 0403 return false; 0404 } 0405 0406 if (QFileInfo(dest).isDir()) { 0407 TrashSizeCache trashSize(trashDirectoryPath(trashId)); 0408 const qint64 pathSize = DiscSpaceUtil::sizeOfPath(dest); 0409 trashSize.add(fileId, pathSize); 0410 } 0411 0412 fileAdded(); 0413 return true; 0414 } 0415 0416 bool TrashImpl::moveFromTrash(const QString &dest, int trashId, const QString &fileId, const QString &relativePath) 0417 { 0418 QString src = filesPath(trashId, fileId); 0419 if (!relativePath.isEmpty()) { 0420 src += QLatin1Char('/') + relativePath; 0421 } 0422 if (!move(src, dest)) { 0423 return false; 0424 } 0425 0426 TrashSizeCache trashSize(trashDirectoryPath(trashId)); 0427 trashSize.remove(fileId); 0428 0429 return true; 0430 } 0431 0432 bool TrashImpl::move(const QString &src, const QString &dest) 0433 { 0434 if (directRename(src, dest)) { 0435 // This notification is done by KIO::moveAs when using the code below 0436 // But if we do a direct rename we need to do the notification ourselves 0437 org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(dest)); 0438 return true; 0439 } 0440 if (m_lastErrorCode != KIO::ERR_UNSUPPORTED_ACTION) { 0441 return false; 0442 } 0443 0444 const auto urlSrc = QUrl::fromLocalFile(src); 0445 const auto urlDest = QUrl::fromLocalFile(dest); 0446 0447 // qCDebug(KIO_TRASH) << urlSrc << "->" << urlDest; 0448 KIO::CopyJob *job = KIO::moveAs(urlSrc, urlDest, KIO::HideProgressInfo); 0449 job->setUiDelegate(nullptr); 0450 connect(job, &KJob::result, this, &TrashImpl::jobFinished); 0451 enterLoop(); 0452 0453 return m_lastErrorCode == 0; 0454 } 0455 0456 void TrashImpl::jobFinished(KJob *job) 0457 { 0458 // qCDebug(KIO_TRASH) << "error=" << job->error() << job->errorText(); 0459 error(job->error(), job->errorText()); 0460 0461 Q_EMIT leaveModality(); 0462 } 0463 0464 bool TrashImpl::copyToTrash(const QString &origPath, int trashId, const QString &fileId) 0465 { 0466 // qCDebug(KIO_TRASH); 0467 if (!adaptTrashSize(origPath, trashId)) { 0468 return false; 0469 } 0470 0471 #ifdef Q_OS_OSX 0472 createTrashInfrastructure(trashId); 0473 #endif 0474 const QString dest = filesPath(trashId, fileId); 0475 if (!copy(origPath, dest)) { 0476 return false; 0477 } 0478 0479 if (QFileInfo(dest).isDir()) { 0480 TrashSizeCache trashSize(trashDirectoryPath(trashId)); 0481 const qint64 pathSize = DiscSpaceUtil::sizeOfPath(dest); 0482 trashSize.add(fileId, pathSize); 0483 } 0484 0485 fileAdded(); 0486 return true; 0487 } 0488 0489 bool TrashImpl::copyFromTrash(const QString &dest, int trashId, const QString &fileId, const QString &relativePath) 0490 { 0491 const QString src = physicalPath(trashId, fileId, relativePath); 0492 return copy(src, dest); 0493 } 0494 0495 bool TrashImpl::copy(const QString &src, const QString &dest) 0496 { 0497 // kio_file's copy() method is quite complex (in order to be fast), let's just call it... 0498 m_lastErrorCode = 0; 0499 const auto urlSrc = QUrl::fromLocalFile(src); 0500 const auto urlDest = QUrl::fromLocalFile(dest); 0501 // qCDebug(KIO_TRASH) << "copying" << src << "to" << dest; 0502 KIO::CopyJob *job = KIO::copyAs(urlSrc, urlDest, KIO::HideProgressInfo); 0503 job->setUiDelegate(nullptr); 0504 connect(job, &KJob::result, this, &TrashImpl::jobFinished); 0505 enterLoop(); 0506 0507 return m_lastErrorCode == 0; 0508 } 0509 0510 bool TrashImpl::directRename(const QString &src, const QString &dest) 0511 { 0512 // qCDebug(KIO_TRASH) << src << "->" << dest; 0513 // Do not use QFile::rename here, we need to be able to move broken symlinks too 0514 // (and we need to make sure errno is set) 0515 if (::rename(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) != 0) { 0516 if (errno == EXDEV) { 0517 error(KIO::ERR_UNSUPPORTED_ACTION, QStringLiteral("rename")); 0518 } else { 0519 if ((errno == EACCES) || (errno == EPERM)) { 0520 error(KIO::ERR_ACCESS_DENIED, dest); 0521 } else if (errno == EROFS) { // The file is on a read-only filesystem 0522 error(KIO::ERR_CANNOT_DELETE, src); 0523 } else if (errno == ENOENT) { 0524 const QString marker(QStringLiteral("Trash/files/")); 0525 const int idx = src.lastIndexOf(marker) + marker.size(); 0526 const QString displayName = QLatin1String("trash:/") + src.mid(idx); 0527 error(KIO::ERR_DOES_NOT_EXIST, displayName); 0528 } else { 0529 error(KIO::ERR_CANNOT_RENAME, src); 0530 } 0531 } 0532 return false; 0533 } 0534 return true; 0535 } 0536 0537 bool TrashImpl::moveInTrash(int trashId, const QString &oldFileId, const QString &newFileId) 0538 { 0539 m_lastErrorCode = 0; 0540 0541 const QString oldInfo = infoPath(trashId, oldFileId); 0542 const QString oldFile = filesPath(trashId, oldFileId); 0543 const QString newInfo = infoPath(trashId, newFileId); 0544 const QString newFile = filesPath(trashId, newFileId); 0545 0546 if (directRename(oldInfo, newInfo)) { 0547 if (directRename(oldFile, newFile)) { 0548 // success 0549 0550 if (QFileInfo(newFile).isDir()) { 0551 TrashSizeCache trashSize(trashDirectoryPath(trashId)); 0552 trashSize.rename(oldFileId, newFileId); 0553 } 0554 return true; 0555 } else { 0556 // rollback 0557 directRename(newInfo, oldInfo); 0558 } 0559 } 0560 return false; 0561 } 0562 0563 bool TrashImpl::del(int trashId, const QString &fileId) 0564 { 0565 #ifdef Q_OS_OSX 0566 createTrashInfrastructure(trashId); 0567 #endif 0568 0569 const QString info = infoPath(trashId, fileId); 0570 const QString file = filesPath(trashId, fileId); 0571 0572 QT_STATBUF buff; 0573 if (QT_LSTAT(QFile::encodeName(info).constData(), &buff) == -1) { 0574 if (errno == EACCES) { 0575 error(KIO::ERR_ACCESS_DENIED, file); 0576 } else { 0577 error(KIO::ERR_DOES_NOT_EXIST, file); 0578 } 0579 return false; 0580 } 0581 0582 const bool isDir = QFileInfo(file).isDir(); 0583 if (!synchronousDel(file, true, isDir)) { 0584 return false; 0585 } 0586 0587 if (isDir) { 0588 TrashSizeCache trashSize(trashDirectoryPath(trashId)); 0589 trashSize.remove(fileId); 0590 } 0591 0592 QFile::remove(info); 0593 fileRemoved(); 0594 return true; 0595 } 0596 0597 bool TrashImpl::synchronousDel(const QString &path, bool setLastErrorCode, bool isDir) 0598 { 0599 const int oldErrorCode = m_lastErrorCode; 0600 const QString oldErrorMsg = m_lastErrorMessage; 0601 const auto url = QUrl::fromLocalFile(path); 0602 // First ensure that all dirs have u+w permissions, 0603 // otherwise we won't be able to delete files in them (#130780). 0604 if (isDir) { 0605 // qCDebug(KIO_TRASH) << "chmod'ing" << url; 0606 KFileItem fileItem(url, QStringLiteral("inode/directory"), KFileItem::Unknown); 0607 KFileItemList fileItemList; 0608 fileItemList.append(fileItem); 0609 KIO::ChmodJob *chmodJob = KIO::chmod(fileItemList, 0200, 0200, QString(), QString(), true /*recursive*/, KIO::HideProgressInfo); 0610 connect(chmodJob, &KJob::result, this, &TrashImpl::jobFinished); 0611 enterLoop(); 0612 } 0613 0614 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 0615 connect(job, &KJob::result, this, &TrashImpl::jobFinished); 0616 enterLoop(); 0617 bool ok = m_lastErrorCode == 0; 0618 if (!setLastErrorCode) { 0619 m_lastErrorCode = oldErrorCode; 0620 m_lastErrorMessage = oldErrorMsg; 0621 } 0622 return ok; 0623 } 0624 0625 bool TrashImpl::emptyTrash() 0626 { 0627 // qCDebug(KIO_TRASH); 0628 // The naive implementation "delete info and files in every trash directory" 0629 // breaks when deleted directories contain files owned by other users. 0630 // We need to ensure that the .trashinfo file is only removed when the 0631 // corresponding files could indeed be removed (#116371) 0632 0633 // On the other hand, we certainly want to remove any file that has no associated 0634 // .trashinfo file for some reason (#167051) 0635 0636 QSet<QString> unremovableFiles; 0637 0638 int myErrorCode = 0; 0639 QString myErrorMsg; 0640 const TrashedFileInfoList fileInfoList = list(); 0641 for (const auto &info : fileInfoList) { 0642 const QString filesPath = info.physicalPath; 0643 if (synchronousDel(filesPath, true, true) || m_lastErrorCode == KIO::ERR_DOES_NOT_EXIST) { 0644 QFile::remove(infoPath(info.trashId, info.fileId)); 0645 } else { 0646 // error code is set by synchronousDel, let's remember it 0647 // (so that successfully removing another file doesn't erase the error) 0648 myErrorCode = m_lastErrorCode; 0649 myErrorMsg = m_lastErrorMessage; 0650 // and remember not to remove this file 0651 unremovableFiles.insert(filesPath); 0652 qCDebug(KIO_TRASH) << "Unremovable:" << filesPath; 0653 } 0654 0655 TrashSizeCache trashSize(trashDirectoryPath(info.trashId)); 0656 trashSize.clear(); 0657 } 0658 0659 // Now do the orphaned-files cleanup 0660 for (auto trit = m_trashDirectories.cbegin(); trit != m_trashDirectories.cend(); ++trit) { 0661 // const int trashId = trit.key(); 0662 const QString filesDir = trit.value() + QLatin1String("/files"); 0663 const QStringList list = listDir(filesDir); 0664 for (const QString &fileName : list) { 0665 if (fileName == QLatin1Char('.') || fileName == QLatin1String("..")) { 0666 continue; 0667 } 0668 const QString filePath = filesDir + QLatin1Char('/') + fileName; 0669 if (!unremovableFiles.contains(filePath)) { 0670 qCWarning(KIO_TRASH) << "Removing orphaned file" << filePath; 0671 QFile::remove(filePath); 0672 } 0673 } 0674 } 0675 0676 m_lastErrorCode = myErrorCode; 0677 m_lastErrorMessage = myErrorMsg; 0678 0679 fileRemoved(); 0680 0681 return m_lastErrorCode == 0; 0682 } 0683 0684 TrashImpl::TrashedFileInfoList TrashImpl::list() 0685 { 0686 // Here we scan for trash directories unconditionally. This allows 0687 // noticing plugged-in [e.g. removable] devices, or new mounts etc. 0688 scanTrashDirectories(); 0689 0690 TrashedFileInfoList lst; 0691 // For each known trash directory... 0692 for (auto it = m_trashDirectories.cbegin(); it != m_trashDirectories.cend(); ++it) { 0693 const int trashId = it.key(); 0694 QString infoPath = it.value(); 0695 infoPath += QLatin1String("/info"); 0696 // Code taken from kio_file 0697 const QStringList entryNames = listDir(infoPath); 0698 // char path_buffer[PATH_MAX]; 0699 // getcwd(path_buffer, PATH_MAX - 1); 0700 // if ( chdir( infoPathEnc ) ) 0701 // continue; 0702 0703 const QLatin1String tail(".trashinfo"); 0704 const int tailLength = tail.size(); 0705 for (const QString &fileName : entryNames) { 0706 if (fileName == QLatin1Char('.') || fileName == QLatin1String("..")) { 0707 continue; 0708 } 0709 if (!fileName.endsWith(tail)) { 0710 qCWarning(KIO_TRASH) << "Invalid info file found in" << infoPath << ":" << fileName; 0711 continue; 0712 } 0713 0714 TrashedFileInfo info; 0715 if (infoForFile(trashId, fileName.chopped(tailLength), info)) { 0716 lst << info; 0717 } 0718 } 0719 } 0720 return lst; 0721 } 0722 0723 // Returns the entries in a given directory - including "." and ".." 0724 QStringList TrashImpl::listDir(const QString &physicalPath) 0725 { 0726 return QDir(physicalPath).entryList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::System); 0727 } 0728 0729 bool TrashImpl::infoForFile(int trashId, const QString &fileId, TrashedFileInfo &info) 0730 { 0731 // qCDebug(KIO_TRASH) << trashId << fileId; 0732 info.trashId = trashId; // easy :) 0733 info.fileId = fileId; // equally easy 0734 info.physicalPath = filesPath(trashId, fileId); 0735 return readInfoFile(infoPath(trashId, fileId), info, trashId); 0736 } 0737 0738 bool TrashImpl::trashSpaceInfo(const QString &path, TrashSpaceInfo &info) 0739 { 0740 const int trashId = findTrashDirectory(path); 0741 if (trashId < 0) { 0742 qCWarning(KIO_TRASH) << "No trash directory found! TrashImpl::findTrashDirectory returned" << trashId; 0743 return false; 0744 } 0745 0746 const KConfig config(QStringLiteral("ktrashrc")); 0747 0748 const QString trashPath = trashDirectoryPath(trashId); 0749 const auto group = config.group(trashPath); 0750 0751 const bool useSizeLimit = group.readEntry("UseSizeLimit", true); 0752 const double percent = group.readEntry("Percent", 10.0); 0753 0754 DiscSpaceUtil util(trashPath + QLatin1String("/files/")); 0755 qint64 total = util.size(); 0756 if (useSizeLimit) { 0757 total *= percent / 100.0; 0758 } 0759 0760 TrashSizeCache trashSize(trashPath); 0761 const qint64 used = trashSize.calculateSize(); 0762 0763 info.totalSize = total; 0764 info.availableSize = total - used; 0765 0766 return true; 0767 } 0768 0769 bool TrashImpl::readInfoFile(const QString &infoPath, TrashedFileInfo &info, int trashId) 0770 { 0771 KConfig cfg(infoPath, KConfig::SimpleConfig); 0772 if (!cfg.hasGroup(QStringLiteral("Trash Info"))) { 0773 error(KIO::ERR_CANNOT_OPEN_FOR_READING, infoPath); 0774 return false; 0775 } 0776 const KConfigGroup group = cfg.group(QStringLiteral("Trash Info")); 0777 info.origPath = QUrl::fromPercentEncoding(group.readEntry("Path").toLatin1()); 0778 if (info.origPath.isEmpty()) { 0779 return false; // path is mandatory... 0780 } 0781 if (trashId == 0) { 0782 Q_ASSERT(info.origPath[0] == QLatin1Char('/')); 0783 } else { 0784 const QString topdir = topDirectoryPath(trashId); // includes trailing slash 0785 info.origPath.prepend(topdir); 0786 } 0787 const QString line = group.readEntry("DeletionDate"); 0788 if (!line.isEmpty()) { 0789 info.deletionDate = QDateTime::fromString(line, Qt::ISODate); 0790 } 0791 return true; 0792 } 0793 0794 QString TrashImpl::physicalPath(int trashId, const QString &fileId, const QString &relativePath) 0795 { 0796 QString filePath = filesPath(trashId, fileId); 0797 if (!relativePath.isEmpty()) { 0798 filePath += QLatin1Char('/') + relativePath; 0799 } 0800 return filePath; 0801 } 0802 0803 void TrashImpl::error(int e, const QString &s) 0804 { 0805 if (e) { 0806 qCDebug(KIO_TRASH) << e << s; 0807 } 0808 m_lastErrorCode = e; 0809 m_lastErrorMessage = s; 0810 } 0811 0812 bool TrashImpl::isEmpty() const 0813 { 0814 // For each known trash directory... 0815 if (!m_trashDirectoriesScanned) { 0816 scanTrashDirectories(); 0817 } 0818 0819 for (auto it = m_trashDirectories.cbegin(); it != m_trashDirectories.cend(); ++it) { 0820 const QString infoPath = it.value() + QLatin1String("/info"); 0821 0822 DIR *dp = ::opendir(QFile::encodeName(infoPath).constData()); 0823 if (dp) { 0824 struct dirent *ep; 0825 ep = readdir(dp); 0826 ep = readdir(dp); // ignore '.' and '..' dirent 0827 ep = readdir(dp); // look for third file 0828 closedir(dp); 0829 if (ep != nullptr) { 0830 // qCDebug(KIO_TRASH) << ep->d_name << "in" << infoPath << "-> not empty"; 0831 return false; // not empty 0832 } 0833 } 0834 } 0835 return true; 0836 } 0837 0838 void TrashImpl::fileAdded() 0839 { 0840 m_config.reparseConfiguration(); 0841 KConfigGroup group = m_config.group(QStringLiteral("Status")); 0842 if (group.readEntry("Empty", true) == true) { 0843 group.writeEntry("Empty", false); 0844 m_config.sync(); 0845 } 0846 // The apps showing the trash (e.g. kdesktop) will be notified 0847 // of this change when KDirNotify::FilesAdded("trash:/") is emitted, 0848 // which will be done by the job soon after this. 0849 } 0850 0851 void TrashImpl::fileRemoved() 0852 { 0853 if (isEmpty()) { 0854 deleteEmptyTrashInfrastructure(); 0855 KConfigGroup group = m_config.group(QStringLiteral("Status")); 0856 group.writeEntry("Empty", true); 0857 m_config.sync(); 0858 org::kde::KDirNotify::emitFilesChanged({QUrl::fromEncoded("trash:/")}); 0859 } 0860 // The apps showing the trash (e.g. kdesktop) will be notified 0861 // of this change when KDirNotify::FilesRemoved(...) is emitted, 0862 // which will be done by the job soon after this. 0863 } 0864 0865 #ifdef Q_OS_OSX 0866 #include <CoreFoundation/CoreFoundation.h> 0867 #include <DiskArbitration/DiskArbitration.h> 0868 #include <sys/mount.h> 0869 0870 int TrashImpl::idForMountPoint(const QString &mountPoint) const 0871 { 0872 DADiskRef disk; 0873 CFDictionaryRef descDict; 0874 DASessionRef session = DASessionCreate(NULL); 0875 int devId = -1; 0876 if (session) { 0877 QByteArray mp = QFile::encodeName(mountPoint); 0878 struct statfs statFS; 0879 statfs(mp.constData(), &statFS); 0880 disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, statFS.f_mntfromname); 0881 if (disk) { 0882 descDict = DADiskCopyDescription(disk); 0883 if (descDict) { 0884 CFNumberRef cfMajor = (CFNumberRef)CFDictionaryGetValue(descDict, kDADiskDescriptionMediaBSDMajorKey); 0885 CFNumberRef cfMinor = (CFNumberRef)CFDictionaryGetValue(descDict, kDADiskDescriptionMediaBSDMinorKey); 0886 int major, minor; 0887 if (CFNumberGetValue(cfMajor, kCFNumberIntType, &major) && CFNumberGetValue(cfMinor, kCFNumberIntType, &minor)) { 0888 qCWarning(KIO_TRASH) << "major=" << major << " minor=" << minor; 0889 devId = 1000 * major + minor; 0890 } 0891 CFRelease(cfMajor); 0892 CFRelease(cfMinor); 0893 } else { 0894 qCWarning(KIO_TRASH) << "couldn't get DADiskCopyDescription from" << disk; 0895 } 0896 CFRelease(disk); 0897 } else { 0898 qCWarning(KIO_TRASH) << "DADiskCreateFromBSDName failed on statfs from" << mp; 0899 } 0900 CFRelease(session); 0901 } else { 0902 qCWarning(KIO_TRASH) << "couldn't create DASession"; 0903 } 0904 return devId; 0905 } 0906 0907 #else 0908 0909 int TrashImpl::idForDevice(const Solid::Device &device) const 0910 { 0911 const Solid::Block *block = device.as<Solid::Block>(); 0912 if (block) { 0913 // qCDebug(KIO_TRASH) << "major=" << block->deviceMajor() << "minor=" << block->deviceMinor(); 0914 return block->deviceMajor() * 1000 + block->deviceMinor(); 0915 } else { 0916 const Solid::NetworkShare *netshare = device.as<Solid::NetworkShare>(); 0917 0918 if (netshare) { 0919 QString url = netshare->url().url(); 0920 0921 QLockFile configLock(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/trashrc.nextid.lock")); 0922 0923 if (!configLock.lock()) { 0924 return -1; 0925 } 0926 0927 m_config.reparseConfiguration(); 0928 KConfigGroup group = m_config.group(QStringLiteral("NetworkShares")); 0929 int id = group.readEntry(url, -1); 0930 0931 if (id == -1) { 0932 id = group.readEntry("NextID", 0); 0933 // qCDebug(KIO_TRASH) << "new share=" << url << " id=" << id; 0934 0935 group.writeEntry(url, id); 0936 group.writeEntry("NextID", id + 1); 0937 group.sync(); 0938 } 0939 0940 return 6000000 + id; 0941 } 0942 0943 // Not a block device nor a network share 0944 return -1; 0945 } 0946 } 0947 0948 void TrashImpl::refreshDevices() const 0949 { 0950 // this is needed because Solid's fstab backend uses QSocketNotifier 0951 // to get notifications about changes to mtab 0952 // otherwise we risk getting old device list 0953 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 0954 } 0955 #endif 0956 0957 void TrashImpl::insertTrashDir(int id, const QString &trashDir, const QString &topdir) const 0958 { 0959 m_trashDirectories.insert(id, trashDir); 0960 qCDebug(KIO_TRASH) << "found" << trashDir << "gave it id" << id; 0961 m_topDirectories.insert(id, Utils::slashAppended(topdir)); 0962 } 0963 0964 int TrashImpl::findTrashDirectory(const QString &origPath) 0965 { 0966 // qCDebug(KIO_TRASH) << origPath; 0967 // Check if it's on the same device as $HOME 0968 QT_STATBUF buff; 0969 if (QT_LSTAT(QFile::encodeName(origPath).constData(), &buff) == 0 && buff.st_dev == m_homeDevice) { 0970 return 0; 0971 } 0972 0973 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(origPath); 0974 if (!mp) { 0975 // qCDebug(KIO_TRASH) << "KMountPoint found no mount point for" << origPath; 0976 return 0; 0977 } 0978 0979 QString mountPoint = mp->mountPoint(); 0980 const QString trashDir = trashForMountPoint(mountPoint, true); 0981 // qCDebug(KIO_TRASH) << "mountPoint=" << mountPoint << "trashDir=" << trashDir; 0982 0983 #ifndef Q_OS_OSX 0984 if (trashDir.isEmpty()) { 0985 return 0; // no trash available on partition 0986 } 0987 #endif 0988 0989 int id = idForTrashDirectory(trashDir); 0990 if (id > -1) { 0991 qCDebug(KIO_TRASH) << "Found Trash dir" << trashDir << "with id" << id; 0992 return id; 0993 } 0994 0995 #ifdef Q_OS_OSX 0996 id = idForMountPoint(mountPoint); 0997 #else 0998 refreshDevices(); 0999 const QString query = QLatin1String("[StorageAccess.accessible == true AND StorageAccess.filePath == '%1']").arg(mountPoint); 1000 const QList<Solid::Device> lst = Solid::Device::listFromQuery(query); 1001 qCDebug(KIO_TRASH) << "Queried Solid with" << query << "got" << lst.count() << "devices"; 1002 if (lst.isEmpty()) { // not a device. Maybe some tmpfs mount for instance. 1003 return 0; 1004 } 1005 1006 // Pretend we got exactly one... 1007 const Solid::Device device = lst.at(0); 1008 id = idForDevice(device); 1009 #endif 1010 if (id == -1) { 1011 return 0; 1012 } 1013 1014 // New trash dir found, register it 1015 insertTrashDir(id, trashDir, mountPoint); 1016 return id; 1017 } 1018 1019 KIO::UDSEntry TrashImpl::trashUDSEntry(KIO::StatDetails details) 1020 { 1021 KIO::UDSEntry entry; 1022 if (details & KIO::StatRecursiveSize) { 1023 KIO::filesize_t size = 0; 1024 long latestModifiedDate = 0; 1025 1026 for (const QString &trashPath : std::as_const(m_trashDirectories)) { 1027 TrashSizeCache trashSize(trashPath); 1028 TrashSizeCache::SizeAndModTime res = trashSize.calculateSizeAndLatestModDate(); 1029 size += res.size; 1030 1031 // Find latest modification date 1032 if (res.mtime > latestModifiedDate) { 1033 latestModifiedDate = res.mtime; 1034 } 1035 } 1036 1037 entry.reserve(3); 1038 entry.fastInsert(KIO::UDSEntry::UDS_RECURSIVE_SIZE, static_cast<long long>(size)); 1039 1040 entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, latestModifiedDate / 1000); 1041 // access date is unreliable for the trash folder, use the modified date instead 1042 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, latestModifiedDate / 1000); 1043 } 1044 return entry; 1045 } 1046 1047 void TrashImpl::scanTrashDirectories() const 1048 { 1049 #ifndef Q_OS_OSX 1050 refreshDevices(); 1051 #endif 1052 1053 const QList<Solid::Device> lst = Solid::Device::listFromQuery(QStringLiteral("StorageAccess.accessible == true")); 1054 for (const Solid::Device &device : lst) { 1055 QString topdir = device.as<Solid::StorageAccess>()->filePath(); 1056 QString trashDir = trashForMountPoint(topdir, false); 1057 if (!trashDir.isEmpty()) { 1058 // OK, trashDir is a valid trash directory. Ensure it's registered. 1059 int trashId = idForTrashDirectory(trashDir); 1060 if (trashId == -1) { 1061 // new trash dir found, register it 1062 #ifdef Q_OS_OSX 1063 trashId = idForMountPoint(topdir); 1064 #else 1065 trashId = idForDevice(device); 1066 #endif 1067 if (trashId == -1) { 1068 continue; 1069 } 1070 1071 insertTrashDir(trashId, trashDir, topdir); 1072 } 1073 } 1074 } 1075 m_trashDirectoriesScanned = true; 1076 } 1077 1078 TrashImpl::TrashDirMap TrashImpl::trashDirectories() const 1079 { 1080 if (!m_trashDirectoriesScanned) { 1081 scanTrashDirectories(); 1082 } 1083 return m_trashDirectories; 1084 } 1085 1086 TrashImpl::TrashDirMap TrashImpl::topDirectories() const 1087 { 1088 if (!m_trashDirectoriesScanned) { 1089 scanTrashDirectories(); 1090 } 1091 return m_topDirectories; 1092 } 1093 1094 QString TrashImpl::trashForMountPoint(const QString &topdir, bool createIfNeeded) const 1095 { 1096 // (1) Administrator-created $topdir/.Trash directory 1097 1098 #ifndef Q_OS_OSX 1099 const QString rootTrashDir = topdir + QLatin1String("/.Trash"); 1100 #else 1101 const QString rootTrashDir = topdir + QLatin1String("/.Trashes"); 1102 #endif 1103 const QByteArray rootTrashDir_c = QFile::encodeName(rootTrashDir); 1104 // Can't use QFileInfo here since we need to test for the sticky bit 1105 uid_t uid = getuid(); 1106 QT_STATBUF buff; 1107 const unsigned int requiredBits = S_ISVTX; // Sticky bit required 1108 if (QT_LSTAT(rootTrashDir_c.constData(), &buff) == 0) { 1109 if ((S_ISDIR(buff.st_mode)) // must be a dir 1110 && (!S_ISLNK(buff.st_mode)) // not a symlink 1111 && ((buff.st_mode & requiredBits) == requiredBits) // 1112 && (::access(rootTrashDir_c.constData(), W_OK) == 0) // must be user-writable 1113 ) { 1114 if (buff.st_dev == m_homeDevice) // bind mount, maybe 1115 return QString(); 1116 #ifndef Q_OS_OSX 1117 const QString trashDir = rootTrashDir + QLatin1Char('/') + QString::number(uid); 1118 #else 1119 QString trashDir = rootTrashDir + QLatin1Char('/') + QString::number(uid); 1120 #endif 1121 const QByteArray trashDir_c = QFile::encodeName(trashDir); 1122 if (QT_LSTAT(trashDir_c.constData(), &buff) == 0) { 1123 if ((buff.st_uid == uid) // must be owned by user 1124 && (S_ISDIR(buff.st_mode)) // must be a dir 1125 && (!S_ISLNK(buff.st_mode)) // not a symlink 1126 && (buff.st_mode & 0777) == 0700) { // rwx for user 1127 #ifdef Q_OS_OSX 1128 trashDir += QStringLiteral("/KDE.trash"); 1129 #endif 1130 return trashDir; 1131 } 1132 qCWarning(KIO_TRASH) << "Directory" << trashDir << "exists but didn't pass the security checks, can't use it"; 1133 } else if (createIfNeeded && initTrashDirectory(trashDir_c)) { 1134 return trashDir; 1135 } 1136 } else { 1137 qCWarning(KIO_TRASH) << "Root trash dir" << rootTrashDir << "exists but didn't pass the security checks, can't use it"; 1138 } 1139 } 1140 1141 #ifndef Q_OS_OSX 1142 // (2) $topdir/.Trash-$uid 1143 const QString trashDir = topdir + QLatin1String("/.Trash-") + QString::number(uid); 1144 const QByteArray trashDir_c = QFile::encodeName(trashDir); 1145 if (QT_LSTAT(trashDir_c.constData(), &buff) == 0) { 1146 if ((buff.st_uid == uid) // must be owned by user 1147 && S_ISDIR(buff.st_mode) // must be a dir 1148 && !S_ISLNK(buff.st_mode) // not a symlink 1149 && ((buff.st_mode & 0700) == 0700)) { // and we need write access to it 1150 1151 if (buff.st_dev == m_homeDevice) // bind mount, maybe 1152 return QString(); 1153 if (checkTrashSubdirs(trashDir_c)) { 1154 return trashDir; 1155 } 1156 } 1157 qCWarning(KIO_TRASH) << "Directory" << trashDir << "exists but didn't pass the security checks, can't use it"; 1158 // Exists, but not usable 1159 return QString(); 1160 } 1161 if (createIfNeeded && initTrashDirectory(trashDir_c)) { 1162 return trashDir; 1163 } 1164 #endif 1165 return QString(); 1166 } 1167 1168 int TrashImpl::idForTrashDirectory(const QString &trashDir) const 1169 { 1170 // If this is too slow we can always use a reverse map... 1171 for (auto it = m_trashDirectories.cbegin(); it != m_trashDirectories.cend(); ++it) { 1172 if (it.value() == trashDir) { 1173 return it.key(); 1174 } 1175 } 1176 return -1; 1177 } 1178 1179 bool TrashImpl::initTrashDirectory(const QByteArray &trashDir_c) const 1180 { 1181 if (mkdir(trashDir_c.constData(), 0700) != 0) { 1182 return false; 1183 } 1184 return checkTrashSubdirs(trashDir_c); 1185 } 1186 1187 bool TrashImpl::checkTrashSubdirs(const QByteArray &trashDir_c) const 1188 { 1189 const QString trashDir = QFile::decodeName(trashDir_c); 1190 const QString info = trashDir + QLatin1String("/info"); 1191 const QString files = trashDir + QLatin1String("/files"); 1192 return testDir(info) == 0 && testDir(files) == 0; 1193 } 1194 1195 QString TrashImpl::trashDirectoryPath(int trashId) const 1196 { 1197 // Never scanned for trash dirs? (This can happen after killing kio_trash 1198 // and reusing a directory listing from the earlier instance.) 1199 if (!m_trashDirectoriesScanned) { 1200 scanTrashDirectories(); 1201 } 1202 Q_ASSERT(m_trashDirectories.contains(trashId)); 1203 return m_trashDirectories[trashId]; 1204 } 1205 1206 QString TrashImpl::topDirectoryPath(int trashId) const 1207 { 1208 if (!m_trashDirectoriesScanned) { 1209 scanTrashDirectories(); 1210 } 1211 assert(trashId != 0); 1212 Q_ASSERT(m_topDirectories.contains(trashId)); 1213 return m_topDirectories[trashId]; 1214 } 1215 1216 // Helper method. Creates a URL with the format trash:/trashid-fileid or 1217 // trash:/trashid-fileid/relativePath/To/File for a file inside a trashed directory. 1218 QUrl TrashImpl::makeURL(int trashId, const QString &fileId, const QString &relativePath) 1219 { 1220 QUrl url; 1221 url.setScheme(QStringLiteral("trash")); 1222 QString path = QLatin1Char('/') + QString::number(trashId) + QLatin1Char('-') + fileId; 1223 if (!relativePath.isEmpty()) { 1224 path += QLatin1Char('/') + relativePath; 1225 } 1226 url.setPath(path); 1227 return url; 1228 } 1229 1230 // Helper method. Parses a trash URL with the URL scheme defined in makeURL. 1231 // The trash:/ URL itself isn't parsed here, must be caught by the caller before hand. 1232 bool TrashImpl::parseURL(const QUrl &url, int &trashId, QString &fileId, QString &relativePath) 1233 { 1234 if (url.scheme() != QLatin1String("trash")) { 1235 return false; 1236 } 1237 const QString path = url.path(); 1238 if (path.isEmpty()) { 1239 return false; 1240 } 1241 int start = 0; 1242 if (path[0] == QLatin1Char('/')) { // always true I hope 1243 start = 1; 1244 } 1245 int slashPos = path.indexOf(QLatin1Char('-'), 0); // don't match leading slash 1246 if (slashPos <= 0) { 1247 return false; 1248 } 1249 bool ok = false; 1250 1251 trashId = QStringView(path).mid(start, slashPos - start).toInt(&ok); 1252 1253 Q_ASSERT_X(ok, Q_FUNC_INFO, qUtf8Printable(url.toString())); 1254 if (!ok) { 1255 return false; 1256 } 1257 start = slashPos + 1; 1258 slashPos = path.indexOf(QLatin1Char('/'), start); 1259 if (slashPos <= 0) { 1260 fileId = path.mid(start); 1261 relativePath.clear(); 1262 return true; 1263 } 1264 fileId = path.mid(start, slashPos - start); 1265 relativePath = path.mid(slashPos + 1); 1266 return true; 1267 } 1268 1269 bool TrashImpl::adaptTrashSize(const QString &origPath, int trashId) 1270 { 1271 KConfig config(QStringLiteral("ktrashrc")); 1272 1273 const QString trashPath = trashDirectoryPath(trashId); 1274 KConfigGroup group = config.group(trashPath); 1275 1276 const bool useTimeLimit = group.readEntry("UseTimeLimit", false); 1277 const bool useSizeLimit = group.readEntry("UseSizeLimit", true); 1278 const double percent = group.readEntry("Percent", 10.0); 1279 const int actionType = group.readEntry("LimitReachedAction", 0); 1280 1281 if (useTimeLimit) { // delete all files in trash older than X days 1282 const int maxDays = group.readEntry("Days", 7); 1283 const QDateTime currentDate = QDateTime::currentDateTime(); 1284 1285 const TrashedFileInfoList trashedFiles = list(); 1286 for (const auto &info : trashedFiles) { 1287 if (info.trashId != trashId) { 1288 continue; 1289 } 1290 1291 if (info.deletionDate.daysTo(currentDate) > maxDays) { 1292 del(info.trashId, info.fileId); 1293 } 1294 } 1295 } 1296 1297 if (!useSizeLimit) { // check if size limit exceeded 1298 return true; 1299 } 1300 1301 // calculate size of the files to be put into the trash 1302 const qint64 additionalSize = DiscSpaceUtil::sizeOfPath(origPath); 1303 1304 #ifdef Q_OS_OSX 1305 createTrashInfrastructure(trashId); 1306 #endif 1307 DiscSpaceUtil util(trashPath + QLatin1String("/files/")); 1308 auto cache = TrashSizeCache(trashPath); 1309 auto trashSize = cache.calculateSize(); 1310 1311 if (util.usage(trashSize + additionalSize) < percent) { 1312 return true; 1313 } 1314 1315 // before we start to remove any files from the trash, 1316 // check whether the new file will fit into the trash 1317 // at all... 1318 const qint64 partitionSize = util.size(); 1319 1320 if ((util.usage(partitionSize + additionalSize)) >= percent) { 1321 m_lastErrorCode = KIO::ERR_TRASH_FILE_TOO_LARGE; 1322 m_lastErrorMessage = KIO::buildErrorString(m_lastErrorCode, {}); 1323 return false; 1324 } 1325 1326 if (actionType == 0) { // warn the user only 1327 m_lastErrorCode = KIO::ERR_WORKER_DEFINED; 1328 m_lastErrorMessage = i18n("The trash is full. Empty it or remove items manually."); 1329 return false; 1330 } 1331 1332 // Start removing some other files from the trash 1333 1334 QDir::SortFlags sortFlags; 1335 if (actionType == 1) { 1336 sortFlags = QDir::Time | QDir::Reversed; // Delete oldest files first 1337 } else if (actionType == 2) { 1338 sortFlags = QDir::Size; // Delete biggest files first 1339 } else { 1340 qWarning() << "Called with actionType" << actionType << ", which theoretically should never happen!"; 1341 return false; // Bail out 1342 } 1343 1344 const auto dirCache = cache.readDirCache(); 1345 constexpr QDir::Filters dirFilters = QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot; 1346 const QFileInfoList infoList = QDir(trashPath + QLatin1String("/files")).entryInfoList(dirFilters, sortFlags); 1347 for (const auto &info : infoList) { 1348 auto fileSizeFreed = info.size(); 1349 if (info.isDir()) { 1350 fileSizeFreed = dirCache.constFind(info.path().toUtf8())->size; 1351 } 1352 1353 del(trashId, info.fileName()); // delete trashed file 1354 trashSize -= fileSizeFreed; 1355 1356 if (util.usage(trashSize + additionalSize) < percent) { // check whether we have enough space now 1357 return true; 1358 } 1359 } 1360 1361 return true; 1362 } 1363 1364 #include "moc_trashimpl.cpp"