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"