1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org> |
4 | SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> |
5 | SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> |
6 | SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org> |
7 | |
8 | SPDX-License-Identifier: LGPL-2.0-only |
9 | */ |
10 | |
11 | #include "slavebase.h" |
12 | |
13 | #include <config-kiocore.h> |
14 | |
15 | #include <qplatformdefs.h> |
16 | #include <signal.h> |
17 | #include <stdlib.h> |
18 | #ifdef Q_OS_WIN |
19 | #include <process.h> |
20 | #endif |
21 | |
22 | #include <QCoreApplication> |
23 | #include <QDataStream> |
24 | #include <QDateTime> |
25 | #include <QElapsedTimer> |
26 | #include <QFile> |
27 | #include <QList> |
28 | #include <QMap> |
29 | #include <QSsl> |
30 | #include <QtGlobal> |
31 | |
32 | #include <KAboutData> |
33 | #include <KConfig> |
34 | #include <KConfigGroup> |
35 | #include <KLocalizedString> |
36 | #include <QThread> |
37 | |
38 | #ifndef Q_OS_ANDROID |
39 | #include <KCrash> |
40 | #endif |
41 | |
42 | #include "authinfo.h" |
43 | #include "kremoteencoding.h" |
44 | |
45 | #include "commands_p.h" |
46 | #include "connection_p.h" |
47 | #include "ioworker_defaults.h" |
48 | #include "kio_version.h" |
49 | #include "kiocoredebug.h" |
50 | #include "kioglobal_p.h" |
51 | #include "kpasswdserverclient.h" |
52 | #include "workerinterface_p.h" |
53 | |
54 | #if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID) |
55 | #include <KAuth/Action> |
56 | #endif |
57 | |
58 | // TODO: Enable once file KIO worker is ported away and add endif, similar in the header file |
59 | // #if KIOCORE_BUILD_DEPRECATED_SINCE(version where file:/ KIO worker was ported) |
60 | |
61 | #if KIO_ASSERT_WORKER_STATES |
62 | #define KIO_STATE_ASSERT(cond, where, what) Q_ASSERT_X(cond, where, what) |
63 | #else |
64 | /* clang-format off */ |
65 | #define KIO_STATE_ASSERT(cond, where, what) \ |
66 | do { \ |
67 | if (!(cond)) { \ |
68 | qCWarning(KIO_CORE) << what; \ |
69 | } \ |
70 | } while (false) |
71 | #endif |
72 | /* clang-format on */ |
73 | |
74 | extern "C" { |
75 | static void sigpipe_handler(int sig); |
76 | } |
77 | |
78 | using namespace KIO; |
79 | |
80 | typedef QList<QByteArray> AuthKeysList; |
81 | typedef QMap<QString, QByteArray> AuthKeysMap; |
82 | |
83 | /* clang-format off */ |
84 | #define KIO_DATA \ |
85 | QByteArray data; \ |
86 | QDataStream stream(&data, QIODevice::WriteOnly); \ |
87 | stream |
88 | /* clang-format on */ |
89 | |
90 | static constexpr int KIO_MAX_ENTRIES_PER_BATCH = 200; |
91 | static constexpr int KIO_MAX_SEND_BATCH_TIME = 300; |
92 | |
93 | namespace KIO |
94 | { |
95 | class SlaveBasePrivate |
96 | { |
97 | public: |
98 | SlaveBase *const q; |
99 | explicit SlaveBasePrivate(SlaveBase *owner) |
100 | : q(owner) |
101 | , nextTimeoutMsecs(0) |
102 | , m_confirmationAsked(false) |
103 | , m_privilegeOperationStatus(OperationNotAllowed) |
104 | { |
105 | if (!qEnvironmentVariableIsEmpty(varName: "KIOWORKER_ENABLE_TESTMODE" )) { |
106 | QStandardPaths::setTestModeEnabled(true); |
107 | } else if (!qEnvironmentVariableIsEmpty(varName: "KIOSLAVE_ENABLE_TESTMODE" )) { |
108 | QStandardPaths::setTestModeEnabled(true); |
109 | qCWarning(KIO_CORE) |
110 | << "KIOSLAVE_ENABLE_TESTMODE is deprecated for KF6, and will be unsupported soon. Please use KIOWORKER_ENABLE_TESTMODE with KF6." ; |
111 | } |
112 | pendingListEntries.reserve(asize: KIO_MAX_ENTRIES_PER_BATCH); |
113 | appConnection.setReadMode(Connection::ReadMode::Polled); |
114 | } |
115 | ~SlaveBasePrivate() = default; |
116 | |
117 | UDSEntryList pendingListEntries; |
118 | QElapsedTimer m_timeSinceLastBatch; |
119 | Connection appConnection{Connection::Type::Worker}; |
120 | QString poolSocket; |
121 | bool isConnectedToApp; |
122 | |
123 | QString slaveid; |
124 | bool resume : 1; |
125 | bool needSendCanResume : 1; |
126 | bool onHold : 1; |
127 | bool inOpenLoop : 1; |
128 | std::atomic<bool> wasKilled = false; |
129 | std::atomic<bool> exit_loop = false; |
130 | std::atomic<bool> runInThread = false; |
131 | MetaData configData; |
132 | KConfig *config = nullptr; |
133 | KConfigGroup *configGroup = nullptr; |
134 | QMap<QString, QVariant> mapConfig; |
135 | QUrl onHoldUrl; |
136 | |
137 | QElapsedTimer lastTimeout; |
138 | QElapsedTimer nextTimeout; |
139 | qint64 nextTimeoutMsecs; |
140 | KIO::filesize_t totalSize; |
141 | KRemoteEncoding *remotefile = nullptr; |
142 | enum { |
143 | Idle, |
144 | InsideMethod, |
145 | InsideTimeoutSpecial, |
146 | FinishedCalled, |
147 | ErrorCalled |
148 | } m_state; |
149 | bool m_finalityCommand = true; // whether finished() or error() may/must be called |
150 | QByteArray timeoutData; |
151 | |
152 | #ifdef WITH_QTDBUS |
153 | std::unique_ptr<KPasswdServerClient> m_passwdServerClient; |
154 | #endif |
155 | bool m_rootEntryListed = false; |
156 | |
157 | bool m_confirmationAsked; |
158 | QSet<QString> m_tempAuths; |
159 | QString m_warningTitle; |
160 | QString m_warningMessage; |
161 | int m_privilegeOperationStatus; |
162 | |
163 | void updateTempAuthStatus() |
164 | { |
165 | #if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID) |
166 | QSet<QString>::iterator it = m_tempAuths.begin(); |
167 | while (it != m_tempAuths.end()) { |
168 | KAuth::Action action(*it); |
169 | if (action.status() != KAuth::Action::AuthorizedStatus) { |
170 | it = m_tempAuths.erase(i: it); |
171 | } else { |
172 | ++it; |
173 | } |
174 | } |
175 | #endif |
176 | } |
177 | |
178 | bool hasTempAuth() const |
179 | { |
180 | return !m_tempAuths.isEmpty(); |
181 | } |
182 | |
183 | // Reconstructs configGroup from configData and mIncomingMetaData |
184 | void rebuildConfig() |
185 | { |
186 | mapConfig.clear(); |
187 | |
188 | // mIncomingMetaData cascades over config, so we write config first, |
189 | // to let it be overwritten |
190 | MetaData::ConstIterator end = configData.constEnd(); |
191 | for (MetaData::ConstIterator it = configData.constBegin(); it != end; ++it) { |
192 | mapConfig.insert(key: it.key(), value: it->toUtf8()); |
193 | } |
194 | |
195 | end = q->mIncomingMetaData.constEnd(); |
196 | for (MetaData::ConstIterator it = q->mIncomingMetaData.constBegin(); it != end; ++it) { |
197 | mapConfig.insert(key: it.key(), value: it->toUtf8()); |
198 | } |
199 | |
200 | delete configGroup; |
201 | configGroup = nullptr; |
202 | delete config; |
203 | config = nullptr; |
204 | } |
205 | |
206 | bool finalState() const |
207 | { |
208 | return ((m_state == FinishedCalled) || (m_state == ErrorCalled)); |
209 | } |
210 | |
211 | void verifyState(const char *cmdName) |
212 | { |
213 | Q_UNUSED(cmdName) |
214 | KIO_STATE_ASSERT(finalState(), |
215 | Q_FUNC_INFO, |
216 | qUtf8Printable(QStringLiteral("%1 did not call finished() or error()! Please fix the %2 KIO worker." ) |
217 | .arg(QLatin1String(cmdName)) |
218 | .arg(QCoreApplication::applicationName()))); |
219 | // Force the command into finished state. We'll not reach this for Debug builds |
220 | // that fail the assertion. For Release builds we'll have made sure that the |
221 | // command is actually finished after the verification regardless of what |
222 | // the slave did. |
223 | if (!finalState()) { |
224 | q->finished(); |
225 | } |
226 | } |
227 | |
228 | void verifyErrorFinishedNotCalled(const char *cmdName) |
229 | { |
230 | Q_UNUSED(cmdName) |
231 | KIO_STATE_ASSERT(!finalState(), |
232 | Q_FUNC_INFO, |
233 | qUtf8Printable(QStringLiteral("%1 called finished() or error(), but it's not supposed to! Please fix the %2 KIO worker." ) |
234 | .arg(QLatin1String(cmdName)) |
235 | .arg(QCoreApplication::applicationName()))); |
236 | } |
237 | |
238 | #ifdef WITH_QTDBUS |
239 | KPasswdServerClient *passwdServerClient() |
240 | { |
241 | if (!m_passwdServerClient) { |
242 | m_passwdServerClient = std::make_unique<KPasswdServerClient>(); |
243 | } |
244 | |
245 | return m_passwdServerClient.get(); |
246 | } |
247 | #endif |
248 | }; |
249 | |
250 | } |
251 | |
252 | static volatile bool slaveWriteError = false; |
253 | |
254 | #ifdef Q_OS_UNIX |
255 | static SlaveBase *globalSlave; |
256 | |
257 | extern "C" { |
258 | static void genericsig_handler(int sigNumber) |
259 | { |
260 | ::signal(sig: sigNumber, SIG_IGN); |
261 | // WABA: Don't do anything that requires malloc, we can deadlock on it since |
262 | // a SIGTERM signal can come in while we are in malloc/free. |
263 | // qDebug()<<"kioslave : exiting due to signal "<<sigNumber; |
264 | // set the flag which will be checked in dispatchLoop() and which *should* be checked |
265 | // in lengthy operations in the various slaves |
266 | if (globalSlave != nullptr) { |
267 | globalSlave->setKillFlag(); |
268 | } |
269 | ::signal(SIGALRM, SIG_DFL); |
270 | alarm(seconds: 5); // generate an alarm signal in 5 seconds, in this time the slave has to exit |
271 | } |
272 | } |
273 | #endif |
274 | |
275 | ////////////// |
276 | |
277 | SlaveBase::SlaveBase(const QByteArray &protocol, const QByteArray &pool_socket, const QByteArray &app_socket) |
278 | : mProtocol(protocol) |
279 | , d(new SlaveBasePrivate(this)) |
280 | |
281 | { |
282 | Q_ASSERT(!app_socket.isEmpty()); |
283 | d->poolSocket = QFile::decodeName(localFileName: pool_socket); |
284 | |
285 | if (QThread::currentThread() == qApp->thread()) { |
286 | #ifndef Q_OS_ANDROID |
287 | // Setup KCrash for crash reports, but not when running the worker in-app |
288 | if (QCoreApplication::arguments()[0].endsWith(s: QLatin1String("kioworker" ))) { |
289 | KAboutData about(QStringLiteral("kioworker" ), QString(), QStringLiteral(KIO_VERSION_STRING)); |
290 | KAboutData::setApplicationData(about); |
291 | KCrash::initialize(); |
292 | } |
293 | #endif |
294 | |
295 | #ifdef Q_OS_UNIX |
296 | struct sigaction act; |
297 | act.sa_handler = sigpipe_handler; |
298 | sigemptyset(set: &act.sa_mask); |
299 | act.sa_flags = 0; |
300 | sigaction(SIGPIPE, act: &act, oact: nullptr); |
301 | |
302 | ::signal(SIGINT, handler: &genericsig_handler); |
303 | ::signal(SIGQUIT, handler: &genericsig_handler); |
304 | ::signal(SIGTERM, handler: &genericsig_handler); |
305 | |
306 | globalSlave = this; |
307 | #endif |
308 | } |
309 | |
310 | d->isConnectedToApp = true; |
311 | |
312 | // by kahl for netmgr (need a way to identify slaves) |
313 | d->slaveid = QString::fromUtf8(ba: protocol) + QString::number(getpid()); |
314 | d->resume = false; |
315 | d->needSendCanResume = false; |
316 | d->mapConfig = QMap<QString, QVariant>(); |
317 | d->onHold = false; |
318 | // d->processed_size = 0; |
319 | d->totalSize = 0; |
320 | connectSlave(path: QFile::decodeName(localFileName: app_socket)); |
321 | |
322 | d->remotefile = nullptr; |
323 | d->inOpenLoop = false; |
324 | } |
325 | |
326 | SlaveBase::~SlaveBase() |
327 | { |
328 | delete d->configGroup; |
329 | delete d->config; |
330 | delete d->remotefile; |
331 | } |
332 | |
333 | void SlaveBase::dispatchLoop() |
334 | { |
335 | while (!d->exit_loop) { |
336 | if (d->nextTimeout.isValid() && (d->nextTimeout.hasExpired(timeout: d->nextTimeoutMsecs))) { |
337 | QByteArray data = d->timeoutData; |
338 | d->nextTimeout.invalidate(); |
339 | d->timeoutData = QByteArray(); |
340 | d->m_state = d->InsideTimeoutSpecial; |
341 | special(data); |
342 | d->m_state = d->Idle; |
343 | } |
344 | |
345 | Q_ASSERT(d->appConnection.inited()); |
346 | |
347 | int ms = -1; |
348 | if (d->nextTimeout.isValid()) { |
349 | ms = qMax<int>(a: d->nextTimeoutMsecs - d->nextTimeout.elapsed(), b: 1); |
350 | } |
351 | |
352 | int ret = -1; |
353 | if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms)) { |
354 | // dispatch application messages |
355 | int cmd; |
356 | QByteArray data; |
357 | ret = d->appConnection.read(cmd: &cmd, data); |
358 | |
359 | if (ret != -1) { |
360 | if (d->inOpenLoop) { |
361 | dispatchOpenCommand(command: cmd, data); |
362 | } else { |
363 | dispatch(command: cmd, data); |
364 | } |
365 | } |
366 | } else { |
367 | ret = d->appConnection.isConnected() ? 0 : -1; |
368 | } |
369 | |
370 | if (ret == -1) { // some error occurred, perhaps no more application |
371 | // When the app exits, should the slave be put back in the pool ? |
372 | if (!d->exit_loop && d->isConnectedToApp && !d->poolSocket.isEmpty()) { |
373 | disconnectSlave(); |
374 | d->isConnectedToApp = false; |
375 | closeConnection(); |
376 | d->updateTempAuthStatus(); |
377 | connectSlave(path: d->poolSocket); |
378 | } else { |
379 | break; |
380 | } |
381 | } |
382 | |
383 | // I think we get here when we were killed in dispatch() and not in select() |
384 | if (wasKilled()) { |
385 | // qDebug() << "worker was killed, returning"; |
386 | break; |
387 | } |
388 | |
389 | // execute deferred deletes |
390 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
391 | } |
392 | |
393 | // execute deferred deletes |
394 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
395 | } |
396 | |
397 | void SlaveBase::connectSlave(const QString &address) |
398 | { |
399 | d->appConnection.connectToRemote(address: QUrl(address)); |
400 | |
401 | if (!d->appConnection.inited()) { |
402 | /*qDebug() << "failed to connect to" << address << endl |
403 | << "Reason:" << d->appConnection.errorString();*/ |
404 | exit(); |
405 | } |
406 | |
407 | d->inOpenLoop = false; |
408 | } |
409 | |
410 | void SlaveBase::disconnectSlave() |
411 | { |
412 | d->appConnection.close(); |
413 | } |
414 | |
415 | void SlaveBase::setMetaData(const QString &key, const QString &value) |
416 | { |
417 | mOutgoingMetaData.insert(key, value); // replaces existing key if already there |
418 | } |
419 | |
420 | QString SlaveBase::metaData(const QString &key) const |
421 | { |
422 | auto it = mIncomingMetaData.find(key); |
423 | if (it != mIncomingMetaData.end()) { |
424 | return *it; |
425 | } |
426 | return d->configData.value(key); |
427 | } |
428 | |
429 | MetaData SlaveBase::allMetaData() const |
430 | { |
431 | return mIncomingMetaData; |
432 | } |
433 | |
434 | bool SlaveBase::hasMetaData(const QString &key) const |
435 | { |
436 | if (mIncomingMetaData.contains(key)) { |
437 | return true; |
438 | } |
439 | if (d->configData.contains(key)) { |
440 | return true; |
441 | } |
442 | return false; |
443 | } |
444 | |
445 | QMap<QString, QVariant> SlaveBase::mapConfig() const |
446 | { |
447 | return d->mapConfig; |
448 | } |
449 | |
450 | bool SlaveBase::configValue(const QString &key, bool defaultValue) const |
451 | { |
452 | return d->mapConfig.value(key, defaultValue).toBool(); |
453 | } |
454 | |
455 | int SlaveBase::configValue(const QString &key, int defaultValue) const |
456 | { |
457 | return d->mapConfig.value(key, defaultValue).toInt(); |
458 | } |
459 | |
460 | QString SlaveBase::configValue(const QString &key, const QString &defaultValue) const |
461 | { |
462 | return d->mapConfig.value(key, defaultValue).toString(); |
463 | } |
464 | |
465 | KConfigGroup *SlaveBase::config() |
466 | { |
467 | if (!d->config) { |
468 | d->config = new KConfig(QString(), KConfig::SimpleConfig); |
469 | |
470 | d->configGroup = new KConfigGroup(d->config, QString()); |
471 | |
472 | auto end = d->mapConfig.cend(); |
473 | for (auto it = d->mapConfig.cbegin(); it != end; ++it) { |
474 | d->configGroup->writeEntry(key: it.key(), value: it->toString().toUtf8(), pFlags: KConfigGroup::WriteConfigFlags()); |
475 | } |
476 | } |
477 | |
478 | return d->configGroup; |
479 | } |
480 | |
481 | void SlaveBase::sendMetaData() |
482 | { |
483 | sendAndKeepMetaData(); |
484 | mOutgoingMetaData.clear(); |
485 | } |
486 | |
487 | void SlaveBase::sendAndKeepMetaData() |
488 | { |
489 | if (!mOutgoingMetaData.isEmpty()) { |
490 | KIO_DATA << mOutgoingMetaData; |
491 | |
492 | send(cmd: INF_META_DATA, arr: data); |
493 | } |
494 | } |
495 | |
496 | KRemoteEncoding *SlaveBase::remoteEncoding() |
497 | { |
498 | if (d->remotefile) { |
499 | return d->remotefile; |
500 | } |
501 | |
502 | const QByteArray charset(metaData(QStringLiteral("Charset" )).toLatin1()); |
503 | return (d->remotefile = new KRemoteEncoding(charset.constData())); |
504 | } |
505 | |
506 | void SlaveBase::data(const QByteArray &data) |
507 | { |
508 | sendMetaData(); |
509 | send(cmd: MSG_DATA, arr: data); |
510 | } |
511 | |
512 | void SlaveBase::dataReq() |
513 | { |
514 | // sendMetaData(); |
515 | if (d->needSendCanResume) { |
516 | canResume(offset: 0); |
517 | } |
518 | send(cmd: MSG_DATA_REQ); |
519 | } |
520 | |
521 | void SlaveBase::opened() |
522 | { |
523 | sendMetaData(); |
524 | send(cmd: MSG_OPENED); |
525 | d->inOpenLoop = true; |
526 | } |
527 | |
528 | void SlaveBase::error(int _errid, const QString &_text) |
529 | { |
530 | if (d->m_state == d->InsideTimeoutSpecial) { |
531 | qWarning(catFunc: KIO_CORE) << "TimeoutSpecialCommand failed with" << _errid << _text; |
532 | return; |
533 | } |
534 | |
535 | KIO_STATE_ASSERT( |
536 | d->m_finalityCommand, |
537 | Q_FUNC_INFO, |
538 | qUtf8Printable(QStringLiteral("error() was called, but it's not supposed to! Please fix the %1 KIO worker." ).arg(QCoreApplication::applicationName()))); |
539 | |
540 | if (d->m_state == d->ErrorCalled) { |
541 | KIO_STATE_ASSERT(false, |
542 | Q_FUNC_INFO, |
543 | qUtf8Printable(QStringLiteral("error() called twice! Please fix the %1 KIO worker." ).arg(QCoreApplication::applicationName()))); |
544 | return; |
545 | } else if (d->m_state == d->FinishedCalled) { |
546 | KIO_STATE_ASSERT( |
547 | false, |
548 | Q_FUNC_INFO, |
549 | qUtf8Printable(QStringLiteral("error() called after finished()! Please fix the %1 KIO worker." ).arg(QCoreApplication::applicationName()))); |
550 | return; |
551 | } |
552 | |
553 | d->m_state = d->ErrorCalled; |
554 | mIncomingMetaData.clear(); // Clear meta data |
555 | d->rebuildConfig(); |
556 | mOutgoingMetaData.clear(); |
557 | KIO_DATA << static_cast<qint32>(_errid) << _text; |
558 | |
559 | send(cmd: MSG_ERROR, arr: data); |
560 | // reset |
561 | d->totalSize = 0; |
562 | d->inOpenLoop = false; |
563 | d->m_confirmationAsked = false; |
564 | d->m_privilegeOperationStatus = OperationNotAllowed; |
565 | } |
566 | |
567 | void SlaveBase::connected() |
568 | { |
569 | send(cmd: MSG_CONNECTED); |
570 | } |
571 | |
572 | void SlaveBase::finished() |
573 | { |
574 | if (d->m_state == d->InsideTimeoutSpecial) { |
575 | return; |
576 | } |
577 | |
578 | if (!d->pendingListEntries.isEmpty()) { |
579 | if (!d->m_rootEntryListed) { |
580 | qCWarning(KIO_CORE) << "UDSEntry for '.' not found, creating a default one. Please fix the" << QCoreApplication::applicationName() << "KIO worker." ; |
581 | KIO::UDSEntry entry; |
582 | entry.reserve(size: 4); |
583 | entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, QStringLiteral("." )); |
584 | entry.fastInsert(field: KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); |
585 | entry.fastInsert(field: KIO::UDSEntry::UDS_SIZE, l: 0); |
586 | entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); |
587 | d->pendingListEntries.append(t: entry); |
588 | } |
589 | |
590 | listEntries(entry: d->pendingListEntries); |
591 | d->pendingListEntries.clear(); |
592 | } |
593 | |
594 | KIO_STATE_ASSERT( |
595 | d->m_finalityCommand, |
596 | Q_FUNC_INFO, |
597 | qUtf8Printable( |
598 | QStringLiteral("finished() was called, but it's not supposed to! Please fix the %2 KIO worker." ).arg(QCoreApplication::applicationName()))); |
599 | |
600 | if (d->m_state == d->FinishedCalled) { |
601 | KIO_STATE_ASSERT(false, |
602 | Q_FUNC_INFO, |
603 | qUtf8Printable(QStringLiteral("finished() called twice! Please fix the %1 KIO worker." ).arg(QCoreApplication::applicationName()))); |
604 | return; |
605 | } else if (d->m_state == d->ErrorCalled) { |
606 | KIO_STATE_ASSERT( |
607 | false, |
608 | Q_FUNC_INFO, |
609 | qUtf8Printable(QStringLiteral("finished() called after error()! Please fix the %1 KIO worker." ).arg(QCoreApplication::applicationName()))); |
610 | return; |
611 | } |
612 | |
613 | d->m_state = d->FinishedCalled; |
614 | mIncomingMetaData.clear(); // Clear meta data |
615 | d->rebuildConfig(); |
616 | sendMetaData(); |
617 | send(cmd: MSG_FINISHED); |
618 | |
619 | // reset |
620 | d->totalSize = 0; |
621 | d->inOpenLoop = false; |
622 | d->m_rootEntryListed = false; |
623 | d->m_confirmationAsked = false; |
624 | d->m_privilegeOperationStatus = OperationNotAllowed; |
625 | } |
626 | |
627 | void SlaveBase::slaveStatus(const QString &host, bool connected) |
628 | { |
629 | qint64 pid = getpid(); |
630 | qint8 b = connected ? 1 : 0; |
631 | KIO_DATA << pid << mProtocol << host << b << d->onHold << d->onHoldUrl << d->hasTempAuth(); |
632 | send(cmd: MSG_WORKER_STATUS, arr: data); |
633 | } |
634 | |
635 | void SlaveBase::canResume() |
636 | { |
637 | send(cmd: MSG_CANRESUME); |
638 | } |
639 | |
640 | void SlaveBase::totalSize(KIO::filesize_t _bytes) |
641 | { |
642 | KIO_DATA << static_cast<quint64>(_bytes); |
643 | send(cmd: INF_TOTAL_SIZE, arr: data); |
644 | |
645 | // this one is usually called before the first item is listed in listDir() |
646 | d->totalSize = _bytes; |
647 | } |
648 | |
649 | void SlaveBase::processedSize(KIO::filesize_t _bytes) |
650 | { |
651 | bool emitSignal = false; |
652 | |
653 | if (_bytes == d->totalSize) { |
654 | emitSignal = true; |
655 | } else { |
656 | if (d->lastTimeout.isValid()) { |
657 | emitSignal = d->lastTimeout.hasExpired(timeout: 100); // emit size 10 times a second |
658 | } else { |
659 | emitSignal = true; |
660 | } |
661 | } |
662 | |
663 | if (emitSignal) { |
664 | KIO_DATA << static_cast<quint64>(_bytes); |
665 | send(cmd: INF_PROCESSED_SIZE, arr: data); |
666 | d->lastTimeout.start(); |
667 | } |
668 | |
669 | // d->processed_size = _bytes; |
670 | } |
671 | |
672 | void SlaveBase::written(KIO::filesize_t _bytes) |
673 | { |
674 | KIO_DATA << static_cast<quint64>(_bytes); |
675 | send(cmd: MSG_WRITTEN, arr: data); |
676 | } |
677 | |
678 | void SlaveBase::position(KIO::filesize_t _pos) |
679 | { |
680 | KIO_DATA << static_cast<quint64>(_pos); |
681 | send(cmd: INF_POSITION, arr: data); |
682 | } |
683 | |
684 | void SlaveBase::truncated(KIO::filesize_t _length) |
685 | { |
686 | KIO_DATA << static_cast<quint64>(_length); |
687 | send(cmd: INF_TRUNCATED, arr: data); |
688 | } |
689 | |
690 | void SlaveBase::processedPercent(float /* percent */) |
691 | { |
692 | // qDebug() << "STUB"; |
693 | } |
694 | |
695 | void SlaveBase::speed(unsigned long _bytes_per_second) |
696 | { |
697 | KIO_DATA << static_cast<quint32>(_bytes_per_second); |
698 | send(cmd: INF_SPEED, arr: data); |
699 | } |
700 | |
701 | void SlaveBase::redirection(const QUrl &_url) |
702 | { |
703 | KIO_DATA << _url; |
704 | send(cmd: INF_REDIRECTION, arr: data); |
705 | } |
706 | |
707 | static bool isSubCommand(int cmd) |
708 | { |
709 | /* clang-format off */ |
710 | return cmd == CMD_REPARSECONFIGURATION |
711 | || cmd == CMD_META_DATA |
712 | || cmd == CMD_CONFIG |
713 | || cmd == CMD_WORKER_STATUS; |
714 | /* clang-format on */ |
715 | } |
716 | |
717 | void SlaveBase::mimeType(const QString &_type) |
718 | { |
719 | qCDebug(KIO_CORE) << "detected mimetype" << _type; |
720 | int cmd = CMD_NONE; |
721 | do { |
722 | if (wasKilled()) { |
723 | break; |
724 | } |
725 | |
726 | // Send the meta-data each time we send the MIME type. |
727 | if (!mOutgoingMetaData.isEmpty()) { |
728 | qCDebug(KIO_CORE) << "sending mimetype meta data" ; |
729 | KIO_DATA << mOutgoingMetaData; |
730 | send(cmd: INF_META_DATA, arr: data); |
731 | } |
732 | KIO_DATA << _type; |
733 | send(cmd: INF_MIME_TYPE, arr: data); |
734 | while (true) { |
735 | cmd = 0; |
736 | int ret = -1; |
737 | if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms: -1)) { |
738 | ret = d->appConnection.read(cmd: &cmd, data); |
739 | } |
740 | if (ret == -1) { |
741 | qCDebug(KIO_CORE) << "read error on app connection while sending mimetype" ; |
742 | exit(); |
743 | break; |
744 | } |
745 | qCDebug(KIO_CORE) << "got reply after sending mimetype" << cmd; |
746 | if (cmd == CMD_HOST) { // Ignore. |
747 | continue; |
748 | } |
749 | if (!isSubCommand(cmd)) { |
750 | break; |
751 | } |
752 | |
753 | dispatch(command: cmd, data); |
754 | } |
755 | } while (cmd != CMD_NONE); |
756 | mOutgoingMetaData.clear(); |
757 | } |
758 | |
759 | void SlaveBase::exit() // possibly called from another thread, only use atomics in here |
760 | { |
761 | d->exit_loop = true; |
762 | if (d->runInThread) { |
763 | d->wasKilled = true; |
764 | } else { |
765 | // Using ::exit() here is too much (crashes in qdbus's qglobalstatic object), |
766 | // so let's cleanly exit dispatchLoop() instead. |
767 | // Update: we do need to call exit(), otherwise a long download (get()) would |
768 | // keep going until it ends, even though the application exited. |
769 | ::exit(status: 255); |
770 | } |
771 | } |
772 | |
773 | void SlaveBase::warning(const QString &_msg) |
774 | { |
775 | KIO_DATA << _msg; |
776 | send(cmd: INF_WARNING, arr: data); |
777 | } |
778 | |
779 | void SlaveBase::infoMessage(const QString &_msg) |
780 | { |
781 | KIO_DATA << _msg; |
782 | send(cmd: INF_INFOMESSAGE, arr: data); |
783 | } |
784 | |
785 | void SlaveBase::statEntry(const UDSEntry &entry) |
786 | { |
787 | KIO_DATA << entry; |
788 | send(cmd: MSG_STAT_ENTRY, arr: data); |
789 | } |
790 | |
791 | void SlaveBase::listEntry(const UDSEntry &entry) |
792 | { |
793 | // #366795: many slaves don't create an entry for ".", so we keep track if they do |
794 | // and we provide a fallback in finished() otherwise. |
795 | if (entry.stringValue(field: KIO::UDSEntry::UDS_NAME) == QLatin1Char('.')) { |
796 | d->m_rootEntryListed = true; |
797 | } |
798 | |
799 | // We start measuring the time from the point we start filling the list |
800 | if (d->pendingListEntries.isEmpty()) { |
801 | d->m_timeSinceLastBatch.restart(); |
802 | } |
803 | |
804 | d->pendingListEntries.append(t: entry); |
805 | |
806 | // If more then KIO_MAX_SEND_BATCH_TIME time is passed, emit the current batch |
807 | // Also emit if we have piled up a large number of entries already, to save memory (and time) |
808 | if (d->m_timeSinceLastBatch.elapsed() > KIO_MAX_SEND_BATCH_TIME || d->pendingListEntries.size() > KIO_MAX_ENTRIES_PER_BATCH) { |
809 | listEntries(entry: d->pendingListEntries); |
810 | d->pendingListEntries.clear(); |
811 | |
812 | // Restart time |
813 | d->m_timeSinceLastBatch.restart(); |
814 | } |
815 | } |
816 | |
817 | void SlaveBase::listEntries(const UDSEntryList &list) |
818 | { |
819 | QByteArray data; |
820 | QDataStream stream(&data, QIODevice::WriteOnly); |
821 | |
822 | for (const UDSEntry &entry : list) { |
823 | stream << entry; |
824 | } |
825 | |
826 | send(cmd: MSG_LIST_ENTRIES, arr: data); |
827 | } |
828 | |
829 | static void sigpipe_handler(int) |
830 | { |
831 | // We ignore a SIGPIPE in slaves. |
832 | // A SIGPIPE can happen in two cases: |
833 | // 1) Communication error with application. |
834 | // 2) Communication error with network. |
835 | slaveWriteError = true; |
836 | |
837 | // Don't add anything else here, especially no debug output |
838 | } |
839 | |
840 | void SlaveBase::setHost(QString const &, quint16, QString const &, QString const &) |
841 | { |
842 | } |
843 | |
844 | // TODO: move unsupportedActionErrorString() to workerbase.cpp |
845 | // once SlaveBase is dissolved and folded into WorkerBase |
846 | // forward declaration is already in workerbase.h |
847 | namespace KIO |
848 | { |
849 | KIOCORE_EXPORT QString unsupportedActionErrorString(const QString &protocol, int cmd) |
850 | { |
851 | switch (cmd) { |
852 | case CMD_CONNECT: |
853 | return i18n("Opening connections is not supported with the protocol %1." , protocol); |
854 | case CMD_DISCONNECT: |
855 | return i18n("Closing connections is not supported with the protocol %1." , protocol); |
856 | case CMD_STAT: |
857 | return i18n("Accessing files is not supported with the protocol %1." , protocol); |
858 | case CMD_PUT: |
859 | return i18n("Writing to %1 is not supported." , protocol); |
860 | case CMD_SPECIAL: |
861 | return i18n("There are no special actions available for protocol %1." , protocol); |
862 | case CMD_LISTDIR: |
863 | return i18n("Listing folders is not supported for protocol %1." , protocol); |
864 | case CMD_GET: |
865 | return i18n("Retrieving data from %1 is not supported." , protocol); |
866 | case CMD_MIMETYPE: |
867 | return i18n("Retrieving mime type information from %1 is not supported." , protocol); |
868 | case CMD_RENAME: |
869 | return i18n("Renaming or moving files within %1 is not supported." , protocol); |
870 | case CMD_SYMLINK: |
871 | return i18n("Creating symlinks is not supported with protocol %1." , protocol); |
872 | case CMD_COPY: |
873 | return i18n("Copying files within %1 is not supported." , protocol); |
874 | case CMD_DEL: |
875 | return i18n("Deleting files from %1 is not supported." , protocol); |
876 | case CMD_MKDIR: |
877 | return i18n("Creating folders is not supported with protocol %1." , protocol); |
878 | case CMD_CHMOD: |
879 | return i18n("Changing the attributes of files is not supported with protocol %1." , protocol); |
880 | case CMD_CHOWN: |
881 | return i18n("Changing the ownership of files is not supported with protocol %1." , protocol); |
882 | case CMD_OPEN: |
883 | return i18n("Opening files is not supported with protocol %1." , protocol); |
884 | default: |
885 | return i18n("Protocol %1 does not support action %2." , protocol, cmd); |
886 | } /*end switch*/ |
887 | } |
888 | } |
889 | |
890 | void SlaveBase::openConnection() |
891 | { |
892 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CONNECT)); |
893 | } |
894 | void SlaveBase::closeConnection() |
895 | { |
896 | } // No response! |
897 | void SlaveBase::stat(QUrl const &) |
898 | { |
899 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_STAT)); |
900 | } |
901 | void SlaveBase::put(QUrl const &, int, JobFlags) |
902 | { |
903 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_PUT)); |
904 | } |
905 | void SlaveBase::special(const QByteArray &) |
906 | { |
907 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SPECIAL)); |
908 | } |
909 | void SlaveBase::listDir(QUrl const &) |
910 | { |
911 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_LISTDIR)); |
912 | } |
913 | void SlaveBase::get(QUrl const &) |
914 | { |
915 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_GET)); |
916 | } |
917 | void SlaveBase::open(QUrl const &, QIODevice::OpenMode) |
918 | { |
919 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_OPEN)); |
920 | } |
921 | void SlaveBase::read(KIO::filesize_t) |
922 | { |
923 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_READ)); |
924 | } |
925 | void SlaveBase::write(const QByteArray &) |
926 | { |
927 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_WRITE)); |
928 | } |
929 | void SlaveBase::seek(KIO::filesize_t) |
930 | { |
931 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SEEK)); |
932 | } |
933 | void SlaveBase::close() |
934 | { |
935 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CLOSE)); |
936 | } |
937 | void SlaveBase::mimetype(QUrl const &url) |
938 | { |
939 | get(url); |
940 | } |
941 | void SlaveBase::rename(QUrl const &, QUrl const &, JobFlags) |
942 | { |
943 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_RENAME)); |
944 | } |
945 | void SlaveBase::symlink(QString const &, QUrl const &, JobFlags) |
946 | { |
947 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SYMLINK)); |
948 | } |
949 | void SlaveBase::copy(QUrl const &, QUrl const &, int, JobFlags) |
950 | { |
951 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_COPY)); |
952 | } |
953 | void SlaveBase::del(QUrl const &, bool) |
954 | { |
955 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_DEL)); |
956 | } |
957 | void SlaveBase::setLinkDest(const QUrl &, const QString &) |
958 | { |
959 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SETLINKDEST)); |
960 | } |
961 | void SlaveBase::mkdir(QUrl const &, int) |
962 | { |
963 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_MKDIR)); |
964 | } |
965 | void SlaveBase::chmod(QUrl const &, int) |
966 | { |
967 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CHMOD)); |
968 | } |
969 | void SlaveBase::setModificationTime(QUrl const &, const QDateTime &) |
970 | { |
971 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_SETMODIFICATIONTIME)); |
972 | } |
973 | void SlaveBase::chown(QUrl const &, const QString &, const QString &) |
974 | { |
975 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_CHOWN)); |
976 | } |
977 | |
978 | void SlaveBase::slave_status() |
979 | { |
980 | slaveStatus(host: QString(), connected: false); |
981 | } |
982 | |
983 | void SlaveBase::reparseConfiguration() |
984 | { |
985 | delete d->remotefile; |
986 | d->remotefile = nullptr; |
987 | } |
988 | |
989 | int SlaveBase::openPasswordDialogV2(AuthInfo &info, const QString &errorMsg) |
990 | { |
991 | const long windowId = metaData(QStringLiteral("window-id" )).toLong(); |
992 | const unsigned long userTimestamp = metaData(QStringLiteral("user-timestamp" )).toULong(); |
993 | QString errorMessage; |
994 | if (metaData(QStringLiteral("no-auth-prompt" )).compare(other: QLatin1String("true" ), cs: Qt::CaseInsensitive) == 0) { |
995 | errorMessage = QStringLiteral("<NoAuthPrompt>" ); |
996 | } else { |
997 | errorMessage = errorMsg; |
998 | } |
999 | |
1000 | AuthInfo dlgInfo(info); |
1001 | // Make sure the modified flag is not set. |
1002 | dlgInfo.setModified(false); |
1003 | // Prevent queryAuthInfo from caching the user supplied password since |
1004 | // we need the ioslaves to first authenticate against the server with |
1005 | // it to ensure it is valid. |
1006 | dlgInfo.setExtraField(QStringLiteral("skip-caching-on-query" ), value: true); |
1007 | |
1008 | #ifdef WITH_QTDBUS |
1009 | KPasswdServerClient *passwdServerClient = d->passwdServerClient(); |
1010 | const int errCode = passwdServerClient->queryAuthInfo(info: &dlgInfo, errorMsg: errorMessage, windowId, usertime: userTimestamp); |
1011 | if (errCode == KJob::NoError) { |
1012 | info = dlgInfo; |
1013 | } |
1014 | return errCode; |
1015 | #else |
1016 | return KJob::NoError; |
1017 | #endif |
1018 | } |
1019 | |
1020 | int SlaveBase::messageBox(MessageBoxType type, const QString &text, const QString &title, const QString &primaryActionText, const QString &secondaryActionText) |
1021 | { |
1022 | return messageBox(text, type, title, primaryActionText, secondaryActionText, dontAskAgainName: QString()); |
1023 | } |
1024 | |
1025 | int SlaveBase::messageBox(const QString &text, |
1026 | MessageBoxType type, |
1027 | const QString &title, |
1028 | const QString &primaryActionText, |
1029 | const QString &secondaryActionText, |
1030 | const QString &dontAskAgainName) |
1031 | { |
1032 | KIO_DATA << static_cast<qint32>(type) << text << title << primaryActionText << secondaryActionText << dontAskAgainName; |
1033 | send(cmd: INF_MESSAGEBOX, arr: data); |
1034 | if (waitForAnswer(expected1: CMD_MESSAGEBOXANSWER, expected2: 0, data) != -1) { |
1035 | QDataStream stream(data); |
1036 | int answer; |
1037 | stream >> answer; |
1038 | return answer; |
1039 | } else { |
1040 | return 0; // communication failure |
1041 | } |
1042 | } |
1043 | |
1044 | int SlaveBase::sslError(const QVariantMap &sslData) |
1045 | { |
1046 | KIO_DATA << sslData; |
1047 | send(cmd: INF_SSLERROR, arr: data); |
1048 | if (waitForAnswer(expected1: CMD_SSLERRORANSWER, expected2: 0, data) != -1) { |
1049 | QDataStream stream(data); |
1050 | int answer; |
1051 | stream >> answer; |
1052 | return answer; |
1053 | } else { |
1054 | return 0; // communication failure |
1055 | } |
1056 | } |
1057 | |
1058 | bool SlaveBase::canResume(KIO::filesize_t offset) |
1059 | { |
1060 | // qDebug() << "offset=" << KIO::number(offset); |
1061 | d->needSendCanResume = false; |
1062 | KIO_DATA << static_cast<quint64>(offset); |
1063 | send(cmd: MSG_RESUME, arr: data); |
1064 | if (offset) { |
1065 | int cmd; |
1066 | if (waitForAnswer(expected1: CMD_RESUMEANSWER, expected2: CMD_NONE, data, pCmd: &cmd) != -1) { |
1067 | // qDebug() << "returning" << (cmd == CMD_RESUMEANSWER); |
1068 | return cmd == CMD_RESUMEANSWER; |
1069 | } else { |
1070 | return false; |
1071 | } |
1072 | } else { // No resuming possible -> no answer to wait for |
1073 | return true; |
1074 | } |
1075 | } |
1076 | |
1077 | int SlaveBase::waitForAnswer(int expected1, int expected2, QByteArray &data, int *pCmd) |
1078 | { |
1079 | int cmd = 0; |
1080 | int result = -1; |
1081 | for (;;) { |
1082 | if (d->appConnection.hasTaskAvailable() || d->appConnection.waitForIncomingTask(ms: -1)) { |
1083 | result = d->appConnection.read(cmd: &cmd, data); |
1084 | } |
1085 | if (result == -1) { |
1086 | // qDebug() << "read error."; |
1087 | return -1; |
1088 | } |
1089 | |
1090 | if (cmd == expected1 || cmd == expected2) { |
1091 | if (pCmd) { |
1092 | *pCmd = cmd; |
1093 | } |
1094 | return result; |
1095 | } |
1096 | if (isSubCommand(cmd)) { |
1097 | dispatch(command: cmd, data); |
1098 | } else { |
1099 | qFatal(msg: "Fatal Error: Got cmd %d, while waiting for an answer!" , cmd); |
1100 | } |
1101 | } |
1102 | } |
1103 | |
1104 | int SlaveBase::readData(QByteArray &buffer) |
1105 | { |
1106 | int result = waitForAnswer(expected1: MSG_DATA, expected2: 0, data&: buffer); |
1107 | // qDebug() << "readData: length = " << result << " "; |
1108 | return result; |
1109 | } |
1110 | |
1111 | void SlaveBase::setTimeoutSpecialCommand(int timeout, const QByteArray &data) |
1112 | { |
1113 | if (timeout > 0) { |
1114 | d->nextTimeoutMsecs = timeout * 1000; // from seconds to milliseconds |
1115 | d->nextTimeout.start(); |
1116 | } else if (timeout == 0) { |
1117 | d->nextTimeoutMsecs = 1000; // Immediate timeout |
1118 | d->nextTimeout.start(); |
1119 | } else { |
1120 | d->nextTimeout.invalidate(); // Canceled |
1121 | } |
1122 | |
1123 | d->timeoutData = data; |
1124 | } |
1125 | |
1126 | void SlaveBase::dispatch(int command, const QByteArray &data) |
1127 | { |
1128 | QDataStream stream(data); |
1129 | |
1130 | QUrl url; |
1131 | int i; |
1132 | |
1133 | d->m_finalityCommand = true; // default |
1134 | |
1135 | switch (command) { |
1136 | case CMD_HOST: { |
1137 | QString passwd; |
1138 | QString host; |
1139 | QString user; |
1140 | quint16 port; |
1141 | stream >> host >> port >> user >> passwd; |
1142 | d->m_state = d->InsideMethod; |
1143 | d->m_finalityCommand = false; |
1144 | setHost(host, port, user, passwd); |
1145 | d->m_state = d->Idle; |
1146 | break; |
1147 | } |
1148 | case CMD_CONNECT: { |
1149 | openConnection(); |
1150 | break; |
1151 | } |
1152 | case CMD_DISCONNECT: { |
1153 | closeConnection(); |
1154 | break; |
1155 | } |
1156 | case CMD_WORKER_STATUS: { |
1157 | d->m_state = d->InsideMethod; |
1158 | d->m_finalityCommand = false; |
1159 | slave_status(); |
1160 | // TODO verify that the slave has called slaveStatus()? |
1161 | d->m_state = d->Idle; |
1162 | break; |
1163 | } |
1164 | case CMD_REPARSECONFIGURATION: { |
1165 | d->m_state = d->InsideMethod; |
1166 | d->m_finalityCommand = false; |
1167 | reparseConfiguration(); |
1168 | d->m_state = d->Idle; |
1169 | break; |
1170 | } |
1171 | case CMD_CONFIG: { |
1172 | stream >> d->configData; |
1173 | d->rebuildConfig(); |
1174 | delete d->remotefile; |
1175 | d->remotefile = nullptr; |
1176 | break; |
1177 | } |
1178 | case CMD_GET: { |
1179 | stream >> url; |
1180 | d->m_state = d->InsideMethod; |
1181 | get(url); |
1182 | d->verifyState(cmdName: "get()" ); |
1183 | d->m_state = d->Idle; |
1184 | break; |
1185 | } |
1186 | case CMD_OPEN: { |
1187 | stream >> url >> i; |
1188 | QIODevice::OpenMode mode = QFlag(i); |
1189 | d->m_state = d->InsideMethod; |
1190 | open(url, mode); // krazy:exclude=syscalls |
1191 | d->m_state = d->Idle; |
1192 | break; |
1193 | } |
1194 | case CMD_PUT: { |
1195 | int permissions; |
1196 | qint8 iOverwrite; |
1197 | qint8 iResume; |
1198 | stream >> url >> iOverwrite >> iResume >> permissions; |
1199 | JobFlags flags; |
1200 | if (iOverwrite != 0) { |
1201 | flags |= Overwrite; |
1202 | } |
1203 | if (iResume != 0) { |
1204 | flags |= Resume; |
1205 | } |
1206 | |
1207 | // Remember that we need to send canResume(), TransferJob is expecting |
1208 | // it. Well, in theory this shouldn't be done if resume is true. |
1209 | // (the resume bool is currently unused) |
1210 | d->needSendCanResume = true /* !resume */; |
1211 | |
1212 | d->m_state = d->InsideMethod; |
1213 | put(url, permissions, flags); |
1214 | d->verifyState(cmdName: "put()" ); |
1215 | d->m_state = d->Idle; |
1216 | break; |
1217 | } |
1218 | case CMD_STAT: { |
1219 | stream >> url; |
1220 | d->m_state = d->InsideMethod; |
1221 | stat(url); // krazy:exclude=syscalls |
1222 | d->verifyState(cmdName: "stat()" ); |
1223 | d->m_state = d->Idle; |
1224 | break; |
1225 | } |
1226 | case CMD_MIMETYPE: { |
1227 | stream >> url; |
1228 | d->m_state = d->InsideMethod; |
1229 | mimetype(url); |
1230 | d->verifyState(cmdName: "mimetype()" ); |
1231 | d->m_state = d->Idle; |
1232 | break; |
1233 | } |
1234 | case CMD_LISTDIR: { |
1235 | stream >> url; |
1236 | d->m_state = d->InsideMethod; |
1237 | listDir(url); |
1238 | d->verifyState(cmdName: "listDir()" ); |
1239 | d->m_state = d->Idle; |
1240 | break; |
1241 | } |
1242 | case CMD_MKDIR: { |
1243 | stream >> url >> i; |
1244 | d->m_state = d->InsideMethod; |
1245 | mkdir(url, i); // krazy:exclude=syscalls |
1246 | d->verifyState(cmdName: "mkdir()" ); |
1247 | d->m_state = d->Idle; |
1248 | break; |
1249 | } |
1250 | case CMD_RENAME: { |
1251 | qint8 iOverwrite; |
1252 | QUrl url2; |
1253 | stream >> url >> url2 >> iOverwrite; |
1254 | JobFlags flags; |
1255 | if (iOverwrite != 0) { |
1256 | flags |= Overwrite; |
1257 | } |
1258 | d->m_state = d->InsideMethod; |
1259 | rename(url, url2, flags); // krazy:exclude=syscalls |
1260 | d->verifyState(cmdName: "rename()" ); |
1261 | d->m_state = d->Idle; |
1262 | break; |
1263 | } |
1264 | case CMD_SYMLINK: { |
1265 | qint8 iOverwrite; |
1266 | QString target; |
1267 | stream >> target >> url >> iOverwrite; |
1268 | JobFlags flags; |
1269 | if (iOverwrite != 0) { |
1270 | flags |= Overwrite; |
1271 | } |
1272 | d->m_state = d->InsideMethod; |
1273 | symlink(target, url, flags); |
1274 | d->verifyState(cmdName: "symlink()" ); |
1275 | d->m_state = d->Idle; |
1276 | break; |
1277 | } |
1278 | case CMD_COPY: { |
1279 | int permissions; |
1280 | qint8 iOverwrite; |
1281 | QUrl url2; |
1282 | stream >> url >> url2 >> permissions >> iOverwrite; |
1283 | JobFlags flags; |
1284 | if (iOverwrite != 0) { |
1285 | flags |= Overwrite; |
1286 | } |
1287 | d->m_state = d->InsideMethod; |
1288 | copy(url, url2, permissions, flags); |
1289 | d->verifyState(cmdName: "copy()" ); |
1290 | d->m_state = d->Idle; |
1291 | break; |
1292 | } |
1293 | case CMD_DEL: { |
1294 | qint8 isFile; |
1295 | stream >> url >> isFile; |
1296 | d->m_state = d->InsideMethod; |
1297 | del(url, isFile != 0); |
1298 | d->verifyState(cmdName: "del()" ); |
1299 | d->m_state = d->Idle; |
1300 | break; |
1301 | } |
1302 | case CMD_CHMOD: { |
1303 | stream >> url >> i; |
1304 | d->m_state = d->InsideMethod; |
1305 | chmod(url, i); |
1306 | d->verifyState(cmdName: "chmod()" ); |
1307 | d->m_state = d->Idle; |
1308 | break; |
1309 | } |
1310 | case CMD_CHOWN: { |
1311 | QString owner; |
1312 | QString group; |
1313 | stream >> url >> owner >> group; |
1314 | d->m_state = d->InsideMethod; |
1315 | chown(url, owner, group); |
1316 | d->verifyState(cmdName: "chown()" ); |
1317 | d->m_state = d->Idle; |
1318 | break; |
1319 | } |
1320 | case CMD_SETMODIFICATIONTIME: { |
1321 | QDateTime dt; |
1322 | stream >> url >> dt; |
1323 | d->m_state = d->InsideMethod; |
1324 | setModificationTime(url, dt); |
1325 | d->verifyState(cmdName: "setModificationTime()" ); |
1326 | d->m_state = d->Idle; |
1327 | break; |
1328 | } |
1329 | case CMD_SPECIAL: { |
1330 | d->m_state = d->InsideMethod; |
1331 | special(data); |
1332 | d->verifyState(cmdName: "special()" ); |
1333 | d->m_state = d->Idle; |
1334 | break; |
1335 | } |
1336 | case CMD_META_DATA: { |
1337 | // qDebug() << "(" << getpid() << ") Incoming meta-data..."; |
1338 | stream >> mIncomingMetaData; |
1339 | d->rebuildConfig(); |
1340 | break; |
1341 | } |
1342 | case CMD_NONE: { |
1343 | qCWarning(KIO_CORE) << "Got unexpected CMD_NONE!" ; |
1344 | break; |
1345 | } |
1346 | case CMD_FILESYSTEMFREESPACE: { |
1347 | stream >> url; |
1348 | |
1349 | void *data = static_cast<void *>(&url); |
1350 | |
1351 | d->m_state = d->InsideMethod; |
1352 | virtual_hook(id: GetFileSystemFreeSpace, data); |
1353 | d->verifyState(cmdName: "fileSystemFreeSpace()" ); |
1354 | d->m_state = d->Idle; |
1355 | break; |
1356 | } |
1357 | default: { |
1358 | // Some command we don't understand. |
1359 | // Just ignore it, it may come from some future version of KIO. |
1360 | break; |
1361 | } |
1362 | } |
1363 | } |
1364 | |
1365 | bool SlaveBase::checkCachedAuthentication(AuthInfo &info) |
1366 | { |
1367 | #ifdef WITH_QTDBUS |
1368 | KPasswdServerClient *passwdServerClient = d->passwdServerClient(); |
1369 | return (passwdServerClient->checkAuthInfo(info: &info, windowId: metaData(QStringLiteral("window-id" )).toLong(), usertime: metaData(QStringLiteral("user-timestamp" )).toULong())); |
1370 | #else |
1371 | return false; |
1372 | #endif |
1373 | } |
1374 | |
1375 | void SlaveBase::dispatchOpenCommand(int command, const QByteArray &data) |
1376 | { |
1377 | QDataStream stream(data); |
1378 | |
1379 | switch (command) { |
1380 | case CMD_READ: { |
1381 | KIO::filesize_t bytes; |
1382 | stream >> bytes; |
1383 | read(bytes); |
1384 | break; |
1385 | } |
1386 | case CMD_WRITE: { |
1387 | write(data); |
1388 | break; |
1389 | } |
1390 | case CMD_SEEK: { |
1391 | KIO::filesize_t offset; |
1392 | stream >> offset; |
1393 | seek(offset); |
1394 | break; |
1395 | } |
1396 | case CMD_TRUNCATE: { |
1397 | KIO::filesize_t length; |
1398 | stream >> length; |
1399 | void *data = static_cast<void *>(&length); |
1400 | virtual_hook(id: Truncate, data); |
1401 | break; |
1402 | } |
1403 | case CMD_NONE: |
1404 | break; |
1405 | case CMD_CLOSE: |
1406 | close(); // must call finish(), which will set d->inOpenLoop=false |
1407 | break; |
1408 | default: |
1409 | // Some command we don't understand. |
1410 | // Just ignore it, it may come from some future version of KIO. |
1411 | break; |
1412 | } |
1413 | } |
1414 | |
1415 | bool SlaveBase::cacheAuthentication(const AuthInfo &info) |
1416 | { |
1417 | #ifdef WITH_QTDBUS |
1418 | KPasswdServerClient *passwdServerClient = d->passwdServerClient(); |
1419 | passwdServerClient->addAuthInfo(info, windowId: metaData(QStringLiteral("window-id" )).toLongLong()); |
1420 | #endif |
1421 | return true; |
1422 | } |
1423 | |
1424 | #if KIOCORE_BUILD_DEPRECATED_SINCE(6, 11) |
1425 | int SlaveBase::connectTimeout() |
1426 | { |
1427 | bool ok; |
1428 | QString tmp = metaData(QStringLiteral("ConnectTimeout" )); |
1429 | int result = tmp.toInt(ok: &ok); |
1430 | if (ok) { |
1431 | return result; |
1432 | } |
1433 | return DEFAULT_CONNECT_TIMEOUT; |
1434 | } |
1435 | #endif |
1436 | |
1437 | #if KIOCORE_BUILD_DEPRECATED_SINCE(6, 11) |
1438 | int SlaveBase::proxyConnectTimeout() |
1439 | { |
1440 | bool ok; |
1441 | QString tmp = metaData(QStringLiteral("ProxyConnectTimeout" )); |
1442 | int result = tmp.toInt(ok: &ok); |
1443 | if (ok) { |
1444 | return result; |
1445 | } |
1446 | return DEFAULT_PROXY_CONNECT_TIMEOUT; |
1447 | } |
1448 | #endif |
1449 | |
1450 | #if KIOCORE_BUILD_DEPRECATED_SINCE(6, 11) |
1451 | int SlaveBase::responseTimeout() |
1452 | { |
1453 | bool ok; |
1454 | QString tmp = metaData(QStringLiteral("ResponseTimeout" )); |
1455 | int result = tmp.toInt(ok: &ok); |
1456 | if (ok) { |
1457 | return result; |
1458 | } |
1459 | return DEFAULT_RESPONSE_TIMEOUT; |
1460 | } |
1461 | #endif |
1462 | |
1463 | #if KIOCORE_BUILD_DEPRECATED_SINCE(6, 11) |
1464 | int SlaveBase::readTimeout() |
1465 | { |
1466 | bool ok; |
1467 | QString tmp = metaData(QStringLiteral("ReadTimeout" )); |
1468 | int result = tmp.toInt(ok: &ok); |
1469 | if (ok) { |
1470 | return result; |
1471 | } |
1472 | return DEFAULT_READ_TIMEOUT; |
1473 | } |
1474 | #endif |
1475 | |
1476 | bool SlaveBase::wasKilled() const |
1477 | { |
1478 | return d->wasKilled; |
1479 | } |
1480 | |
1481 | void SlaveBase::setKillFlag() |
1482 | { |
1483 | d->wasKilled = true; |
1484 | } |
1485 | |
1486 | void SlaveBase::send(int cmd, const QByteArray &arr) |
1487 | { |
1488 | if (d->runInThread) { |
1489 | if (!d->appConnection.send(cmd, arr)) { |
1490 | exit(); |
1491 | } |
1492 | } else { |
1493 | slaveWriteError = false; |
1494 | if (!d->appConnection.send(cmd, arr)) |
1495 | // Note that slaveWriteError can also be set by sigpipe_handler |
1496 | { |
1497 | slaveWriteError = true; |
1498 | } |
1499 | if (slaveWriteError) { |
1500 | qCWarning(KIO_CORE) << "An error occurred during write. The worker terminates now." ; |
1501 | exit(); |
1502 | } |
1503 | } |
1504 | } |
1505 | |
1506 | void SlaveBase::virtual_hook(int id, void *data) |
1507 | { |
1508 | Q_UNUSED(data); |
1509 | |
1510 | switch (id) { |
1511 | case GetFileSystemFreeSpace: { |
1512 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_FILESYSTEMFREESPACE)); |
1513 | break; |
1514 | } |
1515 | case Truncate: { |
1516 | error(errid: ERR_UNSUPPORTED_ACTION, text: unsupportedActionErrorString(protocol: protocolName(), cmd: CMD_TRUNCATE)); |
1517 | break; |
1518 | } |
1519 | } |
1520 | } |
1521 | |
1522 | void SlaveBase::setRunInThread(bool b) |
1523 | { |
1524 | d->runInThread = b; |
1525 | } |
1526 | |
1527 | void SlaveBase::lookupHost(const QString &host) |
1528 | { |
1529 | KIO_DATA << host; |
1530 | send(cmd: MSG_HOST_INFO_REQ, arr: data); |
1531 | } |
1532 | |
1533 | int SlaveBase::waitForHostInfo(QHostInfo &info) |
1534 | { |
1535 | QByteArray data; |
1536 | int result = waitForAnswer(expected1: CMD_HOST_INFO, expected2: 0, data); |
1537 | |
1538 | if (result == -1) { |
1539 | info.setError(QHostInfo::UnknownError); |
1540 | info.setErrorString(i18n("Unknown Error" )); |
1541 | return result; |
1542 | } |
1543 | |
1544 | QDataStream stream(data); |
1545 | QString hostName; |
1546 | QList<QHostAddress> addresses; |
1547 | int error; |
1548 | QString errorString; |
1549 | |
1550 | stream >> hostName >> addresses >> error >> errorString; |
1551 | |
1552 | info.setHostName(hostName); |
1553 | info.setAddresses(addresses); |
1554 | info.setError(QHostInfo::HostInfoError(error)); |
1555 | info.setErrorString(errorString); |
1556 | |
1557 | return result; |
1558 | } |
1559 | |
1560 | PrivilegeOperationStatus SlaveBase::requestPrivilegeOperation(const QString &operationDetails) |
1561 | { |
1562 | if (d->m_privilegeOperationStatus == OperationNotAllowed) { |
1563 | QByteArray buffer; |
1564 | send(cmd: MSG_PRIVILEGE_EXEC); |
1565 | waitForAnswer(expected1: MSG_PRIVILEGE_EXEC, expected2: 0, data&: buffer); |
1566 | QDataStream ds(buffer); |
1567 | ds >> d->m_privilegeOperationStatus >> d->m_warningTitle >> d->m_warningMessage; |
1568 | } |
1569 | |
1570 | if (metaData(QStringLiteral("UnitTesting" )) != QLatin1String("true" ) && d->m_privilegeOperationStatus == OperationAllowed && !d->m_confirmationAsked) { |
1571 | // WORKER_MESSAGEBOX_DETAILS_HACK |
1572 | // SlaveBase::messageBox() overloads miss a parameter to pass an details argument. |
1573 | // As workaround details are passed instead via metadata before and then cached by the WorkerInterface, |
1574 | // to be used in the upcoming messageBox call (needs WarningContinueCancelDetailed type) |
1575 | // TODO: add a messageBox() overload taking details and use here, |
1576 | // then remove or adapt all code marked with WORKER_MESSAGEBOX_DETAILS |
1577 | setMetaData(QStringLiteral("privilege_conf_details" ), value: operationDetails); |
1578 | sendMetaData(); |
1579 | |
1580 | int result = messageBox(text: d->m_warningMessage, type: WarningContinueCancelDetailed, title: d->m_warningTitle, primaryActionText: QString(), secondaryActionText: QString(), dontAskAgainName: QString()); |
1581 | d->m_privilegeOperationStatus = result == Continue ? OperationAllowed : OperationCanceled; |
1582 | d->m_confirmationAsked = true; |
1583 | } |
1584 | |
1585 | return KIO::PrivilegeOperationStatus(d->m_privilegeOperationStatus); |
1586 | } |
1587 | |
1588 | void SlaveBase::addTemporaryAuthorization(const QString &action) |
1589 | { |
1590 | d->m_tempAuths.insert(value: action); |
1591 | } |
1592 | |