KIO

worker.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
5 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-only
8*/
9
10#include "worker_p.h"
11
12#include <config-kiocore.h>
13#include <qplatformdefs.h>
14#include <stdio.h>
15
16#include <QCoreApplication>
17#include <QDataStream>
18#include <QDir>
19#include <QFile>
20#include <QLibraryInfo>
21#include <QPluginLoader>
22#include <QProcess>
23#include <QStandardPaths>
24#include <QTimer>
25
26#include <KLibexec>
27#include <KLocalizedString>
28
29#include "commands_p.h"
30#include "connection_p.h"
31#include "connectionserver.h"
32#include "dataprotocol_p.h"
33#include "kioglobal_p.h"
34#include <config-kiocore.h> // KDE_INSTALL_FULL_LIBEXECDIR_KF
35#include <kprotocolinfo.h>
36
37#include "kiocoredebug.h"
38#include "workerbase.h"
39#include "workerfactory.h"
40#include "workerthread_p.h"
41
42using namespace KIO;
43
44static constexpr int s_workerConnectionTimeoutMin = 2;
45
46// Without debug info we consider it an error if the worker doesn't connect
47// within 10 seconds.
48// With debug info we give the worker an hour so that developers have a chance
49// to debug their worker.
50#ifdef NDEBUG
51static constexpr int s_workerConnectionTimeoutMax = 10;
52#else
53static constexpr int s_workerConnectionTimeoutMax = 3600;
54#endif
55
56void Worker::accept()
57{
58 m_workerConnServer->setNextPendingConnection(m_connection);
59 m_workerConnServer->deleteLater();
60 m_workerConnServer = nullptr;
61
62 connect(m_connection, &Connection::readyRead, this, &Worker::gotInput);
63}
64
65void Worker::timeout()
66{
67 if (m_dead) { // already dead? then workerDied was emitted and we are done
68 return;
69 }
70 if (m_connection->isConnected()) {
71 return;
72 }
73
74 /*qDebug() << "worker failed to connect to application pid=" << m_pid
75 << " protocol=" << m_protocol;*/
76 if (m_pid && KIOPrivate::isProcessAlive(m_pid)) {
77 int delta_t = m_contact_started.elapsed() / 1000;
78 // qDebug() << "worker is slow... pid=" << m_pid << " t=" << delta_t;
79 if (delta_t < s_workerConnectionTimeoutMax) {
80 QTimer::singleShot(1000 * s_workerConnectionTimeoutMin, this, &Worker::timeout);
81 return;
82 }
83 }
84 // qDebug() << "Houston, we lost our worker, pid=" << m_pid;
85 m_connection->close();
86 m_dead = true;
87 QString arg = m_protocol;
88 if (!m_host.isEmpty()) {
89 arg += QLatin1String("://") + m_host;
90 }
91 // qDebug() << "worker failed to connect pid =" << m_pid << arg;
92
93 ref();
94 // Tell the job about the problem.
95 Q_EMIT error(ERR_WORKER_DIED, arg);
96 // Tell the scheduler about the problem.
97 Q_EMIT workerDied(this);
98 // After the above signal we're dead!!
99 deref();
100}
101
102Worker::Worker(const QString &protocol, QObject *parent)
103 : WorkerInterface(parent)
104 , m_protocol(protocol)
105 , m_workerProtocol(protocol)
106 , m_workerConnServer(new KIO::ConnectionServer)
107{
108 m_contact_started.start();
109 m_workerConnServer->setParent(this);
110 m_workerConnServer->listenForRemote();
111 if (!m_workerConnServer->isListening()) {
112 qCWarning(KIO_CORE) << "KIO Connection server not listening, could not connect";
113 }
114 m_connection = new Connection(Connection::Type::Application, this);
115 connect(m_workerConnServer, &ConnectionServer::newConnection, this, &Worker::accept);
116}
117
118Worker::~Worker()
119{
120 // qDebug() << "destructing worker object pid =" << m_pid;
121 delete m_workerConnServer;
122}
123
124QString Worker::protocol() const
125{
126 return m_protocol;
127}
128
129void Worker::setProtocol(const QString &protocol)
130{
131 m_protocol = protocol;
132}
133
134QString Worker::workerProtocol() const
135{
136 return m_workerProtocol;
137}
138
139QString Worker::host() const
140{
141 return m_host;
142}
143
144quint16 Worker::port() const
145{
146 return m_port;
147}
148
149QString Worker::user() const
150{
151 return m_user;
152}
153
154QString Worker::passwd() const
155{
156 return m_passwd;
157}
158
159void Worker::setIdle()
160{
161 m_idleSince.start();
162}
163
164void Worker::ref()
165{
166 m_refCount++;
167}
168
169void Worker::deref()
170{
171 m_refCount--;
172 if (!m_refCount) {
173 aboutToDelete();
174 if (m_workerThread) {
175 // When on a thread, delete in a thread to prevent deadlocks between the main thread and the worker thread.
176 // This most notably can happen when the worker thread uses QDBus, because traffic will generally be routed
177 // through the main loop.
178 // Generally speaking we'd want to avoid waiting in the main thread anyway, the worker stopping isn't really
179 // useful for anything but delaying deletion.
180 // https://bugs.kde.org/show_bug.cgi?id=468673
181 WorkerThread *workerThread = nullptr;
182 std::swap(workerThread, m_workerThread);
183 workerThread->setParent(nullptr);
184 connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
185 workerThread->quit();
186 }
187 delete this; // yes it reads funny, but it's too late for a deleteLater() here, no event loop anymore
188 }
189}
190
191void Worker::aboutToDelete()
192{
193 m_connection->disconnect(this);
194 this->disconnect();
195}
196
197void Worker::setWorkerThread(WorkerThread *thread)
198{
199 m_workerThread = thread;
200}
201
202int Worker::idleTime() const
203{
204 if (!m_idleSince.isValid()) {
205 return 0;
206 }
207 return m_idleSince.elapsed() / 1000;
208}
209
210void Worker::setPID(qint64 pid)
211{
212 m_pid = pid;
213}
214
215qint64 Worker::worker_pid() const
216{
217 return m_pid;
218}
219
220void Worker::setJob(KIO::SimpleJob *job)
221{
222 m_job = job;
223}
224
225KIO::SimpleJob *Worker::job() const
226{
227 return m_job;
228}
229
230bool Worker::isAlive() const
231{
232 return !m_dead;
233}
234
235void Worker::suspend()
236{
237 m_connection->suspend();
238}
239
240void Worker::resume()
241{
242 m_connection->resume();
243}
244
245bool Worker::suspended()
246{
247 return m_connection->suspended();
248}
249
250void Worker::send(int cmd, const QByteArray &arr)
251{
252 m_connection->send(cmd, arr);
253}
254
255void Worker::gotInput()
256{
257 if (m_dead) { // already dead? then workerDied was emitted and we are done
258 return;
259 }
260 ref();
261 if (!dispatch()) {
262 m_connection->close();
263 m_dead = true;
264 QString arg = m_protocol;
265 if (!m_host.isEmpty()) {
266 arg += QLatin1String("://") + m_host;
267 }
268 // qDebug() << "worker died pid =" << m_pid << arg;
269 // Tell the job about the problem.
270 Q_EMIT error(ERR_WORKER_DIED, arg);
271 // Tell the scheduler about the problem.
272 Q_EMIT workerDied(this);
273 }
274 deref();
275 // Here we might be dead!!
276}
277
278void Worker::kill()
279{
280 m_dead = true; // OO can be such simple.
281 if (m_pid) {
282 qCDebug(KIO_CORE) << "killing worker process pid" << m_pid << "(" << m_protocol + QLatin1String("://") + m_host << ")";
283 KIOPrivate::sendTerminateSignal(m_pid);
284 m_pid = 0;
285 } else if (m_workerThread) {
286 qCDebug(KIO_CORE) << "aborting worker thread for " << m_protocol + QLatin1String("://") + m_host;
287 m_workerThread->abort();
288 }
289 deref();
290}
291
292void Worker::setHost(const QString &host, quint16 port, const QString &user, const QString &passwd)
293{
294 m_host = host;
295 m_port = port;
296 m_user = user;
297 m_passwd = passwd;
298
299 QByteArray data;
300 QDataStream stream(&data, QIODevice::WriteOnly);
301 stream << m_host << m_port << m_user << m_passwd;
302 m_connection->send(CMD_HOST, data);
303}
304
305void Worker::resetHost()
306{
307 m_host = QStringLiteral("<reset>");
308}
309
310void Worker::setConfig(const MetaData &config)
311{
312 QByteArray data;
313 QDataStream stream(&data, QIODevice::WriteOnly);
314 stream << config;
315 m_connection->send(CMD_CONFIG, data);
316}
317
318// TODO KF6: return std::unique_ptr
319Worker *Worker::createWorker(const QString &protocol, const QUrl &url, int &error, QString &error_text)
320{
321 Q_UNUSED(url)
322 // qDebug() << "createWorker" << protocol << "for" << url;
323 // Firstly take into account all special workers
324 if (protocol == QLatin1String("data")) {
325 return new DataProtocol();
326 }
327
328 const QString _name = KProtocolInfo::exec(protocol);
329 if (_name.isEmpty()) {
330 error_text = i18n("Unknown protocol '%1'.", protocol);
332 return nullptr;
333 }
334
335 // find the KIO worker using QPluginLoader; kioworker would do this
336 // anyway, but if it doesn't exist, we want to be able to return
337 // a useful error message immediately
338 QPluginLoader loader(_name);
339 const QString lib_path = loader.fileName();
340 if (lib_path.isEmpty()) {
341 error_text = i18n("Can not find a KIO worker for protocol '%1'.", protocol);
343 return nullptr;
344 }
345
346 if (protocol == QLatin1String("admin") && !lib_path.startsWith(QLatin1String{KDE_INSTALL_FULL_KIO_PLUGINDIR})) {
347 error_text = i18nc("@info %2 and %3 are paths",
348 "The KIO worker for protocol “%1” in %2 was not loaded because all KIO workers which are located outside of %3 and ask for elevated "
349 "privileges are considered insecure.",
350 protocol,
351 lib_path,
352 QLatin1String{KDE_INSTALL_FULL_KIO_PLUGINDIR});
354 return nullptr;
355 }
356
357 auto *worker = new Worker(protocol);
358 const QUrl workerAddress = worker->m_workerConnServer->address();
359 if (workerAddress.isEmpty()) {
360 error_text = i18n("Can not create a socket for launching a KIO worker for protocol '%1'.", protocol);
362 delete worker;
363 return nullptr;
364 }
365
366 // Threads are enabled by default, set KIO_ENABLE_WORKER_THREADS=0 to disable them
367 const auto useThreads = []() {
368 return qgetenv("KIO_ENABLE_WORKER_THREADS") != "0";
369 };
370 static bool bUseThreads = useThreads();
371
372 // Threads have performance benefits, but degrade robustness
373 // (a worker crashing kills the app). So let's only enable the feature for kio_file, for now.
374 if (protocol == QLatin1String("admin") || (bUseThreads && protocol == QLatin1String("file"))) {
375 auto *factory = qobject_cast<WorkerFactory *>(loader.instance());
376 if (factory) {
377 auto *thread = new WorkerThread(worker, factory, workerAddress.toString().toLocal8Bit());
378 thread->start();
379 worker->setWorkerThread(thread);
380 return worker;
381 } else {
382 qCWarning(KIO_CORE) << lib_path << "doesn't implement WorkerFactory?";
383 }
384 }
385
386 const QStringList args = QStringList{lib_path, protocol, QString(), workerAddress.toString()};
387 // qDebug() << "kioworker" << ", " << lib_path << ", " << protocol << ", " << QString() << ", " << workerAddress;
388
389 // search paths
390 QStringList searchPaths = KLibexec::kdeFrameworksPaths(QStringLiteral("libexec/kf6"));
391 searchPaths.append(QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF)); // look at our installation location
392 QString kioworkerExecutable = QStandardPaths::findExecutable(QStringLiteral("kioworker"), searchPaths);
393 if (kioworkerExecutable.isEmpty()) {
394 // Fallback to PATH. On win32 we install to bin/ which tests outside
395 // KIO cannot not find at the time ctest is run because it
396 // isn't the same as applicationDirPath().
397 kioworkerExecutable = QStandardPaths::findExecutable(QStringLiteral("kioworker"));
398 }
399 if (kioworkerExecutable.isEmpty()) {
400 error_text = i18n("Can not find 'kioworker' executable at '%1'", searchPaths.join(QLatin1String(", ")));
402 delete worker;
403 return nullptr;
404 }
405
406 qint64 pid = 0;
407 QProcess process;
408 process.setProgram(kioworkerExecutable);
409 process.setArguments(args);
410#ifdef Q_OS_UNIX
411 process.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
412#endif
413 process.startDetached(&pid);
414 worker->setPID(pid);
415
416 return worker;
417}
418
419#include "moc_worker_p.cpp"
MetaData is a simple map of key/value strings.
Definition metadata.h:23
A simple job (one url and one command).
Definition simplejob.h:27
bool suspend()
static QString exec(const QString &protocol)
Returns the library / executable to open for the protocol protocol Example : "kio_ftp",...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
A namespace for KIO globals.
@ ERR_CANNOT_CREATE_WORKER
used by Worker::createWorker,
Definition global.h:197
@ ERR_WORKER_DIED
Definition global.h:155
QStringList kdeFrameworksPaths(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString decodeName(const QByteArray &localFileName)
void append(QList< T > &&value)
void deleteLater()
void setArguments(const QStringList &arguments)
void setProgram(const QString &program)
void setUnixProcessParameters(UnixProcessFlags flagsOnly)
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString findExecutable(const QString &executableName, const QStringList &paths)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLocal8Bit() const const
QString join(QChar separator) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void finished()
bool isEmpty() const const
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:50:09 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.