1 /*
2 * Copyright 2012 Jacek Caban for CodeWeavers
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #define OEMRESOURCE
20
21 #include <assert.h>
22
23 #include "urlmon_main.h"
24 #include "resource.h"
25
26 #include "advpub.h"
27 #include "fdi.h"
28
29 #include "wine/debug.h"
30
31 WINE_DEFAULT_DEBUG_CHANNEL(urlmon);
32
33 static const WCHAR ctxW[] = {'c','t','x',0};
34 static const WCHAR cab_extW[] = {'.','c','a','b',0};
35 static const WCHAR infW[] = {'i','n','f',0};
36 static const WCHAR dllW[] = {'d','l','l',0};
37 static const WCHAR ocxW[] = {'o','c','x',0};
38
39 enum install_type {
40 INSTALL_UNKNOWN,
41 INSTALL_DLL,
42 INSTALL_INF
43 };
44
45 typedef struct {
46 IUri *uri;
47 IBindStatusCallback *callback;
48 BOOL release_on_stop;
49 BOOL cancel;
50 WCHAR *install_file;
51 const WCHAR *cache_file;
52 const WCHAR *tmp_dir;
53 const WCHAR *file_name;
54 enum install_type install_type;
55 HWND hwnd;
56 int counter;
57 INT_PTR timer;
58 } install_ctx_t;
59
release_install_ctx(install_ctx_t * ctx)60 static void release_install_ctx(install_ctx_t *ctx)
61 {
62 if(ctx->uri)
63 IUri_Release(ctx->uri);
64 if(ctx->callback)
65 IBindStatusCallback_Release(ctx->callback);
66 heap_free(ctx->install_file);
67 heap_free(ctx);
68 }
69
file_exists(const WCHAR * file_name)70 static inline BOOL file_exists(const WCHAR *file_name)
71 {
72 return GetFileAttributesW(file_name) != INVALID_FILE_ATTRIBUTES;
73 }
74
75 #ifdef __REACTOS__
76
77 /* The following definitions were copied from dll/win32/advpack32/files.c */
78
79 /* SESSION Operation */
80 #define EXTRACT_FILLFILELIST 0x00000001
81 #define EXTRACT_EXTRACTFILES 0x00000002
82
83 struct FILELIST{
84 LPSTR FileName;
85 struct FILELIST *next;
86 BOOL DoExtract;
87 };
88
89 typedef struct {
90 INT FileSize;
91 ERF Error;
92 struct FILELIST *FileList;
93 INT FileCount;
94 INT Operation;
95 CHAR Destination[MAX_PATH];
96 CHAR CurrentFile[MAX_PATH];
97 CHAR Reserved[MAX_PATH];
98 struct FILELIST *FilterList;
99 } SESSION;
100
101 static HRESULT (WINAPI *pExtract)(SESSION*, LPCSTR);
102
103
104 /* The following functions were copied from dll/win32/advpack32/files.c
105 All unused arguments are removed */
106
free_file_node(struct FILELIST * pNode)107 static void free_file_node(struct FILELIST *pNode)
108 {
109 HeapFree(GetProcessHeap(), 0, pNode->FileName);
110 HeapFree(GetProcessHeap(), 0, pNode);
111 }
112
free_file_list(SESSION * session)113 static void free_file_list(SESSION* session)
114 {
115 struct FILELIST *next, *curr = session->FileList;
116
117 while (curr)
118 {
119 next = curr->next;
120 free_file_node(curr);
121 curr = next;
122 }
123 }
124
Modified_ExtractFilesA(LPCSTR CabName,LPCSTR ExpandDir)125 HRESULT WINAPI Modified_ExtractFilesA(LPCSTR CabName, LPCSTR ExpandDir)
126 {
127 SESSION session;
128 HMODULE hCabinet;
129 HRESULT res = S_OK;
130 LPSTR szConvertedList = NULL;
131
132 TRACE("(%s, %s)\n", debugstr_a(CabName), debugstr_a(ExpandDir));
133
134 if (!CabName || !ExpandDir)
135 return E_INVALIDARG;
136
137 if (GetFileAttributesA(ExpandDir) == INVALID_FILE_ATTRIBUTES)
138 return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
139
140 hCabinet = LoadLibraryA("cabinet.dll");
141 if (!hCabinet)
142 return E_FAIL;
143
144 ZeroMemory(&session, sizeof(SESSION));
145
146 pExtract = (void *)GetProcAddress(hCabinet, "Extract");
147 if (!pExtract)
148 {
149 res = E_FAIL;
150 goto done;
151 }
152
153 lstrcpyA(session.Destination, ExpandDir);
154
155 session.Operation |= (EXTRACT_FILLFILELIST | EXTRACT_EXTRACTFILES);
156 res = pExtract(&session, CabName);
157
158 done:
159 free_file_list(&session);
160 FreeLibrary(hCabinet);
161 HeapFree(GetProcessHeap(), 0, szConvertedList);
162
163 return res;
164 }
165
166
167
Modified_ExtractFilesW(LPCWSTR CabName,LPCWSTR ExpandDir)168 HRESULT WINAPI Modified_ExtractFilesW(LPCWSTR CabName, LPCWSTR ExpandDir)
169 {
170 char *cab_name = NULL, *expand_dir = NULL;
171 HRESULT hres = S_OK;
172
173 TRACE("(%s, %s, %d)\n", debugstr_w(CabName), debugstr_w(ExpandDir));
174
175 if(CabName) {
176 cab_name = heap_strdupWtoA(CabName);
177 if(!cab_name)
178 return E_OUTOFMEMORY;
179 }
180
181 if(ExpandDir) {
182 expand_dir = heap_strdupWtoA(ExpandDir);
183 if(!expand_dir)
184 hres = E_OUTOFMEMORY;
185 }
186
187 /* cabinet.dll, which does the real job of extracting files, doesn't have UNICODE API,
188 so we need W->A conversion at some point anyway. */
189 if(SUCCEEDED(hres))
190 hres = Modified_ExtractFilesA(cab_name, expand_dir);
191
192 heap_free(cab_name);
193 heap_free(expand_dir);
194 return hres;
195 }
196
197 #endif
198
199
extract_cab_file(install_ctx_t * ctx)200 static HRESULT extract_cab_file(install_ctx_t *ctx)
201 {
202 size_t path_len, file_len;
203 WCHAR *ptr;
204 HRESULT hres;
205
206 #ifdef __REACTOS__
207 hres = Modified_ExtractFilesW(ctx->cache_file, ctx->tmp_dir);
208 #else
209 hres = ExtractFilesW(ctx->cache_file, ctx->tmp_dir, 0, NULL, NULL, 0);
210 #endif
211 if(FAILED(hres)) {
212 WARN("ExtractFilesW failed: %08x\n", hres);
213 return hres;
214 }
215
216 path_len = lstrlenW(ctx->tmp_dir);
217 file_len = lstrlenW(ctx->file_name);
218 ctx->install_file = heap_alloc((path_len+file_len+2)*sizeof(WCHAR));
219 if(!ctx->install_file)
220 return E_OUTOFMEMORY;
221
222 memcpy(ctx->install_file, ctx->tmp_dir, path_len*sizeof(WCHAR));
223 ctx->install_file[path_len] = '\\';
224 memcpy(ctx->install_file+path_len+1, ctx->file_name, (file_len+1)*sizeof(WCHAR));
225
226 /* NOTE: Assume that file_name contains ".cab" extension */
227 ptr = ctx->install_file+path_len+1+file_len-3;
228
229 memcpy(ptr, infW, sizeof(infW));
230 if(file_exists(ctx->install_file)) {
231 ctx->install_type = INSTALL_INF;
232 return S_OK;
233 }
234
235 memcpy(ptr, dllW, sizeof(dllW));
236 if(file_exists(ctx->install_file)) {
237 ctx->install_type = INSTALL_DLL;
238 return S_OK;
239 }
240
241 memcpy(ptr, ocxW, sizeof(ocxW));
242 if(file_exists(ctx->install_file)) {
243 ctx->install_type = INSTALL_DLL;
244 return S_OK;
245 }
246
247 FIXME("No known install file\n");
248 return E_NOTIMPL;
249 }
250
setup_dll(install_ctx_t * ctx)251 static HRESULT setup_dll(install_ctx_t *ctx)
252 {
253 HMODULE module;
254 HRESULT hres;
255
256 HRESULT (WINAPI *reg_func)(void);
257
258 module = LoadLibraryW(ctx->install_file);
259 if(!module)
260 return E_FAIL;
261
262 reg_func = (void*)GetProcAddress(module, "DllRegisterServer");
263 if(reg_func) {
264 hres = reg_func();
265 }else {
266 WARN("no DllRegisterServer function\n");
267 hres = E_FAIL;
268 }
269
270 FreeLibrary(module);
271 return hres;
272 }
273
expand_command(install_ctx_t * ctx,const WCHAR * cmd,WCHAR * buf,size_t * size)274 static void expand_command(install_ctx_t *ctx, const WCHAR *cmd, WCHAR *buf, size_t *size)
275 {
276 const WCHAR *ptr = cmd, *prev_ptr = cmd;
277 size_t len = 0, len2;
278
279 static const WCHAR expand_dirW[] = {'%','E','X','T','R','A','C','T','_','D','I','R','%'};
280
281 while((ptr = wcschr(ptr, '%'))) {
282 if(buf)
283 memcpy(buf+len, prev_ptr, ptr-prev_ptr);
284 len += ptr-prev_ptr;
285
286 if(!_wcsnicmp(ptr, expand_dirW, ARRAY_SIZE(expand_dirW))) {
287 len2 = lstrlenW(ctx->tmp_dir);
288 if(buf)
289 memcpy(buf+len, ctx->tmp_dir, len2*sizeof(WCHAR));
290 len += len2;
291 ptr += ARRAY_SIZE(expand_dirW);
292 }else {
293 FIXME("Can't expand %s\n", debugstr_w(ptr));
294 if(buf)
295 buf[len] = '%';
296 len++;
297 ptr++;
298 }
299
300 prev_ptr = ptr;
301 }
302
303 if(buf)
304 lstrcpyW(buf+len, prev_ptr);
305 *size = len + lstrlenW(prev_ptr) + 1;
306 }
307
process_hook_section(install_ctx_t * ctx,const WCHAR * sect_name)308 static HRESULT process_hook_section(install_ctx_t *ctx, const WCHAR *sect_name)
309 {
310 WCHAR buf[2048], val[2*MAX_PATH];
311 const WCHAR *key;
312 DWORD len;
313 HRESULT hres;
314
315 static const WCHAR runW[] = {'r','u','n',0};
316
317 len = GetPrivateProfileStringW(sect_name, NULL, NULL, buf, ARRAY_SIZE(buf), ctx->install_file);
318 if(!len)
319 return S_OK;
320
321 for(key = buf; *key; key += lstrlenW(key)+1) {
322 if(!wcsicmp(key, runW)) {
323 WCHAR *cmd;
324 size_t size;
325
326 len = GetPrivateProfileStringW(sect_name, runW, NULL, val, ARRAY_SIZE(val), ctx->install_file);
327
328 TRACE("Run %s\n", debugstr_w(val));
329
330 expand_command(ctx, val, NULL, &size);
331
332 cmd = heap_alloc(size*sizeof(WCHAR));
333 if(!cmd)
334 heap_free(cmd);
335
336 expand_command(ctx, val, cmd, &size);
337 hres = RunSetupCommandW(ctx->hwnd, cmd, NULL, ctx->tmp_dir, NULL, NULL, 0, NULL);
338 heap_free(cmd);
339 if(FAILED(hres))
340 return hres;
341 }else {
342 FIXME("Unsupported hook %s\n", debugstr_w(key));
343 return E_NOTIMPL;
344 }
345 }
346
347 return S_OK;
348 }
349
install_inf_file(install_ctx_t * ctx)350 static HRESULT install_inf_file(install_ctx_t *ctx)
351 {
352 WCHAR buf[2048], sect_name[128];
353 BOOL default_install = TRUE;
354 const WCHAR *key;
355 DWORD len;
356 HRESULT hres;
357
358 static const WCHAR setup_hooksW[] = {'S','e','t','u','p',' ','H','o','o','k','s',0};
359 static const WCHAR add_codeW[] = {'A','d','d','.','C','o','d','e',0};
360
361 len = GetPrivateProfileStringW(setup_hooksW, NULL, NULL, buf, ARRAY_SIZE(buf), ctx->install_file);
362 if(len) {
363 default_install = FALSE;
364
365 for(key = buf; *key; key += lstrlenW(key)+1) {
366 TRACE("[Setup Hooks] key: %s\n", debugstr_w(key));
367
368 len = GetPrivateProfileStringW(setup_hooksW, key, NULL, sect_name, ARRAY_SIZE(sect_name),
369 ctx->install_file);
370 if(!len) {
371 WARN("Could not get key value\n");
372 return E_FAIL;
373 }
374
375 hres = process_hook_section(ctx, sect_name);
376 if(FAILED(hres))
377 return hres;
378 }
379 }
380
381 len = GetPrivateProfileStringW(add_codeW, NULL, NULL, buf, ARRAY_SIZE(buf), ctx->install_file);
382 if(len) {
383 default_install = FALSE;
384
385 for(key = buf; *key; key += lstrlenW(key)+1) {
386 TRACE("[Add.Code] key: %s\n", debugstr_w(key));
387
388 len = GetPrivateProfileStringW(add_codeW, key, NULL, sect_name, ARRAY_SIZE(sect_name),
389 ctx->install_file);
390 if(!len) {
391 WARN("Could not get key value\n");
392 return E_FAIL;
393 }
394
395 hres = RunSetupCommandW(ctx->hwnd, ctx->install_file, sect_name,
396 ctx->tmp_dir, NULL, NULL, RSC_FLAG_INF, NULL);
397 if(FAILED(hres)) {
398 WARN("RunSetupCommandW failed: %08x\n", hres);
399 return hres;
400 }
401 }
402 }
403
404 if(default_install) {
405 hres = RunSetupCommandW(ctx->hwnd, ctx->install_file, NULL, ctx->tmp_dir, NULL, NULL, RSC_FLAG_INF, NULL);
406 if(FAILED(hres)) {
407 WARN("RunSetupCommandW failed: %08x\n", hres);
408 return hres;
409 }
410 }
411
412 return S_OK;
413 }
414
install_cab_file(install_ctx_t * ctx)415 static HRESULT install_cab_file(install_ctx_t *ctx)
416 {
417 WCHAR tmp_path[MAX_PATH], tmp_dir[MAX_PATH];
418 BOOL res = FALSE, leave_temp = FALSE;
419 DWORD i;
420 HRESULT hres;
421
422 GetTempPathW(ARRAY_SIZE(tmp_path), tmp_path);
423
424 for(i=0; !res && i < 100; i++) {
425 GetTempFileNameW(tmp_path, NULL, GetTickCount() + i*17037, tmp_dir);
426 res = CreateDirectoryW(tmp_dir, NULL);
427 }
428 if(!res)
429 return E_FAIL;
430
431 ctx->tmp_dir = tmp_dir;
432
433 TRACE("Using temporary directory %s\n", debugstr_w(tmp_dir));
434
435 hres = extract_cab_file(ctx);
436 if(SUCCEEDED(hres)) {
437 if(ctx->callback)
438 IBindStatusCallback_OnProgress(ctx->callback, 0, 0, BINDSTATUS_INSTALLINGCOMPONENTS, ctx->install_file);
439
440 switch(ctx->install_type) {
441 case INSTALL_INF:
442 hres = install_inf_file(ctx);
443 break;
444 case INSTALL_DLL:
445 FIXME("Installing DLL, registering in temporary location\n");
446 hres = setup_dll(ctx);
447 if(SUCCEEDED(hres))
448 leave_temp = TRUE;
449 break;
450 default:
451 assert(0);
452 }
453 }
454
455 if(!leave_temp)
456 RemoveDirectoryW(ctx->tmp_dir);
457 return hres;
458 }
459
update_counter(install_ctx_t * ctx,HWND hwnd)460 static void update_counter(install_ctx_t *ctx, HWND hwnd)
461 {
462 WCHAR text[100];
463
464 if(--ctx->counter <= 0) {
465 HWND button_hwnd;
466
467 KillTimer(hwnd, ctx->timer);
468 LoadStringW(urlmon_instance, IDS_AXINSTALL_INSTALL, text, ARRAY_SIZE(text));
469
470 button_hwnd = GetDlgItem(hwnd, ID_AXINSTALL_INSTALL_BTN);
471 EnableWindow(button_hwnd, TRUE);
472 }else {
473 WCHAR buf[100];
474 LoadStringW(urlmon_instance, IDS_AXINSTALL_INSTALLN, buf, ARRAY_SIZE(buf));
475 swprintf(text, buf, ctx->counter);
476 }
477
478 SetDlgItemTextW(hwnd, ID_AXINSTALL_INSTALL_BTN, text);
479 }
480
init_warning_dialog(HWND hwnd,install_ctx_t * ctx)481 static BOOL init_warning_dialog(HWND hwnd, install_ctx_t *ctx)
482 {
483 BSTR display_uri;
484 HRESULT hres;
485
486 if(!SetPropW(hwnd, ctxW, ctx))
487 return FALSE;
488
489 hres = IUri_GetDisplayUri(ctx->uri, &display_uri);
490 if(FAILED(hres))
491 return FALSE;
492
493 SetDlgItemTextW(hwnd, ID_AXINSTALL_LOCATION, display_uri);
494 SysFreeString(display_uri);
495
496 SendDlgItemMessageW(hwnd, ID_AXINSTALL_ICON, STM_SETICON,
497 (WPARAM)LoadIconW(0, (const WCHAR*)OIC_WARNING), 0);
498
499 ctx->counter = 4;
500 update_counter(ctx, hwnd);
501 ctx->timer = SetTimer(hwnd, 1, 1000, NULL);
502 return TRUE;
503 }
504
warning_proc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)505 static INT_PTR WINAPI warning_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
506 {
507 switch(msg) {
508 case WM_INITDIALOG: {
509 if(!init_warning_dialog(hwnd, (install_ctx_t*)lparam))
510 EndDialog(hwnd, 0);
511 return TRUE;
512 }
513 case WM_COMMAND:
514 switch(wparam) {
515 case ID_AXINSTALL_INSTALL_BTN: {
516 install_ctx_t *ctx = GetPropW(hwnd, ctxW);
517 if(ctx)
518 ctx->cancel = FALSE;
519 EndDialog(hwnd, 0);
520 return FALSE;
521 }
522 case IDCANCEL:
523 EndDialog(hwnd, 0);
524 return FALSE;
525 }
526 case WM_TIMER:
527 update_counter(GetPropW(hwnd, ctxW), hwnd);
528 return TRUE;
529 }
530
531 return FALSE;
532 }
533
install_warning(install_ctx_t * ctx)534 static BOOL install_warning(install_ctx_t *ctx)
535 {
536 IWindowForBindingUI *window_iface;
537 HWND parent_hwnd = NULL;
538 HRESULT hres;
539
540 if(!ctx->callback) {
541 FIXME("no callback\n");
542 return FALSE;
543 }
544
545 hres = IBindStatusCallback_QueryInterface(ctx->callback, &IID_IWindowForBindingUI, (void**)&window_iface);
546 if(FAILED(hres))
547 return FALSE;
548
549 hres = IWindowForBindingUI_GetWindow(window_iface, &IID_ICodeInstall, &ctx->hwnd);
550 IWindowForBindingUI_Release(window_iface);
551 if(FAILED(hres))
552 return FALSE;
553
554 ctx->cancel = TRUE;
555 DialogBoxParamW(urlmon_instance, MAKEINTRESOURCEW(ID_AXINSTALL_WARNING_DLG), parent_hwnd, warning_proc, (LPARAM)ctx);
556 return !ctx->cancel;
557 }
558
install_file(install_ctx_t * ctx,const WCHAR * cache_file)559 static HRESULT install_file(install_ctx_t *ctx, const WCHAR *cache_file)
560 {
561 BSTR path;
562 HRESULT hres;
563
564 TRACE("%s\n", debugstr_w(cache_file));
565
566 ctx->cache_file = cache_file;
567
568 if(!install_warning(ctx)) {
569 TRACE("Installation cancelled\n");
570 return S_OK;
571 }
572
573 hres = IUri_GetPath(ctx->uri, &path);
574 if(SUCCEEDED(hres)) {
575 const WCHAR *ptr, *ptr2, *ext;
576
577 ptr = wcsrchr(path, '/');
578 if(!ptr)
579 ptr = path;
580 else
581 ptr++;
582
583 ptr2 = wcsrchr(ptr, '\\');
584 if(ptr2)
585 ptr = ptr2+1;
586
587 ctx->file_name = ptr;
588 ext = wcsrchr(ptr, '.');
589 if(!ext)
590 ext = ptr;
591
592 if(!wcsicmp(ext, cab_extW)) {
593 hres = install_cab_file(ctx);
594 }else {
595 FIXME("Unsupported extension %s\n", debugstr_w(ext));
596 hres = E_NOTIMPL;
597 }
598 SysFreeString(path);
599 }
600
601 return hres;
602 }
603
failure_msgbox(install_ctx_t * ctx,HRESULT hres)604 static void failure_msgbox(install_ctx_t *ctx, HRESULT hres)
605 {
606 WCHAR buf[1024], fmt[1024];
607
608 LoadStringW(urlmon_instance, IDS_AXINSTALL_FAILURE, fmt, ARRAY_SIZE(fmt));
609 swprintf(buf, fmt, hres);
610 MessageBoxW(ctx->hwnd, buf, NULL, MB_OK);
611 }
612
distunit_on_stop(void * ctx,const WCHAR * cache_file,HRESULT hresult,const WCHAR * error_str)613 static HRESULT distunit_on_stop(void *ctx, const WCHAR *cache_file, HRESULT hresult, const WCHAR *error_str)
614 {
615 install_ctx_t *install_ctx = ctx;
616
617 TRACE("(%p %s %08x %s)\n", ctx, debugstr_w(cache_file), hresult, debugstr_w(error_str));
618
619 if(hresult == S_OK) {
620 hresult = install_file(install_ctx, cache_file);
621 if(FAILED(hresult))
622 failure_msgbox(ctx, hresult);
623 }
624
625 if(install_ctx->callback)
626 IBindStatusCallback_OnStopBinding(install_ctx->callback, hresult, error_str);
627
628 if(install_ctx->release_on_stop)
629 release_install_ctx(install_ctx);
630 return S_OK;
631 }
632
633 /***********************************************************************
634 * AsyncInstallDistributionUnit (URLMON.@)
635 */
AsyncInstallDistributionUnit(const WCHAR * szDistUnit,const WCHAR * szTYPE,const WCHAR * szExt,DWORD dwFileVersionMS,DWORD dwFileVersionLS,const WCHAR * szURL,IBindCtx * pbc,void * pvReserved,DWORD flags)636 HRESULT WINAPI AsyncInstallDistributionUnit(const WCHAR *szDistUnit, const WCHAR *szTYPE, const WCHAR *szExt,
637 DWORD dwFileVersionMS, DWORD dwFileVersionLS, const WCHAR *szURL, IBindCtx *pbc, void *pvReserved, DWORD flags)
638 {
639 install_ctx_t *ctx;
640 HRESULT hres;
641
642 TRACE("(%s %s %s %x %x %s %p %p %x)\n", debugstr_w(szDistUnit), debugstr_w(szTYPE), debugstr_w(szExt),
643 dwFileVersionMS, dwFileVersionLS, debugstr_w(szURL), pbc, pvReserved, flags);
644
645 if(szDistUnit || szTYPE || szExt)
646 FIXME("Unsupported arguments\n");
647
648 ctx = heap_alloc_zero(sizeof(*ctx));
649 if(!ctx)
650 return E_OUTOFMEMORY;
651
652 hres = CreateUri(szURL, 0, 0, &ctx->uri);
653 if(FAILED(hres)) {
654 heap_free(ctx);
655 return E_OUTOFMEMORY;
656 }
657
658 ctx->callback = bsc_from_bctx(pbc);
659
660 hres = download_to_cache(ctx->uri, distunit_on_stop, ctx, ctx->callback);
661 if(hres == MK_S_ASYNCHRONOUS)
662 ctx->release_on_stop = TRUE;
663 else
664 release_install_ctx(ctx);
665
666 return hres;
667 }
668