File indexing completed on 2023-10-03 03:20:05
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"