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