File indexing completed on 2024-04-28 07:43:43
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "connectionbackend_p.h" 0011 #include <KLocalizedString> 0012 #include <QCoreApplication> 0013 #include <QElapsedTimer> 0014 #include <QFile> 0015 #include <QLocalServer> 0016 #include <QLocalSocket> 0017 #include <QPointer> 0018 #include <QStandardPaths> 0019 #include <QTemporaryFile> 0020 #include <cerrno> 0021 0022 #include "kiocoredebug.h" 0023 0024 using namespace KIO; 0025 0026 ConnectionBackend::ConnectionBackend(QObject *parent) 0027 : QObject(parent) 0028 , state(Idle) 0029 , socket(nullptr) 0030 , len(-1) 0031 , cmd(0) 0032 , signalEmitted(false) 0033 { 0034 localServer = nullptr; 0035 } 0036 0037 ConnectionBackend::~ConnectionBackend() 0038 { 0039 } 0040 0041 void ConnectionBackend::setSuspended(bool enable) 0042 { 0043 if (state != Connected) { 0044 return; 0045 } 0046 Q_ASSERT(socket); 0047 Q_ASSERT(!localServer); // !tcpServer as well 0048 0049 if (enable) { 0050 // qCDebug(KIO_CORE) << socket << "suspending"; 0051 socket->setReadBufferSize(1); 0052 } else { 0053 // qCDebug(KIO_CORE) << socket << "resuming"; 0054 // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119 0055 socket->setReadBufferSize(StandardBufferSize); 0056 if (socket->bytesAvailable() >= HeaderSize) { 0057 // there are bytes available 0058 QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection); 0059 } 0060 0061 // We read all bytes here, but we don't use readAll() because we need 0062 // to read at least one byte (even if there isn't any) so that the 0063 // socket notifier is re-enabled 0064 QByteArray data = socket->read(socket->bytesAvailable() + 1); 0065 for (int i = data.size(); --i >= 0;) { 0066 socket->ungetChar(data[i]); 0067 } 0068 // Workaround Qt5 bug, readyRead isn't always emitted here... 0069 QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection); 0070 } 0071 } 0072 0073 bool ConnectionBackend::connectToRemote(const QUrl &url) 0074 { 0075 Q_ASSERT(state == Idle); 0076 Q_ASSERT(!socket); 0077 Q_ASSERT(!localServer); // !tcpServer as well 0078 0079 QLocalSocket *sock = new QLocalSocket(this); 0080 QString path = url.path(); 0081 sock->connectToServer(path); 0082 socket = sock; 0083 0084 connect(socket, &QIODevice::readyRead, this, &ConnectionBackend::socketReadyRead); 0085 connect(socket, &QLocalSocket::disconnected, this, &ConnectionBackend::socketDisconnected); 0086 state = Connected; 0087 return true; 0088 } 0089 0090 void ConnectionBackend::socketDisconnected() 0091 { 0092 state = Idle; 0093 Q_EMIT disconnected(); 0094 } 0095 0096 bool ConnectionBackend::listenForRemote() 0097 { 0098 Q_ASSERT(state == Idle); 0099 Q_ASSERT(!socket); 0100 Q_ASSERT(!localServer); // !tcpServer as well 0101 0102 const QString prefix = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); 0103 static QBasicAtomicInt s_socketCounter = Q_BASIC_ATOMIC_INITIALIZER(1); 0104 QString appName = QCoreApplication::instance()->applicationName(); 0105 appName.replace(QLatin1Char('/'), QLatin1Char('_')); // #357499 0106 QTemporaryFile socketfile(prefix + QLatin1Char('/') + appName + QStringLiteral("XXXXXX.%1.kioworker.socket").arg(s_socketCounter.fetchAndAddAcquire(1))); 0107 if (!socketfile.open()) { 0108 errorString = i18n("Unable to create KIO worker: %1", QString::fromUtf8(strerror(errno))); 0109 return false; 0110 } 0111 0112 QString sockname = socketfile.fileName(); 0113 address.clear(); 0114 address.setScheme(QStringLiteral("local")); 0115 address.setPath(sockname); 0116 socketfile.setAutoRemove(false); 0117 socketfile.remove(); // can't bind if there is such a file 0118 0119 localServer = new QLocalServer(this); 0120 if (!localServer->listen(sockname)) { 0121 errorString = localServer->errorString(); 0122 delete localServer; 0123 localServer = nullptr; 0124 return false; 0125 } 0126 0127 connect(localServer, &QLocalServer::newConnection, this, &ConnectionBackend::newConnection); 0128 0129 state = Listening; 0130 return true; 0131 } 0132 0133 bool ConnectionBackend::waitForIncomingTask(int ms) 0134 { 0135 Q_ASSERT(state == Connected); 0136 Q_ASSERT(socket); 0137 if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) { 0138 state = Idle; 0139 return false; // socket has probably closed, what do we do? 0140 } 0141 0142 signalEmitted = false; 0143 if (socket->bytesAvailable()) { 0144 socketReadyRead(); 0145 } 0146 if (signalEmitted) { 0147 return true; // there was enough data in the socket 0148 } 0149 0150 // not enough data in the socket, so wait for more 0151 QElapsedTimer timer; 0152 timer.start(); 0153 0154 while (socket->state() == QLocalSocket::LocalSocketState::ConnectedState && !signalEmitted && (ms == -1 || timer.elapsed() < ms)) { 0155 if (!socket->waitForReadyRead(ms == -1 ? -1 : ms - timer.elapsed())) { 0156 break; 0157 } 0158 } 0159 0160 if (signalEmitted) { 0161 return true; 0162 } 0163 if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) { 0164 state = Idle; 0165 } 0166 return false; 0167 } 0168 0169 bool ConnectionBackend::sendCommand(int cmd, const QByteArray &data) const 0170 { 0171 Q_ASSERT(state == Connected); 0172 Q_ASSERT(socket); 0173 0174 char buffer[HeaderSize + 2]; 0175 // KF6 TODO: check if this breaks 32bit support, 0176 // see https://invent.kde.org/frameworks/kio/-/merge_requests/1141#note_606633 0177 sprintf(buffer, "%6llx_%2x_", data.size(), cmd); 0178 socket->write(buffer, HeaderSize); 0179 socket->write(data); 0180 0181 // qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of" 0182 // << data.size() << "bytes (" << socket->bytesToWrite() 0183 // << "bytes left to write )"; 0184 0185 // blocking mode: 0186 while (socket->bytesToWrite() > 0 && socket->state() == QLocalSocket::LocalSocketState::ConnectedState) { 0187 socket->waitForBytesWritten(-1); 0188 } 0189 0190 return socket->state() == QLocalSocket::LocalSocketState::ConnectedState; 0191 } 0192 0193 ConnectionBackend *ConnectionBackend::nextPendingConnection() 0194 { 0195 Q_ASSERT(state == Listening); 0196 Q_ASSERT(localServer); 0197 Q_ASSERT(!socket); 0198 0199 // qCDebug(KIO_CORE) << "Got a new connection"; 0200 0201 QLocalSocket *newSocket = localServer->nextPendingConnection(); 0202 0203 if (!newSocket) { 0204 return nullptr; // there was no connection... 0205 } 0206 0207 ConnectionBackend *result = new ConnectionBackend(); 0208 result->state = Connected; 0209 result->socket = newSocket; 0210 newSocket->setParent(result); 0211 connect(newSocket, &QIODevice::readyRead, result, &ConnectionBackend::socketReadyRead); 0212 connect(newSocket, &QLocalSocket::disconnected, result, &ConnectionBackend::socketDisconnected); 0213 0214 return result; 0215 } 0216 0217 void ConnectionBackend::socketReadyRead() 0218 { 0219 bool shouldReadAnother; 0220 do { 0221 if (!socket) 0222 // might happen if the invokeMethods were delivered after we disconnected 0223 { 0224 return; 0225 } 0226 0227 // qCDebug(KIO_CORE) << this << "Got" << socket->bytesAvailable() << "bytes"; 0228 if (len == -1) { 0229 // We have to read the header 0230 char buffer[HeaderSize]; 0231 0232 if (socket->bytesAvailable() < HeaderSize) { 0233 return; // wait for more data 0234 } 0235 0236 socket->read(buffer, sizeof buffer); 0237 buffer[6] = 0; 0238 buffer[9] = 0; 0239 0240 char *p = buffer; 0241 while (*p == ' ') { 0242 p++; 0243 } 0244 len = strtol(p, nullptr, 16); 0245 0246 p = buffer + 7; 0247 while (*p == ' ') { 0248 p++; 0249 } 0250 cmd = strtol(p, nullptr, 16); 0251 0252 // qCDebug(KIO_CORE) << this << "Beginning of command" << hex << cmd << "of size" << len; 0253 } 0254 0255 QPointer<ConnectionBackend> that = this; 0256 0257 // qCDebug(KIO_CORE) << socket << "Want to read" << len << "bytes"; 0258 if (socket->bytesAvailable() >= len) { 0259 Task task; 0260 task.cmd = cmd; 0261 if (len) { 0262 task.data = socket->read(len); 0263 } 0264 len = -1; 0265 0266 signalEmitted = true; 0267 Q_EMIT commandReceived(task); 0268 } else if (len > StandardBufferSize) { 0269 qCDebug(KIO_CORE) << socket << "Jumbo packet of" << len << "bytes"; 0270 // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119 0271 socket->setReadBufferSize(len + 1); 0272 } 0273 0274 // If we're dead, better don't try anything. 0275 if (that.isNull()) { 0276 return; 0277 } 0278 0279 // Do we have enough for an another read? 0280 if (len == -1) { 0281 shouldReadAnother = socket->bytesAvailable() >= HeaderSize; 0282 } else { 0283 shouldReadAnother = socket->bytesAvailable() >= len; 0284 } 0285 } while (shouldReadAnother); 0286 } 0287 0288 #include "moc_connectionbackend_p.cpp"