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"