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 #include <QtCore/QByteArray>
24 #include <QtCore/QElapsedTimer>
25 #include <QtCore/QSharedMemory>
26
27 #include "singleapplication.h"
28 #include "singleapplication_p.h"
29
30 /**
31 * @brief Constructor. Checks and fires up LocalServer or closes the program
32 * if another instance already exists
33 * @param argc
34 * @param argv
35 * @param allowSecondary Whether to enable secondary instance support
36 * @param options Optional flags to toggle specific behaviour
37 * @param timeout Maximum time blocking functions are allowed during app load
38 */
SingleApplication(int & argc,char * argv[],bool allowSecondary,Options options,int timeout)39 SingleApplication::SingleApplication(int& argc,
40 char* argv[],
41 bool allowSecondary,
42 Options options,
43 int timeout)
44 : app_t(argc, argv)
45 , d_ptr(new SingleApplicationPrivate(this))
46 {
47 Q_D(SingleApplication);
48
49 #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
50 // On Android and iOS since the library is not supported fallback to
51 // standard QApplication behaviour by simply returning at this point.
52 qWarning()
53 << "SingleApplication is not supported on Android and iOS systems.";
54 return;
55 #endif
56
57 // Store the current mode of the program
58 d->options = options;
59
60 // Generating an application ID used for identifying the shared memory
61 // block and QLocalServer
62 d->genBlockServerName();
63
64 // To mitigate QSharedMemory issues with large amount of processes
65 // attempting to attach at the same time
66 d->randomSleep();
67
68 #ifdef Q_OS_UNIX
69 // By explicitly attaching it and then deleting it we make sure that the
70 // memory is deleted even after the process has crashed on Unix.
71 d->memory = new QSharedMemory(d->blockServerName);
72 d->memory->attach();
73 delete d->memory;
74 #endif
75 // Guarantee thread safe behaviour with a shared memory block.
76 d->memory = new QSharedMemory(d->blockServerName);
77
78 // Create a shared memory block
79 if (d->memory->create(sizeof(InstancesInfo))) {
80 // Initialize the shared memory block
81 if (!d->memory->lock()) {
82 qCritical()
83 << "SingleApplication: Unable to lock memory block after create.";
84 abortSafely();
85 }
86 d->initializeMemoryBlock();
87 } else {
88 if (d->memory->error() == QSharedMemory::AlreadyExists) {
89 // Attempt to attach to the memory segment
90 if (!d->memory->attach()) {
91 qCritical() << "SingleApplication: Unable to attach to shared "
92 "memory block.";
93 abortSafely();
94 }
95 if (!d->memory->lock()) {
96 qCritical() << "SingleApplication: Unable to lock memory block "
97 "after attach.";
98 abortSafely();
99 }
100 } else {
101 qCritical() << "SingleApplication: Unable to create block.";
102 abortSafely();
103 }
104 }
105
106 auto* inst = static_cast<InstancesInfo*>(d->memory->data());
107 QElapsedTimer time;
108 time.start();
109
110 // Make sure the shared memory block is initialised and in consistent state
111 while (true) {
112 // If the shared memory block's checksum is valid continue
113 if (d->blockChecksum() == inst->checksum)
114 break;
115
116 // If more than 5s have elapsed, assume the primary instance crashed and
117 // assume it's position
118 if (time.elapsed() > 5000) {
119 qWarning() << "SingleApplication: Shared memory block has been in "
120 "an inconsistent state from more than 5s. Assuming "
121 "primary instance failure.";
122 d->initializeMemoryBlock();
123 }
124
125 // Otherwise wait for a random period and try again. The random sleep
126 // here limits the probability of a collision between two racing apps
127 // and allows the app to initialise faster
128 if (!d->memory->unlock()) {
129 qDebug()
130 << "SingleApplication: Unable to unlock memory for random wait.";
131 qDebug() << d->memory->errorString();
132 }
133 d->randomSleep();
134 if (!d->memory->lock()) {
135 qCritical()
136 << "SingleApplication: Unable to lock memory after random wait.";
137 abortSafely();
138 }
139 }
140
141 if (inst->primary == false) {
142 d->startPrimary();
143 if (!d->memory->unlock()) {
144 qDebug() << "SingleApplication: Unable to unlock memory after "
145 "primary start.";
146 qDebug() << d->memory->errorString();
147 }
148 return;
149 }
150
151 // Check if another instance can be started
152 if (allowSecondary) {
153 d->startSecondary();
154 if (d->options & Mode::SecondaryNotification) {
155 d->connectToPrimary(timeout,
156 SingleApplicationPrivate::SecondaryInstance);
157 }
158 if (!d->memory->unlock()) {
159 qDebug() << "SingleApplication: Unable to unlock memory after "
160 "secondary start.";
161 qDebug() << d->memory->errorString();
162 }
163 return;
164 }
165
166 if (!d->memory->unlock()) {
167 qDebug()
168 << "SingleApplication: Unable to unlock memory at end of execution.";
169 qDebug() << d->memory->errorString();
170 }
171
172 d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
173
174 delete d;
175
176 ::exit(EXIT_SUCCESS);
177 }
178
~SingleApplication()179 SingleApplication::~SingleApplication()
180 {
181 Q_D(SingleApplication);
182 delete d;
183 }
184
185 /**
186 * Checks if the current application instance is primary.
187 * @return Returns true if the instance is primary, false otherwise.
188 */
isPrimary()189 bool SingleApplication::isPrimary()
190 {
191 Q_D(SingleApplication);
192 return d->server != nullptr;
193 }
194
195 /**
196 * Checks if the current application instance is secondary.
197 * @return Returns true if the instance is secondary, false otherwise.
198 */
isSecondary()199 bool SingleApplication::isSecondary()
200 {
201 Q_D(SingleApplication);
202 return d->server == nullptr;
203 }
204
205 /**
206 * Allows you to identify an instance by returning unique consecutive instance
207 * ids. It is reset when the first (primary) instance of your app starts and
208 * only incremented afterwards.
209 * @return Returns a unique instance id.
210 */
instanceId()211 quint32 SingleApplication::instanceId()
212 {
213 Q_D(SingleApplication);
214 return d->instanceNumber;
215 }
216
217 /**
218 * Returns the OS PID (Process Identifier) of the process running the primary
219 * instance. Especially useful when SingleApplication is coupled with OS.
220 * specific APIs.
221 * @return Returns the primary instance PID.
222 */
primaryPid()223 qint64 SingleApplication::primaryPid()
224 {
225 Q_D(SingleApplication);
226 return d->primaryPid();
227 }
228
229 /**
230 * Returns the username the primary instance is running as.
231 * @return Returns the username the primary instance is running as.
232 */
primaryUser()233 QString SingleApplication::primaryUser()
234 {
235 Q_D(SingleApplication);
236 return d->primaryUser();
237 }
238
239 /**
240 * Returns the username the current instance is running as.
241 * @return Returns the username the current instance is running as.
242 */
currentUser()243 QString SingleApplication::currentUser()
244 {
245 Q_D(SingleApplication);
246 return d->getUsername();
247 }
248
249 /**
250 * Sends message to the Primary Instance.
251 * @param message The message to send.
252 * @param timeout the maximum timeout in milliseconds for blocking functions.
253 * @return true if the message was sent successfuly, false otherwise.
254 */
sendMessage(const QByteArray & message,int timeout)255 bool SingleApplication::sendMessage(const QByteArray& message, int timeout)
256 {
257 Q_D(SingleApplication);
258
259 // Nobody to connect to
260 if (isPrimary())
261 return false;
262
263 // Make sure the socket is connected
264 if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
265 return false;
266
267 d->socket->write(message);
268 bool dataWritten = d->socket->waitForBytesWritten(timeout);
269 d->socket->flush();
270 return dataWritten;
271 }
272
273 /**
274 * Cleans up the shared memory block and exits with a failure.
275 * This function halts program execution.
276 */
abortSafely()277 void SingleApplication::abortSafely()
278 {
279 Q_D(SingleApplication);
280
281 qCritical() << "SingleApplication: " << d->memory->error()
282 << d->memory->errorString();
283 delete d;
284 ::exit(EXIT_FAILURE);
285 }
286