317 lines
9 KiB
C++
317 lines
9 KiB
C++
/* -*- 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;
|
|
}
|