From 1588793b47ee914878ecf43c91399dee6df0b5ea Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sat, 18 Nov 2023 18:14:47 -0500 Subject: [PATCH] Make ProcessBider cross platform Required to potentially allow CLIFp to run off of the Launcher's services. --- app/CMakeLists.txt | 7 +- app/src/task/t-bideprocess.cpp | 33 +---- app/src/task/t-bideprocess.h | 43 ------ app/src/tools/processbider.cpp | 191 +++++++------------------ app/src/tools/processbider.h | 46 +++--- app/src/tools/processbider_p.h | 73 ++++++++++ app/src/tools/processbider_p_linux.cpp | 48 +++++++ app/src/tools/processbider_p_win.cpp | 137 ++++++++++++++++++ 8 files changed, 349 insertions(+), 229 deletions(-) create mode 100644 app/src/tools/processbider_p.h create mode 100644 app/src/tools/processbider_p_linux.cpp create mode 100644 app/src/tools/processbider_p_win.cpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index f91780d..14cb167 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -57,6 +57,9 @@ set(CLIFP_SOURCE tools/mounter_qmp.cpp tools/mounter_router.h tools/mounter_router.cpp + tools/processbider_p.h + tools/processbider.h + tools/processbider.cpp frontend/message.h frontend/statusrelay.h frontend/statusrelay.cpp @@ -90,8 +93,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Windows) task/t-exec_win.cpp task/t-bideprocess.h task/t-bideprocess.cpp - tools/processbider.h - tools/processbider.cpp + tools/processbider_p_win.cpp ) list(APPEND CLIFP_LINKS @@ -106,6 +108,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) task/t-awaitdocker.h task/t-awaitdocker.cpp task/t-exec_linux.cpp + tools/processbider_p_linux.cpp ) list(APPEND CLIFP_LINKS PRIVATE diff --git a/app/src/task/t-bideprocess.cpp b/app/src/task/t-bideprocess.cpp index e380e48..bb80fbd 100644 --- a/app/src/task/t-bideprocess.cpp +++ b/app/src/task/t-bideprocess.cpp @@ -1,29 +1,6 @@ // Unit Include #include "t-bideprocess.h" -//=============================================================================================================== -// TBideProcessError -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -//Private: -TBideProcessError::TBideProcessError(Type t, const QString& s) : - mType(t), - mSpecific(s) -{} - -//-Instance Functions------------------------------------------------------------- -//Public: -bool TBideProcessError::isValid() const { return mType != NoError; } -QString TBideProcessError::specific() const { return mSpecific; } -TBideProcessError::Type TBideProcessError::type() const { return mType; } - -//Private: -Qx::Severity TBideProcessError::deriveSeverity() const { return Qx::Err; } -quint32 TBideProcessError::deriveValue() const { return mType; } -QString TBideProcessError::derivePrimary() const { return ERR_STRINGS.value(mType); } -QString TBideProcessError::deriveSecondary() const { return mSpecific; } - //=============================================================================================================== // TBideProcess //=============================================================================================================== @@ -32,9 +9,10 @@ QString TBideProcessError::deriveSecondary() const { return mSpecific; } //Public: TBideProcess::TBideProcess(QObject* parent) : Task(parent), - mProcessBider(nullptr, STANDARD_GRACE) + mProcessBider(nullptr) { // Setup bider + mProcessBider.setRespawnGrace(STANDARD_GRACE); connect(&mProcessBider, &ProcessBider::statusChanged, this, [this](QString statusMessage){ emit eventOccurred(NAME, statusMessage); }); @@ -61,7 +39,8 @@ void TBideProcess::setProcessName(QString processName) { mProcessName = processN void TBideProcess::perform() { // Start bide - mProcessBider.start(mProcessName); + mProcessBider.setProcessName(mProcessName); + mProcessBider.start(); } void TBideProcess::stop() @@ -69,8 +48,8 @@ void TBideProcess::stop() if(mProcessBider.isRunning()) { emit eventOccurred(NAME, LOG_EVENT_STOPPING_BIDE_PROCESS); - if(!mProcessBider.closeProcess()) - emit errorOccurred(NAME, TBideProcessError(TBideProcessError::CantClose)); + if(ProcessBiderError err = mProcessBider.closeProcess(); err.isValid()) + emit errorOccurred(NAME, err); } } diff --git a/app/src/task/t-bideprocess.h b/app/src/task/t-bideprocess.h index eebb9cd..d1b899d 100644 --- a/app/src/task/t-bideprocess.h +++ b/app/src/task/t-bideprocess.h @@ -1,53 +1,10 @@ #ifndef TBIDEPROCESS_H #define TBIDEPROCESS_H -// Qx Includes -#include - // Project Includes #include "task/task.h" #include "tools/processbider.h" -class QX_ERROR_TYPE(TBideProcessError, "TBideProcessError", 1251) -{ - friend class TBideProcess; - //-Class Enums------------------------------------------------------------- -public: - enum Type - { - NoError = 0, - CantClose = 1, - }; - - //-Class Variables------------------------------------------------------------- -private: - static inline const QHash ERR_STRINGS{ - {NoError, u""_s}, - {CantClose, u"Could not automatically end the running title! It will have to be closed manually."_s}, - }; - - //-Instance Variables------------------------------------------------------------- -private: - Type mType; - QString mSpecific; - - //-Constructor------------------------------------------------------------- -private: - TBideProcessError(Type t = NoError, const QString& s = {}); - - //-Instance Functions------------------------------------------------------------- -public: - bool isValid() const; - Type type() const; - QString specific() const; - -private: - Qx::Severity deriveSeverity() const override; - quint32 deriveValue() const override; - QString derivePrimary() const override; - QString deriveSecondary() const override; -}; - class TBideProcess : public Task { Q_OBJECT; diff --git a/app/src/tools/processbider.cpp b/app/src/tools/processbider.cpp index 03d6867..e5aa2a9 100644 --- a/app/src/tools/processbider.cpp +++ b/app/src/tools/processbider.cpp @@ -1,13 +1,9 @@ // Unit Include #include "processbider.h" +#include "processbider_p.h" // Qx Includes -#include #include -#include - -// Windows Include -#include //=============================================================================================================== // ProcessBiderError @@ -38,73 +34,25 @@ QString ProcessBiderError::deriveSecondary() const { return mSpecific; } //-Constructor------------------------------------------------------------- //Public: -ProcessBider::ProcessBider(QObject* parent, uint respawnGrace) : +ProcessBider::ProcessBider(QObject* parent, const QString& name) : QThread(parent), - mRespawnGrace(respawnGrace), - mProcessHandle(nullptr) + mProcessName(name), + mRespawnGrace(30000), + mPollRate(500) {} -//-Class Functions------------------------------------------------------------- -//Private: -bool ProcessBider::closeAdminProcess(DWORD processId, bool force) -{ - /* Killing an elevated process from this process while it is unelevated requires (without COM non-sense) starting - * a new process as admin to do the job. While a special purpose executable could be made, taskkill already - * perfectly suitable here - */ - - // Setup taskkill args - QString tkArgs; - if(force) - tkArgs += u"/F "_s; - tkArgs += u"/PID "_s; - tkArgs += QString::number(processId); - const std::wstring tkArgsStd = tkArgs.toStdWString(); - - // Setup taskkill info - SHELLEXECUTEINFOW tkExecInfo = {0}; - tkExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); // Required - tkExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; // Causes hProcess member to be set to process handle - tkExecInfo.hwnd = NULL; - tkExecInfo.lpVerb = L"runas"; - tkExecInfo.lpFile = L"taskkill"; - tkExecInfo.lpParameters = tkArgsStd.data(); - tkExecInfo.lpDirectory = NULL; - tkExecInfo.nShow = SW_HIDE; - - // Start taskkill - if(!ShellExecuteEx(&tkExecInfo)) - return false; - - // Check for handle - HANDLE tkHandle = tkExecInfo.hProcess; - if(!tkHandle) - return false; - - // Wait for taskkill to finish (should be fast) - if(WaitForSingleObject(tkHandle, 5000) != WAIT_OBJECT_0) - return false; - - DWORD exitCode; - if(!GetExitCodeProcess(tkHandle, &exitCode)) - return false; - - // Cleanup taskkill handle - CloseHandle(tkHandle); - - // Return taskkill result - return exitCode == 0; -} - //-Instance Functions------------------------------------------------------------- //Private: ProcessBiderError ProcessBider::doWait() { - // Lock other threads from interaction while managing process handle - QMutexLocker handleLocker(&mProcessHandleMutex); + mWaiter = new ProcessWaiter(mProcessName); + mWaiter->setPollRate(mPollRate); + + // Block outer access until waiting + QWriteLocker writeLock(&mRWLock); // Wait until process has stopped running for grace period - DWORD spProcessId; + quint32 procId; do { // Yield for grace period @@ -113,49 +61,29 @@ ProcessBiderError ProcessBider::doWait() QThread::sleep(mRespawnGrace); // Find process ID by name - spProcessId = Qx::processId(mProcessName); + procId = Qx::processId(mProcessName); // Check that process was found (is running) - if(spProcessId) + if(procId) { emit statusChanged(LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); - // Get process handle and see if it is valid - DWORD rights = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; - if((mProcessHandle = OpenProcess(rights, FALSE, spProcessId)) == NULL) - { - Qx::SystemError nativeError = Qx::SystemError::fromHresult(HRESULT_FROM_WIN32(GetLastError())); - ProcessBiderError err(ProcessBiderError::HandleAquisition, nativeError.cause()); - - emit errorOccurred(err); - return err; - } - // Attempt to wait on process to terminate emit statusChanged(LOG_EVENT_BIDE_ON.arg(mProcessName)); - handleLocker.unlock(); // Allow interaction while waiting - DWORD waitError = WaitForSingleObject(mProcessHandle, INFINITE); - handleLocker.relock(); // Explicitly lock again - - // Close handle to process - CloseHandle(mProcessHandle); - mProcessHandle = nullptr; - - /* Here the status can technically can be WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT, or WAIT_FAILED, but the first - * and third should never occur here (the wait won't ever be abandoned, and the timeout is infinite), so this check is fine - */ - if(waitError != WAIT_OBJECT_0) + mWaiter->updateId(procId); + writeLock.unlock(); // To allow close attempts + if(!mWaiter->wait()) // Blocks until process ends { - Qx::SystemError nativeError = Qx::SystemError::fromHresult(HRESULT_FROM_WIN32(GetLastError())); - ProcessBiderError err(ProcessBiderError::ProcessHook, nativeError.cause()); - + ProcessBiderError err(ProcessBiderError::Wait, mProcessName); emit errorOccurred(err); return err; } + writeLock.relock(); + emit statusChanged(LOG_EVENT_BIDE_QUIT.arg(mProcessName)); } } - while(spProcessId); + while(procId); // Return success emit statusChanged(LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); @@ -165,63 +93,52 @@ ProcessBiderError ProcessBider::doWait() void ProcessBider::run() { ProcessBiderError status = doWait(); + qxDelete(mWaiter); emit bideFinished(status); } //Public: -void ProcessBider::setRespawnGrace(uint respawnGrace) { mRespawnGrace = respawnGrace; } +void ProcessBider::setProcessName(const QString& name) +{ + mRWLock.lockForWrite(); + mProcessName = name; + mRWLock.unlock(); +} +void ProcessBider::setRespawnGrace(uint respawnGrace) +{ + mRWLock.lockForWrite(); + mRespawnGrace = respawnGrace; + mRWLock.unlock(); +} -bool ProcessBider::closeProcess() +void ProcessBider::setPollRate(uint pollRate) { - if(!mProcessHandle) - return false; - - // Lock access to handle and auto-unlock when done - QMutexLocker handleLocker(&mProcessHandleMutex); - - /* Get process ID for use in some of the following calls so that the specific permissions the mProcessHandle - * was opened with don't have to be considered - */ - DWORD processId = GetProcessId(mProcessHandle); - - // Check if admin rights are needed (CLIFp shouldn't be run as admin, but check anyway) - bool selfElevated; - if(Qx::processIsElevated(selfElevated).isValid()) - selfElevated = false; // If check fails, assume CLIFP is not elevated to be safe - bool waitProcessElevated; - if(Qx::processIsElevated(waitProcessElevated, processId).isValid()) - waitProcessElevated = true; // If check fails, assume process is elevated to be safe - - bool elevate = !selfElevated && waitProcessElevated; - - // Try clean close first - if(!elevate) - Qx::cleanKillProcess(processId); - else - closeAdminProcess(processId, false); - - // Wait for process to close (allow up to 2 seconds) - DWORD waitRes = WaitForSingleObject(mProcessHandle, 2000); - - // See if process closed - if(waitRes == WAIT_OBJECT_0) - return true; - - // Force close - if(!elevate) - return !Qx::forceKillProcess(processId).isValid(); - else - return closeAdminProcess(processId, true); + mRWLock.lockForWrite(); + mPollRate = pollRate; + mRWLock.unlock(); +} + +/* TODO: Since this doesn't allow explicitly abandoning the wait, if for some reason the process opens itself over and over, + * it's still possible to get stuck in a dead lock even if the close is successful + */ +ProcessBiderError ProcessBider::closeProcess() +{ + QReadLocker readLocker(&mRWLock); + if(mWaiter && mWaiter->isWaiting() && !mWaiter->close()) + { + ProcessBiderError err(ProcessBiderError::Close, mProcessName); + emit errorOccurred(err); + return err; + } + + return ProcessBiderError(); } //-Signals & Slots------------------------------------------------------------------------------------------------------------ //Public Slots: -void ProcessBider::start(QString processName) +void ProcessBider::start() { // Start new thread for waiting if(!isRunning()) - { - mProcessName = processName; QThread::start(); - } } diff --git a/app/src/tools/processbider.h b/app/src/tools/processbider.h index db493c2..3595f20 100644 --- a/app/src/tools/processbider.h +++ b/app/src/tools/processbider.h @@ -3,18 +3,18 @@ // Qt Includes #include -#include +#include // Qx Includes -#include -#include +#include +#include /* This uses the approach of sub-classing QThread instead of the worker/object model. This means that by default there is no event * loop running in the new thread (not needed with current setup), and that only the contents of run() take place in the new thread, * with everything else happening in the thread that contains the instance of this class. * * This does mean that a Mutex must be used to protected against access to the same data between threads where necessary, but in - * this case that is desirable as when the parent thread calls `endProcess()` we want it to get blocked if the run() thread is + * this case that is desirable as when the parent thread calls `closeProcess()` we want it to get blocked if the run() thread is * busy doing work until it goes back to waiting (or the wait process stops on its own), since we want to make sure that the wait * process has ended before Driver takes its next steps. * @@ -26,6 +26,10 @@ * caveats since a thread spawned by the OS is used to trigger the specified callback function. It would potentially be safe if that * callback function simply emits an internal signal with a queued connection that then triggers the thread managing the object to * handle the quit upon its next event loop cycle. + * + * NOTE: Technically the thread synchronization here is imperfect as a blocked closeProcess() is unlocked one step before the wait + * actually starts so there could be a race between that and the waiter being marked as "in wait". Not a great way to avoid this though + * since the lock can't be unlocked any later since the waiting thread deadlocks once the wait is started. */ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) @@ -36,16 +40,16 @@ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) enum Type { NoError = 0, - HandleAquisition = 1, - ProcessHook = 2 + Wait = 1, + Close = 2 }; //-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, - {HandleAquisition, u"Could not get a wait handle to a restartable process, the title will likely not work correctly."_s}, - {ProcessHook, u"Could not hook a restartable process for waiting, the title will likely not work correctly."_s}, + {Wait, u"Could not setup a wait on the process."_s}, + {Close, u"Could not close the wait on process."_s}, }; //-Instance Variables------------------------------------------------------------- @@ -70,6 +74,8 @@ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) QString deriveSecondary() const override; }; +class ProcessWaiter; + class ProcessBider : public QThread { Q_OBJECT @@ -84,35 +90,35 @@ class ProcessBider : public QThread //-Instance Variables------------------------------------------------------------------------------------------------------------ private: + // Work + ProcessWaiter* mWaiter; + QReadWriteLock mRWLock; + // Process Info QString mProcessName; uint mRespawnGrace; - - // Process Handling - HANDLE mProcessHandle; - QMutex mProcessHandleMutex; + uint mPollRate; //-Constructor------------------------------------------------------------------------------------------------- public: - ProcessBider(QObject* parent = nullptr, uint respawnGrace = 30000); - -//-Class Functions--------------------------------------------------------------------------------------------------------- -private: - static bool closeAdminProcess(DWORD processId, bool force); + ProcessBider(QObject* parent = nullptr, const QString& name = {}); //-Instance Functions--------------------------------------------------------------------------------------------------------- private: + // Run in wait thread ProcessBiderError doWait(); void run() override; public: + // Run in external thread + void setProcessName(const QString& name); void setRespawnGrace(uint respawnGrace); - - bool closeProcess(); + void setPollRate(uint pollRate); // Ignored on Windows + ProcessBiderError closeProcess(); //-Signals & Slots------------------------------------------------------------------------------------------------------------ public slots: - void start(QString processName); + void start(); signals: void statusChanged(QString statusMessage); diff --git a/app/src/tools/processbider_p.h b/app/src/tools/processbider_p.h new file mode 100644 index 0000000..78900b4 --- /dev/null +++ b/app/src/tools/processbider_p.h @@ -0,0 +1,73 @@ +#ifndef PROCESSWAITER_P_H +#define PROCESSWAITER_P_H + +#include +#include +#ifdef __linux__ + #include +#endif +#ifdef _WIN32 + typedef void* HANDLE; +#endif + +class ProcessWaiter +{ +//-Instance Variables------------------------------------------------------------------------------------------------------------ +private: + bool mWaiting; + QString mName; + quint32 mId; + uint mPollRate; + QMutex mMutex; + +#ifdef _WIN32 + HANDLE mHandle; +#endif +#ifdef __linux__ + QWaitCondition mCloseNotifier; +#endif + +//-Constructor------------------------------------------------------------------------------------------------- +public: + ProcessWaiter(const QString& name) : + mWaiting(false), + mName(name), + mId(0), + mPollRate(500) + {} + +//-Instance Functions--------------------------------------------------------------------------------------------------------- +private: + bool _wait(); + bool _close(); + +public: + // Used in waiting thread + bool wait() + { + mWaiting = true; + bool r =_wait(); + mWaiting = false; + return r; + } + + // Used from external thread; + void updateId(quint32 id) + { + mMutex.lock(); + mId = id; + mMutex.unlock(); + }; + + void setPollRate(uint pollRate) + { + mMutex.lock(); + mPollRate = pollRate; + mMutex.unlock(); + } + + bool isWaiting() { return mWaiting; } + bool close() { return mWaiting ? _close() : true; } +}; + +#endif // PROCESSWAITER_P_H diff --git a/app/src/tools/processbider_p_linux.cpp b/app/src/tools/processbider_p_linux.cpp new file mode 100644 index 0000000..74e55ec --- /dev/null +++ b/app/src/tools/processbider_p_linux.cpp @@ -0,0 +1,48 @@ +// Unit Include +#include "processbider_p.h" + +// Qt Includes +#include + +// Qx Includes +#include + +//=============================================================================================================== +// ProcessWaiter +//=============================================================================================================== + +//-Instance Functions------------------------------------------------------------- +//Public: +bool ProcessWaiter::_wait() +{ + // Poll for process existence + QString currentName = mName; + while(currentName == mName) + { + QThread::msleep(mPollRate); + mMutex.lock(); // Don't allow close during check + currentName = Qx::processName(mId); + mMutex.unlock(); + } + + // Notify that the process closed + mCloseNotifier.wakeAll(); + + return true; +} + +bool ProcessWaiter::_close() +{ + // NOTE: Does not handle killing processes that require greater permissions + + // Try clean close first + Qx::cleanKillProcess(mId); + + // See if process closes (max 2 seconds, though al) + mMutex.lock(); + if(mCloseNotifier.wait(&mMutex, std::max(uint(2000), mPollRate + 100))) + return true; + + // Force close + return !Qx::forceKillProcess(mId).isValid(); +} diff --git a/app/src/tools/processbider_p_win.cpp b/app/src/tools/processbider_p_win.cpp new file mode 100644 index 0000000..afdf1c7 --- /dev/null +++ b/app/src/tools/processbider_p_win.cpp @@ -0,0 +1,137 @@ +// Unit Include +#include "processbider_p.h" + +// Qx Includes +#include +#include +#include + +// Windows Include +#include + +namespace +{ + +bool closeAdminProcess(DWORD processId, bool force) +{ + /* Killing an elevated process from this process while it is unelevated requires (without COM non-sense) starting + * a new process as admin to do the job. While a special purpose executable could be made, taskkill already + * perfectly suitable here + */ + + // Setup taskkill args + QString tkArgs; + if(force) + tkArgs += u"/F "_s; + tkArgs += u"/PID "_s; + tkArgs += QString::number(processId); + const std::wstring tkArgsStd = tkArgs.toStdWString(); + + // Setup taskkill info + SHELLEXECUTEINFOW tkExecInfo = {0}; + tkExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); // Required + tkExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; // Causes hProcess member to be set to process handle + tkExecInfo.hwnd = NULL; + tkExecInfo.lpVerb = L"runas"; + tkExecInfo.lpFile = L"taskkill"; + tkExecInfo.lpParameters = tkArgsStd.data(); + tkExecInfo.lpDirectory = NULL; + tkExecInfo.nShow = SW_HIDE; + + // Start taskkill + if(!ShellExecuteEx(&tkExecInfo)) + return false; + + // Check for handle + HANDLE tkHandle = tkExecInfo.hProcess; + if(!tkHandle) + return false; + + // Wait for taskkill to finish (should be fast) + if(WaitForSingleObject(tkHandle, 5000) != WAIT_OBJECT_0) + return false; + + DWORD exitCode; + if(!GetExitCodeProcess(tkHandle, &exitCode)) + return false; + + // Cleanup taskkill handle + CloseHandle(tkHandle); + + // Return taskkill result + return exitCode == 0; +} + +} + +//=============================================================================================================== +// ProcessWaiter +//=============================================================================================================== + +//-Instance Functions------------------------------------------------------------- +//Public: +bool ProcessWaiter::_wait() +{ + // Prevent changes while setting up the wait + QMutexLocker mutLock(&mMutex); + + // Get process handle and see if it is valid + DWORD rights = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; + if((mHandle = OpenProcess(rights, FALSE, mId)) == NULL) // Can use Qx::lastError() for error info here if desired + return false; + + // Attempt to wait on process to terminate + mutLock.unlock(); // Allow changes while waiting + DWORD waitError = WaitForSingleObject(mHandle, INFINITE); + mutLock.relock(); // Prevent changes during cleanup + + // Close handle to process + CloseHandle(mHandle); + mHandle = nullptr; + + /* Here the status can technically can be WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT, or WAIT_FAILED, but the first + * and third should never occur here (the wait won't ever be abandoned, and the timeout is infinite), so this check is fine + */ + if(waitError != WAIT_OBJECT_0) // Can use Qx::lastError() for error info here if desired + return false; + + return true; +} + +bool ProcessWaiter::_close() +{ + if(!mHandle) + return true; + + // Prevent wait setup/cleanup during closure and auto-unlock when done + QMutexLocker mutLock(&mMutex); + + // Check if admin rights are needed (CLIFp shouldn't be run as admin, but check anyway) + bool selfElevated; + if(Qx::processIsElevated(selfElevated).isValid()) + selfElevated = false; // If check fails, assume CLIFP is not elevated to be safe + bool waitProcessElevated; + if(Qx::processIsElevated(waitProcessElevated, mId).isValid()) + waitProcessElevated = true; // If check fails, assume process is elevated to be safe + + bool elevate = !selfElevated && waitProcessElevated; + + // Try clean close first + if(!elevate) + Qx::cleanKillProcess(mId); + else + closeAdminProcess(mId, false); + + // Wait for process to close (allow up to 2 seconds) + DWORD waitRes = WaitForSingleObject(mHandle, 2000); + + // See if process closed + if(waitRes == WAIT_OBJECT_0) + return true; + + // Force close + if(!elevate) + return !Qx::forceKillProcess(mId).isValid(); + else + return closeAdminProcess(mId, true); +}