tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }