File indexing completed on 2024-04-28 03:55:18

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0005     SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "worker_p.h"
0011 
0012 #include <qplatformdefs.h>
0013 #include <stdio.h>
0014 
0015 #include <QCoreApplication>
0016 #include <QDataStream>
0017 #include <QDir>
0018 #include <QFile>
0019 #include <QLibraryInfo>
0020 #include <QPluginLoader>
0021 #include <QProcess>
0022 #include <QStandardPaths>
0023 #include <QTimer>
0024 
0025 #include <KLibexec>
0026 #include <KLocalizedString>
0027 
0028 #include "commands_p.h"
0029 #include "connection_p.h"
0030 #include "connectionserver.h"
0031 #include "dataprotocol_p.h"
0032 #include "kioglobal_p.h"
0033 #include <config-kiocore.h> // KDE_INSTALL_FULL_LIBEXECDIR_KF
0034 #include <kprotocolinfo.h>
0035 
0036 #include "kiocoredebug.h"
0037 #include "workerbase.h"
0038 #include "workerfactory.h"
0039 #include "workerthread_p.h"
0040 
0041 using namespace KIO;
0042 
0043 static constexpr int s_workerConnectionTimeoutMin = 2;
0044 
0045 // Without debug info we consider it an error if the worker doesn't connect
0046 // within 10 seconds.
0047 // With debug info we give the worker an hour so that developers have a chance
0048 // to debug their worker.
0049 #ifdef NDEBUG
0050 static constexpr int s_workerConnectionTimeoutMax = 10;
0051 #else
0052 static constexpr int s_workerConnectionTimeoutMax = 3600;
0053 #endif
0054 
0055 void Worker::accept()
0056 {
0057     m_workerConnServer->setNextPendingConnection(m_connection);
0058     m_workerConnServer->deleteLater();
0059     m_workerConnServer = nullptr;
0060 
0061     connect(m_connection, &Connection::readyRead, this, &Worker::gotInput);
0062 }
0063 
0064 void Worker::timeout()
0065 {
0066     if (m_dead) { // already dead? then workerDied was emitted and we are done
0067         return;
0068     }
0069     if (m_connection->isConnected()) {
0070         return;
0071     }
0072 
0073     /*qDebug() << "worker failed to connect to application pid=" << m_pid
0074                  << " protocol=" << m_protocol;*/
0075     if (m_pid && KIOPrivate::isProcessAlive(m_pid)) {
0076         int delta_t = m_contact_started.elapsed() / 1000;
0077         // qDebug() << "worker is slow... pid=" << m_pid << " t=" << delta_t;
0078         if (delta_t < s_workerConnectionTimeoutMax) {
0079             QTimer::singleShot(1000 * s_workerConnectionTimeoutMin, this, &Worker::timeout);
0080             return;
0081         }
0082     }
0083     // qDebug() << "Houston, we lost our worker, pid=" << m_pid;
0084     m_connection->close();
0085     m_dead = true;
0086     QString arg = m_protocol;
0087     if (!m_host.isEmpty()) {
0088         arg += QLatin1String("://") + m_host;
0089     }
0090     // qDebug() << "worker failed to connect pid =" << m_pid << arg;
0091 
0092     ref();
0093     // Tell the job about the problem.
0094     Q_EMIT error(ERR_WORKER_DIED, arg);
0095     // Tell the scheduler about the problem.
0096     Q_EMIT workerDied(this);
0097     // After the above signal we're dead!!
0098     deref();
0099 }
0100 
0101 Worker::Worker(const QString &protocol, QObject *parent)
0102     : WorkerInterface(parent)
0103     , m_protocol(protocol)
0104     , m_workerProtocol(protocol)
0105     , m_workerConnServer(new KIO::ConnectionServer)
0106 {
0107     m_contact_started.start();
0108     m_workerConnServer->setParent(this);
0109     m_workerConnServer->listenForRemote();
0110     if (!m_workerConnServer->isListening()) {
0111         qCWarning(KIO_CORE) << "KIO Connection server not listening, could not connect";
0112     }
0113     m_connection = new Connection(this);
0114     connect(m_workerConnServer, &ConnectionServer::newConnection, this, &Worker::accept);
0115 }
0116 
0117 Worker::~Worker()
0118 {
0119     // qDebug() << "destructing worker object pid =" << m_pid;
0120     delete m_workerConnServer;
0121 }
0122 
0123 QString Worker::protocol() const
0124 {
0125     return m_protocol;
0126 }
0127 
0128 void Worker::setProtocol(const QString &protocol)
0129 {
0130     m_protocol = protocol;
0131 }
0132 
0133 QString Worker::workerProtocol() const
0134 {
0135     return m_workerProtocol;
0136 }
0137 
0138 QString Worker::host() const
0139 {
0140     return m_host;
0141 }
0142 
0143 quint16 Worker::port() const
0144 {
0145     return m_port;
0146 }
0147 
0148 QString Worker::user() const
0149 {
0150     return m_user;
0151 }
0152 
0153 QString Worker::passwd() const
0154 {
0155     return m_passwd;
0156 }
0157 
0158 void Worker::setIdle()
0159 {
0160     m_idleSince.start();
0161 }
0162 
0163 void Worker::ref()
0164 {
0165     m_refCount++;
0166 }
0167 
0168 void Worker::deref()
0169 {
0170     m_refCount--;
0171     if (!m_refCount) {
0172         aboutToDelete();
0173         if (m_workerThread) {
0174             // When on a thread, delete in a thread to prevent deadlocks between the main thread and the worker thread.
0175             // This most notably can happen when the worker thread uses QDBus, because traffic will generally be routed
0176             // through the main loop.
0177             // Generally speaking we'd want to avoid waiting in the main thread anyway, the worker stopping isn't really
0178             // useful for anything but delaying deletion.
0179             // https://bugs.kde.org/show_bug.cgi?id=468673
0180             WorkerThread *workerThread = nullptr;
0181             std::swap(workerThread, m_workerThread);
0182             workerThread->setParent(nullptr);
0183             connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
0184             workerThread->quit();
0185         }
0186         delete this; // yes it reads funny, but it's too late for a deleteLater() here, no event loop anymore
0187     }
0188 }
0189 
0190 void Worker::aboutToDelete()
0191 {
0192     m_connection->disconnect(this);
0193     this->disconnect();
0194 }
0195 
0196 void Worker::setWorkerThread(WorkerThread *thread)
0197 {
0198     m_workerThread = thread;
0199 }
0200 
0201 int Worker::idleTime() const
0202 {
0203     if (!m_idleSince.isValid()) {
0204         return 0;
0205     }
0206     return m_idleSince.elapsed() / 1000;
0207 }
0208 
0209 void Worker::setPID(qint64 pid)
0210 {
0211     m_pid = pid;
0212 }
0213 
0214 qint64 Worker::worker_pid() const
0215 {
0216     return m_pid;
0217 }
0218 
0219 void Worker::setJob(KIO::SimpleJob *job)
0220 {
0221     if (!m_sslMetaData.isEmpty()) {
0222         Q_EMIT metaData(m_sslMetaData);
0223     }
0224     m_job = job;
0225 }
0226 
0227 KIO::SimpleJob *Worker::job() const
0228 {
0229     return m_job;
0230 }
0231 
0232 bool Worker::isAlive() const
0233 {
0234     return !m_dead;
0235 }
0236 
0237 void Worker::suspend()
0238 {
0239     m_connection->suspend();
0240 }
0241 
0242 void Worker::resume()
0243 {
0244     m_connection->resume();
0245 }
0246 
0247 bool Worker::suspended()
0248 {
0249     return m_connection->suspended();
0250 }
0251 
0252 void Worker::send(int cmd, const QByteArray &arr)
0253 {
0254     m_connection->send(cmd, arr);
0255 }
0256 
0257 void Worker::gotInput()
0258 {
0259     if (m_dead) { // already dead? then workerDied was emitted and we are done
0260         return;
0261     }
0262     ref();
0263     if (!dispatch()) {
0264         m_connection->close();
0265         m_dead = true;
0266         QString arg = m_protocol;
0267         if (!m_host.isEmpty()) {
0268             arg += QLatin1String("://") + m_host;
0269         }
0270         // qDebug() << "worker died pid =" << m_pid << arg;
0271         // Tell the job about the problem.
0272         Q_EMIT error(ERR_WORKER_DIED, arg);
0273         // Tell the scheduler about the problem.
0274         Q_EMIT workerDied(this);
0275     }
0276     deref();
0277     // Here we might be dead!!
0278 }
0279 
0280 void Worker::kill()
0281 {
0282     m_dead = true; // OO can be such simple.
0283     if (m_pid) {
0284         qCDebug(KIO_CORE) << "killing worker process pid" << m_pid << "(" << m_protocol + QLatin1String("://") + m_host << ")";
0285         KIOPrivate::sendTerminateSignal(m_pid);
0286         m_pid = 0;
0287     } else if (m_workerThread) {
0288         qCDebug(KIO_CORE) << "aborting worker thread for " << m_protocol + QLatin1String("://") + m_host;
0289         m_workerThread->abort();
0290     }
0291     deref();
0292 }
0293 
0294 void Worker::setHost(const QString &host, quint16 port, const QString &user, const QString &passwd)
0295 {
0296     m_host = host;
0297     m_port = port;
0298     m_user = user;
0299     m_passwd = passwd;
0300     m_sslMetaData.clear();
0301 
0302     QByteArray data;
0303     QDataStream stream(&data, QIODevice::WriteOnly);
0304     stream << m_host << m_port << m_user << m_passwd;
0305     m_connection->send(CMD_HOST, data);
0306 }
0307 
0308 void Worker::resetHost()
0309 {
0310     m_sslMetaData.clear();
0311     m_host = QStringLiteral("<reset>");
0312 }
0313 
0314 void Worker::setConfig(const MetaData &config)
0315 {
0316     QByteArray data;
0317     QDataStream stream(&data, QIODevice::WriteOnly);
0318     stream << config;
0319     m_connection->send(CMD_CONFIG, data);
0320 }
0321 
0322 // TODO KF6: return std::unique_ptr
0323 Worker *Worker::createWorker(const QString &protocol, const QUrl &url, int &error, QString &error_text)
0324 {
0325     Q_UNUSED(url)
0326     // qDebug() << "createWorker" << protocol << "for" << url;
0327     // Firstly take into account all special workers
0328     if (protocol == QLatin1String("data")) {
0329         return new DataProtocol();
0330     }
0331 
0332     const QString _name = KProtocolInfo::exec(protocol);
0333     if (_name.isEmpty()) {
0334         error_text = i18n("Unknown protocol '%1'.", protocol);
0335         error = KIO::ERR_CANNOT_CREATE_WORKER;
0336         return nullptr;
0337     }
0338 
0339     // find the KIO worker using QPluginLoader; kioworker would do this
0340     // anyway, but if it doesn't exist, we want to be able to return
0341     // a useful error message immediately
0342     QPluginLoader loader(_name);
0343     const QString lib_path = loader.fileName();
0344     if (lib_path.isEmpty()) {
0345         error_text = i18n("Can not find a KIO worker for protocol '%1'.", protocol);
0346         error = KIO::ERR_CANNOT_CREATE_WORKER;
0347         return nullptr;
0348     }
0349 
0350     auto *worker = new Worker(protocol);
0351     const QUrl workerAddress = worker->m_workerConnServer->address();
0352     if (workerAddress.isEmpty()) {
0353         error_text = i18n("Can not create a socket for launching a KIO worker for protocol '%1'.", protocol);
0354         error = KIO::ERR_CANNOT_CREATE_WORKER;
0355         delete worker;
0356         return nullptr;
0357     }
0358 
0359     // Threads are enabled by default, set KIO_ENABLE_WORKER_THREADS=0 to disable them
0360     const auto useThreads = []() {
0361         return qgetenv("KIO_ENABLE_WORKER_THREADS") != "0";
0362     };
0363     static bool bUseThreads = useThreads();
0364 
0365     // Threads have performance benefits, but degrade robustness
0366     // (a worker crashing kills the app). So let's only enable the feature for kio_file, for now.
0367     if (protocol == QLatin1String("admin") || (bUseThreads && protocol == QLatin1String("file"))) {
0368         auto *factory = qobject_cast<WorkerFactory *>(loader.instance());
0369         if (factory) {
0370             auto *thread = new WorkerThread(worker, factory, workerAddress.toString().toLocal8Bit());
0371             thread->start();
0372             worker->setWorkerThread(thread);
0373             return worker;
0374         } else {
0375             qCWarning(KIO_CORE) << lib_path << "doesn't implement WorkerFactory?";
0376         }
0377     }
0378 
0379     const QStringList args = QStringList{lib_path, protocol, QString(), workerAddress.toString()};
0380     // qDebug() << "kioworker" << ", " << lib_path << ", " << protocol << ", " << QString() << ", " << workerAddress;
0381 
0382     // search paths
0383     QStringList searchPaths = KLibexec::kdeFrameworksPaths(QStringLiteral("libexec/kf6"));
0384     searchPaths.append(QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF)); // look at our installation location
0385     QString kioworkerExecutable = QStandardPaths::findExecutable(QStringLiteral("kioworker"), searchPaths);
0386     if (kioworkerExecutable.isEmpty()) {
0387         // Fallback to PATH. On win32 we install to bin/ which tests outside
0388         // KIO cannot not find at the time ctest is run because it
0389         // isn't the same as applicationDirPath().
0390         kioworkerExecutable = QStandardPaths::findExecutable(QStringLiteral("kioworker"));
0391     }
0392     if (kioworkerExecutable.isEmpty()) {
0393         error_text = i18n("Can not find 'kioworker' executable at '%1'", searchPaths.join(QLatin1String(", ")));
0394         error = KIO::ERR_CANNOT_CREATE_WORKER;
0395         delete worker;
0396         return nullptr;
0397     }
0398 
0399     qint64 pid = 0;
0400     QProcess process;
0401     process.setProgram(kioworkerExecutable);
0402     process.setArguments(args);
0403 #ifdef Q_OS_UNIX
0404     process.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
0405 #endif
0406     process.startDetached(&pid);
0407     worker->setPID(pid);
0408 
0409     return worker;
0410 }
0411 
0412 #include "moc_worker_p.cpp"