File indexing completed on 2024-04-28 11:41:09

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 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "slave.h"
0010 
0011 #include <qplatformdefs.h>
0012 #include <stdio.h>
0013 
0014 #include <QCoreApplication>
0015 #include <QDataStream>
0016 #include <QDir>
0017 #include <QElapsedTimer>
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 "slavebase.h"
0038 #include "slaveinterface_p.h"
0039 #include "workerfactory.h"
0040 #include "workerthread_p.h"
0041 
0042 using namespace KIO;
0043 
0044 static constexpr int s_slaveConnectionTimeoutMin = 2;
0045 
0046 // Without debug info we consider it an error if the slave doesn't connect
0047 // within 10 seconds.
0048 // With debug info we give the slave an hour so that developers have a chance
0049 // to debug their slave.
0050 #ifdef NDEBUG
0051 static constexpr int s_slaveConnectionTimeoutMax = 10;
0052 #else
0053 static constexpr int s_slaveConnectionTimeoutMax = 3600;
0054 #endif
0055 
0056 namespace KIO
0057 {
0058 /**
0059  * @internal
0060  */
0061 class SlavePrivate : public SlaveInterfacePrivate
0062 {
0063 public:
0064     explicit SlavePrivate(const QString &protocol)
0065         : m_protocol(protocol)
0066         , m_slaveProtocol(protocol)
0067         , slaveconnserver(new KIO::ConnectionServer)
0068         , m_job(nullptr)
0069         , m_pid(0)
0070         , m_port(0)
0071 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 103)
0072         , contacted(false)
0073 #endif
0074         , dead(false)
0075         , m_refCount(1)
0076     {
0077         contact_started.start();
0078         slaveconnserver->listenForRemote();
0079         if (!slaveconnserver->isListening()) {
0080             qCWarning(KIO_CORE) << "KIO Connection server not listening, could not connect";
0081         }
0082     }
0083     ~SlavePrivate() override
0084     {
0085         delete slaveconnserver;
0086     }
0087 
0088     WorkerThread *m_workerThread = nullptr; // only set for in-process workers
0089     QString m_protocol;
0090     QString m_slaveProtocol;
0091     QString m_host;
0092     QString m_user;
0093     QString m_passwd;
0094     KIO::ConnectionServer *slaveconnserver;
0095     KIO::SimpleJob *m_job;
0096     qint64 m_pid; // only set for out-of-process workers
0097     quint16 m_port;
0098 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 103)
0099     bool contacted;
0100 #endif
0101     bool dead;
0102     QElapsedTimer contact_started;
0103     QElapsedTimer m_idleSince;
0104     int m_refCount;
0105 };
0106 }
0107 
0108 void Slave::accept()
0109 {
0110     Q_D(Slave);
0111     d->slaveconnserver->setNextPendingConnection(d->connection);
0112     d->slaveconnserver->deleteLater();
0113     d->slaveconnserver = nullptr;
0114 
0115     connect(d->connection, &Connection::readyRead, this, &Slave::gotInput);
0116 }
0117 
0118 void Slave::timeout()
0119 {
0120     Q_D(Slave);
0121     if (d->dead) { // already dead? then slaveDied was emitted and we are done
0122         return;
0123     }
0124     if (d->connection->isConnected()) {
0125         return;
0126     }
0127 
0128     /*qDebug() << "worker failed to connect to application pid=" << d->m_pid
0129                  << " protocol=" << d->m_protocol;*/
0130     if (d->m_pid && KIOPrivate::isProcessAlive(d->m_pid)) {
0131         int delta_t = d->contact_started.elapsed() / 1000;
0132         // qDebug() << "worker is slow... pid=" << d->m_pid << " t=" << delta_t;
0133         if (delta_t < s_slaveConnectionTimeoutMax) {
0134             QTimer::singleShot(1000 * s_slaveConnectionTimeoutMin, this, &Slave::timeout);
0135             return;
0136         }
0137     }
0138     // qDebug() << "Houston, we lost our worker, pid=" << d->m_pid;
0139     d->connection->close();
0140     d->dead = true;
0141     QString arg = d->m_protocol;
0142     if (!d->m_host.isEmpty()) {
0143         arg += QLatin1String("://") + d->m_host;
0144     }
0145     // qDebug() << "worker failed to connect pid =" << d->m_pid << arg;
0146 
0147     ref();
0148     // Tell the job about the problem.
0149     Q_EMIT error(ERR_WORKER_DIED, arg);
0150     // Tell the scheduler about the problem.
0151     Q_EMIT slaveDied(this);
0152     // After the above signal we're dead!!
0153     deref();
0154 }
0155 
0156 Slave::Slave(const QString &protocol, QObject *parent)
0157     : SlaveInterface(*new SlavePrivate(protocol), parent)
0158 {
0159     Q_D(Slave);
0160     d->slaveconnserver->setParent(this);
0161     d->connection = new Connection(this);
0162     connect(d->slaveconnserver, &ConnectionServer::newConnection, this, &Slave::accept);
0163 }
0164 
0165 Slave::~Slave()
0166 {
0167     // qDebug() << "destructing slave object pid =" << d->m_pid;
0168     // delete d;
0169 }
0170 
0171 QString Slave::protocol()
0172 {
0173     Q_D(Slave);
0174     return d->m_protocol;
0175 }
0176 
0177 void Slave::setProtocol(const QString &protocol)
0178 {
0179     Q_D(Slave);
0180     d->m_protocol = protocol;
0181 }
0182 
0183 QString Slave::slaveProtocol()
0184 {
0185     Q_D(Slave);
0186     return d->m_slaveProtocol;
0187 }
0188 
0189 QString Slave::host()
0190 {
0191     Q_D(Slave);
0192     return d->m_host;
0193 }
0194 
0195 quint16 Slave::port()
0196 {
0197     Q_D(Slave);
0198     return d->m_port;
0199 }
0200 
0201 QString Slave::user()
0202 {
0203     Q_D(Slave);
0204     return d->m_user;
0205 }
0206 
0207 QString Slave::passwd()
0208 {
0209     Q_D(Slave);
0210     return d->m_passwd;
0211 }
0212 
0213 void Slave::setIdle()
0214 {
0215     Q_D(Slave);
0216     d->m_idleSince.start();
0217 }
0218 
0219 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 103)
0220 bool Slave::isConnected()
0221 {
0222     Q_D(Slave);
0223     return d->contacted;
0224 }
0225 #endif
0226 
0227 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 103)
0228 void Slave::setConnected(bool c)
0229 {
0230     Q_D(Slave);
0231     d->contacted = c;
0232 }
0233 #endif
0234 
0235 void Slave::ref()
0236 {
0237     Q_D(Slave);
0238     d->m_refCount++;
0239 }
0240 
0241 void Slave::deref()
0242 {
0243     Q_D(Slave);
0244     d->m_refCount--;
0245     if (!d->m_refCount) {
0246         aboutToDelete();
0247         delete this; // yes it reads funny, but it's too late for a deleteLater() here, no event loop anymore
0248     }
0249 }
0250 
0251 void Slave::aboutToDelete()
0252 {
0253     Q_D(Slave);
0254     d->connection->disconnect(this);
0255     this->disconnect();
0256 }
0257 
0258 void Slave::setWorkerThread(WorkerThread *thread)
0259 {
0260     Q_D(Slave);
0261     d->m_workerThread = thread;
0262 }
0263 
0264 int Slave::idleTime()
0265 {
0266     Q_D(Slave);
0267     if (!d->m_idleSince.isValid()) {
0268         return 0;
0269     }
0270     return d->m_idleSince.elapsed() / 1000;
0271 }
0272 
0273 void Slave::setPID(qint64 pid)
0274 {
0275     Q_D(Slave);
0276     d->m_pid = pid;
0277 }
0278 
0279 qint64 Slave::slave_pid()
0280 {
0281     Q_D(Slave);
0282     return d->m_pid;
0283 }
0284 
0285 void Slave::setJob(KIO::SimpleJob *job)
0286 {
0287     Q_D(Slave);
0288     if (!d->sslMetaData.isEmpty()) {
0289         Q_EMIT metaData(d->sslMetaData);
0290     }
0291     d->m_job = job;
0292 }
0293 
0294 KIO::SimpleJob *Slave::job() const
0295 {
0296     Q_D(const Slave);
0297     return d->m_job;
0298 }
0299 
0300 bool Slave::isAlive()
0301 {
0302     Q_D(Slave);
0303     return !d->dead;
0304 }
0305 
0306 // TODO KF6: remove, unused
0307 void Slave::hold(const QUrl &url)
0308 {
0309     Q_D(Slave);
0310     ref();
0311     {
0312         QByteArray data;
0313         QDataStream stream(&data, QIODevice::WriteOnly);
0314         stream << url;
0315         d->connection->send(CMD_SLAVE_HOLD, data);
0316         d->connection->close();
0317         d->dead = true;
0318         Q_EMIT slaveDied(this);
0319     }
0320     deref();
0321 }
0322 
0323 void Slave::suspend()
0324 {
0325     Q_D(Slave);
0326     d->connection->suspend();
0327 }
0328 
0329 void Slave::resume()
0330 {
0331     Q_D(Slave);
0332     d->connection->resume();
0333 }
0334 
0335 bool Slave::suspended()
0336 {
0337     Q_D(Slave);
0338     return d->connection->suspended();
0339 }
0340 
0341 void Slave::send(int cmd, const QByteArray &arr)
0342 {
0343     Q_D(Slave);
0344     d->connection->send(cmd, arr);
0345 }
0346 
0347 void Slave::gotInput()
0348 {
0349     Q_D(Slave);
0350     if (d->dead) { // already dead? then slaveDied was emitted and we are done
0351         return;
0352     }
0353     ref();
0354     if (!dispatch()) {
0355         d->connection->close();
0356         d->dead = true;
0357         QString arg = d->m_protocol;
0358         if (!d->m_host.isEmpty()) {
0359             arg += QLatin1String("://") + d->m_host;
0360         }
0361         // qDebug() << "worker died pid =" << d->m_pid << arg;
0362         // Tell the job about the problem.
0363         Q_EMIT error(ERR_WORKER_DIED, arg);
0364         // Tell the scheduler about the problem.
0365         Q_EMIT slaveDied(this);
0366     }
0367     deref();
0368     // Here we might be dead!!
0369 }
0370 
0371 void Slave::kill()
0372 {
0373     Q_D(Slave);
0374     d->dead = true; // OO can be such simple.
0375     if (d->m_pid) {
0376         qCDebug(KIO_CORE) << "killing worker process pid" << d->m_pid << "(" << d->m_protocol + QLatin1String("://") + d->m_host << ")";
0377         KIOPrivate::sendTerminateSignal(d->m_pid);
0378         d->m_pid = 0;
0379     } else if (d->m_workerThread) {
0380         qCDebug(KIO_CORE) << "aborting worker thread for " << d->m_protocol + QLatin1String("://") + d->m_host;
0381         d->m_workerThread->abort();
0382     }
0383     deref();
0384 }
0385 
0386 void Slave::setHost(const QString &host, quint16 port, const QString &user, const QString &passwd)
0387 {
0388     Q_D(Slave);
0389     d->m_host = host;
0390     d->m_port = port;
0391     d->m_user = user;
0392     d->m_passwd = passwd;
0393     d->sslMetaData.clear();
0394 
0395     QByteArray data;
0396     QDataStream stream(&data, QIODevice::WriteOnly);
0397     stream << d->m_host << d->m_port << d->m_user << d->m_passwd;
0398     d->connection->send(CMD_HOST, data);
0399 }
0400 
0401 void Slave::resetHost()
0402 {
0403     Q_D(Slave);
0404     d->sslMetaData.clear();
0405     d->m_host = QStringLiteral("<reset>");
0406 }
0407 
0408 void Slave::setConfig(const MetaData &config)
0409 {
0410     Q_D(Slave);
0411     QByteArray data;
0412     QDataStream stream(&data, QIODevice::WriteOnly);
0413     stream << config;
0414     d->connection->send(CMD_CONFIG, data);
0415 }
0416 
0417 // TODO KF6: return std::unique_ptr
0418 Slave *Slave::createSlave(const QString &protocol, const QUrl &url, int &error, QString &error_text)
0419 {
0420     Q_UNUSED(url)
0421     // qDebug() << "createSlave" << protocol << "for" << url;
0422     // Firstly take into account all special slaves
0423     if (protocol == QLatin1String("data")) {
0424         return new DataProtocol();
0425     }
0426 
0427     const QString _name = KProtocolInfo::exec(protocol);
0428     if (_name.isEmpty()) {
0429         error_text = i18n("Unknown protocol '%1'.", protocol);
0430         error = KIO::ERR_CANNOT_CREATE_WORKER;
0431         return nullptr;
0432     }
0433 
0434     // find the KIO worker using QPluginLoader; kioslave would do this
0435     // anyway, but if it doesn't exist, we want to be able to return
0436     // a useful error message immediately
0437     QPluginLoader loader(_name);
0438     const QString lib_path = loader.fileName();
0439     if (lib_path.isEmpty()) {
0440         error_text = i18n("Can not find a KIO worker for protocol '%1'.", protocol);
0441         error = KIO::ERR_CANNOT_CREATE_WORKER;
0442         return nullptr;
0443     }
0444 
0445     Slave *slave = new Slave(protocol);
0446     QUrl slaveAddress = slave->d_func()->slaveconnserver->address();
0447     if (slaveAddress.isEmpty()) {
0448         error_text = i18n("Can not create a socket for launching a KIO worker for protocol '%1'.", protocol);
0449         error = KIO::ERR_CANNOT_CREATE_WORKER;
0450         delete slave;
0451         return nullptr;
0452     }
0453 
0454     // Threads are enabled by default, set KIO_ENABLE_WORKER_THREADS=0 to disable them
0455     const auto useThreads = []() {
0456         return qgetenv("KIO_ENABLE_WORKER_THREADS") != "0";
0457     };
0458     static bool bUseThreads = useThreads();
0459 
0460     // Threads have performance benefits, but degrade robustness
0461     // (a worker crashing kills the app). So let's only enable the feature for kio_file, for now.
0462     if (protocol == QLatin1String("admin") || (bUseThreads && protocol == QLatin1String("file"))) {
0463         auto *factory = qobject_cast<WorkerFactory *>(loader.instance());
0464         if (factory) {
0465             auto *thread = new WorkerThread(slave, factory, slaveAddress.toString().toLocal8Bit());
0466             thread->start();
0467             slave->setWorkerThread(thread);
0468             return slave;
0469         } else {
0470             qCWarning(KIO_CORE) << lib_path << "doesn't implement WorkerFactory?";
0471         }
0472     }
0473 
0474     const QStringList args = QStringList{lib_path, protocol, QString(), slaveAddress.toString()};
0475     // qDebug() << "kioslave" << ", " << lib_path << ", " << protocol << ", " << QString() << ", " << slaveAddress;
0476 
0477     // search paths
0478     QStringList searchPaths = KLibexec::kdeFrameworksPaths(QStringLiteral("libexec/kf" QT_STRINGIFY(QT_VERSION_MAJOR)));
0479     searchPaths.append(QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF)); // look at our installation location
0480     QString kioslaveExecutable = QStandardPaths::findExecutable(QStringLiteral("kioslave5"), searchPaths);
0481     if (kioslaveExecutable.isEmpty()) {
0482         // Fallback to PATH. On win32 we install to bin/ which tests outside
0483         // KIO cannot not find at the time ctest is run because it
0484         // isn't the same as applicationDirPath().
0485         kioslaveExecutable = QStandardPaths::findExecutable(QStringLiteral("kioslave5"));
0486     }
0487     if (kioslaveExecutable.isEmpty()) {
0488         error_text = i18n("Can not find 'kioslave5' executable at '%1'", searchPaths.join(QLatin1String(", ")));
0489         error = KIO::ERR_CANNOT_CREATE_WORKER;
0490         delete slave;
0491         return nullptr;
0492     }
0493 
0494     qint64 pid = 0;
0495     QProcess::startDetached(kioslaveExecutable, args, QString(), &pid);
0496     slave->setPID(pid);
0497 
0498     return slave;
0499 }
0500 
0501 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 88)
0502 Slave *Slave::holdSlave(const QString &protocol, const QUrl &url)
0503 {
0504     Q_UNUSED(protocol)
0505     Q_UNUSED(url)
0506     return nullptr;
0507 }
0508 #endif
0509 
0510 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 88)
0511 bool Slave::checkForHeldSlave(const QUrl &)
0512 {
0513     return false;
0514 }
0515 #endif
0516 
0517 #include "moc_slave.cpp"