diff --git a/3rdparty/singleapplication/singleapplication.cpp b/3rdparty/singleapplication/singleapplication.cpp index be1e7d833..a4b2dfc85 100644 --- a/3rdparty/singleapplication/singleapplication.cpp +++ b/3rdparty/singleapplication/singleapplication.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -35,149 +35,207 @@ #include #include -#include +#include #include #include #include #include #include #include -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) -# include -#else -# include -#endif #include "singleapplication.h" #include "singleapplication_p.h" /** - * @brief Constructor. - * Checks and fires up LocalServer or closes the program if another instance already exists + * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists * @param argc * @param argv - * @param {bool} allowSecondaryInstances + * @param allowSecondary Whether to enable secondary instance support + * @param options Optional flags to toggle specific behaviour + * @param timeout Maximum time blocking functions are allowed during app load */ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout) - : app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) { + : app_t(argc, argv), + d_ptr(new SingleApplicationPrivate(this)) { Q_D(SingleApplication); // Store the current mode of the program - d->options = options; + d->options_ = options; // Generating an application ID used for identifying the shared memory block and QLocalServer d->genBlockServerName(); + // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time + d->randomSleep(); + #ifdef Q_OS_UNIX - // By explicitly attaching it and then deleting it we make sure that the - // memory is deleted even after the process has crashed on Unix. - d->memory = new QSharedMemory(d->blockServerName); - d->memory->attach(); - delete d->memory; + // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix. + d->memory_ = new QSharedMemory(d->blockServerName_); + d->memory_->attach(); + delete d->memory_; #endif + // Guarantee thread safe behaviour with a shared memory block. - d->memory = new QSharedMemory(d->blockServerName); + d->memory_ = new QSharedMemory(d->blockServerName_); // Create a shared memory block - if (d->memory->create(sizeof(InstancesInfo))) { + if (d->memory_->create(sizeof(InstancesInfo))) { // Initialize the shared memory block - d->memory->lock(); + if (!d->memory_->lock()) { + qCritical() << "SingleApplication: Unable to lock memory block after create."; + abortSafely(); + } d->initializeMemoryBlock(); - d->memory->unlock(); } else { - // Attempt to attach to the memory segment - if (!d->memory->attach()) { - qCritical() << "SingleApplication: Unable to attach to shared memory block."; - qCritical() << d->memory->errorString(); - delete d; - ::exit(EXIT_FAILURE); + if (d->memory_->error() == QSharedMemory::AlreadyExists) { + // Attempt to attach to the memory segment + if (!d->memory_->attach()) { + qCritical() << "SingleApplication: Unable to attach to shared memory block."; + abortSafely(); + } + if (!d->memory_->lock()) { + qCritical() << "SingleApplication: Unable to lock memory block after attach."; + abortSafely(); + } + } + else { + qCritical() << "SingleApplication: Unable to create block."; + abortSafely(); } } - InstancesInfo *inst = static_cast(d->memory->data()); + InstancesInfo *inst = static_cast(d->memory_->data()); QElapsedTimer time; time.start(); // Make sure the shared memory block is initialised and in consistent state - while (true) { - d->memory->lock(); - + forever { + // If the shared memory block's checksum is valid continue if (d->blockChecksum() == inst->checksum) break; + // If more than 5s have elapsed, assume the primary instance crashed and assume it's position if (time.elapsed() > 5000) { qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; d->initializeMemoryBlock(); } - d->memory->unlock(); - - // Random sleep here limits the probability of a collision between two racing apps -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u)); -#else - qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); - QThread::sleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); -#endif + // Otherwise wait for a random period and try again. + // The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster + if (!d->memory_->unlock()) { + qDebug() << "SingleApplication: Unable to unlock memory for random wait."; + qDebug() << d->memory_->errorString(); + } + d->randomSleep(); + if (!d->memory_->lock()) { + qCritical() << "SingleApplication: Unable to lock memory after random wait."; + abortSafely(); + } } if (inst->primary == false) { d->startPrimary(); - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleApplication: Unable to unlock memory after primary start."; + qDebug() << d->memory_->errorString(); + } return; } // Check if another instance can be started if (allowSecondary) { - inst->secondary += 1; - inst->checksum = d->blockChecksum(); - d->instanceNumber = inst->secondary; d->startSecondary(); - if (d->options & Mode::SecondaryNotification) { + if (d->options_ & Mode::SecondaryNotification) { d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance); } - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; + qDebug() << d->memory_->errorString(); + } return; } - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; + qDebug() << d->memory_->errorString(); + } d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance); delete d; ::exit(EXIT_SUCCESS); + } -/** - * @brief Destructor - */ SingleApplication::~SingleApplication() { Q_D(SingleApplication); delete d; } +/** + * Checks if the current application instance is primary. + * @return Returns true if the instance is primary, false otherwise. + */ bool SingleApplication::isPrimary() { Q_D(SingleApplication); - return d->server != nullptr; + return d->server_ != nullptr; } +/** + * Checks if the current application instance is secondary. + * @return Returns true if the instance is secondary, false otherwise. + */ bool SingleApplication::isSecondary() { Q_D(SingleApplication); - return d->server == nullptr; + return d->server_ == nullptr; } +/** + * Allows you to identify an instance by returning unique consecutive instance ids. + * It is reset when the first (primary) instance of your app starts and only incremented afterwards. + * @return Returns a unique instance id. + */ quint32 SingleApplication::instanceId() { Q_D(SingleApplication); - return d->instanceNumber; + return d->instanceNumber_; } +/** + * Returns the OS PID (Process Identifier) of the process running the primary instance. + * Especially useful when SingleApplication is coupled with OS. specific APIs. + * @return Returns the primary instance PID. + */ qint64 SingleApplication::primaryPid() { Q_D(SingleApplication); return d->primaryPid(); } +/** + * Returns the username the primary instance is running as. + * @return Returns the username the primary instance is running as. + */ +QString SingleApplication::primaryUser() { + Q_D(SingleApplication); + return d->primaryUser(); +} + +/** + * Returns the username the current instance is running as. + * @return Returns the username the current instance is running as. + */ +QString SingleApplication::currentUser() { + Q_D(SingleApplication); + return d->getUsername(); +} + +/** + * Sends message to the Primary Instance. + * @param message The message to send. + * @param timeout the maximum timeout in milliseconds for blocking functions. + * @return true if the message was sent successfuly, false otherwise. + */ bool SingleApplication::sendMessage(QByteArray message, int timeout) { Q_D(SingleApplication); @@ -186,10 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) { if (isPrimary()) return false; // Make sure the socket is connected - d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect); + if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) + return false; - d->socket->write(message); - bool dataWritten = d->socket->waitForBytesWritten(timeout); - d->socket->flush(); + d->socket_->write(message); + const bool dataWritten = d->socket_->waitForBytesWritten(timeout); + d->socket_->flush(); return dataWritten; + +} + +/** + * Cleans up the shared memory block and exits with a failure. + * This function halts program execution. + */ +void SingleApplication::abortSafely() { + + Q_D(SingleApplication); + + qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString(); + delete d; + ::exit(EXIT_FAILURE); + } diff --git a/3rdparty/singleapplication/singleapplication.h b/3rdparty/singleapplication/singleapplication.h index dbbdac1ce..217a30b25 100644 --- a/3rdparty/singleapplication/singleapplication.h +++ b/3rdparty/singleapplication/singleapplication.h @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,9 +42,8 @@ class SingleApplicationPrivate; /** - * @brief The SingleApplication class handles multipe instances of the same - * Application - * @see QCoreApplication + * @brief The SingleApplication class handles multipe instances of the same Application + * @see QApplication */ class SingleApplication : public QApplication { Q_OBJECT @@ -118,6 +117,18 @@ class SingleApplication : public QApplication { */ qint64 primaryPid(); + /** + * @brief Returns the username of the user running the primary instance + * @returns {QString} + */ + QString primaryUser(); + + /** + * @brief Returns the username of the current user + * @returns {QString} + */ + QString currentUser(); + /** * @brief Sends a message to the primary instance. Returns true on success. * @param {int} timeout - Timeout for connecting @@ -134,6 +145,7 @@ class SingleApplication : public QApplication { private: SingleApplicationPrivate *d_ptr; Q_DECLARE_PRIVATE(SingleApplication) + void abortSafely(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) diff --git a/3rdparty/singleapplication/singleapplication_p.cpp b/3rdparty/singleapplication/singleapplication_p.cpp index 489110069..f6d3477eb 100644 --- a/3rdparty/singleapplication/singleapplication_p.cpp +++ b/3rdparty/singleapplication/singleapplication_p.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -44,6 +44,8 @@ # include #endif +#include +#include #include #include #include @@ -52,6 +54,12 @@ #include #include #include +#include +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +# include +#else +# include +#endif #include "singleapplication.h" #include "singleapplication_p.h" @@ -61,32 +69,74 @@ # include #endif -SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr) - : q_ptr(_q_ptr), - memory(nullptr), - socket(nullptr), - server(nullptr), - instanceNumber(-1) {} +SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr) + : q_ptr(ptr), + memory_(nullptr), + socket_(nullptr), + server_(nullptr), + instanceNumber_(-1) {} SingleApplicationPrivate::~SingleApplicationPrivate() { - if (socket != nullptr) { - socket->close(); - delete socket; + if (socket_ != nullptr) { + socket_->close(); + delete socket_; + socket_ = nullptr; } - memory->lock(); - InstancesInfo *inst = static_cast(memory->data()); - if (server != nullptr) { - server->close(); - delete server; - inst->primary = false; - inst->primaryPid = -1; - inst->checksum = blockChecksum(); - } - memory->unlock(); + if (memory_ != nullptr) { + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + if (server_ != nullptr) { + server_->close(); + delete server_; + inst->primary = false; + inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; + inst->checksum = blockChecksum(); + } + memory_->unlock(); + + delete memory_; + memory_ = nullptr; + } + +} + +QString SingleApplicationPrivate::getUsername() { + +#ifdef Q_OS_UNIX + QString username; +#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID) + struct passwd *pw = getpwuid(geteuid()); + if (pw) { + username = QString::fromLocal8Bit(pw->pw_name); + } +#endif + if (username.isEmpty()) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + username = qEnvironmentVariable("USER"); +#else + username = QString::fromLocal8Bit(qgetenv("USER")); +#endif + } + return username; +#endif + +#ifdef Q_OS_WIN + wchar_t username[UNLEN + 1]; + // Specifies size of the buffer on input + DWORD usernameLength = UNLEN + 1; + if (GetUserNameW(username, &usernameLength)) { + return QString::fromWCharArray(username); + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return qEnvironmentVariable("USERNAME"); +#else + return QString::fromLocal8Bit(qgetenv("USERNAME")); +#endif +#endif - delete memory; } void SingleApplicationPrivate::genBlockServerName() { @@ -97,11 +147,11 @@ void SingleApplicationPrivate::genBlockServerName() { appData.addData(SingleApplication::app_t::organizationName().toUtf8()); appData.addData(SingleApplication::app_t::organizationDomain().toUtf8()); - if (!(options & SingleApplication::Mode::ExcludeAppVersion)) { + if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) { appData.addData(SingleApplication::app_t::applicationVersion().toUtf8()); } - if (!(options & SingleApplication::Mode::ExcludeAppPath)) { + if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) { #ifdef Q_OS_WIN appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8()); #else @@ -110,153 +160,162 @@ void SingleApplicationPrivate::genBlockServerName() { } // User level block requires a user specific data in the hash - if (options & SingleApplication::Mode::User) { -#ifdef Q_OS_UNIX - QByteArray username; -# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID) - struct passwd *pw = getpwuid(geteuid()); - if (pw) { - username = pw->pw_name; - } -# endif - if (username.isEmpty()) username = qgetenv("USER"); - appData.addData(username); -#endif -#ifdef Q_OS_WIN - wchar_t username[UNLEN + 1]; - // Specifies size of the buffer on input - DWORD usernameLength = UNLEN + 1; - if (GetUserNameW(username, &usernameLength)) { - appData.addData(QString::fromWCharArray(username).toUtf8()); - } - else { - appData.addData(qgetenv("USERNAME")); - } -#endif + if (options_ & SingleApplication::Mode::User) { + appData.addData(getUsername().toUtf8()); } // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements. - blockServerName = appData.result().toBase64().replace("/", "_"); + blockServerName_ = appData.result().toBase64().replace("/", "_"); + } void SingleApplicationPrivate::initializeMemoryBlock() { - InstancesInfo *inst = static_cast(memory->data()); + InstancesInfo *inst = static_cast(memory_->data()); inst->primary = false; inst->secondary = 0; inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; inst->checksum = blockChecksum(); + } void SingleApplicationPrivate::startPrimary() { Q_Q(SingleApplication); - // Successful creation means that no main process exists - // So we start a QLocalServer to listen for connections - QLocalServer::removeServer(blockServerName); - server = new QLocalServer(); - - // Restrict access to the socket according to the - // SingleApplication::Mode::User flag on User level or no restrictions - if (options & SingleApplication::Mode::User) { - server->setSocketOptions(QLocalServer::UserAccessOption); - } - else { - server->setSocketOptions(QLocalServer::WorldAccessOption); - } - - server->listen(blockServerName); - QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished); - // Reset the number of connections - InstancesInfo *inst = static_cast(memory->data()); + InstancesInfo *inst = static_cast(memory_->data()); inst->primary = true; inst->primaryPid = q->applicationPid(); + qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser)); inst->checksum = blockChecksum(); + instanceNumber_ = 0; + // Successful creation means that no main process exists + // So we start a QLocalServer to listen for connections + QLocalServer::removeServer(blockServerName_); + server_ = new QLocalServer(); + + // Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions + if (options_ & SingleApplication::Mode::User) { + server_->setSocketOptions(QLocalServer::UserAccessOption); + } + else { + server_->setSocketOptions(QLocalServer::WorldAccessOption); + } + + server_->listen(blockServerName_); + QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished); - instanceNumber = 0; } -void SingleApplicationPrivate::startSecondary() {} +void SingleApplicationPrivate::startSecondary() { -void SingleApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) { + InstancesInfo *inst = static_cast(memory_->data()); + + inst->secondary += 1; + inst->checksum = blockChecksum(); + instanceNumber_ = inst->secondary; + +} + +bool SingleApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) { + + QElapsedTimer time; + time.start(); // Connect to the Local Server of the Primary Instance if not already connected. - if (socket == nullptr) { - socket = new QLocalSocket(); + if (socket_ == nullptr) { + socket_ = new QLocalSocket(); } - // If already connected - we are done; - if (socket->state() == QLocalSocket::ConnectedState) - return; + if (socket_->state() == QLocalSocket::ConnectedState) return true; - // If not connect - if (socket->state() == QLocalSocket::UnconnectedState || - socket->state() == QLocalSocket::ClosingState) { - socket->connectToServer(blockServerName); - } + if (socket_->state() != QLocalSocket::ConnectedState) { - // Wait for being connected - if (socket->state() == QLocalSocket::ConnectingState) { - socket->waitForConnected(msecs); + forever { + randomSleep(); + + if (socket_->state() != QLocalSocket::ConnectingState) + socket_->connectToServer(blockServerName_); + + if (socket_->state() == QLocalSocket::ConnectingState) { + socket_->waitForConnected(timeout - time.elapsed()); + } + + // If connected break out of the loop + if (socket_->state() == QLocalSocket::ConnectedState) break; + + // If elapsed time since start is longer than the method timeout return + if (time.elapsed() >= timeout) return false; + } } // Initialisation message according to the SingleApplication protocol - if (socket->state() == QLocalSocket::ConnectedState) { - // Notify the parent that a new instance had been started; - QByteArray initMsg; - QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + QByteArray initMsg; + QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + writeStream.setVersion(QDataStream::Qt_5_8); - writeStream.setVersion(QDataStream::Qt_5_6); + writeStream << blockServerName_.toLatin1(); + writeStream << static_cast(connectionType); + writeStream << instanceNumber_; - writeStream << blockServerName.toLatin1(); - writeStream << static_cast(connectionType); - writeStream << instanceNumber; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(initMsg); + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); #else - quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); + quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); #endif - writeStream << checksum; - // The header indicates the message length that follows - QByteArray header; - QDataStream headerStream(&header, QIODevice::WriteOnly); + writeStream << checksum; - headerStream.setVersion(QDataStream::Qt_5_6); + // The header indicates the message length that follows + QByteArray header; + QDataStream headerStream(&header, QIODevice::WriteOnly); + headerStream.setVersion(QDataStream::Qt_5_8); + headerStream << static_cast(initMsg.length()); - headerStream << static_cast(initMsg.length()); + socket_->write(header); + socket_->write(initMsg); + socket_->flush(); + if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true; + + return false; - socket->write(header); - socket->write(initMsg); - socket->flush(); - socket->waitForBytesWritten(msecs); - } } quint16 SingleApplicationPrivate::blockChecksum() { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum))); + quint16 checksum = qChecksum(QByteArray(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum))); #else - quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)); + quint16 checksum = qChecksum(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum)); #endif return checksum; + } qint64 SingleApplicationPrivate::primaryPid() { - qint64 pid; - - memory->lock(); - InstancesInfo *inst = static_cast(memory->data()); - pid = inst->primaryPid; - memory->unlock(); + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + qint64 pid = inst->primaryPid; + memory_->unlock(); return pid; + +} + +QString SingleApplicationPrivate::primaryUser() { + + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + QByteArray username = inst->primaryUser; + memory_->unlock(); + + return QString::fromUtf8(username); + } /** @@ -264,43 +323,41 @@ qint64 SingleApplicationPrivate::primaryPid() { */ void SingleApplicationPrivate::slotConnectionEstablished() { - QLocalSocket *nextConnSocket = server->nextPendingConnection(); - connectionMap.insert(nextConnSocket, ConnectionInfo()); + QLocalSocket *nextConnSocket = server_->nextPendingConnection(); + connectionMap_.insert(nextConnSocket, ConnectionInfo()); - QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, - [nextConnSocket, this]() { - auto &info = connectionMap[nextConnSocket]; - Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); - }); + QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() { + auto &info = connectionMap_[nextConnSocket]; + Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); + }); - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, - [nextConnSocket, this]() { - connectionMap.remove(nextConnSocket); - nextConnSocket->deleteLater(); - }); + QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() { + connectionMap_.remove(nextConnSocket); + nextConnSocket->deleteLater(); + }); + + QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() { + auto &info = connectionMap_[nextConnSocket]; + switch (info.stage) { + case StageHeader: + readInitMessageHeader(nextConnSocket); + break; + case StageBody: + readInitMessageBody(nextConnSocket); + break; + case StageConnected: + Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId); + break; + default: + break; + }; + }); - QObject::connect(nextConnSocket, &QLocalSocket::readyRead, - [nextConnSocket, this]() { - auto &info = connectionMap[nextConnSocket]; - switch (info.stage) { - case StageHeader: - readInitMessageHeader(nextConnSocket); - break; - case StageBody: - readInitMessageBody(nextConnSocket); - break; - case StageConnected: - Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId); - break; - default: - break; - }; - }); } void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { - if (!connectionMap.contains(sock)) { + if (!connectionMap_.contains(sock)) { return; } @@ -309,30 +366,30 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { } QDataStream headerStream(sock); - - headerStream.setVersion(QDataStream::Qt_5_6); + headerStream.setVersion(QDataStream::Qt_5_8); // Read the header to know the message length quint64 msgLen = 0; headerStream >> msgLen; - ConnectionInfo &info = connectionMap[sock]; + ConnectionInfo &info = connectionMap_[sock]; info.stage = StageBody; info.msgLen = msgLen; if (sock->bytesAvailable() >= static_cast(msgLen)) { readInitMessageBody(sock); } + } void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { Q_Q(SingleApplication); - if (!connectionMap.contains(sock)) { + if (!connectionMap_.contains(sock)) { return; } - ConnectionInfo &info = connectionMap[sock]; + ConnectionInfo &info = connectionMap_[sock]; if (sock->bytesAvailable() < static_cast(info.msgLen)) { return; } @@ -340,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { // Read the message body QByteArray msgBytes = sock->read(info.msgLen); QDataStream readStream(msgBytes); - - readStream.setVersion(QDataStream::Qt_5_6); + readStream.setVersion(QDataStream::Qt_5_8); // server name QByteArray latin1Name; @@ -362,12 +418,12 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { readStream >> msgChecksum; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const quint16 actualChecksum = qChecksum(msgBytes); + const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16)))); #else const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); #endif - bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; + bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum; if (!isValid) { sock->close(); @@ -377,23 +433,38 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { info.instanceId = instanceId; info.stage = StageConnected; - if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) { + if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) { Q_EMIT q->instanceStarted(); } if (sock->bytesAvailable() > 0) { Q_EMIT this->slotDataAvailable(sock, instanceId); } + } void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { Q_Q(SingleApplication); Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); + } void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { - if (closedSocket->bytesAvailable() > 0) + if (closedSocket->bytesAvailable() > 0) { Q_EMIT slotDataAvailable(closedSocket, instanceId); + } + +} + +void SingleApplicationPrivate::randomSleep() { + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u)); +#else + qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); + QThread::msleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); +#endif + } diff --git a/3rdparty/singleapplication/singleapplication_p.h b/3rdparty/singleapplication/singleapplication_p.h index f3d080ecc..f9818e197 100644 --- a/3rdparty/singleapplication/singleapplication_p.h +++ b/3rdparty/singleapplication/singleapplication_p.h @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2016 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -36,6 +36,7 @@ #include #include +#include #include #include "singleapplication.h" @@ -48,6 +49,7 @@ struct InstancesInfo { bool primary; quint32 secondary; qint64 primaryPid; + char primaryUser[128]; quint16 checksum; }; @@ -60,6 +62,7 @@ struct ConnectionInfo { class SingleApplicationPrivate : public QObject { Q_OBJECT + public: enum ConnectionType : quint8 { InvalidConnection = 0, @@ -74,32 +77,35 @@ class SingleApplicationPrivate : public QObject { }; Q_DECLARE_PUBLIC(SingleApplication) - explicit SingleApplicationPrivate(SingleApplication *_q_ptr); + explicit SingleApplicationPrivate(SingleApplication *ptr); ~SingleApplicationPrivate() override; + QString getUsername(); void genBlockServerName(); void initializeMemoryBlock(); void startPrimary(); void startSecondary(); - void connectToPrimary(const int msecs, const ConnectionType connectionType); + bool connectToPrimary(const int msecs, const ConnectionType connectionType); quint16 blockChecksum(); qint64 primaryPid(); + QString primaryUser(); void readInitMessageHeader(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket); + void randomSleep(); SingleApplication *q_ptr; - QSharedMemory *memory; - QLocalSocket *socket; - QLocalServer *server; - quint32 instanceNumber; - QString blockServerName; - SingleApplication::Options options; - QMap connectionMap; + QSharedMemory *memory_; + QLocalSocket *socket_; + QLocalServer *server_; + quint32 instanceNumber_; + QString blockServerName_; + SingleApplication::Options options_; + QMap connectionMap_; public slots: void slotConnectionEstablished(); - void slotDataAvailable(QLocalSocket *, const quint32); - void slotClientConnectionClosed(QLocalSocket *, const quint32); + void slotDataAvailable(QLocalSocket*, const quint32); + void slotClientConnectionClosed(QLocalSocket*, const quint32); }; #endif // SINGLEAPPLICATION_P_H diff --git a/3rdparty/singleapplication/singlecoreapplication.cpp b/3rdparty/singleapplication/singlecoreapplication.cpp index 7092c5dc5..6f6971d0b 100644 --- a/3rdparty/singleapplication/singlecoreapplication.cpp +++ b/3rdparty/singleapplication/singlecoreapplication.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,142 +42,200 @@ #include #include #include -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) -# include -#else -# include -#endif #include "singlecoreapplication.h" #include "singlecoreapplication_p.h" /** - * @brief Constructor. Checks and fires up LocalServer or closes the program - * if another instance already exists + * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists * @param argc * @param argv - * @param {bool} allowSecondaryInstances + * @param allowSecondary Whether to enable secondary instance support + * @param options Optional flags to toggle specific behaviour + * @param timeout Maximum time blocking functions are allowed during app load */ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout) - : app_t(argc, argv), d_ptr(new SingleCoreApplicationPrivate(this)) { + : app_t(argc, argv), + d_ptr(new SingleCoreApplicationPrivate(this)) { Q_D(SingleCoreApplication); // Store the current mode of the program - d->options = options; + d->options_ = options; // Generating an application ID used for identifying the shared memory block and QLocalServer d->genBlockServerName(); + // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time + d->randomSleep(); + #ifdef Q_OS_UNIX - // By explicitly attaching it and then deleting it we make sure that the - // memory is deleted even after the process has crashed on Unix. - d->memory = new QSharedMemory(d->blockServerName); - d->memory->attach(); - delete d->memory; + // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix. + d->memory_ = new QSharedMemory(d->blockServerName_); + d->memory_->attach(); + delete d->memory_; #endif + // Guarantee thread safe behaviour with a shared memory block. - d->memory = new QSharedMemory(d->blockServerName); + d->memory_ = new QSharedMemory(d->blockServerName_); // Create a shared memory block - if (d->memory->create(sizeof(InstancesInfo))) { + if (d->memory_->create(sizeof(InstancesInfo))) { // Initialize the shared memory block - d->memory->lock(); + if (!d->memory_->lock()) { + qCritical() << "SingleCoreApplication: Unable to lock memory block after create."; + abortSafely(); + } d->initializeMemoryBlock(); - d->memory->unlock(); } else { - // Attempt to attach to the memory segment - if (!d->memory->attach()) { - qCritical() << "SingleCoreApplication: Unable to attach to shared memory block."; - qCritical() << d->memory->errorString(); - delete d; - ::exit(EXIT_FAILURE); + if (d->memory_->error() == QSharedMemory::AlreadyExists) { + // Attempt to attach to the memory segment + if (!d->memory_->attach()) { + qCritical() << "SingleCoreApplication: Unable to attach to shared memory block."; + abortSafely(); + } + if (!d->memory_->lock()) { + qCritical() << "SingleCoreApplication: Unable to lock memory block after attach."; + abortSafely(); + } + } + else { + qCritical() << "SingleCoreApplication: Unable to create block."; + abortSafely(); } } - InstancesInfo *inst = static_cast(d->memory->data()); + InstancesInfo *inst = static_cast(d->memory_->data()); QElapsedTimer time; time.start(); // Make sure the shared memory block is initialised and in consistent state - while (true) { - d->memory->lock(); - + forever { + // If the shared memory block's checksum is valid continue if (d->blockChecksum() == inst->checksum) break; + // If more than 5s have elapsed, assume the primary instance crashed and assume it's position if (time.elapsed() > 5000) { qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; d->initializeMemoryBlock(); } - d->memory->unlock(); - - // Random sleep here limits the probability of a collision between two racing apps -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u)); -#else - qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); - QThread::sleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); -#endif + // Otherwise wait for a random period and try again. + // The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster + if (!d->memory_->unlock()) { + qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait."; + qDebug() << d->memory_->errorString(); + } + d->randomSleep(); + if (!d->memory_->lock()) { + qCritical() << "SingleCoreApplication: Unable to lock memory after random wait."; + abortSafely(); + } } if (inst->primary == false) { d->startPrimary(); - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start."; + qDebug() << d->memory_->errorString(); + } return; } // Check if another instance can be started if (allowSecondary) { - inst->secondary += 1; - inst->checksum = d->blockChecksum(); - d->instanceNumber = inst->secondary; d->startSecondary(); - if (d->options & Mode::SecondaryNotification) { + if (d->options_ & Mode::SecondaryNotification) { d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance); } - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start."; + qDebug() << d->memory_->errorString(); + } return; } - d->memory->unlock(); + if (!d->memory_->unlock()) { + qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution."; + qDebug() << d->memory_->errorString(); + } d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance); delete d; ::exit(EXIT_SUCCESS); + } -/** - * @brief Destructor - */ SingleCoreApplication::~SingleCoreApplication() { Q_D(SingleCoreApplication); delete d; } +/** + * Checks if the current application instance is primary. + * @return Returns true if the instance is primary, false otherwise. + */ bool SingleCoreApplication::isPrimary() { Q_D(SingleCoreApplication); - return d->server != nullptr; + return d->server_ != nullptr; } +/** + * Checks if the current application instance is secondary. + * @return Returns true if the instance is secondary, false otherwise. + */ bool SingleCoreApplication::isSecondary() { Q_D(SingleCoreApplication); - return d->server == nullptr; + return d->server_ == nullptr; } +/** + * Allows you to identify an instance by returning unique consecutive instance ids. + * It is reset when the first (primary) instance of your app starts and only incremented afterwards. + * @return Returns a unique instance id. + */ quint32 SingleCoreApplication::instanceId() { Q_D(SingleCoreApplication); - return d->instanceNumber; + return d->instanceNumber_; } +/** + * Returns the OS PID (Process Identifier) of the process running the primary instance. + * Especially useful when SingleCoreApplication is coupled with OS. specific APIs. + * @return Returns the primary instance PID. + */ qint64 SingleCoreApplication::primaryPid() { Q_D(SingleCoreApplication); return d->primaryPid(); } +/** + * Returns the username the primary instance is running as. + * @return Returns the username the primary instance is running as. + */ +QString SingleCoreApplication::primaryUser() { + Q_D(SingleCoreApplication); + return d->primaryUser(); +} + +/** + * Returns the username the current instance is running as. + * @return Returns the username the current instance is running as. + */ +QString SingleCoreApplication::currentUser() { + Q_D(SingleCoreApplication); + return d->getUsername(); +} + +/** + * Sends message to the Primary Instance. + * @param message The message to send. + * @param timeout the maximum timeout in milliseconds for blocking functions. + * @return true if the message was sent successfuly, false otherwise. + */ bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) { Q_D(SingleCoreApplication); @@ -186,10 +244,26 @@ bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) { if (isPrimary()) return false; // Make sure the socket is connected - d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect); + if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) + return false; - d->socket->write(message); - bool dataWritten = d->socket->waitForBytesWritten(timeout); - d->socket->flush(); + d->socket_->write(message); + const bool dataWritten = d->socket_->waitForBytesWritten(timeout); + d->socket_->flush(); return dataWritten; + +} + +/** + * Cleans up the shared memory block and exits with a failure. + * This function halts program execution. + */ +void SingleCoreApplication::abortSafely() { + + Q_D(SingleCoreApplication); + + qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString(); + delete d; + ::exit(EXIT_FAILURE); + } diff --git a/3rdparty/singleapplication/singlecoreapplication.h b/3rdparty/singleapplication/singlecoreapplication.h index 4ecf8b6bc..d5f1802da 100644 --- a/3rdparty/singleapplication/singlecoreapplication.h +++ b/3rdparty/singleapplication/singlecoreapplication.h @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,8 +42,7 @@ class SingleCoreApplicationPrivate; /** - * @brief The SingleCoreApplication class handles multiple instances of the same - * Application + * @brief The SingleCoreApplication class handles multipe instances of the same Application * @see QCoreApplication */ class SingleCoreApplication : public QCoreApplication { @@ -89,9 +88,8 @@ class SingleCoreApplication : public QCoreApplication { * operations. It does not guarantee that the SingleCoreApplication * initialisation will be completed in given time, though is a good hint. * Usually 4*timeout would be the worst case (fail) scenario. - * @see See the corresponding QAPPLICATION_CLASS constructor for reference */ - explicit SingleCoreApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000); + explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000); ~SingleCoreApplication() override; /** @@ -118,6 +116,18 @@ class SingleCoreApplication : public QCoreApplication { */ qint64 primaryPid(); + /** + * @brief Returns the username of the user running the primary instance + * @returns {QString} + */ + QString primaryUser(); + + /** + * @brief Returns the username of the current user + * @returns {QString} + */ + QString currentUser(); + /** * @brief Sends a message to the primary instance. Returns true on success. * @param {int} timeout - Timeout for connecting @@ -134,6 +144,7 @@ class SingleCoreApplication : public QCoreApplication { private: SingleCoreApplicationPrivate *d_ptr; Q_DECLARE_PRIVATE(SingleCoreApplication) + void abortSafely(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options) diff --git a/3rdparty/singleapplication/singlecoreapplication_p.cpp b/3rdparty/singleapplication/singlecoreapplication_p.cpp index 97aacf116..09ed635f6 100644 --- a/3rdparty/singleapplication/singlecoreapplication_p.cpp +++ b/3rdparty/singleapplication/singlecoreapplication_p.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -44,6 +44,8 @@ # include #endif +#include +#include #include #include #include @@ -52,6 +54,12 @@ #include #include #include +#include +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +# include +#else +# include +#endif #include "singlecoreapplication.h" #include "singlecoreapplication_p.h" @@ -61,32 +69,74 @@ # include #endif -SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr) - : q_ptr(_q_ptr), - memory(nullptr), - socket(nullptr), - server(nullptr), - instanceNumber(-1) {} +SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr) + : q_ptr(ptr), + memory_(nullptr), + socket_(nullptr), + server_(nullptr), + instanceNumber_(-1) {} SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() { - if (socket != nullptr) { - socket->close(); - delete socket; + if (socket_ != nullptr) { + socket_->close(); + delete socket_; + socket_ = nullptr; } - memory->lock(); - InstancesInfo *inst = static_cast(memory->data()); - if (server != nullptr) { - server->close(); - delete server; - inst->primary = false; - inst->primaryPid = -1; - inst->checksum = blockChecksum(); - } - memory->unlock(); + if (memory_ != nullptr) { + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + if (server_ != nullptr) { + server_->close(); + delete server_; + inst->primary = false; + inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; + inst->checksum = blockChecksum(); + } + memory_->unlock(); + + delete memory_; + memory_ = nullptr; + } + +} + +QString SingleCoreApplicationPrivate::getUsername() { + +#ifdef Q_OS_UNIX + QString username; +#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID) + struct passwd *pw = getpwuid(geteuid()); + if (pw) { + username = QString::fromLocal8Bit(pw->pw_name); + } +#endif + if (username.isEmpty()) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + username = qEnvironmentVariable("USER"); +#else + username = QString::fromLocal8Bit(qgetenv("USER")); +#endif + } + return username; +#endif + +#ifdef Q_OS_WIN + wchar_t username[UNLEN + 1]; + // Specifies size of the buffer on input + DWORD usernameLength = UNLEN + 1; + if (GetUserNameW(username, &usernameLength)) { + return QString::fromWCharArray(username); + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return qEnvironmentVariable("USERNAME"); +#else + return QString::fromLocal8Bit(qgetenv("USERNAME")); +#endif +#endif - delete memory; } void SingleCoreApplicationPrivate::genBlockServerName() { @@ -97,11 +147,11 @@ void SingleCoreApplicationPrivate::genBlockServerName() { appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8()); appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8()); - if (!(options & SingleCoreApplication::Mode::ExcludeAppVersion)) { + if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) { appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8()); } - if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) { + if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) { #ifdef Q_OS_WIN appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8()); #else @@ -110,153 +160,162 @@ void SingleCoreApplicationPrivate::genBlockServerName() { } // User level block requires a user specific data in the hash - if (options & SingleCoreApplication::Mode::User) { -#ifdef Q_OS_UNIX - QByteArray username; -# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID) - struct passwd *pw = getpwuid(geteuid()); - if (pw) { - username = pw->pw_name; - } -# endif - if (username.isEmpty()) username = qgetenv("USER"); - appData.addData(username); -#endif -#ifdef Q_OS_WIN - wchar_t username[UNLEN + 1]; - // Specifies size of the buffer on input - DWORD usernameLength = UNLEN + 1; - if (GetUserNameW(username, &usernameLength)) { - appData.addData(QString::fromWCharArray(username).toUtf8()); - } - else { - appData.addData(qgetenv("USERNAME")); - } -#endif + if (options_ & SingleCoreApplication::Mode::User) { + appData.addData(getUsername().toUtf8()); } // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements. - blockServerName = appData.result().toBase64().replace("/", "_"); + blockServerName_ = appData.result().toBase64().replace("/", "_"); + } void SingleCoreApplicationPrivate::initializeMemoryBlock() { - InstancesInfo *inst = static_cast(memory->data()); + InstancesInfo *inst = static_cast(memory_->data()); inst->primary = false; inst->secondary = 0; inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; inst->checksum = blockChecksum(); + } void SingleCoreApplicationPrivate::startPrimary() { Q_Q(SingleCoreApplication); - // Successful creation means that no main process exists - // So we start a QLocalServer to listen for connections - QLocalServer::removeServer(blockServerName); - server = new QLocalServer(); - - // Restrict access to the socket according to the - // SingleCoreApplication::Mode::User flag on User level or no restrictions - if (options & SingleCoreApplication::Mode::User) { - server->setSocketOptions(QLocalServer::UserAccessOption); - } - else { - server->setSocketOptions(QLocalServer::WorldAccessOption); - } - - server->listen(blockServerName); - QObject::connect(server, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished); - // Reset the number of connections - InstancesInfo *inst = static_cast(memory->data()); + InstancesInfo *inst = static_cast(memory_->data()); inst->primary = true; inst->primaryPid = q->applicationPid(); + qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser)); inst->checksum = blockChecksum(); + instanceNumber_ = 0; + // Successful creation means that no main process exists + // So we start a QLocalServer to listen for connections + QLocalServer::removeServer(blockServerName_); + server_ = new QLocalServer(); + + // Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions + if (options_ & SingleCoreApplication::Mode::User) { + server_->setSocketOptions(QLocalServer::UserAccessOption); + } + else { + server_->setSocketOptions(QLocalServer::WorldAccessOption); + } + + server_->listen(blockServerName_); + QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished); - instanceNumber = 0; } -void SingleCoreApplicationPrivate::startSecondary() {} +void SingleCoreApplicationPrivate::startSecondary() { -void SingleCoreApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) { + InstancesInfo *inst = static_cast(memory_->data()); + + inst->secondary += 1; + inst->checksum = blockChecksum(); + instanceNumber_ = inst->secondary; + +} + +bool SingleCoreApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) { + + QElapsedTimer time; + time.start(); // Connect to the Local Server of the Primary Instance if not already connected. - if (socket == nullptr) { - socket = new QLocalSocket(); + if (socket_ == nullptr) { + socket_ = new QLocalSocket(); } - // If already connected - we are done; - if (socket->state() == QLocalSocket::ConnectedState) - return; + if (socket_->state() == QLocalSocket::ConnectedState) return true; - // If not connect - if (socket->state() == QLocalSocket::UnconnectedState || - socket->state() == QLocalSocket::ClosingState) { - socket->connectToServer(blockServerName); - } + if (socket_->state() != QLocalSocket::ConnectedState) { - // Wait for being connected - if (socket->state() == QLocalSocket::ConnectingState) { - socket->waitForConnected(msecs); + forever { + randomSleep(); + + if (socket_->state() != QLocalSocket::ConnectingState) + socket_->connectToServer(blockServerName_); + + if (socket_->state() == QLocalSocket::ConnectingState) { + socket_->waitForConnected(timeout - time.elapsed()); + } + + // If connected break out of the loop + if (socket_->state() == QLocalSocket::ConnectedState) break; + + // If elapsed time since start is longer than the method timeout return + if (time.elapsed() >= timeout) return false; + } } // Initialisation message according to the SingleCoreApplication protocol - if (socket->state() == QLocalSocket::ConnectedState) { - // Notify the parent that a new instance had been started; - QByteArray initMsg; - QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + QByteArray initMsg; + QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + writeStream.setVersion(QDataStream::Qt_5_8); - writeStream.setVersion(QDataStream::Qt_5_6); + writeStream << blockServerName_.toLatin1(); + writeStream << static_cast(connectionType); + writeStream << instanceNumber_; - writeStream << blockServerName.toLatin1(); - writeStream << static_cast(connectionType); - writeStream << instanceNumber; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(initMsg); + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); #else - quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); + quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); #endif - writeStream << checksum; - // The header indicates the message length that follows - QByteArray header; - QDataStream headerStream(&header, QIODevice::WriteOnly); + writeStream << checksum; - headerStream.setVersion(QDataStream::Qt_5_6); + // The header indicates the message length that follows + QByteArray header; + QDataStream headerStream(&header, QIODevice::WriteOnly); + headerStream.setVersion(QDataStream::Qt_5_8); + headerStream << static_cast(initMsg.length()); - headerStream << static_cast(initMsg.length()); + socket_->write(header); + socket_->write(initMsg); + socket_->flush(); + if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true; + + return false; - socket->write(header); - socket->write(initMsg); - socket->flush(); - socket->waitForBytesWritten(msecs); - } } quint16 SingleCoreApplicationPrivate::blockChecksum() { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum))); + quint16 checksum = qChecksum(QByteArray(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum))); #else - quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)); + quint16 checksum = qChecksum(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum)); #endif return checksum; + } qint64 SingleCoreApplicationPrivate::primaryPid() { - qint64 pid; - - memory->lock(); - InstancesInfo *inst = static_cast(memory->data()); - pid = inst->primaryPid; - memory->unlock(); + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + qint64 pid = inst->primaryPid; + memory_->unlock(); return pid; + +} + +QString SingleCoreApplicationPrivate::primaryUser() { + + memory_->lock(); + InstancesInfo *inst = static_cast(memory_->data()); + QByteArray username = inst->primaryUser; + memory_->unlock(); + + return QString::fromUtf8(username); + } /** @@ -264,43 +323,41 @@ qint64 SingleCoreApplicationPrivate::primaryPid() { */ void SingleCoreApplicationPrivate::slotConnectionEstablished() { - QLocalSocket *nextConnSocket = server->nextPendingConnection(); - connectionMap.insert(nextConnSocket, ConnectionInfo()); + QLocalSocket *nextConnSocket = server_->nextPendingConnection(); + connectionMap_.insert(nextConnSocket, ConnectionInfo()); - QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, - [nextConnSocket, this]() { - auto &info = connectionMap[nextConnSocket]; - Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); - }); + QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() { + auto &info = connectionMap_[nextConnSocket]; + Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); + }); - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, - [nextConnSocket, this]() { - connectionMap.remove(nextConnSocket); - nextConnSocket->deleteLater(); - }); + QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() { + connectionMap_.remove(nextConnSocket); + nextConnSocket->deleteLater(); + }); + + QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() { + auto &info = connectionMap_[nextConnSocket]; + switch (info.stage) { + case StageHeader: + readInitMessageHeader(nextConnSocket); + break; + case StageBody: + readInitMessageBody(nextConnSocket); + break; + case StageConnected: + Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId); + break; + default: + break; + }; + }); - QObject::connect(nextConnSocket, &QLocalSocket::readyRead, - [nextConnSocket, this]() { - auto &info = connectionMap[nextConnSocket]; - switch (info.stage) { - case StageHeader: - readInitMessageHeader(nextConnSocket); - break; - case StageBody: - readInitMessageBody(nextConnSocket); - break; - case StageConnected: - Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId); - break; - default: - break; - }; - }); } void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { - if (!connectionMap.contains(sock)) { + if (!connectionMap_.contains(sock)) { return; } @@ -309,30 +366,30 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { } QDataStream headerStream(sock); - - headerStream.setVersion(QDataStream::Qt_5_6); + headerStream.setVersion(QDataStream::Qt_5_8); // Read the header to know the message length quint64 msgLen = 0; headerStream >> msgLen; - ConnectionInfo &info = connectionMap[sock]; + ConnectionInfo &info = connectionMap_[sock]; info.stage = StageBody; info.msgLen = msgLen; if (sock->bytesAvailable() >= static_cast(msgLen)) { readInitMessageBody(sock); } + } void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { Q_Q(SingleCoreApplication); - if (!connectionMap.contains(sock)) { + if (!connectionMap_.contains(sock)) { return; } - ConnectionInfo &info = connectionMap[sock]; + ConnectionInfo &info = connectionMap_[sock]; if (sock->bytesAvailable() < static_cast(info.msgLen)) { return; } @@ -340,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { // Read the message body QByteArray msgBytes = sock->read(info.msgLen); QDataStream readStream(msgBytes); - - readStream.setVersion(QDataStream::Qt_5_6); + readStream.setVersion(QDataStream::Qt_5_8); // server name QByteArray latin1Name; @@ -362,12 +418,12 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { readStream >> msgChecksum; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const quint16 actualChecksum = qChecksum(msgBytes); + const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16)))); #else const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); #endif - bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; + bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum; if (!isValid) { sock->close(); @@ -377,23 +433,38 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { info.instanceId = instanceId; info.stage = StageConnected; - if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleCoreApplication::Mode::SecondaryNotification)) { + if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) { Q_EMIT q->instanceStarted(); } if (sock->bytesAvailable() > 0) { Q_EMIT this->slotDataAvailable(sock, instanceId); } + } void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { Q_Q(SingleCoreApplication); Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); + } void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { - if (closedSocket->bytesAvailable() > 0) + if (closedSocket->bytesAvailable() > 0) { Q_EMIT slotDataAvailable(closedSocket, instanceId); + } + +} + +void SingleCoreApplicationPrivate::randomSleep() { + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u)); +#else + qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); + QThread::msleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); +#endif + } diff --git a/3rdparty/singleapplication/singlecoreapplication_p.h b/3rdparty/singleapplication/singlecoreapplication_p.h index ec0dfa9f7..ac07a57dc 100644 --- a/3rdparty/singleapplication/singlecoreapplication_p.h +++ b/3rdparty/singleapplication/singlecoreapplication_p.h @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2016 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -31,11 +31,12 @@ // // -#ifndef SINGLECOREAPPLICATION_P_H -#define SINGLECOREAPPLICATION_P_H +#ifndef SINGLEAPPLICATION_P_H +#define SINGLEAPPLICATION_P_H #include #include +#include #include #include "singlecoreapplication.h" @@ -48,6 +49,7 @@ struct InstancesInfo { bool primary; quint32 secondary; qint64 primaryPid; + char primaryUser[128]; quint16 checksum; }; @@ -60,6 +62,7 @@ struct ConnectionInfo { class SingleCoreApplicationPrivate : public QObject { Q_OBJECT + public: enum ConnectionType : quint8 { InvalidConnection = 0, @@ -74,32 +77,35 @@ class SingleCoreApplicationPrivate : public QObject { }; Q_DECLARE_PUBLIC(SingleCoreApplication) - explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr); + explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr); ~SingleCoreApplicationPrivate() override; + QString getUsername(); void genBlockServerName(); void initializeMemoryBlock(); void startPrimary(); void startSecondary(); - void connectToPrimary(const int msecs, const ConnectionType connectionType); + bool connectToPrimary(const int msecs, const ConnectionType connectionType); quint16 blockChecksum(); qint64 primaryPid(); + QString primaryUser(); void readInitMessageHeader(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket); + void randomSleep(); SingleCoreApplication *q_ptr; - QSharedMemory *memory; - QLocalSocket *socket; - QLocalServer *server; - quint32 instanceNumber; - QString blockServerName; - SingleCoreApplication::Options options; - QMap connectionMap; + QSharedMemory *memory_; + QLocalSocket *socket_; + QLocalServer *server_; + quint32 instanceNumber_; + QString blockServerName_; + SingleCoreApplication::Options options_; + QMap connectionMap_; public slots: void slotConnectionEstablished(); - void slotDataAvailable(QLocalSocket *, const quint32); - void slotClientConnectionClosed(QLocalSocket *, const quint32); + void slotDataAvailable(QLocalSocket*, const quint32); + void slotClientConnectionClosed(QLocalSocket*, const quint32); }; -#endif // SINGLECOREAPPLICATION_P_H +#endif // SINGLEAPPLICATION_P_H