aboutsummaryrefslogtreecommitdiff
path: root/src/engine/bitsengine.cpp
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2018-12-29 22:12:08 -0600
committerSean Hall <r.sean.hall@gmail.com>2018-12-29 22:12:08 -0600
commit61847dddd4fd497057c780658e383c4627de19ec (patch)
treef85a845182922538ab9aa6ee85b0db3ab40c1f6e /src/engine/bitsengine.cpp
parent8295f5f8fd28042e1a0a172d5afbba79178064c2 (diff)
downloadwix-61847dddd4fd497057c780658e383c4627de19ec.tar.gz
wix-61847dddd4fd497057c780658e383c4627de19ec.tar.bz2
wix-61847dddd4fd497057c780658e383c4627de19ec.zip
Import code from old v4 repo
Diffstat (limited to 'src/engine/bitsengine.cpp')
-rw-r--r--src/engine/bitsengine.cpp505
1 files changed, 505 insertions, 0 deletions
diff --git a/src/engine/bitsengine.cpp b/src/engine/bitsengine.cpp
new file mode 100644
index 00000000..b8093b77
--- /dev/null
+++ b/src/engine/bitsengine.cpp
@@ -0,0 +1,505 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5// const
6
7const DWORD BITSENGINE_NO_PROGRESS_TIMEOUT = 2 * 60;
8const DWORD BITSENGINE_MSG_WAIT_TIMEOUT = 1;
9
10// functions
11
12static HRESULT CreateJob(
13 __out IBackgroundCopyJob** ppJob
14 );
15static HRESULT SetCredentials(
16 __in IBackgroundCopyJob* pJob,
17 __in_z_opt LPCWSTR wzUser,
18 __in_z_opt LPCWSTR wzPassword
19 );
20static void SendError(
21 __in DOWNLOAD_CACHE_CALLBACK* pCacheCallback,
22 __in IBackgroundCopyJob* pJob,
23 __in HRESULT hrError,
24 __in BG_ERROR_CONTEXT context,
25 __out_opt BOOL* pfRetry
26 );
27
28
29// class
30
31class CBurnBitsCallback : public IBackgroundCopyCallback
32{
33public: // IUnknown
34 virtual STDMETHODIMP QueryInterface(
35 __in const IID& riid,
36 __out void** ppvObject
37 )
38 {
39 HRESULT hr = S_OK;
40
41 ExitOnNull(ppvObject, hr, E_INVALIDARG, "Invalid argument ppvObject");
42 *ppvObject = NULL;
43
44 if (::IsEqualIID(__uuidof(IBackgroundCopyCallback), riid))
45 {
46 *ppvObject = static_cast<IBackgroundCopyCallback*>(this);
47 }
48 else if (::IsEqualIID(IID_IUnknown, riid))
49 {
50 *ppvObject = reinterpret_cast<IUnknown*>(this);
51 }
52 else // no interface for requested iid
53 {
54 ExitFunction1(hr = E_NOINTERFACE);
55 }
56
57 AddRef();
58
59 LExit:
60 return hr;
61 }
62
63 virtual STDMETHODIMP_(ULONG) AddRef()
64 {
65 return ::InterlockedIncrement(&this->m_cReferences);
66 }
67
68 virtual STDMETHODIMP_(ULONG) Release()
69 {
70 long l = ::InterlockedDecrement(&this->m_cReferences);
71 if (0 < l)
72 {
73 return l;
74 }
75
76 delete this;
77 return 0;
78 }
79
80public: // IBackgroundCopyCallback
81 virtual STDMETHODIMP JobTransferred(
82 __in IBackgroundCopyJob* pJob
83 )
84 {
85 HRESULT hr = S_OK;
86
87 hr = SendProgress(pJob);
88 ExitOnFailure(hr, "Failure while sending progress during BITS job transferred.");
89
90 LExit:
91 if (FAILED(hr))
92 {
93 ProcessResult(BG_ERROR_CONTEXT_NONE, hr);
94 }
95 else
96 {
97 ::SetEvent(m_hComplete);
98 }
99
100 return S_OK; // must return S_OK otherwise BITS just keeps calling back.
101 }
102
103 virtual STDMETHODIMP JobError(
104 __in IBackgroundCopyJob* /*pJob*/,
105 __in IBackgroundCopyError* pError
106 )
107 {
108 HRESULT hr = S_OK;
109 BG_ERROR_CONTEXT context = BG_ERROR_CONTEXT_NONE;
110 HRESULT hrError = S_OK;
111
112 hr = pError->GetError(&context, &hrError);
113 ExitOnFailure(hr, "Failed to get error context.");
114
115 if (SUCCEEDED(hrError))
116 {
117 hr = E_UNEXPECTED;
118 }
119
120 LExit:
121 ProcessResult(context, FAILED(hrError) ? hrError : hr);
122
123 return S_OK; // must return S_OK otherwise BITS just keeps calling back.
124 }
125
126 virtual STDMETHODIMP JobModification(
127 __in IBackgroundCopyJob* pJob,
128 __in DWORD /*dwReserved*/
129 )
130 {
131 HRESULT hr = S_OK;
132 BG_JOB_STATE state = BG_JOB_STATE_ERROR;
133
134 ::EnterCriticalSection(&m_cs);
135
136 hr = pJob->GetState(&state);
137 ExitOnFailure(hr, "Failed to get state during job modification.");
138
139 // If we're actually downloading stuff, let's send progress.
140 if (BG_JOB_STATE_TRANSFERRING == state)
141 {
142 hr = SendProgress(pJob);
143 ExitOnFailure(hr, "Failure while sending progress during BITS job modification.");
144 }
145
146 LExit:
147 ::LeaveCriticalSection(&m_cs);
148
149 ProcessResult(BG_ERROR_CONTEXT_NONE, hr);
150
151 return S_OK; // documentation says to always return S_OK
152 }
153
154public:
155 void Reset()
156 {
157 m_hrError = S_OK;
158 m_contextError = BG_ERROR_CONTEXT_NONE;
159
160 ::ResetEvent(m_hComplete);
161 }
162
163 HRESULT WaitForCompletion(
164 __in IBackgroundCopyJob* pJob
165 )
166 {
167 HRESULT hr = S_OK;
168 HANDLE rghEvents[1] = { m_hComplete };
169 MSG msg = { };
170 BOOL fMessageProcessed = FALSE;
171
172 do
173 {
174 fMessageProcessed = FALSE;
175
176 switch (::MsgWaitForMultipleObjects(countof(rghEvents), rghEvents, FALSE, BITSENGINE_MSG_WAIT_TIMEOUT * 1000, QS_ALLINPUT))
177 {
178 case WAIT_OBJECT_0:
179 break;
180
181 case WAIT_OBJECT_0 + 1:
182 ::PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE);
183 fMessageProcessed = TRUE;
184 break;
185
186 case WAIT_TIMEOUT:
187 // Call the progress callback periodically if we are not transferring to ensure that cancelling is responsive
188 // (progress callback is also handles cancelling). Note that if we are transferring, IBackgroundCopyCallback
189 // methods handle progress/cancelling. If we are not transferring, the IBackgroundCopyCallback methods may
190 // not be called until the job times out (minutes for a foreground job, weeks for a background job).
191 SendProgressIfNotTransferring(pJob);
192 fMessageProcessed = TRUE;
193 break;
194
195 default:
196 ExitWithLastError(hr, "Failed while waiting for download.");
197 }
198 } while (fMessageProcessed);
199
200 LExit:
201 return hr;
202 }
203
204 void GetError(
205 __out HRESULT* pHR,
206 __out BG_ERROR_CONTEXT* pContext
207 )
208 {
209 *pHR = m_hrError;
210 *pContext = m_contextError;
211 }
212
213private:
214 HRESULT SendProgress(
215 __in IBackgroundCopyJob* pJob
216 )
217 {
218 HRESULT hr = S_OK;
219 BG_JOB_PROGRESS progress = { };
220
221 if (m_pCallback && m_pCallback->pfnProgress)
222 {
223 hr = pJob->GetProgress(&progress);
224 ExitOnFailure(hr, "Failed to get progress when BITS job was transferred.");
225
226 hr = CacheSendProgressCallback(m_pCallback, progress.BytesTransferred, progress.BytesTotal, INVALID_HANDLE_VALUE);
227 ExitOnFailure(hr, "Failed to send progress from BITS job.");
228 }
229
230 LExit:
231 return hr;
232 }
233
234 void SendProgressIfNotTransferring(
235 __in IBackgroundCopyJob* pJob
236 )
237 {
238 HRESULT hr = S_OK;
239 BG_JOB_STATE state = BG_JOB_STATE_ERROR;
240
241 ::EnterCriticalSection(&m_cs);
242
243 hr = pJob->GetState(&state);
244 ExitOnFailure(hr, "Failed to get BITS job state.");
245
246 if (BG_JOB_STATE_TRANSFERRING != state)
247 {
248 hr = SendProgress(pJob);
249 ExitOnFailure(hr, "Failure while sending progress.");
250 }
251
252 LExit:
253 ::LeaveCriticalSection(&m_cs);
254
255 ProcessResult(BG_ERROR_CONTEXT_NONE, hr);
256 }
257
258 void ProcessResult(
259 __in BG_ERROR_CONTEXT context,
260 __in HRESULT hr
261 )
262 {
263 if (FAILED(hr))
264 {
265 m_contextError = context;
266 m_hrError = hr;
267
268 ::SetEvent(m_hComplete);
269 }
270 }
271
272public:
273 CBurnBitsCallback(
274 __in_opt DOWNLOAD_CACHE_CALLBACK* pCallback,
275 __out HRESULT* pHR
276 )
277 {
278 HRESULT hr = S_OK;
279
280 m_cReferences = 1;
281 ::InitializeCriticalSection(&m_cs);
282
283 m_hComplete = ::CreateEventW(NULL, TRUE, FALSE, NULL);
284 ExitOnNullWithLastError(m_hComplete, hr, "Failed to create BITS job complete event.");
285
286 m_contextError = BG_ERROR_CONTEXT_NONE;
287 m_hrError = S_OK;
288
289 m_pCallback = pCallback;
290
291 LExit:
292 *pHR = hr;
293 }
294
295 ~CBurnBitsCallback()
296 {
297 m_pCallback = NULL;
298 ReleaseHandle(m_hComplete);
299 ::DeleteCriticalSection(&m_cs);
300 }
301
302private:
303 long m_cReferences;
304 CRITICAL_SECTION m_cs;
305 BG_ERROR_CONTEXT m_contextError;
306 HRESULT m_hrError;
307
308 HANDLE m_hComplete;
309 DOWNLOAD_CACHE_CALLBACK* m_pCallback;
310};
311
312
313extern "C" HRESULT BitsDownloadUrl(
314 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
315 __in DOWNLOAD_SOURCE* pDownloadSource,
316 __in_z LPCWSTR wzDestinationPath
317 )
318{
319 HRESULT hr = S_OK;
320 LPWSTR sczDownloadUrl = NULL;
321 CBurnBitsCallback* pBitsCallback = NULL;
322 IBackgroundCopyJob* pJob = NULL;
323 BOOL fRetry = FALSE;
324 BG_ERROR_CONTEXT contextError = BG_ERROR_CONTEXT_NONE;
325
326 // If the URL isn't at least 8 characters long (e.g.: "bits://X") then it
327 // isn't going to do us any good.
328 if (8 > lstrlenW(pDownloadSource->sczUrl))
329 {
330 hr = E_INVALIDARG;
331 ExitOnRootFailure(hr, "Invalid BITS engine URL: %ls", pDownloadSource->sczUrl);
332 }
333
334 // Fix the URL to be "http" instead of "bits".
335 hr = StrAllocString(&sczDownloadUrl, pDownloadSource->sczUrl, 0);
336 ExitOnFailure(hr, "Failed to copy download URL.");
337
338 sczDownloadUrl[0] = L'h';
339 sczDownloadUrl[1] = L't';
340 sczDownloadUrl[2] = L't';
341 sczDownloadUrl[3] = L'p';
342
343 // Create and configure the BITS job.
344 hr = CreateJob(&pJob);
345 ExitOnFailure(hr, "Failed to create BITS job.");
346
347 hr = SetCredentials(pJob, pDownloadSource->sczUser, pDownloadSource->sczPassword);
348 ExitOnFailure(hr, "Failed to set credentials for BITS job.");
349
350 hr = pJob->AddFile(sczDownloadUrl, wzDestinationPath);
351 ExitOnFailure(hr, "Failed to add file to BITS job.");
352
353 // Set the callback into the BITs job.
354 pBitsCallback = new CBurnBitsCallback(pCallback, &hr);
355 ExitOnNull(pBitsCallback, hr, E_OUTOFMEMORY, "Failed to create BITS job callback.");
356 ExitOnFailure(hr, "Failed to initialize BITS job callback.");
357
358 hr = pJob->SetNotifyInterface(pBitsCallback);
359 ExitOnFailure(hr, "Failed to set callback interface for BITS job.");
360
361 // Go into our retry download loop.
362 do
363 {
364 fRetry = FALSE;
365
366 pBitsCallback->Reset(); // ensure we are ready for the download to start (again?).
367
368 hr = pJob->Resume();
369 ExitOnFailure(hr, "Falied to start BITS job.");
370
371 hr = pBitsCallback->WaitForCompletion(pJob);
372 ExitOnFailure(hr, "Failed while waiting for BITS download.");
373
374 // See if there are any errors.
375 pBitsCallback->GetError(&hr, &contextError);
376 if (HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hr)
377 {
378 ExitFunction();
379 }
380 else if (FAILED(hr))
381 {
382 SendError(pCallback, pJob, hr, contextError, &fRetry);
383 }
384 } while (fRetry);
385 ExitOnFailure(hr, "Failed to download BITS job.");
386
387 // After all that, we should have the file downloaded so complete the job to get
388 // the file copied to the destination.
389 hr = pJob->Complete();
390 ExitOnFailure(hr, "Failed to complete BITS job.");
391
392LExit:
393 if (pJob)
394 {
395 pJob->SetNotifyInterface(NULL);
396
397 // If we failed, kill the job.
398 if (FAILED(hr))
399 {
400 pJob->Cancel(); // TODO: should we cancel if we're going to retry the package? Probably the right thing to do.
401 }
402 }
403
404 ReleaseObject(pBitsCallback);
405 ReleaseObject(pJob);
406 ReleaseStr(sczDownloadUrl);
407
408 return hr;
409}
410
411static HRESULT CreateJob(
412 __out IBackgroundCopyJob** ppJob
413 )
414{
415 HRESULT hr = S_OK;
416 IBackgroundCopyManager* pBitsManager = NULL;
417 IBackgroundCopyJob* pJob = NULL;
418 GUID guidJob = { };
419
420 hr = ::CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, CLSCTX_ALL, __uuidof(IBackgroundCopyManager), reinterpret_cast<LPVOID*>(&pBitsManager));
421 ExitOnFailure(hr, "Failed to create IBackgroundCopyManager.");
422
423 hr = pBitsManager->CreateJob(L"WixBurn", BG_JOB_TYPE_DOWNLOAD, &guidJob, &pJob);
424 ExitOnFailure(hr, "Failed to create BITS job.");
425
426 hr = pJob->SetNotifyFlags(BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR | BG_NOTIFY_JOB_MODIFICATION);
427 ExitOnFailure(hr, "Failed to set notification flags for BITS job.");
428
429 hr = pJob->SetNoProgressTimeout(BITSENGINE_NO_PROGRESS_TIMEOUT); // use 2 minutes since default is 14 days.
430 ExitOnFailure(hr, "Failed to set progress timeout.");
431
432 hr = pJob->SetPriority(BG_JOB_PRIORITY_FOREGROUND);
433 ExitOnFailure(hr, "Failed to set BITS job to foreground.");
434
435 *ppJob = pJob;
436 pJob = NULL;
437
438LExit:
439 ReleaseObject(pJob);
440 ReleaseObject(pBitsManager);
441
442 return hr;
443}
444
445static HRESULT SetCredentials(
446 __in IBackgroundCopyJob* pJob,
447 __in_z_opt LPCWSTR wzUser,
448 __in_z_opt LPCWSTR wzPassword
449 )
450{
451 HRESULT hr = S_OK;
452 IBackgroundCopyJob2* pJob2 = NULL;
453 BG_AUTH_CREDENTIALS ac = { };
454
455 // If IBackgroundCopyJob2::SetCredentials() is supported, set the username/password.
456 hr = pJob->QueryInterface(IID_PPV_ARGS(&pJob2));
457 if (SUCCEEDED(hr))
458 {
459 ac.Target = BG_AUTH_TARGET_PROXY;
460 ac.Credentials.Basic.UserName = const_cast<LPWSTR>(wzUser);
461 ac.Credentials.Basic.Password = const_cast<LPWSTR>(wzPassword);
462
463 ac.Scheme = BG_AUTH_SCHEME_NTLM;
464 hr = pJob2->SetCredentials(&ac);
465 ExitOnFailure(hr, "Failed to set background copy NTLM credentials");
466
467 ac.Scheme = BG_AUTH_SCHEME_NEGOTIATE;
468 hr = pJob2->SetCredentials(&ac);
469 ExitOnFailure(hr, "Failed to set background copy negotiate credentials");
470 }
471
472 hr = S_OK;
473
474LExit:
475 ReleaseObject(pJob2);
476
477 return hr;
478}
479
480static void SendError(
481 __in DOWNLOAD_CACHE_CALLBACK* pCacheCallback,
482 __in IBackgroundCopyJob* pJob,
483 __in HRESULT hrError,
484 __in BG_ERROR_CONTEXT /*context*/,
485 __out_opt BOOL* pfRetry
486 )
487{
488 HRESULT hr = S_OK;
489 IBackgroundCopyError* pError = NULL;
490 LPWSTR pszErrorDescription = NULL;
491
492 hr = pJob->GetError(&pError);
493 if (SUCCEEDED(hr))
494 {
495 pError->GetErrorDescription(LANGIDFROMLCID(::GetThreadLocale()), &pszErrorDescription);
496 }
497
498 CacheSendErrorCallback(pCacheCallback, hrError, pszErrorDescription, pfRetry);
499
500 if (pszErrorDescription)
501 {
502 ::CoTaskMemFree(pszErrorDescription);
503 }
504 ReleaseObject(pError);
505}