File indexing completed on 2024-05-12 05:52:02
0001 // The MIT License (MIT) 0002 // 0003 // Copyright (c) Itay Grudev 2015 - 2020 0004 // 0005 // Permission is hereby granted, free of charge, to any person obtaining a copy 0006 // of this software and associated documentation files (the "Software"), to deal 0007 // in the Software without restriction, including without limitation the rights 0008 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 0009 // copies of the Software, and to permit persons to whom the Software is 0010 // furnished to do so, subject to the following conditions: 0011 // 0012 // The above copyright notice and this permission notice shall be included in 0013 // all copies or substantial portions of the Software. 0014 // 0015 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0016 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 0017 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 0018 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 0019 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 0020 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 0021 // THE SOFTWARE. 0022 0023 // 0024 // W A R N I N G !!! 0025 // ----------------- 0026 // 0027 // This file is not part of the SingleApplication API. It is used purely as an 0028 // implementation detail. This header file may change from version to 0029 // version without notice, or may even be removed. 0030 // 0031 0032 #include <cstdlib> 0033 #include <cstddef> 0034 0035 #include <QtCore/QThread> 0036 #include <QtCore/QByteArray> 0037 #include <QtCore/QDataStream> 0038 #include <QtCore/QElapsedTimer> 0039 #include <QtCore/QCryptographicHash> 0040 0041 #include <QtCore/QRandomGenerator> 0042 0043 #include "singleapplication_p.h" 0044 0045 #ifdef Q_OS_UNIX 0046 #include <unistd.h> 0047 #include <sys/types.h> 0048 #include <pwd.h> 0049 #endif 0050 0051 #ifdef Q_OS_WIN 0052 #ifndef NOMINMAX 0053 #define NOMINMAX 1 0054 #endif 0055 #include <windows.h> 0056 #include <lmcons.h> 0057 #endif 0058 0059 SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) 0060 : q_ptr( q_ptr ) 0061 { 0062 server = nullptr; 0063 socket = nullptr; 0064 memory = nullptr; 0065 instanceNumber = 0; 0066 } 0067 0068 SingleApplicationPrivate::~SingleApplicationPrivate() 0069 { 0070 if( socket != nullptr ){ 0071 socket->close(); 0072 delete socket; 0073 } 0074 0075 if( memory != nullptr ){ 0076 memory->lock(); 0077 auto *inst = static_cast<InstancesInfo*>(memory->data()); 0078 if( server != nullptr ){ 0079 server->close(); 0080 delete server; 0081 inst->primary = false; 0082 inst->primaryPid = -1; 0083 inst->primaryUser[0] = '\0'; 0084 inst->checksum = blockChecksum(); 0085 } 0086 memory->unlock(); 0087 0088 delete memory; 0089 } 0090 } 0091 0092 QString SingleApplicationPrivate::getUsername() 0093 { 0094 #ifdef Q_OS_WIN 0095 wchar_t username[UNLEN + 1]; 0096 // Specifies size of the buffer on input 0097 DWORD usernameLength = UNLEN + 1; 0098 if( GetUserNameW( username, &usernameLength ) ) 0099 return QString::fromWCharArray( username ); 0100 return qEnvironmentVariable( "USERNAME" ); 0101 #endif 0102 #ifdef Q_OS_UNIX 0103 QString username; 0104 uid_t uid = geteuid(); 0105 struct passwd *pw = getpwuid( uid ); 0106 if( pw ) 0107 username = QString::fromLocal8Bit( pw->pw_name ); 0108 if ( username.isEmpty() ){ 0109 username = qEnvironmentVariable( "USER" ); 0110 } 0111 return username; 0112 #endif 0113 } 0114 0115 void SingleApplicationPrivate::genBlockServerName() 0116 { 0117 QCryptographicHash appData( QCryptographicHash::Sha256 ); 0118 #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) 0119 appData.addData( "SingleApplication", 17 ); 0120 #else 0121 appData.addData( QByteArrayView{"SingleApplication"} ); 0122 #endif 0123 appData.addData( SingleApplication::app_t::applicationName().toUtf8() ); 0124 appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); 0125 appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); 0126 0127 if ( ! appDataList.isEmpty() ) 0128 appData.addData( appDataList.join(QString()).toUtf8() ); 0129 0130 if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){ 0131 appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); 0132 } 0133 0134 if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){ 0135 #if defined(Q_OS_WIN) 0136 appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() ); 0137 #elif defined(Q_OS_LINUX) 0138 // If the application is running as an AppImage then the APPIMAGE env var should be used 0139 // instead of applicationPath() as each instance is launched with its own executable path 0140 const QByteArray appImagePath = qgetenv( "APPIMAGE" ); 0141 if( appImagePath.isEmpty() ){ // Not running as AppImage: use path to executable file 0142 appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); 0143 } else { // Running as AppImage: Use absolute path to AppImage file 0144 appData.addData( appImagePath ); 0145 }; 0146 #else 0147 appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); 0148 #endif 0149 } 0150 0151 // User level block requires a user specific data in the hash 0152 if( options & SingleApplication::Mode::User ){ 0153 appData.addData( getUsername().toUtf8() ); 0154 } 0155 0156 // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with 0157 // server naming requirements. 0158 blockServerName = QString::fromUtf8(appData.result().toBase64().replace("/", "_")); 0159 } 0160 0161 void SingleApplicationPrivate::initializeMemoryBlock() const 0162 { 0163 auto *inst = static_cast<InstancesInfo*>( memory->data() ); 0164 inst->primary = false; 0165 inst->secondary = 0; 0166 inst->primaryPid = -1; 0167 inst->primaryUser[0] = '\0'; 0168 inst->checksum = blockChecksum(); 0169 } 0170 0171 void SingleApplicationPrivate::startPrimary() 0172 { 0173 // Reset the number of connections 0174 auto *inst = static_cast <InstancesInfo*>( memory->data() ); 0175 0176 inst->primary = true; 0177 inst->primaryPid = QCoreApplication::applicationPid(); 0178 qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) ); 0179 inst->checksum = blockChecksum(); 0180 instanceNumber = 0; 0181 // Successful creation means that no main process exists 0182 // So we start a QLocalServer to listen for connections 0183 QLocalServer::removeServer( blockServerName ); 0184 server = new QLocalServer(); 0185 0186 // Restrict access to the socket according to the 0187 // SingleApplication::Mode::User flag on User level or no restrictions 0188 if( options & SingleApplication::Mode::User ){ 0189 server->setSocketOptions( QLocalServer::UserAccessOption ); 0190 } else { 0191 server->setSocketOptions( QLocalServer::WorldAccessOption ); 0192 } 0193 0194 server->listen( blockServerName ); 0195 QObject::connect( 0196 server, 0197 &QLocalServer::newConnection, 0198 this, 0199 &SingleApplicationPrivate::slotConnectionEstablished 0200 ); 0201 } 0202 0203 void SingleApplicationPrivate::startSecondary() 0204 { 0205 auto *inst = static_cast <InstancesInfo*>( memory->data() ); 0206 0207 inst->secondary += 1; 0208 inst->checksum = blockChecksum(); 0209 instanceNumber = inst->secondary; 0210 } 0211 0212 bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) 0213 { 0214 QElapsedTimer time; 0215 time.start(); 0216 0217 // Connect to the Local Server of the Primary Instance if not already 0218 // connected. 0219 if( socket == nullptr ){ 0220 socket = new QLocalSocket(); 0221 } 0222 0223 if( socket->state() == QLocalSocket::ConnectedState ) return true; 0224 0225 if( socket->state() != QLocalSocket::ConnectedState ){ 0226 0227 while( true ){ 0228 randomSleep(); 0229 0230 if( socket->state() != QLocalSocket::ConnectingState ) 0231 socket->connectToServer( blockServerName ); 0232 0233 if( socket->state() == QLocalSocket::ConnectingState ){ 0234 socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) ); 0235 } 0236 0237 // If connected break out of the loop 0238 if( socket->state() == QLocalSocket::ConnectedState ) break; 0239 0240 // If elapsed time since start is longer than the method timeout return 0241 if( time.elapsed() >= msecs ) return false; 0242 } 0243 } 0244 0245 // Initialisation message according to the SingleApplication protocol 0246 QByteArray initMsg; 0247 QDataStream writeStream(&initMsg, QIODevice::WriteOnly); 0248 0249 writeStream.setVersion(QDataStream::Qt_5_6); 0250 0251 writeStream << blockServerName.toLatin1(); 0252 writeStream << static_cast<quint8>(connectionType); 0253 writeStream << instanceNumber; 0254 quint16 checksum = qChecksum(QByteArray(initMsg.constData(), static_cast<quint32>(initMsg.length()))); 0255 writeStream << checksum; 0256 0257 return writeConfirmedMessage( static_cast<int>(msecs - time.elapsed()), initMsg ); 0258 } 0259 0260 void SingleApplicationPrivate::writeAck( QLocalSocket *sock ) { 0261 sock->putChar('\n'); 0262 } 0263 0264 bool SingleApplicationPrivate::writeConfirmedMessage (int msecs, const QByteArray &msg, SingleApplication::SendMode sendMode) 0265 { 0266 QElapsedTimer time; 0267 time.start(); 0268 0269 // Frame 1: The header indicates the message length that follows 0270 QByteArray header; 0271 QDataStream headerStream(&header, QIODevice::WriteOnly); 0272 0273 headerStream.setVersion(QDataStream::Qt_5_6); 0274 headerStream << static_cast <quint64>( msg.length() ); 0275 0276 if( ! writeConfirmedFrame( static_cast<int>(msecs - time.elapsed()), header )) 0277 return false; 0278 0279 // Frame 2: The message 0280 const bool result = writeConfirmedFrame( static_cast<int>(msecs - time.elapsed()), msg ); 0281 0282 // Block if needed 0283 if (socket && sendMode == SingleApplication::BlockUntilPrimaryExit) 0284 socket->waitForDisconnected(-1); 0285 0286 return result; 0287 } 0288 0289 bool SingleApplicationPrivate::writeConfirmedFrame( int msecs, const QByteArray &msg ) 0290 { 0291 socket->write( msg ); 0292 socket->flush(); 0293 0294 bool result = socket->waitForReadyRead( msecs ); // await ack byte 0295 if (result) { 0296 socket->read( 1 ); 0297 return true; 0298 } 0299 0300 return false; 0301 } 0302 0303 quint16 SingleApplicationPrivate::blockChecksum() const 0304 { 0305 quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum))); 0306 return checksum; 0307 } 0308 0309 qint64 SingleApplicationPrivate::primaryPid() const 0310 { 0311 qint64 pid; 0312 0313 memory->lock(); 0314 auto *inst = static_cast<InstancesInfo*>( memory->data() ); 0315 pid = inst->primaryPid; 0316 memory->unlock(); 0317 0318 return pid; 0319 } 0320 0321 QString SingleApplicationPrivate::primaryUser() const 0322 { 0323 QByteArray username; 0324 0325 memory->lock(); 0326 auto *inst = static_cast<InstancesInfo*>( memory->data() ); 0327 username = inst->primaryUser; 0328 memory->unlock(); 0329 0330 return QString::fromUtf8( username ); 0331 } 0332 0333 /** 0334 * @brief Executed when a connection has been made to the LocalServer 0335 */ 0336 void SingleApplicationPrivate::slotConnectionEstablished() 0337 { 0338 QLocalSocket *nextConnSocket = server->nextPendingConnection(); 0339 connectionMap.insert(nextConnSocket, ConnectionInfo()); 0340 0341 QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, 0342 [nextConnSocket, this](){ 0343 auto &info = connectionMap[nextConnSocket]; 0344 this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); 0345 } 0346 ); 0347 0348 QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); 0349 0350 QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, 0351 [nextConnSocket, this](){ 0352 connectionMap.remove(nextConnSocket); 0353 } 0354 ); 0355 0356 QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, 0357 [nextConnSocket, this](){ 0358 auto &info = connectionMap[nextConnSocket]; 0359 switch(info.stage){ 0360 case StageInitHeader: 0361 readMessageHeader( nextConnSocket, StageInitBody ); 0362 break; 0363 case StageInitBody: 0364 readInitMessageBody(nextConnSocket); 0365 break; 0366 case StageConnectedHeader: 0367 readMessageHeader( nextConnSocket, StageConnectedBody ); 0368 break; 0369 case StageConnectedBody: 0370 this->slotDataAvailable( nextConnSocket, info.instanceId ); 0371 break; 0372 default: 0373 break; 0374 }; 0375 } 0376 ); 0377 } 0378 0379 void SingleApplicationPrivate::readMessageHeader( QLocalSocket *sock, SingleApplicationPrivate::ConnectionStage nextStage ) 0380 { 0381 if (!connectionMap.contains( sock )){ 0382 return; 0383 } 0384 0385 if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){ 0386 return; 0387 } 0388 0389 QDataStream headerStream( sock ); 0390 0391 headerStream.setVersion( QDataStream::Qt_5_6 ); 0392 0393 // Read the header to know the message length 0394 quint64 msgLen = 0; 0395 headerStream >> msgLen; 0396 ConnectionInfo &info = connectionMap[sock]; 0397 info.stage = nextStage; 0398 info.msgLen = msgLen; 0399 0400 writeAck( sock ); 0401 } 0402 0403 bool SingleApplicationPrivate::isFrameComplete( QLocalSocket *sock ) 0404 { 0405 if (!connectionMap.contains( sock )){ 0406 return false; 0407 } 0408 0409 ConnectionInfo &info = connectionMap[sock]; 0410 if( sock->bytesAvailable() < ( qint64 )info.msgLen ){ 0411 return false; 0412 } 0413 0414 return true; 0415 } 0416 0417 void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) 0418 { 0419 Q_Q(SingleApplication); 0420 0421 if( !isFrameComplete( sock ) ) 0422 return; 0423 0424 // Read the message body 0425 QByteArray msgBytes = sock->readAll(); 0426 QDataStream readStream(msgBytes); 0427 0428 readStream.setVersion( QDataStream::Qt_5_6 ); 0429 0430 // server name 0431 QByteArray latin1Name; 0432 readStream >> latin1Name; 0433 0434 // connection type 0435 ConnectionType connectionType = InvalidConnection; 0436 quint8 connTypeVal = InvalidConnection; 0437 readStream >> connTypeVal; 0438 connectionType = static_cast <ConnectionType>( connTypeVal ); 0439 0440 // instance id 0441 quint32 instanceId = 0; 0442 readStream >> instanceId; 0443 0444 // checksum 0445 quint16 msgChecksum = 0; 0446 readStream >> msgChecksum; 0447 0448 const quint16 actualChecksum = qChecksum(QByteArray(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)))); 0449 0450 bool isValid = readStream.status() == QDataStream::Ok && 0451 QLatin1String(latin1Name) == blockServerName && 0452 msgChecksum == actualChecksum; 0453 0454 if( !isValid ){ 0455 sock->close(); 0456 return; 0457 } 0458 0459 ConnectionInfo &info = connectionMap[sock]; 0460 info.instanceId = instanceId; 0461 info.stage = StageConnectedHeader; 0462 0463 if( connectionType == NewInstance || 0464 ( connectionType == SecondaryInstance && 0465 options & SingleApplication::Mode::SecondaryNotification ) ) 0466 { 0467 Q_EMIT q->instanceStarted(); 0468 } 0469 0470 writeAck( sock ); 0471 } 0472 0473 void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId ) 0474 { 0475 Q_Q(SingleApplication); 0476 0477 if ( !isFrameComplete( dataSocket ) ) 0478 return; 0479 0480 const QByteArray message = dataSocket->readAll(); 0481 0482 writeAck( dataSocket ); 0483 0484 ConnectionInfo &info = connectionMap[dataSocket]; 0485 info.stage = StageConnectedHeader; 0486 0487 Q_EMIT q->receivedMessage( instanceId, message); 0488 } 0489 0490 void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId ) 0491 { 0492 if( closedSocket->bytesAvailable() > 0 ) 0493 slotDataAvailable( closedSocket, instanceId ); 0494 } 0495 0496 void SingleApplicationPrivate::randomSleep() 0497 { 0498 QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u )); 0499 } 0500 0501 void SingleApplicationPrivate::addAppData(const QString &data) 0502 { 0503 appDataList.push_back(data); 0504 } 0505 0506 QStringList SingleApplicationPrivate::appData() const 0507 { 0508 return appDataList; 0509 } 0510 0511 #include "moc_singleapplication_p.cpp"