From a0907e46586806d2f4fea9fdeb0a6241a96b3d1b Mon Sep 17 00:00:00 2001
From: Don Gagne <don@thegagnes.com>
Date: Wed, 30 Nov 2016 18:55:19 -0800
Subject: [PATCH] Single instance app guarding mechanism

---
 qgroundcontrol.pro |  2 ++
 src/RunGuard.cc    | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/RunGuard.h     | 38 ++++++++++++++++++++++++
 src/main.cc        | 20 +++++--------
 4 files changed, 135 insertions(+), 12 deletions(-)
 create mode 100644 src/RunGuard.cc
 create mode 100644 src/RunGuard.h

diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index f056999..85adc4f 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -529,6 +529,7 @@ HEADERS += \
     src/Joystick/JoystickSDL.h \
     src/QGCFileDialog.h \
     src/QGCMessageBox.h \
+    src/RunGuard.h \
     src/ViewWidgets/CustomCommandWidget.h \
     src/ViewWidgets/CustomCommandWidgetController.h \
     src/ViewWidgets/ViewWidgetController.h \
@@ -677,6 +678,7 @@ SOURCES += \
     src/GPS/RTCM/RTCMMavlink.cc \
     src/Joystick/JoystickSDL.cc \
     src/QGCFileDialog.cc \
+    src/RunGuard.cc \
     src/ViewWidgets/CustomCommandWidget.cc \
     src/ViewWidgets/CustomCommandWidgetController.cc \
     src/ViewWidgets/ViewWidgetController.cc \
diff --git a/src/RunGuard.cc b/src/RunGuard.cc
new file mode 100644
index 0000000..5e8a492
--- /dev/null
+++ b/src/RunGuard.cc
@@ -0,0 +1,87 @@
+/****************************************************************************
+ *
+ *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#include "RunGuard.h"
+
+#include <QCryptographicHash>
+
+namespace
+{
+
+QString generateKeyHash( const QString& key, const QString& salt )
+{
+    QByteArray data;
+
+    data.append( key.toUtf8() );
+    data.append( salt.toUtf8() );
+    data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();
+
+    return data;
+}
+
+}
+
+RunGuard::RunGuard( const QString& key )
+    : key( key )
+    , memLockKey( generateKeyHash( key, "_memLockKey" ) )
+    , sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
+    , sharedMem( sharedmemKey )
+    , memLock( memLockKey, 1 )
+{
+    memLock.acquire();
+    {
+        QSharedMemory fix( sharedmemKey );    // Fix for *nix: http://habrahabr.ru/post/173281/
+        fix.attach();
+    }
+    memLock.release();
+}
+
+RunGuard::~RunGuard()
+{
+    release();
+}
+
+bool RunGuard::isAnotherRunning()
+{
+    if ( sharedMem.isAttached() )
+        return false;
+
+    memLock.acquire();
+    const bool isRunning = sharedMem.attach();
+    if ( isRunning )
+        sharedMem.detach();
+    memLock.release();
+
+    return isRunning;
+}
+
+bool RunGuard::tryToRun()
+{
+    if ( isAnotherRunning() )   // Extra check
+        return false;
+
+    memLock.acquire();
+    const bool result = sharedMem.create( sizeof( quint64 ) );
+    memLock.release();
+    if ( !result )
+    {
+        release();
+        return false;
+    }
+
+    return true;
+}
+
+void RunGuard::release()
+{
+    memLock.acquire();
+    if ( sharedMem.isAttached() )
+        sharedMem.detach();
+    memLock.release();
+}
diff --git a/src/RunGuard.h b/src/RunGuard.h
new file mode 100644
index 0000000..0e82f5b
--- /dev/null
+++ b/src/RunGuard.h
@@ -0,0 +1,38 @@
+/****************************************************************************
+ *
+ *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#ifndef RunGuard_H
+#define RunGuard_H
+
+#include <QObject>
+#include <QSharedMemory>
+#include <QSystemSemaphore>
+
+class RunGuard
+{
+public:
+    RunGuard( const QString& key );
+    ~RunGuard();
+
+    bool isAnotherRunning();
+    bool tryToRun();
+    void release();
+
+private:
+    const QString key;
+    const QString memLockKey;
+    const QString sharedmemKey;
+
+    QSharedMemory sharedMem;
+    QSystemSemaphore memLock;
+
+    Q_DISABLE_COPY( RunGuard )
+};
+
+#endif
diff --git a/src/main.cc b/src/main.cc
index 80000cd..4dec260 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -27,10 +27,9 @@
 #include "QGCApplication.h"
 #include "AppMessages.h"
 
-#define  SINGLE_INSTANCE_PORT   14499
-
 #ifndef __mobile__
     #include "QGCSerialPortInfo.h"
+    #include "RunGuard.h"
 #endif
 
 #ifdef UNITTEST_BUILD
@@ -103,6 +102,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
 
 int main(int argc, char *argv[])
 {
+#ifndef __mobile__
+    RunGuard guard("QGroundControlRunGuardKey");
+    if (!guard.tryToRun()) {
+        return 0;
+    }
+#endif
+
 #ifdef Q_OS_UNIX
     //Force writing to the console on UNIX/BSD devices
     if (!qEnvironmentVariableIsSet("QT_LOGGING_TO_CONSOLE"))
@@ -112,16 +118,6 @@ int main(int argc, char *argv[])
     // install the message handler
     AppMessages::installHandler();
 
-#ifndef __mobile__
-    //-- Test for another instance already running. If that's the case, we simply exit.
-    QHostAddress host("127.0.0.1");
-    QUdpSocket socket;
-    if(!socket.bind(host, SINGLE_INSTANCE_PORT, QAbstractSocket::DontShareAddress)) {
-        qWarning() << "Another instance already running. Exiting.";
-        exit(-1);
-    }
-#endif
-
 #ifdef Q_OS_MAC
 #ifndef __ios__
     // Prevent Apple's app nap from screwing us over