diff options
Diffstat (limited to 'other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp')
-rw-r--r-- | other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp b/other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp new file mode 100644 index 0000000000..aea1f270ea --- /dev/null +++ b/other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <bits.h> +#include <utility> + +// Avoid conversions, we will only build Unicode anyway. +#if !(defined(UNICODE) && defined(_UNICODE)) +# error "Unicode required" +#endif + +static HINSTANCE gHInst; + +// ***** Section: ScopeExit +// Derived from mfbt mozilla::ScopeExit, I have removed the use of +// GuardObjectNotifier and the MOZ_* annotations. +template <typename ExitFunction> +class ScopeExit { + ExitFunction mExitFunction; + bool mExecuteOnDestruction; + + public: + explicit ScopeExit(ExitFunction &&cleanup) + : mExitFunction(cleanup), mExecuteOnDestruction(true) {} + + ScopeExit(ScopeExit &&rhs) + : mExitFunction(std::move(rhs.mExitFunction)), + mExecuteOnDestruction(rhs.mExecuteOnDestruction) { + rhs.release(); + } + + ~ScopeExit() { + if (mExecuteOnDestruction) { + mExitFunction(); + } + } + + void release() { mExecuteOnDestruction = false; } + + private: + explicit ScopeExit(const ScopeExit &) = delete; + ScopeExit &operator=(const ScopeExit &) = delete; + ScopeExit &operator=(ScopeExit &&) = delete; +}; + +template <typename ExitFunction> +ScopeExit<ExitFunction> MakeScopeExit(ExitFunction &&exitFunction) { + return ScopeExit<ExitFunction>(std::move(exitFunction)); +} + +// ***** Section: NSIS stack +typedef struct _stack_t { + struct _stack_t *next; + WCHAR text[1]; // this should be the length of g_stringsize when allocating +} stack_t; + +static unsigned int g_stringsize; +static stack_t **g_stacktop; + +static int popstringn(LPWSTR str, int maxlen) { + stack_t *th; + if (!g_stacktop || !*g_stacktop) return 1; + th = (*g_stacktop); + if (str) lstrcpynW(str, th->text, maxlen ? maxlen : g_stringsize); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +static void pushstring(LPCWSTR str) { + stack_t *th; + if (!g_stacktop) return; + th = (stack_t *)GlobalAlloc( + GPTR, (sizeof(stack_t) + (g_stringsize) * sizeof(*str))); + lstrcpynW(th->text, str, g_stringsize); + th->next = *g_stacktop; + *g_stacktop = th; +} + +// ***** Section: NSIS Plug-In API (from NSIS api.h) +// NSIS Plug-In Callback Messages +enum NSPIM { + NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup + NSPIM_GUIUNLOAD, // Called after .onGUIEnd +}; + +// Prototype for callbacks registered with +// extra_parameters->RegisterPluginCallback() Return NULL for unknown messages +// Should always be __cdecl for future expansion possibilities +typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); + +#define NSISCALL __stdcall + +typedef struct { + LPVOID exec_flags; + int(NSISCALL *ExecuteCodeSegment)(int, HWND); + void(NSISCALL *validate_filename)(LPWSTR); + int(NSISCALL *RegisterPluginCallback)( + HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already + // registered and < 0 on errors +} extra_parameters; + +// ***** Section: StartBitsThread +UINT_PTR __cdecl NSISPluginCallback(NSPIM msg); + +static struct { + HANDLE thread; + bool shutdown_requested; + CRITICAL_SECTION cs; + CONDITION_VARIABLE cv; +} gStartBitsThread = {nullptr, false, 0, 0}; + +// This thread connects to the BackgroundCopyManager, which may take some time +// if the BITS service is not already running. It also holds open the connection +// until gStartBitsThread.shutdown_requested becomes true. +DWORD WINAPI StartBitsThreadProc(LPVOID) { + EnterCriticalSection(&gStartBitsThread.cs); + auto leaveCS = + MakeScopeExit([] { LeaveCriticalSection(&gStartBitsThread.cs); }); + + if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { + return 0; + } + auto coUninit = MakeScopeExit([] { CoUninitialize(); }); + + IBackgroundCopyManager *bcm = nullptr; + if (FAILED(CoCreateInstance( + __uuidof(BackgroundCopyManager), nullptr, CLSCTX_LOCAL_SERVER, + __uuidof(IBackgroundCopyManager), (LPVOID *)&bcm)) || + !bcm) { + return 0; + } + + do { + SleepConditionVariableCS(&gStartBitsThread.cv, &gStartBitsThread.cs, + INFINITE); + } while (!gStartBitsThread.shutdown_requested); + + bcm->Release(); + return 1; +} + +// Start up the thread +// returns true on success +bool StartBitsServiceBackgroundThreadImpl(extra_parameters *extra_params) { + EnterCriticalSection(&gStartBitsThread.cs); + auto leaveCS = + MakeScopeExit([] { LeaveCriticalSection(&gStartBitsThread.cs); }); + + if (gStartBitsThread.thread) { + // Thread is already started, assumed to be still running. + return true; + } + + // Ensure the callback is registered so the thread can be stopped, and also so + // NSIS doesn't unload this DLL. + extra_params->RegisterPluginCallback(gHInst, NSISPluginCallback); + + gStartBitsThread.shutdown_requested = false; + + gStartBitsThread.thread = + CreateThread(nullptr, 0, StartBitsThreadProc, nullptr, 0, 0); + if (!gStartBitsThread.thread) { + return false; + } + + return true; +} + +// Shut down the Start BITS thread, if it was started. +void ShutdownStartBitsThread() { + EnterCriticalSection(&gStartBitsThread.cs); + if (gStartBitsThread.thread) { + gStartBitsThread.shutdown_requested = true; + WakeAllConditionVariable(&gStartBitsThread.cv); + LeaveCriticalSection(&gStartBitsThread.cs); + + // Give the thread a little time to clean up. + if (WaitForSingleObject(gStartBitsThread.thread, 1000) == WAIT_OBJECT_0) { + EnterCriticalSection(&gStartBitsThread.cs); + gStartBitsThread.thread = nullptr; + LeaveCriticalSection(&gStartBitsThread.cs); + } else { + // Don't attempt to recover if we didn't see the thread end, + // the process will be exiting soon anyway. + } + + } else { + LeaveCriticalSection(&gStartBitsThread.cs); + } +} + +// ***** Section: CancelBitsJobsByName +#define MAX_JOB_NAME 256 + +bool CancelBitsJobsByNameImpl(LPWSTR matchJobName) { + if (FAILED(CoInitialize(nullptr))) { + return false; + } + auto coUninit = MakeScopeExit([] { CoUninitialize(); }); + + IBackgroundCopyManager *bcm = nullptr; + if (FAILED(CoCreateInstance( + __uuidof(BackgroundCopyManager), nullptr, CLSCTX_LOCAL_SERVER, + __uuidof(IBackgroundCopyManager), (LPVOID *)&bcm)) || + !bcm) { + return false; + } + auto bcmRelease = MakeScopeExit([bcm] { bcm->Release(); }); + + IEnumBackgroundCopyJobs *enumerator = nullptr; + // Attempt to enumerate jobs for all users. If that fails, + // try for only the current user. + if (FAILED(bcm->EnumJobs(BG_JOB_ENUM_ALL_USERS, &enumerator))) { + enumerator = nullptr; + if (FAILED(bcm->EnumJobs(0, &enumerator))) { + return false; + } + } + if (!enumerator) { + return false; + } + auto enumeratorRelease = + MakeScopeExit([enumerator] { enumerator->Release(); }); + + bool success = true; + + IBackgroundCopyJob *job = nullptr; + HRESULT nextResult; + while ((nextResult = enumerator->Next(1, &job, nullptr), + SUCCEEDED(nextResult))) { + if (nextResult == S_FALSE) { + break; + } + if (!job) { + success = false; + break; + } + + LPWSTR curJobName = nullptr; + + if (SUCCEEDED(job->GetDisplayName(&curJobName)) && curJobName) { + if (lstrcmpW(curJobName, matchJobName) == 0) { + if (!SUCCEEDED(job->Cancel())) { + // If we can't cancel we can still try the other jobs. + success = false; + } + } + CoTaskMemFree((LPVOID)curJobName); + curJobName = nullptr; + } else { + // We may not be able to access certain jobs, keep trying the rest. + success = false; + } + + job->Release(); + job = nullptr; + } + + if (!SUCCEEDED(nextResult)) { + success = false; + } + + return success; +} + +// ***** Section: DLL entry points +extern "C" { +// Cancel all BITS jobs with the given name. +void __declspec(dllexport) + CancelBitsJobsByName(HWND hwndParent, int string_size, char *variables, + stack_t **stacktop, extra_parameters *) { + g_stacktop = stacktop; + g_stringsize = string_size; + + WCHAR matchJobName[MAX_JOB_NAME + 1]; + matchJobName[0] = L'\0'; + + if (!popstringn(matchJobName, sizeof(matchJobName) / sizeof(WCHAR))) { + if (CancelBitsJobsByNameImpl(matchJobName)) { + pushstring(L"ok"); + return; + } + } + + pushstring(L"error"); +} + +// Start the BITS service in the background, and hold a reference to it until +// the (un)installer exits. +// Does not provide any feedback or touch the stack. +void __declspec(dllexport) + StartBitsServiceBackground(HWND, int, char *, stack_t **, + extra_parameters *extra_params) { + StartBitsServiceBackgroundThreadImpl(extra_params); +} +} + +// Handle messages from NSIS +UINT_PTR __cdecl NSISPluginCallback(NSPIM msg) { + if (msg == NSPIM_UNLOAD) { + ShutdownStartBitsThread(); + } + return 0; +} + +BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_ATTACH) { + gHInst = instance; + InitializeConditionVariable(&gStartBitsThread.cv); + InitializeCriticalSection(&gStartBitsThread.cs); + } + return TRUE; +} |