File indexing completed on 2024-05-12 03:56:43

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"