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 <QCoreApplication>
39 #include <QThread>
40 #include <QSharedMemory>
41 #include <QLocalSocket>
42 #include <QByteArray>
43 #include <QElapsedTimer>
44 #include <QtDebug>
45 
46 #include "singlecoreapplication.h"
47 #include "singlecoreapplication_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  */
SingleCoreApplication(int & argc,char * argv[],bool allowSecondary,Options options,int timeout)57 SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
58     : app_t(argc, argv),
59       d_ptr(new SingleCoreApplicationPrivate(this)) {
60 
61   Q_D(SingleCoreApplication);
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() << "SingleCoreApplication: 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() << "SingleCoreApplication: Unable to attach to shared memory block.";
96         abortSafely();
97       }
98       if (!d->memory_->lock()) {
99         qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
100         abortSafely();
101       }
102     }
103     else {
104       qCritical() << "SingleCoreApplication: 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() << "SingleCoreApplication: 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() << "SingleCoreApplication: Unable to unlock memory for random wait.";
128       qDebug() << d->memory_->errorString();
129     }
130     d->randomSleep();
131     if (!d->memory_->lock()) {
132       qCritical() << "SingleCoreApplication: 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() << "SingleCoreApplication: 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, SingleCoreApplicationPrivate::SecondaryInstance);
151     }
152     if (!d->memory_->unlock()) {
153       qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
154       qDebug() << d->memory_->errorString();
155     }
156     return;
157   }
158 
159   if (!d->memory_->unlock()) {
160     qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
161     qDebug() << d->memory_->errorString();
162   }
163 
164   d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
165 
166   delete d;
167 
168   ::exit(EXIT_SUCCESS);
169 
170 }
171 
~SingleCoreApplication()172 SingleCoreApplication::~SingleCoreApplication() {
173   Q_D(SingleCoreApplication);
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 SingleCoreApplication::isPrimary() {
182   Q_D(SingleCoreApplication);
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 SingleCoreApplication::isSecondary() {
191   Q_D(SingleCoreApplication);
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 SingleCoreApplication::instanceId() {
201   Q_D(SingleCoreApplication);
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 SingleCoreApplication is coupled with OS. specific APIs.
208  * @return Returns the primary instance PID.
209  */
primaryPid()210 qint64 SingleCoreApplication::primaryPid() {
211   Q_D(SingleCoreApplication);
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 SingleCoreApplication::primaryUser() {
220   Q_D(SingleCoreApplication);
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 SingleCoreApplication::currentUser() {
229   Q_D(SingleCoreApplication);
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 SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
240 
241   Q_D(SingleCoreApplication);
242 
243   // Nobody to connect to
244   if (isPrimary()) return false;
245 
246   // Make sure the socket is connected
247   if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::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 SingleCoreApplication::abortSafely() {
263 
264   Q_D(SingleCoreApplication);
265 
266   qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
267   delete d;
268   ::exit(EXIT_FAILURE);
269 
270 }
271