summaryrefslogtreecommitdiffstats
path: root/other-licenses/nsis/Contrib/BitsUtils/BitsUtils.cpp
blob: aea1f270eaf91577254c4984aee9aa8559618c0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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;
}