1 // The MIT License (MIT)
2 //
3 // Copyright (c) Itay Grudev 2015 - 2020
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22
23 //
24 // W A R N I N G !!!
25 // -----------------
26 //
27 // This is a modified version of SingleApplication,
28 // The original version is at:
29 //
30 // https://github.com/itay-grudev/SingleApplication
31 //
32 //
33
34 #include <cstdlib>
35 #include <limits>
36
37 #include <QtGlobal>
38 #include <QApplication>
39 #include <QThread>
40 #include <QSharedMemory>
41 #include <QLocalSocket>
42 #include <QByteArray>
43 #include <QElapsedTimer>
44 #include <QtDebug>
45
46 #include "singleapplication.h"
47 #include "singleapplication_p.h"
48
49 /**
50 * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
51 * @param argc
52 * @param argv
53 * @param allowSecondary Whether to enable secondary instance support
54 * @param options Optional flags to toggle specific behaviour
55 * @param timeout Maximum time blocking functions are allowed during app load
56 */
SingleApplication(int & argc,char * argv[],bool allowSecondary,Options options,int timeout)57 SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
58 : app_t(argc, argv),
59 d_ptr(new SingleApplicationPrivate(this)) {
60
61 Q_D(SingleApplication);
62
63 // Store the current mode of the program
64 d->options_ = options;
65
66 // Generating an application ID used for identifying the shared memory block and QLocalServer
67 d->genBlockServerName();
68
69 // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
70 d->randomSleep();
71
72 #ifdef Q_OS_UNIX
73 // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
74 d->memory_ = new QSharedMemory(d->blockServerName_);
75 d->memory_->attach();
76 delete d->memory_;
77 #endif
78
79 // Guarantee thread safe behaviour with a shared memory block.
80 d->memory_ = new QSharedMemory(d->blockServerName_);
81
82 // Create a shared memory block
83 if (d->memory_->create(sizeof(InstancesInfo))) {
84 // Initialize the shared memory block
85 if (!d->memory_->lock()) {
86 qCritical() << "SingleApplication: Unable to lock memory block after create.";
87 abortSafely();
88 }
89 d->initializeMemoryBlock();
90 }
91 else {
92 if (d->memory_->error() == QSharedMemory::AlreadyExists) {
93 // Attempt to attach to the memory segment
94 if (!d->memory_->attach()) {
95 qCritical() << "SingleApplication: Unable to attach to shared memory block.";
96 abortSafely();
97 }
98 if (!d->memory_->lock()) {
99 qCritical() << "SingleApplication: Unable to lock memory block after attach.";
100 abortSafely();
101 }
102 }
103 else {
104 qCritical() << "SingleApplication: Unable to create block.";
105 abortSafely();
106 }
107 }
108
109 InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
110 QElapsedTimer time;
111 time.start();
112
113 // Make sure the shared memory block is initialised and in consistent state
114 forever {
115 // If the shared memory block's checksum is valid continue
116 if (d->blockChecksum() == inst->checksum) break;
117
118 // If more than 5s have elapsed, assume the primary instance crashed and assume it's position
119 if (time.elapsed() > 5000) {
120 qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
121 d->initializeMemoryBlock();
122 }
123
124 // Otherwise wait for a random period and try again.
125 // The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
126 if (!d->memory_->unlock()) {
127 qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
128 qDebug() << d->memory_->errorString();
129 }
130 d->randomSleep();
131 if (!d->memory_->lock()) {
132 qCritical() << "SingleApplication: Unable to lock memory after random wait.";
133 abortSafely();
134 }
135 }
136
137 if (!inst->primary) {
138 d->startPrimary();
139 if (!d->memory_->unlock()) {
140 qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
141 qDebug() << d->memory_->errorString();
142 }
143 return;
144 }
145
146 // Check if another instance can be started
147 if (allowSecondary) {
148 d->startSecondary();
149 if (d->options_ & Mode::SecondaryNotification) {
150 d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
151 }
152 if (!d->memory_->unlock()) {
153 qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
154 qDebug() << d->memory_->errorString();
155 }
156 return;
157 }
158
159 if (!d->memory_->unlock()) {
160 qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
161 qDebug() << d->memory_->errorString();
162 }
163
164 d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
165
166 delete d;
167
168 ::exit(EXIT_SUCCESS);
169
170 }
171
~SingleApplication()172 SingleApplication::~SingleApplication() {
173 Q_D(SingleApplication);
174 delete d;
175 }
176
177 /**
178 * Checks if the current application instance is primary.
179 * @return Returns true if the instance is primary, false otherwise.
180 */
isPrimary()181 bool SingleApplication::isPrimary() {
182 Q_D(SingleApplication);
183 return d->server_ != nullptr;
184 }
185
186 /**
187 * Checks if the current application instance is secondary.
188 * @return Returns true if the instance is secondary, false otherwise.
189 */
isSecondary()190 bool SingleApplication::isSecondary() {
191 Q_D(SingleApplication);
192 return d->server_ == nullptr;
193 }
194
195 /**
196 * Allows you to identify an instance by returning unique consecutive instance ids.
197 * It is reset when the first (primary) instance of your app starts and only incremented afterwards.
198 * @return Returns a unique instance id.
199 */
instanceId()200 quint32 SingleApplication::instanceId() {
201 Q_D(SingleApplication);
202 return d->instanceNumber_;
203 }
204
205 /**
206 * Returns the OS PID (Process Identifier) of the process running the primary instance.
207 * Especially useful when SingleApplication is coupled with OS. specific APIs.
208 * @return Returns the primary instance PID.
209 */
primaryPid()210 qint64 SingleApplication::primaryPid() {
211 Q_D(SingleApplication);
212 return d->primaryPid();
213 }
214
215 /**
216 * Returns the username the primary instance is running as.
217 * @return Returns the username the primary instance is running as.
218 */
primaryUser()219 QString SingleApplication::primaryUser() {
220 Q_D(SingleApplication);
221 return d->primaryUser();
222 }
223
224 /**
225 * Returns the username the current instance is running as.
226 * @return Returns the username the current instance is running as.
227 */
currentUser()228 QString SingleApplication::currentUser() {
229 Q_D(SingleApplication);
230 return d->getUsername();
231 }
232
233 /**
234 * Sends message to the Primary Instance.
235 * @param message The message to send.
236 * @param timeout the maximum timeout in milliseconds for blocking functions.
237 * @return true if the message was sent successfully, false otherwise.
238 */
sendMessage(const QByteArray & message,const int timeout)239 bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
240
241 Q_D(SingleApplication);
242
243 // Nobody to connect to
244 if (isPrimary()) return false;
245
246 // Make sure the socket is connected
247 if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
248 return false;
249 }
250
251 d->socket_->write(message);
252 const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
253 d->socket_->flush();
254 return dataWritten;
255
256 }
257
258 /**
259 * Cleans up the shared memory block and exits with a failure.
260 * This function halts program execution.
261 */
abortSafely()262 void SingleApplication::abortSafely() {
263
264 Q_D(SingleApplication);
265
266 qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
267 delete d;
268 ::exit(EXIT_FAILURE);
269
270 }
271