File indexing completed on 2023-09-24 04:08:28
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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0176 sprintf(buffer, "%6llx_%2x_", data.size(), cmd); 0177 #else 0178 sprintf(buffer, "%6x_%2x_", data.size(), cmd); 0179 #endif 0180 socket->write(buffer, HeaderSize); 0181 socket->write(data); 0182 0183 // qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of" 0184 // << data.size() << "bytes (" << socket->bytesToWrite() 0185 // << "bytes left to write )"; 0186 0187 // blocking mode: 0188 while (socket->bytesToWrite() > 0 && socket->state() == QLocalSocket::LocalSocketState::ConnectedState) { 0189 socket->waitForBytesWritten(-1); 0190 } 0191 0192 return socket->state() == QLocalSocket::LocalSocketState::ConnectedState; 0193 } 0194 0195 ConnectionBackend *ConnectionBackend::nextPendingConnection() 0196 { 0197 Q_ASSERT(state == Listening); 0198 Q_ASSERT(localServer); 0199 Q_ASSERT(!socket); 0200 0201 // qCDebug(KIO_CORE) << "Got a new connection"; 0202 0203 QLocalSocket *newSocket = localServer->nextPendingConnection(); 0204 0205 if (!newSocket) { 0206 return nullptr; // there was no connection... 0207 } 0208 0209 ConnectionBackend *result = new ConnectionBackend(); 0210 result->state = Connected; 0211 result->socket = newSocket; 0212 newSocket->setParent(result); 0213 connect(newSocket, &QIODevice::readyRead, result, &ConnectionBackend::socketReadyRead); 0214 connect(newSocket, &QLocalSocket::disconnected, result, &ConnectionBackend::socketDisconnected); 0215 0216 return result; 0217 } 0218 0219 void ConnectionBackend::socketReadyRead() 0220 { 0221 bool shouldReadAnother; 0222 do { 0223 if (!socket) 0224 // might happen if the invokeMethods were delivered after we disconnected 0225 { 0226 return; 0227 } 0228 0229 // qCDebug(KIO_CORE) << this << "Got" << socket->bytesAvailable() << "bytes"; 0230 if (len == -1) { 0231 // We have to read the header 0232 char buffer[HeaderSize]; 0233 0234 if (socket->bytesAvailable() < HeaderSize) { 0235 return; // wait for more data 0236 } 0237 0238 socket->read(buffer, sizeof buffer); 0239 buffer[6] = 0; 0240 buffer[9] = 0; 0241 0242 char *p = buffer; 0243 while (*p == ' ') { 0244 p++; 0245 } 0246 len = strtol(p, nullptr, 16); 0247 0248 p = buffer + 7; 0249 while (*p == ' ') { 0250 p++; 0251 } 0252 cmd = strtol(p, nullptr, 16); 0253 0254 // qCDebug(KIO_CORE) << this << "Beginning of command" << hex << cmd << "of size" << len; 0255 } 0256 0257 QPointer<ConnectionBackend> that = this; 0258 0259 // qCDebug(KIO_CORE) << socket << "Want to read" << len << "bytes"; 0260 if (socket->bytesAvailable() >= len) { 0261 Task task; 0262 task.cmd = cmd; 0263 if (len) { 0264 task.data = socket->read(len); 0265 } 0266 len = -1; 0267 0268 signalEmitted = true; 0269 Q_EMIT commandReceived(task); 0270 } else if (len > StandardBufferSize) { 0271 qCDebug(KIO_CORE) << socket << "Jumbo packet of" << len << "bytes"; 0272 // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119 0273 socket->setReadBufferSize(len + 1); 0274 } 0275 0276 // If we're dead, better don't try anything. 0277 if (that.isNull()) { 0278 return; 0279 } 0280 0281 // Do we have enough for an another read? 0282 if (len == -1) { 0283 shouldReadAnother = socket->bytesAvailable() >= HeaderSize; 0284 } else { 0285 shouldReadAnother = socket->bytesAvailable() >= len; 0286 } 0287 } while (shouldReadAnother); 0288 } 0289 0290 #include "moc_connectionbackend_p.cpp"