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