BitsUtils.cpp (9186B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include <windows.h> 7 #include <bits.h> 8 #include <utility> 9 10 // Avoid conversions, we will only build Unicode anyway. 11 #if !(defined(UNICODE) && defined(_UNICODE)) 12 # error "Unicode required" 13 #endif 14 15 static HINSTANCE gHInst; 16 17 // ***** Section: ScopeExit 18 // Derived from mfbt mozilla::ScopeExit, I have removed the use of 19 // GuardObjectNotifier and the MOZ_* annotations. 20 template <typename ExitFunction> 21 class ScopeExit { 22 ExitFunction mExitFunction; 23 bool mExecuteOnDestruction; 24 25 public: 26 explicit ScopeExit(ExitFunction &&cleanup) 27 : mExitFunction(cleanup), mExecuteOnDestruction(true) {} 28 29 ScopeExit(ScopeExit &&rhs) 30 : mExitFunction(std::move(rhs.mExitFunction)), 31 mExecuteOnDestruction(rhs.mExecuteOnDestruction) { 32 rhs.release(); 33 } 34 35 ~ScopeExit() { 36 if (mExecuteOnDestruction) { 37 mExitFunction(); 38 } 39 } 40 41 void release() { mExecuteOnDestruction = false; } 42 43 private: 44 explicit ScopeExit(const ScopeExit &) = delete; 45 ScopeExit &operator=(const ScopeExit &) = delete; 46 ScopeExit &operator=(ScopeExit &&) = delete; 47 }; 48 49 template <typename ExitFunction> 50 ScopeExit<ExitFunction> MakeScopeExit(ExitFunction &&exitFunction) { 51 return ScopeExit<ExitFunction>(std::move(exitFunction)); 52 } 53 54 // ***** Section: NSIS stack 55 typedef struct _stack_t { 56 struct _stack_t *next; 57 WCHAR text[1]; // this should be the length of g_stringsize when allocating 58 } stack_t; 59 60 static unsigned int g_stringsize; 61 static stack_t **g_stacktop; 62 63 static int popstringn(LPWSTR str, int maxlen) { 64 stack_t *th; 65 if (!g_stacktop || !*g_stacktop) return 1; 66 th = (*g_stacktop); 67 if (str) lstrcpynW(str, th->text, maxlen ? maxlen : g_stringsize); 68 *g_stacktop = th->next; 69 GlobalFree((HGLOBAL)th); 70 return 0; 71 } 72 73 static void pushstring(LPCWSTR str) { 74 stack_t *th; 75 if (!g_stacktop) return; 76 th = (stack_t *)GlobalAlloc( 77 GPTR, (sizeof(stack_t) + (g_stringsize) * sizeof(*str))); 78 lstrcpynW(th->text, str, g_stringsize); 79 th->next = *g_stacktop; 80 *g_stacktop = th; 81 } 82 83 // ***** Section: NSIS Plug-In API (from NSIS api.h) 84 // NSIS Plug-In Callback Messages 85 enum NSPIM { 86 NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup 87 NSPIM_GUIUNLOAD, // Called after .onGUIEnd 88 }; 89 90 // Prototype for callbacks registered with 91 // extra_parameters->RegisterPluginCallback() Return NULL for unknown messages 92 // Should always be __cdecl for future expansion possibilities 93 typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); 94 95 #define NSISCALL __stdcall 96 97 typedef struct { 98 LPVOID exec_flags; 99 int(NSISCALL *ExecuteCodeSegment)(int, HWND); 100 void(NSISCALL *validate_filename)(LPWSTR); 101 int(NSISCALL *RegisterPluginCallback)( 102 HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already 103 // registered and < 0 on errors 104 } extra_parameters; 105 106 // ***** Section: StartBitsThread 107 UINT_PTR __cdecl NSISPluginCallback(NSPIM msg); 108 109 static struct { 110 HANDLE thread; 111 bool shutdown_requested; 112 CRITICAL_SECTION cs; 113 CONDITION_VARIABLE cv; 114 } gStartBitsThread = {nullptr, false, 0, 0}; 115 116 // This thread connects to the BackgroundCopyManager, which may take some time 117 // if the BITS service is not already running. It also holds open the connection 118 // until gStartBitsThread.shutdown_requested becomes true. 119 DWORD WINAPI StartBitsThreadProc(LPVOID) { 120 EnterCriticalSection(&gStartBitsThread.cs); 121 auto leaveCS = 122 MakeScopeExit([] { LeaveCriticalSection(&gStartBitsThread.cs); }); 123 124 if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) { 125 return 0; 126 } 127 auto coUninit = MakeScopeExit([] { CoUninitialize(); }); 128 129 IBackgroundCopyManager *bcm = nullptr; 130 if (FAILED(CoCreateInstance( 131 __uuidof(BackgroundCopyManager), nullptr, CLSCTX_LOCAL_SERVER, 132 __uuidof(IBackgroundCopyManager), (LPVOID *)&bcm)) || 133 !bcm) { 134 return 0; 135 } 136 137 do { 138 SleepConditionVariableCS(&gStartBitsThread.cv, &gStartBitsThread.cs, 139 INFINITE); 140 } while (!gStartBitsThread.shutdown_requested); 141 142 bcm->Release(); 143 return 1; 144 } 145 146 // Start up the thread 147 // returns true on success 148 bool StartBitsServiceBackgroundThreadImpl(extra_parameters *extra_params) { 149 EnterCriticalSection(&gStartBitsThread.cs); 150 auto leaveCS = 151 MakeScopeExit([] { LeaveCriticalSection(&gStartBitsThread.cs); }); 152 153 if (gStartBitsThread.thread) { 154 // Thread is already started, assumed to be still running. 155 return true; 156 } 157 158 // Ensure the callback is registered so the thread can be stopped, and also so 159 // NSIS doesn't unload this DLL. 160 extra_params->RegisterPluginCallback(gHInst, NSISPluginCallback); 161 162 gStartBitsThread.shutdown_requested = false; 163 164 gStartBitsThread.thread = 165 CreateThread(nullptr, 0, StartBitsThreadProc, nullptr, 0, 0); 166 if (!gStartBitsThread.thread) { 167 return false; 168 } 169 170 return true; 171 } 172 173 // Shut down the Start BITS thread, if it was started. 174 void ShutdownStartBitsThread() { 175 EnterCriticalSection(&gStartBitsThread.cs); 176 if (gStartBitsThread.thread) { 177 gStartBitsThread.shutdown_requested = true; 178 WakeAllConditionVariable(&gStartBitsThread.cv); 179 LeaveCriticalSection(&gStartBitsThread.cs); 180 181 // Give the thread a little time to clean up. 182 if (WaitForSingleObject(gStartBitsThread.thread, 1000) == WAIT_OBJECT_0) { 183 EnterCriticalSection(&gStartBitsThread.cs); 184 gStartBitsThread.thread = nullptr; 185 LeaveCriticalSection(&gStartBitsThread.cs); 186 } else { 187 // Don't attempt to recover if we didn't see the thread end, 188 // the process will be exiting soon anyway. 189 } 190 191 } else { 192 LeaveCriticalSection(&gStartBitsThread.cs); 193 } 194 } 195 196 // ***** Section: CancelBitsJobsByName 197 #define MAX_JOB_NAME 256 198 199 bool CancelBitsJobsByNameImpl(LPWSTR matchJobName) { 200 if (FAILED(CoInitialize(nullptr))) { 201 return false; 202 } 203 auto coUninit = MakeScopeExit([] { CoUninitialize(); }); 204 205 IBackgroundCopyManager *bcm = nullptr; 206 if (FAILED(CoCreateInstance( 207 __uuidof(BackgroundCopyManager), nullptr, CLSCTX_LOCAL_SERVER, 208 __uuidof(IBackgroundCopyManager), (LPVOID *)&bcm)) || 209 !bcm) { 210 return false; 211 } 212 auto bcmRelease = MakeScopeExit([bcm] { bcm->Release(); }); 213 214 IEnumBackgroundCopyJobs *enumerator = nullptr; 215 // Attempt to enumerate jobs for all users. If that fails, 216 // try for only the current user. 217 if (FAILED(bcm->EnumJobs(BG_JOB_ENUM_ALL_USERS, &enumerator))) { 218 enumerator = nullptr; 219 if (FAILED(bcm->EnumJobs(0, &enumerator))) { 220 return false; 221 } 222 } 223 if (!enumerator) { 224 return false; 225 } 226 auto enumeratorRelease = 227 MakeScopeExit([enumerator] { enumerator->Release(); }); 228 229 bool success = true; 230 231 IBackgroundCopyJob *job = nullptr; 232 HRESULT nextResult; 233 while ((nextResult = enumerator->Next(1, &job, nullptr), 234 SUCCEEDED(nextResult))) { 235 if (nextResult == S_FALSE) { 236 break; 237 } 238 if (!job) { 239 success = false; 240 break; 241 } 242 243 LPWSTR curJobName = nullptr; 244 245 if (SUCCEEDED(job->GetDisplayName(&curJobName)) && curJobName) { 246 if (lstrcmpW(curJobName, matchJobName) == 0) { 247 if (!SUCCEEDED(job->Cancel())) { 248 // If we can't cancel we can still try the other jobs. 249 success = false; 250 } 251 } 252 CoTaskMemFree((LPVOID)curJobName); 253 curJobName = nullptr; 254 } else { 255 // We may not be able to access certain jobs, keep trying the rest. 256 success = false; 257 } 258 259 job->Release(); 260 job = nullptr; 261 } 262 263 if (!SUCCEEDED(nextResult)) { 264 success = false; 265 } 266 267 return success; 268 } 269 270 // ***** Section: DLL entry points 271 extern "C" { 272 // Cancel all BITS jobs with the given name. 273 void __declspec(dllexport) 274 CancelBitsJobsByName(HWND hwndParent, int string_size, char *variables, 275 stack_t **stacktop, extra_parameters *) { 276 g_stacktop = stacktop; 277 g_stringsize = string_size; 278 279 WCHAR matchJobName[MAX_JOB_NAME + 1]; 280 matchJobName[0] = L'\0'; 281 282 if (!popstringn(matchJobName, sizeof(matchJobName) / sizeof(WCHAR))) { 283 if (CancelBitsJobsByNameImpl(matchJobName)) { 284 pushstring(L"ok"); 285 return; 286 } 287 } 288 289 pushstring(L"error"); 290 } 291 292 // Start the BITS service in the background, and hold a reference to it until 293 // the (un)installer exits. 294 // Does not provide any feedback or touch the stack. 295 void __declspec(dllexport) 296 StartBitsServiceBackground(HWND, int, char *, stack_t **, 297 extra_parameters *extra_params) { 298 StartBitsServiceBackgroundThreadImpl(extra_params); 299 } 300 } 301 302 // Handle messages from NSIS 303 UINT_PTR __cdecl NSISPluginCallback(NSPIM msg) { 304 if (msg == NSPIM_UNLOAD) { 305 ShutdownStartBitsThread(); 306 } 307 return 0; 308 } 309 310 BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID) { 311 if (reason == DLL_PROCESS_ATTACH) { 312 gHInst = instance; 313 InitializeConditionVariable(&gStartBitsThread.cv); 314 InitializeCriticalSection(&gStartBitsThread.cs); 315 } 316 return TRUE; 317 }