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