File indexing completed on 2025-02-09 04:24:06
0001 /* 0002 SPDX-FileCopyrightText: 2000-2002 Stephan Kulow <coolo@kde.org> 0003 SPDX-FileCopyrightText: 2000-2002 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 2000-2002 Waldo Bastian <bastian@kde.org> 0005 SPDX-FileCopyrightText: 2006 Allan Sandfeld Jensen <sandfeld@kde.org> 0006 SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org> 0007 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0008 0009 SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "file.h" 0013 0014 #include <QDirIterator> 0015 0016 #include <QStorageInfo> 0017 0018 #include "../../utils_p.h" 0019 #include "kioglobal_p.h" 0020 #include "statjob.h" 0021 0022 #include <assert.h> 0023 #include <cerrno> 0024 #ifdef Q_OS_WIN 0025 #include <qt_windows.h> 0026 #include <sys/utime.h> 0027 #include <winsock2.h> //struct timeval 0028 #else 0029 #include <utime.h> 0030 #endif 0031 0032 #include <QCoreApplication> 0033 #include <QDate> 0034 #include <QTemporaryFile> 0035 #include <QVarLengthArray> 0036 #ifdef Q_OS_WIN 0037 #include <QDir> 0038 #include <QFileInfo> 0039 #endif 0040 0041 #include <KConfigGroup> 0042 #include <KLocalizedString> 0043 #include <KShell> 0044 #include <QDataStream> 0045 #include <QDebug> 0046 #include <QMimeDatabase> 0047 #include <QStandardPaths> 0048 #include <kmountpoint.h> 0049 0050 #include <ioworker_defaults.h> 0051 #include <kdirnotify.h> 0052 #include <workerfactory.h> 0053 0054 Q_LOGGING_CATEGORY(KIO_FILE, "kf.kio.workers.file") 0055 0056 class KIOPluginFactory : public KIO::WorkerFactory 0057 { 0058 Q_OBJECT 0059 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.file" FILE "file.json") 0060 0061 public: 0062 std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override 0063 { 0064 return std::make_unique<FileProtocol>(pool, app); 0065 } 0066 }; 0067 0068 using namespace KIO; 0069 0070 static constexpr int s_maxIPCSize = 1024 * 32; 0071 0072 static QString readLogFile(const QByteArray &_filename); 0073 0074 extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) 0075 { 0076 QCoreApplication app(argc, argv); // needed for QSocketNotifier 0077 app.setApplicationName(QStringLiteral("kio_file")); 0078 0079 if (argc != 4) { 0080 fprintf(stderr, "Usage: kio_file protocol domain-socket1 domain-socket2\n"); 0081 exit(-1); 0082 } 0083 0084 FileProtocol worker(argv[2], argv[3]); 0085 0086 // Make sure the first qDebug is after the worker ctor (which sets a SIGPIPE handler) 0087 // This is useful in case kdeinit was autostarted by another app, which then exited and closed fd2 0088 // (e.g. ctest does that, or closing the terminal window would do that) 0089 // qDebug() << "Starting" << getpid(); 0090 0091 worker.dispatchLoop(); 0092 0093 // qDebug() << "Done"; 0094 return 0; 0095 } 0096 0097 static QFile::Permissions modeToQFilePermissions(int mode) 0098 { 0099 QFile::Permissions perms; 0100 if (mode & S_IRUSR) { 0101 perms |= QFile::ReadOwner; 0102 } 0103 if (mode & S_IWUSR) { 0104 perms |= QFile::WriteOwner; 0105 } 0106 if (mode & S_IXUSR) { 0107 perms |= QFile::ExeOwner; 0108 } 0109 if (mode & S_IRGRP) { 0110 perms |= QFile::ReadGroup; 0111 } 0112 if (mode & S_IWGRP) { 0113 perms |= QFile::WriteGroup; 0114 } 0115 if (mode & S_IXGRP) { 0116 perms |= QFile::ExeGroup; 0117 } 0118 if (mode & S_IROTH) { 0119 perms |= QFile::ReadOther; 0120 } 0121 if (mode & S_IWOTH) { 0122 perms |= QFile::WriteOther; 0123 } 0124 if (mode & S_IXOTH) { 0125 perms |= QFile::ExeOther; 0126 } 0127 0128 return perms; 0129 } 0130 0131 FileProtocol::FileProtocol(const QByteArray &pool, const QByteArray &app) 0132 : KIO::WorkerBase(QByteArrayLiteral("file"), pool, app) 0133 , mFile(nullptr) 0134 { 0135 testMode = !qEnvironmentVariableIsEmpty("KIOWORKER_FILE_ENABLE_TESTMODE"); 0136 } 0137 0138 FileProtocol::~FileProtocol() 0139 { 0140 } 0141 0142 WorkerResult FileProtocol::chmod(const QUrl &url, int permissions) 0143 { 0144 const QString path(url.toLocalFile()); 0145 const QByteArray _path(QFile::encodeName(path)); 0146 /* FIXME: Should be atomic */ 0147 #ifdef Q_OS_UNIX 0148 // QFile::Permissions does not support special attributes like sticky 0149 if (::chmod(_path.constData(), permissions) == -1 || 0150 #else 0151 if (!QFile::setPermissions(path, modeToQFilePermissions(permissions)) || 0152 #endif 0153 (setACL(_path.data(), permissions, false) == -1) || 0154 /* if not a directory, cannot set default ACLs */ 0155 (setACL(_path.data(), permissions, true) == -1 && errno != ENOTDIR)) { 0156 auto result = execWithElevatedPrivilege(CHMOD, {_path, permissions}, errno); 0157 if (!result.success()) { 0158 if (!resultWasCancelled(result)) { 0159 switch (result.error()) { 0160 case EPERM: 0161 case EACCES: 0162 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, path); 0163 break; 0164 #if defined(ENOTSUP) 0165 case ENOTSUP: // from setACL since chmod can't return ENOTSUP 0166 return WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, i18n("Setting ACL for %1", path)); 0167 break; 0168 #endif 0169 case ENOSPC: 0170 return WorkerResult::fail(KIO::ERR_DISK_FULL, path); 0171 break; 0172 default: 0173 return WorkerResult::fail(KIO::ERR_CANNOT_CHMOD, path); 0174 } 0175 } 0176 } 0177 } 0178 0179 return WorkerResult::pass(); 0180 } 0181 0182 WorkerResult FileProtocol::setModificationTime(const QUrl &url, const QDateTime &mtime) 0183 { 0184 const QString path(url.toLocalFile()); 0185 QT_STATBUF statbuf; 0186 if (QT_LSTAT(QFile::encodeName(path).constData(), &statbuf) == 0) { 0187 struct utimbuf utbuf; 0188 utbuf.actime = statbuf.st_atime; // access time, unchanged 0189 utbuf.modtime = mtime.toSecsSinceEpoch(); // modification time 0190 if (::utime(QFile::encodeName(path).constData(), &utbuf) != 0) { 0191 auto result = execWithElevatedPrivilege(UTIME, {path, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno); 0192 if (!result.success()) { 0193 if (!resultWasCancelled(result)) { 0194 // TODO: errno could be EACCES, EPERM, EROFS 0195 return WorkerResult::fail(KIO::ERR_CANNOT_SETTIME, path); 0196 } 0197 } 0198 } 0199 return WorkerResult::pass(); 0200 } else { 0201 return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, path); 0202 } 0203 } 0204 0205 WorkerResult FileProtocol::mkdir(const QUrl &url, int permissions) 0206 { 0207 const QString path(url.toLocalFile()); 0208 0209 // qDebug() << path << "permission=" << permissions; 0210 0211 // Remove existing file or symlink, if requested (#151851) 0212 if (metaData(QStringLiteral("overwrite")) == QLatin1String("true")) { 0213 if (!QFile::remove(path)) { 0214 execWithElevatedPrivilege(DEL, {path}, errno); 0215 } 0216 } 0217 0218 QT_STATBUF buff; 0219 if (QT_LSTAT(QFile::encodeName(path).constData(), &buff) == -1) { 0220 bool dirCreated = QDir().mkdir(path); 0221 if (!dirCreated) { 0222 auto result = execWithElevatedPrivilege(MKDIR, {path}, errno); 0223 if (!result.success()) { 0224 if (!resultWasCancelled(result)) { 0225 // TODO: add access denied & disk full (or another reasons) handling (into Qt, possibly) 0226 return WorkerResult::fail(KIO::ERR_CANNOT_MKDIR, path); 0227 } 0228 return WorkerResult::pass(); 0229 } 0230 dirCreated = true; 0231 } 0232 0233 if (dirCreated) { 0234 if (permissions != -1) { 0235 return chmod(url, permissions); 0236 } 0237 return WorkerResult::pass(); 0238 } 0239 } 0240 0241 if (Utils::isDirMask(buff.st_mode)) { 0242 // qDebug() << "ERR_DIR_ALREADY_EXIST"; 0243 return WorkerResult::fail(KIO::ERR_DIR_ALREADY_EXIST, path); 0244 } 0245 return WorkerResult::fail(KIO::ERR_FILE_ALREADY_EXIST, path); 0246 } 0247 0248 WorkerResult FileProtocol::redirect(const QUrl &url) 0249 { 0250 QUrl redir(url); 0251 redir.setScheme(configValue(QStringLiteral("DefaultRemoteProtocol"), QStringLiteral("smb"))); 0252 0253 // if we would redirect into the Windows world, let's also check for the 0254 // DavWWWRoot "token" which in the Windows world tells win explorer to access 0255 // a webdav url 0256 // https://www.webdavsystem.com/server/access/windows 0257 const QLatin1String davRoot("/DavWWWRoot/"); 0258 if ((redir.scheme() == QLatin1String("smb")) && redir.path().startsWith(davRoot)) { 0259 redir.setPath(redir.path().mid(davRoot.size() - 1)); // remove /DavWWWRoot 0260 redir.setScheme(QStringLiteral("webdav")); 0261 } 0262 0263 redirection(redir); 0264 return WorkerResult::pass(); 0265 } 0266 0267 WorkerResult FileProtocol::get(const QUrl &url) 0268 { 0269 if (!url.isLocalFile()) { 0270 return redirect(url); 0271 } 0272 0273 const QString path(url.toLocalFile()); 0274 QT_STATBUF buff; 0275 if (QT_STAT(QFile::encodeName(path).constData(), &buff) == -1) { 0276 if (errno == EACCES) { 0277 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, path); 0278 } else { 0279 return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, path); 0280 } 0281 } 0282 0283 if (Utils::isDirMask(buff.st_mode)) { 0284 return WorkerResult::fail(KIO::ERR_IS_DIRECTORY, path); 0285 } 0286 if (!Utils::isRegFileMask(buff.st_mode)) { 0287 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, path); 0288 } 0289 0290 QFile f(path); 0291 if (!f.open(QIODevice::ReadOnly)) { 0292 auto result = tryOpen(f, QFile::encodeName(path), O_RDONLY, S_IRUSR, errno); 0293 if (!result.success()) { 0294 if (!resultWasCancelled(result)) { 0295 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, path); 0296 } 0297 return WorkerResult::pass(); 0298 } 0299 } 0300 0301 #if HAVE_FADVISE 0302 // TODO check return code 0303 posix_fadvise(f.handle(), 0, 0, POSIX_FADV_SEQUENTIAL); 0304 #endif 0305 0306 // Determine the MIME type of the file to be retrieved, and emit it. 0307 // This is mandatory in all workers (for KRun/BrowserRun to work) 0308 // In real "remote" workers, this is usually done using mimeTypeForFileNameAndData 0309 // after receiving some data. But we don't know how much data the mimemagic rules 0310 // need, so for local files, better use mimeTypeForFile. 0311 QMimeDatabase db; 0312 QMimeType mt = db.mimeTypeForFile(url.toLocalFile()); 0313 mimeType(mt.name()); 0314 // Emit total size AFTER the MIME type 0315 totalSize(buff.st_size); 0316 0317 KIO::filesize_t processed_size = 0; 0318 0319 QString resumeOffset = metaData(QStringLiteral("range-start")); 0320 if (resumeOffset.isEmpty()) { 0321 resumeOffset = metaData(QStringLiteral("resume")); // old name 0322 } 0323 if (!resumeOffset.isEmpty()) { 0324 bool ok; 0325 KIO::fileoffset_t offset = resumeOffset.toLongLong(&ok); 0326 if (ok && (offset > 0) && (offset < buff.st_size)) { 0327 if (f.seek(offset)) { 0328 canResume(); 0329 processed_size = offset; 0330 // qDebug() << "Resume offset:" << KIO::number(offset); 0331 } 0332 } 0333 } 0334 0335 char buffer[s_maxIPCSize]; 0336 QByteArray array; 0337 0338 while (1) { 0339 if (wasKilled()) { 0340 return WorkerResult::pass(); 0341 } 0342 int n = f.read(buffer, s_maxIPCSize); 0343 if (n == -1) { 0344 if (errno == EINTR) { 0345 continue; 0346 } 0347 f.close(); 0348 return WorkerResult::fail(ERR_CANNOT_READ, path); 0349 } 0350 if (n == 0) { 0351 break; // Finished 0352 } 0353 0354 array = QByteArray::fromRawData(buffer, n); 0355 data(array); 0356 array.clear(); 0357 0358 processed_size += n; 0359 processedSize(processed_size); 0360 0361 // qDebug() << "Processed: " << KIO::number (processed_size); 0362 } 0363 0364 data(QByteArray()); 0365 0366 f.close(); 0367 0368 processedSize(buff.st_size); 0369 return WorkerResult::pass(); 0370 } 0371 0372 KIO::StatDetails FileProtocol::getStatDetails() 0373 { 0374 const QString statDetails = metaData(QStringLiteral("details")); 0375 return statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt()); 0376 } 0377 0378 WorkerResult FileProtocol::open(const QUrl &url, QIODevice::OpenMode mode) 0379 { 0380 // qDebug() << url; 0381 0382 QString openPath = url.toLocalFile(); 0383 QT_STATBUF buff; 0384 if (QT_STAT(QFile::encodeName(openPath).constData(), &buff) == -1) { 0385 if (errno == EACCES) { 0386 return WorkerResult::fail(KIO::ERR_ACCESS_DENIED, openPath); 0387 } else { 0388 return WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, openPath); 0389 } 0390 } 0391 0392 if (Utils::isDirMask(buff.st_mode)) { 0393 return WorkerResult::fail(KIO::ERR_IS_DIRECTORY, openPath); 0394 } 0395 if (!Utils::isRegFileMask(buff.st_mode)) { 0396 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, openPath); 0397 } 0398 0399 mFile = new QFile(openPath); 0400 if (!mFile->open(mode)) { 0401 if (mode & QIODevice::ReadOnly) { 0402 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, openPath); 0403 } else { 0404 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_WRITING, openPath); 0405 } 0406 } 0407 // Determine the MIME type of the file to be retrieved, and emit it. 0408 // This is mandatory in all workers (for KRun/BrowserRun to work). 0409 // If we're not opening the file ReadOnly or ReadWrite, don't attempt to 0410 // read the file and send the MIME type. 0411 if (mode & QIODevice::ReadOnly) { 0412 QMimeDatabase db; 0413 QMimeType mt = db.mimeTypeForFile(url.toLocalFile()); 0414 mimeType(mt.name()); 0415 } 0416 0417 totalSize(buff.st_size); 0418 position(0); 0419 0420 return WorkerResult::pass(); 0421 } 0422 0423 WorkerResult FileProtocol::read(KIO::filesize_t bytes) 0424 { 0425 // qDebug() << "File::open -- read"; 0426 Q_ASSERT(mFile && mFile->isOpen()); 0427 0428 QVarLengthArray<char> buffer(bytes); 0429 0430 qint64 bytesRead = mFile->read(buffer.data(), bytes); 0431 0432 if (bytesRead == -1) { 0433 const auto fileName = mFile->fileName(); 0434 qCWarning(KIO_FILE) << "Couldn't read. Error:" << mFile->errorString(); 0435 closeWithoutFinish(); 0436 return WorkerResult::fail(KIO::ERR_CANNOT_READ, fileName); 0437 } else { 0438 const QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); 0439 data(fileData); 0440 return WorkerResult::pass(); 0441 } 0442 } 0443 0444 WorkerResult FileProtocol::write(const QByteArray &data) 0445 { 0446 // qDebug() << "File::open -- write"; 0447 Q_ASSERT(mFile && mFile->isWritable()); 0448 0449 qint64 bytesWritten = mFile->write(data); 0450 0451 if (bytesWritten == -1) { 0452 if (mFile->error() == QFileDevice::ResourceError) { // disk full 0453 const auto fileName = mFile->fileName(); 0454 closeWithoutFinish(); 0455 return WorkerResult::fail(KIO::ERR_DISK_FULL, fileName); 0456 } else { 0457 const auto fileName = mFile->fileName(); 0458 qCWarning(KIO_FILE) << "Couldn't write. Error:" << mFile->errorString(); 0459 closeWithoutFinish(); 0460 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, fileName); 0461 } 0462 } else { 0463 mFile->flush(); 0464 written(bytesWritten); 0465 0466 return WorkerResult::pass(); 0467 } 0468 } 0469 0470 KIO::WorkerResult FileProtocol::seek(KIO::filesize_t offset) 0471 { 0472 // qDebug() << "File::open -- seek"; 0473 Q_ASSERT(mFile && mFile->isOpen()); 0474 0475 if (mFile->seek(offset)) { 0476 position(offset); 0477 return WorkerResult::pass(); 0478 } else { 0479 const auto fileName = mFile->fileName(); 0480 closeWithoutFinish(); 0481 return WorkerResult::fail(KIO::ERR_CANNOT_SEEK, fileName); 0482 } 0483 } 0484 0485 KIO::WorkerResult FileProtocol::truncate(KIO::filesize_t length) 0486 { 0487 Q_ASSERT(mFile && mFile->isOpen()); 0488 0489 if (mFile->resize(length)) { 0490 truncated(length); 0491 return WorkerResult::pass(); 0492 } else { 0493 const auto fileName = mFile->fileName(); 0494 closeWithoutFinish(); 0495 return WorkerResult::fail(KIO::ERR_CANNOT_TRUNCATE, fileName); 0496 } 0497 } 0498 0499 void FileProtocol::closeWithoutFinish() 0500 { 0501 Q_ASSERT(mFile); 0502 0503 delete mFile; 0504 mFile = nullptr; 0505 } 0506 0507 bool FileProtocol::resultWasCancelled(KIO::WorkerResult result) 0508 { 0509 int err = result.error(); 0510 return err == KIO::ERR_USER_CANCELED || err == KIO::ERR_PRIVILEGE_NOT_REQUIRED; 0511 } 0512 0513 KIO::WorkerResult FileProtocol::close() 0514 { 0515 // qDebug() << "File::open -- close "; 0516 closeWithoutFinish(); 0517 return WorkerResult::pass(); 0518 } 0519 0520 KIO::WorkerResult FileProtocol::put(const QUrl &url, int _mode, KIO::JobFlags _flags) 0521 { 0522 if (privilegeOperationUnitTestMode()) { 0523 return WorkerResult::pass(); 0524 } 0525 0526 const QString dest_orig = url.toLocalFile(); 0527 0528 // qDebug() << dest_orig << "mode=" << _mode; 0529 0530 QString dest_part(dest_orig + QLatin1String(".part")); 0531 0532 QT_STATBUF buff_orig; 0533 const bool bOrigExists = (QT_LSTAT(QFile::encodeName(dest_orig).constData(), &buff_orig) != -1); 0534 bool bPartExists = false; 0535 const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); 0536 0537 if (bMarkPartial) { 0538 QT_STATBUF buff_part; 0539 bPartExists = (QT_LSTAT(QFile::encodeName(dest_part).constData(), &buff_part) != -1); 0540 0541 if (bPartExists // 0542 && !(_flags & KIO::Resume) // 0543 && !(_flags & KIO::Overwrite) // 0544 && buff_part.st_size > 0 // 0545 && Utils::isRegFileMask(buff_part.st_mode) // 0546 ) { 0547 // qDebug() << "calling canResume with" << KIO::number(buff_part.st_size); 0548 0549 // Maybe we can use this partial file for resuming 0550 // Tell about the size we have, and the app will tell us 0551 // if it's ok to resume or not. 0552 _flags |= canResume(buff_part.st_size) ? KIO::Resume : KIO::DefaultFlags; 0553 0554 // qDebug() << "got answer" << (_flags & KIO::Resume); 0555 } 0556 } 0557 0558 if (bOrigExists && !(_flags & KIO::Overwrite) && !(_flags & KIO::Resume)) { 0559 if (Utils::isDirMask(buff_orig.st_mode)) { 0560 return WorkerResult::fail(KIO::ERR_DIR_ALREADY_EXIST, dest_orig); 0561 } else { 0562 return WorkerResult::fail(KIO::ERR_FILE_ALREADY_EXIST, dest_orig); 0563 } 0564 return WorkerResult::pass(); 0565 } 0566 0567 // Don't change permissions of the original file 0568 if (bOrigExists && _mode == -1) { 0569 _mode = static_cast<int>(buff_orig.st_mode); 0570 // Make sure the value fit by casting it back. mode_t is possibly larger than int 0571 Q_ASSERT(static_cast<decltype(buff_orig.st_mode)>(_mode) == buff_orig.st_mode); 0572 } 0573 #if !defined(Q_OS_WIN) 0574 uid_t owner = -1; 0575 gid_t group = -1; 0576 if (bOrigExists) { 0577 owner = buff_orig.st_uid; 0578 group = buff_orig.st_gid; 0579 } 0580 #endif 0581 0582 int result; 0583 int error = 0; 0584 QString dest; 0585 QFile f; 0586 0587 // Loop until we got 0 (end of data) 0588 do { 0589 QByteArray buffer; 0590 dataReq(); // Request for data 0591 result = readData(buffer); 0592 0593 if (result >= 0) { 0594 if (dest.isEmpty()) { 0595 if (bMarkPartial) { 0596 // qDebug() << "Appending .part extension to" << dest_orig; 0597 dest = dest_part; 0598 if (bPartExists && !(_flags & KIO::Resume)) { 0599 // qDebug() << "Deleting partial file" << dest_part; 0600 QFile::remove(dest_part); 0601 // Catch errors when we try to open the file. 0602 } 0603 } else { 0604 dest = dest_orig; 0605 if (bOrigExists && !(_flags & KIO::Resume)) { 0606 // qDebug() << "Deleting destination file" << dest_orig; 0607 QFile::remove(dest_orig); 0608 // Catch errors when we try to open the file. 0609 } 0610 } 0611 0612 f.setFileName(dest); 0613 0614 if ((_flags & KIO::Resume)) { 0615 f.open(QIODevice::ReadWrite | QIODevice::Append); 0616 } else { 0617 f.open(QIODevice::Truncate | QIODevice::WriteOnly); 0618 if (_mode != -1) { 0619 // WABA: Make sure that we keep writing permissions ourselves, 0620 // otherwise we can be in for a surprise on NFS. 0621 mode_t initialMode = _mode | S_IWUSR | S_IRUSR; 0622 f.setPermissions(modeToQFilePermissions(initialMode)); 0623 } 0624 } 0625 0626 if (!f.isOpen()) { 0627 int oflags = 0; 0628 int filemode = _mode; 0629 0630 if ((_flags & KIO::Resume)) { 0631 oflags = O_RDWR | O_APPEND; 0632 } else { 0633 oflags = O_WRONLY | O_TRUNC | O_CREAT; 0634 if (_mode != -1) { 0635 filemode = _mode | S_IWUSR | S_IRUSR; 0636 } 0637 } 0638 0639 auto result = tryOpen(f, QFile::encodeName(dest), oflags, filemode, errno); 0640 if (!result.success()) { 0641 if (!resultWasCancelled(result)) { 0642 // qDebug() << "####################### COULD NOT WRITE" << dest << "_mode=" << _mode; 0643 // qDebug() << "QFile error==" << f.error() << "(" << f.errorString() << ")"; 0644 0645 if (f.error() == QFileDevice::PermissionsError) { 0646 return WorkerResult::fail(KIO::ERR_WRITE_ACCESS_DENIED, dest); 0647 } else { 0648 return WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest); 0649 } 0650 } 0651 return WorkerResult::pass(); 0652 } else { 0653 #ifndef Q_OS_WIN 0654 if ((_flags & KIO::Resume)) { 0655 execWithElevatedPrivilege(CHOWN, {dest, getuid(), getgid()}, errno); 0656 QFile::setPermissions(dest, modeToQFilePermissions(filemode)); 0657 } 0658 #endif 0659 } 0660 } 0661 } 0662 0663 if (f.write(buffer) == -1) { 0664 if (f.error() == QFile::ResourceError) { // disk full 0665 error = KIO::ERR_DISK_FULL; 0666 result = -2; // means: remove dest file 0667 } else { 0668 qCWarning(KIO_FILE) << "Couldn't write. Error:" << f.errorString(); 0669 error = KIO::ERR_CANNOT_WRITE; 0670 } 0671 } 0672 } else { 0673 qCWarning(KIO_FILE) << "readData() returned" << result; 0674 error = KIO::ERR_CANNOT_WRITE; 0675 } 0676 } while (result > 0); 0677 0678 // An error occurred deal with it. 0679 if (result < 0) { 0680 // qDebug() << "Error during 'put'. Aborting."; 0681 0682 if (f.isOpen()) { 0683 f.close(); 0684 0685 QT_STATBUF buff; 0686 if (QT_STAT(QFile::encodeName(dest).constData(), &buff) == 0) { 0687 int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); 0688 if (buff.st_size < size) { 0689 QFile::remove(dest); 0690 } 0691 } 0692 } 0693 return WorkerResult::fail(error, dest_orig); 0694 } 0695 0696 if (!f.isOpen()) { // we got nothing to write out, so we never opened the file 0697 return WorkerResult::pass(); 0698 } 0699 0700 f.close(); 0701 0702 if (f.error() != QFile::NoError) { 0703 qCWarning(KIO_FILE) << "Error when closing file descriptor:" << f.errorString(); 0704 return WorkerResult::fail(KIO::ERR_CANNOT_WRITE, dest_orig); 0705 } 0706 0707 // after full download rename the file back to original name 0708 if (bMarkPartial) { 0709 // QFile::rename() never overwrites the destination file unlike ::remove, 0710 // so we must remove it manually first 0711 if (_flags & KIO::Overwrite) { 0712 if (!QFile::remove(dest_orig)) { 0713 execWithElevatedPrivilege(DEL, {dest_orig}, errno); 0714 } 0715 } 0716 0717 if (!QFile::rename(dest, dest_orig)) { 0718 auto result = execWithElevatedPrivilege(RENAME, {dest, dest_orig}, errno); 0719 if (!result.success()) { 0720 if (!resultWasCancelled(result)) { 0721 qCWarning(KIO_FILE) << " Couldn't rename " << dest << " to " << dest_orig; 0722 return WorkerResult::fail(KIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig); 0723 } 0724 return WorkerResult::pass(); 0725 } 0726 } 0727 org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(dest), QUrl::fromLocalFile(dest_orig)); 0728 } 0729 0730 // set final permissions 0731 if (_mode != -1 && !(_flags & KIO::Resume)) { 0732 if (!QFile::setPermissions(dest_orig, modeToQFilePermissions(_mode))) { 0733 // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. 0734 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest_orig); 0735 if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) { 0736 if (!tryChangeFileAttr(CHMOD, {dest_orig, _mode}, errno).success()) { 0737 warning(i18n("Could not change permissions for\n%1", dest_orig)); 0738 } 0739 } 0740 } 0741 } 0742 0743 // set original owner and group 0744 #if !defined(Q_OS_WIN) 0745 if (bOrigExists) { 0746 if (::chown(qUtf8Printable(dest_orig), owner, group) < 0) { 0747 warning(i18nc("@info", "Could not change owner and group for\n%1", dest_orig)); 0748 } 0749 } 0750 #endif 0751 0752 // set modification time 0753 const QString mtimeStr = metaData(QStringLiteral("modified")); 0754 if (!mtimeStr.isEmpty()) { 0755 QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); 0756 if (dt.isValid()) { 0757 QT_STATBUF dest_statbuf; 0758 if (QT_STAT(QFile::encodeName(dest_orig).constData(), &dest_statbuf) == 0) { 0759 #ifndef Q_OS_WIN 0760 struct timeval utbuf[2]; 0761 // access time 0762 utbuf[0].tv_sec = dest_statbuf.st_atime; // access time, unchanged ## TODO preserve msec 0763 utbuf[0].tv_usec = 0; 0764 // modification time 0765 utbuf[1].tv_sec = dt.toSecsSinceEpoch(); 0766 utbuf[1].tv_usec = dt.time().msec() * 1000; 0767 utimes(QFile::encodeName(dest_orig).constData(), utbuf); 0768 #else 0769 struct utimbuf utbuf; 0770 utbuf.actime = dest_statbuf.st_atime; 0771 utbuf.modtime = dt.toSecsSinceEpoch(); 0772 if (utime(QFile::encodeName(dest_orig).constData(), &utbuf) != 0) { 0773 tryChangeFileAttr(UTIME, {dest_orig, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno); 0774 } 0775 #endif 0776 } 0777 } 0778 } 0779 0780 // We have done our job => finish 0781 return WorkerResult::pass(); 0782 } 0783 0784 WorkerResult FileProtocol::special(const QByteArray &data) 0785 { 0786 int tmp; 0787 QDataStream stream(data); 0788 0789 stream >> tmp; 0790 switch (tmp) { 0791 case 1: { 0792 QString fstype; 0793 QString dev; 0794 QString point; 0795 qint8 iRo; 0796 0797 stream >> iRo >> fstype >> dev >> point; 0798 0799 bool ro = (iRo != 0); 0800 0801 // qDebug() << "MOUNTING fstype=" << fstype << " dev=" << dev << " point=" << point << " ro=" << ro; 0802 return mount(ro, fstype.toLatin1().constData(), dev, point); 0803 } 0804 case 2: { 0805 QString point; 0806 stream >> point; 0807 return unmount(point); 0808 } 0809 default: 0810 break; 0811 } 0812 return WorkerResult::pass(); 0813 } 0814 0815 static QStringList fallbackSystemPath() 0816 { 0817 return QStringList{ 0818 QStringLiteral("/sbin"), 0819 QStringLiteral("/bin"), 0820 }; 0821 } 0822 0823 WorkerResult FileProtocol::mount(bool _ro, const char *_fstype, const QString &_dev, const QString &_point) 0824 { 0825 // qDebug() << "fstype=" << _fstype; 0826 0827 const QLatin1String label("LABEL="); 0828 const QLatin1String uuid("UUID="); 0829 QTemporaryFile tmpFile; 0830 tmpFile.setAutoRemove(false); 0831 tmpFile.open(); 0832 QByteArray tmpFileName = QFile::encodeName(tmpFile.fileName()); 0833 QByteArray dev; 0834 if (_dev.startsWith(label)) { // turn LABEL=foo into -L foo (#71430) 0835 QString labelName = _dev.mid(label.size()); 0836 dev = "-L " + QFile::encodeName(KShell::quoteArg(labelName)); // is it correct to assume same encoding as filesystem? 0837 } else if (_dev.startsWith(uuid)) { // and UUID=bar into -U bar 0838 QString uuidName = _dev.mid(uuid.size()); 0839 dev = "-U " + QFile::encodeName(KShell::quoteArg(uuidName)); 0840 } else { 0841 dev = QFile::encodeName(KShell::quoteArg(_dev)); // get those ready to be given to a shell 0842 } 0843 0844 QByteArray point = QFile::encodeName(KShell::quoteArg(_point)); 0845 bool fstype_empty = !_fstype || !*_fstype; 0846 QByteArray fstype = KShell::quoteArg(QString::fromLatin1(_fstype)).toLatin1(); // good guess 0847 QByteArray readonly = _ro ? "-r" : ""; 0848 QByteArray mountProg = QStandardPaths::findExecutable(QStringLiteral("mount")).toLocal8Bit(); 0849 if (mountProg.isEmpty()) { 0850 mountProg = QStandardPaths::findExecutable(QStringLiteral("mount"), fallbackSystemPath()).toLocal8Bit(); 0851 } 0852 if (mountProg.isEmpty()) { 0853 return WorkerResult::fail(KIO::ERR_CANNOT_MOUNT, i18n("Could not find program \"mount\"")); 0854 } 0855 0856 // Two steps, in case mount doesn't like it when we pass all options 0857 for (int step = 0; step <= 1; step++) { 0858 QByteArray buffer = mountProg + ' '; 0859 // Mount using device only if no fstype nor mountpoint (KDE-1.x like) 0860 if (!dev.isEmpty() && _point.isEmpty() && fstype_empty) { 0861 buffer += dev; 0862 } else if (!_point.isEmpty() && dev.isEmpty() && fstype_empty) { 0863 // Mount using the mountpoint, if no fstype nor device (impossible in first step) 0864 buffer += point; 0865 } else if (!_point.isEmpty() && !dev.isEmpty() && fstype_empty) { // mount giving device + mountpoint but no fstype 0866 buffer += readonly + ' ' + dev + ' ' + point; 0867 } else { // mount giving device + mountpoint + fstype 0868 buffer += readonly + " -t " + fstype + ' ' + dev + ' ' + point; 0869 } 0870 if (fstype == "ext2" || fstype == "ext3" || fstype == "ext4") { 0871 buffer += " -o errors=remount-ro"; 0872 } 0873 0874 buffer += " 2>" + tmpFileName; 0875 // qDebug() << buffer; 0876 0877 int mount_ret = system(buffer.constData()); 0878 0879 QString err = readLogFile(tmpFileName); 0880 if (err.isEmpty() && mount_ret == 0) { 0881 return WorkerResult::pass(); 0882 } else { 0883 // Didn't work - or maybe we just got a warning 0884 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(_dev); 0885 // Is the device mounted ? 0886 if (mp && mount_ret == 0) { 0887 // qDebug() << "mount got a warning:" << err; 0888 warning(err); 0889 return WorkerResult::pass(); 0890 } else { 0891 if ((step == 0) && !_point.isEmpty()) { 0892 // qDebug() << err; 0893 // qDebug() << "Mounting with those options didn't work, trying with only mountpoint"; 0894 fstype = ""; 0895 fstype_empty = true; 0896 dev = ""; 0897 // The reason for trying with only mountpoint (instead of 0898 // only device) is that some people (hi Malte!) have the 0899 // same device associated with two mountpoints 0900 // for different fstypes, like /dev/fd0 /mnt/e2floppy and 0901 // /dev/fd0 /mnt/dosfloppy. 0902 // If the user has the same mountpoint associated with two 0903 // different devices, well they shouldn't specify the 0904 // mountpoint but just the device. 0905 } else { 0906 return WorkerResult::fail(KIO::ERR_CANNOT_MOUNT, err); 0907 } 0908 } 0909 } 0910 } 0911 return WorkerResult::pass(); 0912 } 0913 0914 WorkerResult FileProtocol::unmount(const QString &_point) 0915 { 0916 QByteArray buffer; 0917 0918 QTemporaryFile tmpFile; 0919 tmpFile.setAutoRemove(false); 0920 tmpFile.open(); 0921 0922 QByteArray umountProg = QStandardPaths::findExecutable(QStringLiteral("umount")).toLocal8Bit(); 0923 if (umountProg.isEmpty()) { 0924 umountProg = QStandardPaths::findExecutable(QStringLiteral("umount"), fallbackSystemPath()).toLocal8Bit(); 0925 } 0926 if (umountProg.isEmpty()) { 0927 return WorkerResult::fail(KIO::ERR_CANNOT_UNMOUNT, i18n("Could not find program \"umount\"")); 0928 } 0929 0930 QByteArray tmpFileName = QFile::encodeName(tmpFile.fileName()); 0931 0932 buffer = umountProg + ' ' + QFile::encodeName(KShell::quoteArg(_point)) + " 2>" + tmpFileName; 0933 system(buffer.constData()); 0934 0935 QString err = readLogFile(tmpFileName); 0936 if (err.isEmpty()) { 0937 return WorkerResult::pass(); 0938 } else { 0939 return WorkerResult::fail(KIO::ERR_CANNOT_UNMOUNT, err); 0940 } 0941 } 0942 0943 /************************************* 0944 * 0945 * Utilities 0946 * 0947 *************************************/ 0948 0949 static QString readLogFile(const QByteArray &_filename) 0950 { 0951 QString result; 0952 QFile file(QFile::decodeName(_filename)); 0953 if (file.open(QIODevice::ReadOnly)) { 0954 result = QString::fromLocal8Bit(file.readAll()); 0955 } 0956 (void)file.remove(); 0957 return result; 0958 } 0959 0960 // We could port this to KTempDir::removeDir but then we wouldn't be able to tell the user 0961 // where exactly the deletion failed, in case of errors. 0962 WorkerResult FileProtocol::deleteRecursive(const QString &path) 0963 { 0964 // qDebug() << path; 0965 QDirIterator it(path, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); 0966 QStringList dirsToDelete; 0967 while (it.hasNext()) { 0968 const QString itemPath = it.next(); 0969 // qDebug() << "itemPath=" << itemPath; 0970 const QFileInfo info = it.fileInfo(); 0971 if (info.isDir() && !info.isSymLink()) { 0972 dirsToDelete.prepend(itemPath); 0973 } else { 0974 // qDebug() << "QFile::remove" << itemPath; 0975 if (!QFile::remove(itemPath)) { 0976 auto result = execWithElevatedPrivilege(DEL, {itemPath}, errno); 0977 if (!result.success()) { 0978 if (!resultWasCancelled(result)) { 0979 return WorkerResult::fail(KIO::ERR_CANNOT_DELETE, itemPath); 0980 } 0981 return result; 0982 } 0983 } 0984 } 0985 } 0986 QDir dir; 0987 for (const QString &itemPath : std::as_const(dirsToDelete)) { 0988 // qDebug() << "QDir::rmdir" << itemPath; 0989 if (!dir.rmdir(itemPath)) { 0990 auto result = execWithElevatedPrivilege(RMDIR, {itemPath}, errno); 0991 if (!result.success()) { 0992 if (!resultWasCancelled(result)) { 0993 return WorkerResult::fail(KIO::ERR_CANNOT_DELETE, itemPath); 0994 } 0995 return result; 0996 } 0997 } 0998 } 0999 return WorkerResult::pass(); 1000 } 1001 1002 WorkerResult FileProtocol::fileSystemFreeSpace(const QUrl &url) 1003 { 1004 if (url.isLocalFile()) { 1005 QStorageInfo storageInfo(url.toLocalFile()); 1006 if (storageInfo.isValid() && storageInfo.isReady()) { 1007 setMetaData(QStringLiteral("total"), QString::number(storageInfo.bytesTotal())); 1008 setMetaData(QStringLiteral("available"), QString::number(storageInfo.bytesAvailable())); 1009 1010 return WorkerResult::pass(); 1011 } else { 1012 return WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.url()); 1013 } 1014 } else { 1015 return WorkerResult::fail(KIO::ERR_UNSUPPORTED_PROTOCOL, url.url()); 1016 } 1017 } 1018 1019 // needed for JSON file embedding 1020 #include "file.moc" 1021 1022 #include "moc_file.cpp"