msvcrt: Handle the SIGBREAK signal.
[wine] / dlls / msi / media.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2008 James Hawkins
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22
23 #include "windef.h"
24 #include "winerror.h"
25 #include "wine/debug.h"
26 #include "fdi.h"
27 #include "msipriv.h"
28 #include "winuser.h"
29 #include "winreg.h"
30 #include "shlwapi.h"
31 #include "wine/unicode.h"
32
33 WINE_DEFAULT_DEBUG_CHANNEL(msi);
34
35 /* from msvcrt/fcntl.h */
36 #define _O_RDONLY      0
37 #define _O_WRONLY      1
38 #define _O_RDWR        2
39 #define _O_ACCMODE     (_O_RDONLY|_O_WRONLY|_O_RDWR)
40 #define _O_APPEND      0x0008
41 #define _O_RANDOM      0x0010
42 #define _O_SEQUENTIAL  0x0020
43 #define _O_TEMPORARY   0x0040
44 #define _O_NOINHERIT   0x0080
45 #define _O_CREAT       0x0100
46 #define _O_TRUNC       0x0200
47 #define _O_EXCL        0x0400
48 #define _O_SHORT_LIVED 0x1000
49 #define _O_TEXT        0x4000
50 #define _O_BINARY      0x8000
51
52 static BOOL source_matches_volume(MSIMEDIAINFO *mi, LPCWSTR source_root)
53 {
54     WCHAR volume_name[MAX_PATH + 1];
55     WCHAR root[MAX_PATH + 1];
56
57     strcpyW(root, source_root);
58     PathStripToRootW(root);
59     PathAddBackslashW(root);
60
61     if (!GetVolumeInformationW(root, volume_name, MAX_PATH + 1,
62                                NULL, NULL, NULL, NULL, 0))
63     {
64         ERR("Failed to get volume information\n");
65         return FALSE;
66     }
67
68     return !lstrcmpW(mi->volume_label, volume_name);
69 }
70
71 static UINT msi_change_media(MSIPACKAGE *package, MSIMEDIAINFO *mi)
72 {
73     LPWSTR error, error_dialog;
74     LPWSTR source_dir;
75     UINT r = ERROR_SUCCESS;
76
77     static const WCHAR error_prop[] = {'E','r','r','o','r','D','i','a','l','o','g',0};
78
79     if ((msi_get_property_int(package, szUILevel, 0) & INSTALLUILEVEL_MASK) ==
80          INSTALLUILEVEL_NONE && !gUIHandlerA && !gUIHandlerW && !gUIHandlerRecord)
81         return ERROR_SUCCESS;
82
83     error = generate_error_string(package, 1302, 1, mi->disk_prompt);
84     error_dialog = msi_dup_property(package, error_prop);
85     source_dir = msi_dup_property(package, cszSourceDir);
86
87     while (r == ERROR_SUCCESS && !source_matches_volume(mi, source_dir))
88     {
89         r = msi_spawn_error_dialog(package, error_dialog, error);
90
91         if (gUIHandlerW)
92         {
93             gUIHandlerW(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, error);
94         }
95         else if (gUIHandlerA)
96         {
97             char *msg = strdupWtoA(error);
98             gUIHandlerA(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, msg);
99             msi_free(msg);
100         }
101         else if (gUIHandlerRecord)
102         {
103             MSIHANDLE rec = MsiCreateRecord(1);
104             MsiRecordSetStringW(rec, 0, error);
105             gUIHandlerRecord(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, rec);
106             MsiCloseHandle(rec);
107         }
108     }
109
110     msi_free(error);
111     msi_free(error_dialog);
112     msi_free(source_dir);
113
114     return r;
115 }
116
117 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream,
118                                     WCHAR* source)
119 {
120     UINT rc;
121     USHORT* data;
122     UINT size;
123     DWORD write;
124     HANDLE hfile;
125     WCHAR tmp[MAX_PATH];
126
127     static const WCHAR cszTempFolder[]= {
128         'T','e','m','p','F','o','l','d','e','r',0};
129
130     rc = read_raw_stream_data(package->db, stream, &data, &size);
131     if (rc != ERROR_SUCCESS)
132         return rc;
133
134     write = MAX_PATH;
135     if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
136         GetTempPathW(MAX_PATH, tmp);
137
138     GetTempFileNameW(tmp, stream, 0, source);
139
140     track_tempfile(package, source);
141     hfile = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
142                         FILE_ATTRIBUTE_NORMAL, NULL);
143
144     if (hfile == INVALID_HANDLE_VALUE)
145     {
146         ERR("Unable to create file %s\n", debugstr_w(source));
147         rc = ERROR_FUNCTION_FAILED;
148         goto end;
149     }
150
151     WriteFile(hfile, data, size, &write, NULL);
152     CloseHandle(hfile);
153     TRACE("wrote %i bytes to %s\n", write, debugstr_w(source));
154
155 end:
156     msi_free(data);
157     return rc;
158 }
159
160 static void * CDECL cabinet_alloc(ULONG cb)
161 {
162     return msi_alloc(cb);
163 }
164
165 static void CDECL cabinet_free(void *pv)
166 {
167     msi_free(pv);
168 }
169
170 static INT_PTR CDECL cabinet_open(char *pszFile, int oflag, int pmode)
171 {
172     HANDLE handle;
173     DWORD dwAccess = 0;
174     DWORD dwShareMode = 0;
175     DWORD dwCreateDisposition = OPEN_EXISTING;
176
177     switch (oflag & _O_ACCMODE)
178     {
179     case _O_RDONLY:
180         dwAccess = GENERIC_READ;
181         dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
182         break;
183     case _O_WRONLY:
184         dwAccess = GENERIC_WRITE;
185         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
186         break;
187     case _O_RDWR:
188         dwAccess = GENERIC_READ | GENERIC_WRITE;
189         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
190         break;
191     }
192
193     if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
194         dwCreateDisposition = CREATE_NEW;
195     else if (oflag & _O_CREAT)
196         dwCreateDisposition = CREATE_ALWAYS;
197
198     handle = CreateFileA(pszFile, dwAccess, dwShareMode, NULL,
199                          dwCreateDisposition, 0, NULL);
200     if (handle == INVALID_HANDLE_VALUE)
201         return 0;
202
203     return (INT_PTR)handle;
204 }
205
206 static UINT CDECL cabinet_read(INT_PTR hf, void *pv, UINT cb)
207 {
208     HANDLE handle = (HANDLE)hf;
209     DWORD read;
210
211     if (ReadFile(handle, pv, cb, &read, NULL))
212         return read;
213
214     return 0;
215 }
216
217 static UINT CDECL cabinet_write(INT_PTR hf, void *pv, UINT cb)
218 {
219     HANDLE handle = (HANDLE)hf;
220     DWORD written;
221
222     if (WriteFile(handle, pv, cb, &written, NULL))
223         return written;
224
225     return 0;
226 }
227
228 static int CDECL cabinet_close(INT_PTR hf)
229 {
230     HANDLE handle = (HANDLE)hf;
231     return CloseHandle(handle) ? 0 : -1;
232 }
233
234 static LONG CDECL cabinet_seek(INT_PTR hf, LONG dist, int seektype)
235 {
236     HANDLE handle = (HANDLE)hf;
237     /* flags are compatible and so are passed straight through */
238     return SetFilePointer(handle, dist, NULL, seektype);
239 }
240
241 static UINT CDECL msi_media_get_disk_info(MSIPACKAGE *package, MSIMEDIAINFO *mi)
242 {
243     MSIRECORD *row;
244     LPWSTR ptr;
245
246     static const WCHAR query[] = {
247         'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
248         '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
249         '`','D','i','s','k','I','d','`',' ','=',' ','%','i',0};
250
251     row = MSI_QueryGetRecord(package->db, query, mi->disk_id);
252     if (!row)
253     {
254         TRACE("Unable to query row\n");
255         return ERROR_FUNCTION_FAILED;
256     }
257
258     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
259     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
260     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
261
262     if (!mi->first_volume)
263         mi->first_volume = strdupW(mi->volume_label);
264
265     ptr = strrchrW(mi->source, '\\') + 1;
266     lstrcpyW(ptr, mi->cabinet);
267     msiobj_release(&row->hdr);
268
269     return ERROR_SUCCESS;
270 }
271
272 static INT_PTR cabinet_partial_file(FDINOTIFICATIONTYPE fdint,
273                                     PFDINOTIFICATION pfdin)
274 {
275     MSICABDATA *data = pfdin->pv;
276     data->mi->is_continuous = FALSE;
277     return 0;
278 }
279
280 static INT_PTR cabinet_next_cabinet(FDINOTIFICATIONTYPE fdint,
281                                     PFDINOTIFICATION pfdin)
282 {
283     MSICABDATA *data = pfdin->pv;
284     MSIMEDIAINFO *mi = data->mi;
285     LPWSTR cab = strdupAtoW(pfdin->psz1);
286     INT_PTR res = -1;
287     UINT rc;
288
289     msi_free(mi->disk_prompt);
290     msi_free(mi->cabinet);
291     msi_free(mi->volume_label);
292     mi->disk_prompt = NULL;
293     mi->cabinet = NULL;
294     mi->volume_label = NULL;
295
296     mi->disk_id++;
297     mi->is_continuous = TRUE;
298
299     rc = msi_media_get_disk_info(data->package, mi);
300     if (rc != ERROR_SUCCESS)
301     {
302         ERR("Failed to get next cabinet information: %d\n", rc);
303         goto done;
304     }
305
306     if (lstrcmpiW(mi->cabinet, cab))
307     {
308         ERR("Continuous cabinet does not match the next cabinet in the Media table\n");
309         goto done;
310     }
311
312     TRACE("Searching for %s\n", debugstr_w(mi->source));
313
314     res = 0;
315     if (GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
316     {
317         if (msi_change_media(data->package, mi) != ERROR_SUCCESS)
318             res = -1;
319     }
320
321 done:
322     msi_free(cab);
323     return res;
324 }
325
326 static INT_PTR cabinet_copy_file(FDINOTIFICATIONTYPE fdint,
327                                  PFDINOTIFICATION pfdin)
328 {
329     MSICABDATA *data = pfdin->pv;
330     HANDLE handle = 0;
331     LPWSTR path = NULL;
332     DWORD attrs;
333
334     data->curfile = strdupAtoW(pfdin->psz1);
335     if (!data->cb(data->package, data->curfile, MSICABEXTRACT_BEGINEXTRACT, &path,
336                   &attrs, data->user))
337         goto done;
338
339     TRACE("extracting %s\n", debugstr_w(path));
340
341     attrs = attrs & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
342     if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL;
343
344     handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0,
345                          NULL, CREATE_ALWAYS, attrs, NULL);
346     if (handle == INVALID_HANDLE_VALUE)
347     {
348         DWORD err = GetLastError();
349         DWORD attrs2 = GetFileAttributesW(path);
350
351         if (attrs2 == INVALID_FILE_ATTRIBUTES)
352         {
353             ERR("failed to create %s (error %d)\n", debugstr_w(path), err);
354             goto done;
355         }
356         else if (err == ERROR_ACCESS_DENIED && (attrs2 & FILE_ATTRIBUTE_READONLY))
357         {
358             TRACE("removing read-only attribute on %s\n", debugstr_w(path));
359             SetFileAttributesW( path, attrs2 & ~FILE_ATTRIBUTE_READONLY );
360             handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs2, NULL);
361
362             if (handle != INVALID_HANDLE_VALUE) goto done;
363             err = GetLastError();
364         }
365         if (err == ERROR_SHARING_VIOLATION || err == ERROR_USER_MAPPED_FILE)
366         {
367             WCHAR tmpfileW[MAX_PATH], *tmppathW, *p;
368             DWORD len;
369
370             TRACE("file in use, scheduling rename operation\n");
371
372             GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
373             len = strlenW(path) + strlenW(tmpfileW) + 1;
374             if (!(tmppathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
375                 return ERROR_OUTOFMEMORY;
376
377             strcpyW(tmppathW, path);
378             if ((p = strrchrW(tmppathW, '\\'))) *p = 0;
379             strcatW(tmppathW, tmpfileW);
380
381             handle = CreateFileW(tmppathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL);
382
383             if (handle != INVALID_HANDLE_VALUE &&
384                 MoveFileExW(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
385                 MoveFileExW(tmppathW, path, MOVEFILE_DELAY_UNTIL_REBOOT))
386             {
387                 data->package->need_reboot = 1;
388             }
389             else
390                 WARN("failed to schedule rename operation %s (error %d)\n", debugstr_w(path), GetLastError());
391
392             HeapFree(GetProcessHeap(), 0, tmppathW);
393         }
394         else
395             WARN("failed to create %s (error %d)\n", debugstr_w(path), err);
396     }
397
398 done:
399     msi_free(path);
400
401     return (INT_PTR)handle;
402 }
403
404 static INT_PTR cabinet_close_file_info(FDINOTIFICATIONTYPE fdint,
405                                        PFDINOTIFICATION pfdin)
406 {
407     MSICABDATA *data = pfdin->pv;
408     FILETIME ft;
409     FILETIME ftLocal;
410     HANDLE handle = (HANDLE)pfdin->hf;
411
412     data->mi->is_continuous = FALSE;
413
414     if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
415         return -1;
416     if (!LocalFileTimeToFileTime(&ft, &ftLocal))
417         return -1;
418     if (!SetFileTime(handle, &ftLocal, 0, &ftLocal))
419         return -1;
420
421     CloseHandle(handle);
422
423     data->cb(data->package, data->curfile, MSICABEXTRACT_FILEEXTRACTED, NULL, NULL,
424              data->user);
425
426     msi_free(data->curfile);
427     data->curfile = NULL;
428
429     return 1;
430 }
431
432 static INT_PTR CDECL cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
433 {
434     TRACE("(%d)\n", fdint);
435
436     switch (fdint)
437     {
438     case fdintPARTIAL_FILE:
439         return cabinet_partial_file(fdint, pfdin);
440
441     case fdintNEXT_CABINET:
442         return cabinet_next_cabinet(fdint, pfdin);
443
444     case fdintCOPY_FILE:
445         return cabinet_copy_file(fdint, pfdin);
446
447     case fdintCLOSE_FILE_INFO:
448         return cabinet_close_file_info(fdint, pfdin);
449
450     default:
451         return 0;
452     }
453 }
454
455 /***********************************************************************
456  *            msi_cabextract
457  *
458  * Extract files from a cab file.
459  */
460 BOOL msi_cabextract(MSIPACKAGE* package, MSIMEDIAINFO *mi, LPVOID data)
461 {
462     LPSTR cabinet, cab_path = NULL;
463     LPWSTR ptr;
464     HFDI hfdi;
465     ERF erf;
466     BOOL ret = FALSE;
467
468     TRACE("Extracting %s\n", debugstr_w(mi->source));
469
470     hfdi = FDICreate(cabinet_alloc, cabinet_free, cabinet_open, cabinet_read,
471                      cabinet_write, cabinet_close, cabinet_seek, 0, &erf);
472     if (!hfdi)
473     {
474         ERR("FDICreate failed\n");
475         return FALSE;
476     }
477
478     ptr = strrchrW(mi->source, '\\') + 1;
479     cabinet = strdupWtoA(ptr);
480     if (!cabinet)
481         goto done;
482
483     cab_path = strdupWtoA(mi->source);
484     if (!cab_path)
485         goto done;
486
487     cab_path[ptr - mi->source] = '\0';
488
489     ret = FDICopy(hfdi, cabinet, cab_path, 0, cabinet_notify, NULL, data);
490     if (!ret)
491         ERR("FDICopy failed\n");
492
493 done:
494     FDIDestroy(hfdi);
495     msi_free(cabinet);
496     msi_free(cab_path);
497
498     if (ret)
499         mi->is_extracted = TRUE;
500
501     return ret;
502 }
503
504 void msi_free_media_info(MSIMEDIAINFO *mi)
505 {
506     msi_free(mi->disk_prompt);
507     msi_free(mi->cabinet);
508     msi_free(mi->volume_label);
509     msi_free(mi->first_volume);
510     msi_free(mi);
511 }
512
513 static UINT get_drive_type(const WCHAR *path)
514 {
515     WCHAR root[MAX_PATH + 1];
516
517     strcpyW(root, path);
518     PathStripToRootW(root);
519     PathAddBackslashW(root);
520
521     return GetDriveTypeW(root);
522 }
523
524 static UINT msi_load_media_info(MSIPACKAGE *package, MSIFILE *file, MSIMEDIAINFO *mi)
525 {
526     MSIRECORD *row;
527     LPWSTR source_dir;
528     LPWSTR source;
529     DWORD options;
530     UINT r;
531
532     static const WCHAR query[] = {
533         'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
534         '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
535         '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
536         ' ','%','i',' ','A','N','D',' ','`','D','i','s','k','I','d','`',' ','>','=',
537         ' ','%','i',' ','O','R','D','E','R',' ','B','Y',' ',
538         '`','D','i','s','k','I','d','`',0};
539
540     row = MSI_QueryGetRecord(package->db, query, file->Sequence, mi->disk_id);
541     if (!row)
542     {
543         TRACE("Unable to query row\n");
544         return ERROR_FUNCTION_FAILED;
545     }
546
547     mi->is_extracted = FALSE;
548     mi->disk_id = MSI_RecordGetInteger(row, 1);
549     mi->last_sequence = MSI_RecordGetInteger(row, 2);
550     msi_free(mi->disk_prompt);
551     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
552     msi_free(mi->cabinet);
553     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
554     msi_free(mi->volume_label);
555     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
556     msiobj_release(&row->hdr);
557
558     if (!mi->first_volume)
559         mi->first_volume = strdupW(mi->volume_label);
560
561     source_dir = msi_dup_property(package, cszSourceDir);
562     lstrcpyW(mi->source, source_dir);
563     mi->type = get_drive_type(source_dir);
564
565     if (file->IsCompressed && mi->cabinet)
566     {
567         if (mi->cabinet[0] == '#')
568         {
569             r = writeout_cabinet_stream(package, &mi->cabinet[1], mi->source);
570             if (r != ERROR_SUCCESS)
571             {
572                 ERR("Failed to extract cabinet stream\n");
573                 return ERROR_FUNCTION_FAILED;
574             }
575         }
576         else
577             lstrcatW(mi->source, mi->cabinet);
578     }
579
580     options = MSICODE_PRODUCT;
581     if (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE)
582     {
583         source = source_dir;
584         options |= MSISOURCETYPE_MEDIA;
585     }
586     else if (package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
587     {
588         source = package->BaseURL;
589         options |= MSISOURCETYPE_URL;
590     }
591     else
592     {
593         source = mi->source;
594         options |= MSISOURCETYPE_NETWORK;
595     }
596
597     msi_package_add_media_disk(package, package->Context,
598                                MSICODE_PRODUCT, mi->disk_id,
599                                mi->volume_label, mi->disk_prompt);
600
601     msi_package_add_info(package, package->Context,
602                          options, INSTALLPROPERTY_LASTUSEDSOURCEW, source);
603
604     msi_free(source_dir);
605     return ERROR_SUCCESS;
606 }
607
608 /* FIXME: search NETWORK and URL sources as well */
609 static UINT find_published_source(MSIPACKAGE *package, MSIMEDIAINFO *mi)
610 {
611     WCHAR source[MAX_PATH];
612     WCHAR volume[MAX_PATH];
613     WCHAR prompt[MAX_PATH];
614     DWORD volumesz, promptsz;
615     DWORD index, size, id;
616     UINT r;
617
618     size = MAX_PATH;
619     r = MsiSourceListGetInfoW(package->ProductCode, NULL,
620                               package->Context, MSICODE_PRODUCT,
621                               INSTALLPROPERTY_LASTUSEDSOURCEW, source, &size);
622     if (r != ERROR_SUCCESS)
623         return r;
624
625     index = 0;
626     volumesz = MAX_PATH;
627     promptsz = MAX_PATH;
628     while (MsiSourceListEnumMediaDisksW(package->ProductCode, NULL,
629                                         package->Context,
630                                         MSICODE_PRODUCT, index++, &id,
631                                         volume, &volumesz, prompt, &promptsz) == ERROR_SUCCESS)
632     {
633         mi->disk_id = id;
634         mi->volume_label = msi_realloc(mi->volume_label, ++volumesz * sizeof(WCHAR));
635         lstrcpyW(mi->volume_label, volume);
636         mi->disk_prompt = msi_realloc(mi->disk_prompt, ++promptsz * sizeof(WCHAR));
637         lstrcpyW(mi->disk_prompt, prompt);
638
639         if (source_matches_volume(mi, source))
640         {
641             /* FIXME: what about SourceDir */
642             lstrcpyW(mi->source, source);
643             return ERROR_SUCCESS;
644         }
645     }
646
647     return ERROR_FUNCTION_FAILED;
648 }
649
650 UINT ready_media(MSIPACKAGE *package, MSIFILE *file, MSIMEDIAINFO *mi)
651 {
652     UINT rc = ERROR_SUCCESS;
653
654     /* media info for continuous cabinet is already loaded */
655     if (mi->is_continuous)
656         return ERROR_SUCCESS;
657
658     rc = msi_load_media_info(package, file, mi);
659     if (rc != ERROR_SUCCESS)
660     {
661         ERR("Unable to load media info\n");
662         return ERROR_FUNCTION_FAILED;
663     }
664
665     /* cabinet is internal, no checks needed */
666     if (!mi->cabinet || mi->cabinet[0] == '#')
667         return ERROR_SUCCESS;
668
669     /* package should be downloaded */
670     if (file->IsCompressed &&
671         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES &&
672         package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
673     {
674         WCHAR temppath[MAX_PATH];
675
676         msi_download_file(mi->source, temppath);
677         lstrcpyW(mi->source, temppath);
678         return ERROR_SUCCESS;
679     }
680
681     /* check volume matches, change media if not */
682     if (mi->volume_label && mi->disk_id > 1 &&
683         lstrcmpW(mi->first_volume, mi->volume_label))
684     {
685         LPWSTR source = msi_dup_property(package, cszSourceDir);
686         BOOL matches;
687
688         matches = source_matches_volume(mi, source);
689         msi_free(source);
690
691         if ((mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE) && !matches)
692         {
693             rc = msi_change_media(package, mi);
694             if (rc != ERROR_SUCCESS)
695                 return rc;
696         }
697     }
698
699     if (file->IsCompressed &&
700         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
701     {
702         rc = find_published_source(package, mi);
703         if (rc != ERROR_SUCCESS)
704         {
705             ERR("Cabinet not found: %s\n", debugstr_w(mi->source));
706             return ERROR_INSTALL_FAILURE;
707         }
708     }
709
710     return ERROR_SUCCESS;
711 }