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