msi: Include run mode in fixmes.
[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     {
338         /* We're not extracting this file, so free the filename. */
339         msi_free(data->curfile);
340         data->curfile = NULL;
341         goto done;
342     }
343
344     TRACE("extracting %s\n", debugstr_w(path));
345
346     attrs = attrs & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
347     if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL;
348
349     handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0,
350                          NULL, CREATE_ALWAYS, attrs, NULL);
351     if (handle == INVALID_HANDLE_VALUE)
352     {
353         DWORD err = GetLastError();
354         DWORD attrs2 = GetFileAttributesW(path);
355
356         if (attrs2 == INVALID_FILE_ATTRIBUTES)
357         {
358             ERR("failed to create %s (error %d)\n", debugstr_w(path), err);
359             goto done;
360         }
361         else if (err == ERROR_ACCESS_DENIED && (attrs2 & FILE_ATTRIBUTE_READONLY))
362         {
363             TRACE("removing read-only attribute on %s\n", debugstr_w(path));
364             SetFileAttributesW( path, attrs2 & ~FILE_ATTRIBUTE_READONLY );
365             handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs2, NULL);
366
367             if (handle != INVALID_HANDLE_VALUE) goto done;
368             err = GetLastError();
369         }
370         if (err == ERROR_SHARING_VIOLATION || err == ERROR_USER_MAPPED_FILE)
371         {
372             WCHAR tmpfileW[MAX_PATH], *tmppathW, *p;
373             DWORD len;
374
375             TRACE("file in use, scheduling rename operation\n");
376
377             GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
378             len = strlenW(path) + strlenW(tmpfileW) + 1;
379             if (!(tmppathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
380                 return ERROR_OUTOFMEMORY;
381
382             strcpyW(tmppathW, path);
383             if ((p = strrchrW(tmppathW, '\\'))) *p = 0;
384             strcatW(tmppathW, tmpfileW);
385
386             handle = CreateFileW(tmppathW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL);
387
388             if (handle != INVALID_HANDLE_VALUE &&
389                 MoveFileExW(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
390                 MoveFileExW(tmppathW, path, MOVEFILE_DELAY_UNTIL_REBOOT))
391             {
392                 data->package->need_reboot = 1;
393             }
394             else
395                 WARN("failed to schedule rename operation %s (error %d)\n", debugstr_w(path), GetLastError());
396
397             HeapFree(GetProcessHeap(), 0, tmppathW);
398         }
399         else
400             WARN("failed to create %s (error %d)\n", debugstr_w(path), err);
401     }
402
403 done:
404     msi_free(path);
405
406     return (INT_PTR)handle;
407 }
408
409 static INT_PTR cabinet_close_file_info(FDINOTIFICATIONTYPE fdint,
410                                        PFDINOTIFICATION pfdin)
411 {
412     MSICABDATA *data = pfdin->pv;
413     FILETIME ft;
414     FILETIME ftLocal;
415     HANDLE handle = (HANDLE)pfdin->hf;
416
417     data->mi->is_continuous = FALSE;
418
419     if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
420         return -1;
421     if (!LocalFileTimeToFileTime(&ft, &ftLocal))
422         return -1;
423     if (!SetFileTime(handle, &ftLocal, 0, &ftLocal))
424         return -1;
425
426     CloseHandle(handle);
427
428     data->cb(data->package, data->curfile, MSICABEXTRACT_FILEEXTRACTED, NULL, NULL,
429              data->user);
430
431     msi_free(data->curfile);
432     data->curfile = NULL;
433
434     return 1;
435 }
436
437 static INT_PTR CDECL cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
438 {
439     TRACE("(%d)\n", fdint);
440
441     switch (fdint)
442     {
443     case fdintPARTIAL_FILE:
444         return cabinet_partial_file(fdint, pfdin);
445
446     case fdintNEXT_CABINET:
447         return cabinet_next_cabinet(fdint, pfdin);
448
449     case fdintCOPY_FILE:
450         return cabinet_copy_file(fdint, pfdin);
451
452     case fdintCLOSE_FILE_INFO:
453         return cabinet_close_file_info(fdint, pfdin);
454
455     default:
456         return 0;
457     }
458 }
459
460 /***********************************************************************
461  *            msi_cabextract
462  *
463  * Extract files from a cab file.
464  */
465 BOOL msi_cabextract(MSIPACKAGE* package, MSIMEDIAINFO *mi, LPVOID data)
466 {
467     LPSTR cabinet, cab_path = NULL;
468     LPWSTR ptr;
469     HFDI hfdi;
470     ERF erf;
471     BOOL ret = FALSE;
472
473     TRACE("Extracting %s\n", debugstr_w(mi->source));
474
475     hfdi = FDICreate(cabinet_alloc, cabinet_free, cabinet_open, cabinet_read,
476                      cabinet_write, cabinet_close, cabinet_seek, 0, &erf);
477     if (!hfdi)
478     {
479         ERR("FDICreate failed\n");
480         return FALSE;
481     }
482
483     ptr = strrchrW(mi->source, '\\') + 1;
484     cabinet = strdupWtoA(ptr);
485     if (!cabinet)
486         goto done;
487
488     cab_path = strdupWtoA(mi->source);
489     if (!cab_path)
490         goto done;
491
492     cab_path[ptr - mi->source] = '\0';
493
494     ret = FDICopy(hfdi, cabinet, cab_path, 0, cabinet_notify, NULL, data);
495     if (!ret)
496         ERR("FDICopy failed\n");
497
498 done:
499     FDIDestroy(hfdi);
500     msi_free(cabinet);
501     msi_free(cab_path);
502
503     if (ret)
504         mi->is_extracted = TRUE;
505
506     return ret;
507 }
508
509 void msi_free_media_info(MSIMEDIAINFO *mi)
510 {
511     msi_free(mi->disk_prompt);
512     msi_free(mi->cabinet);
513     msi_free(mi->volume_label);
514     msi_free(mi->first_volume);
515     msi_free(mi);
516 }
517
518 static UINT get_drive_type(const WCHAR *path)
519 {
520     WCHAR root[MAX_PATH + 1];
521
522     strcpyW(root, path);
523     PathStripToRootW(root);
524     PathAddBackslashW(root);
525
526     return GetDriveTypeW(root);
527 }
528
529 static UINT msi_load_media_info(MSIPACKAGE *package, MSIFILE *file, MSIMEDIAINFO *mi)
530 {
531     MSIRECORD *row;
532     LPWSTR source_dir;
533     LPWSTR source;
534     DWORD options;
535     UINT r;
536
537     static const WCHAR query[] = {
538         'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
539         '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
540         '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
541         ' ','%','i',' ','A','N','D',' ','`','D','i','s','k','I','d','`',' ','>','=',
542         ' ','%','i',' ','O','R','D','E','R',' ','B','Y',' ',
543         '`','D','i','s','k','I','d','`',0};
544
545     row = MSI_QueryGetRecord(package->db, query, file->Sequence, mi->disk_id);
546     if (!row)
547     {
548         TRACE("Unable to query row\n");
549         return ERROR_FUNCTION_FAILED;
550     }
551
552     mi->is_extracted = FALSE;
553     mi->disk_id = MSI_RecordGetInteger(row, 1);
554     mi->last_sequence = MSI_RecordGetInteger(row, 2);
555     msi_free(mi->disk_prompt);
556     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
557     msi_free(mi->cabinet);
558     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
559     msi_free(mi->volume_label);
560     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
561     msiobj_release(&row->hdr);
562
563     if (!mi->first_volume)
564         mi->first_volume = strdupW(mi->volume_label);
565
566     source_dir = msi_dup_property(package, cszSourceDir);
567     lstrcpyW(mi->source, source_dir);
568     mi->type = get_drive_type(source_dir);
569
570     if (file->IsCompressed && mi->cabinet)
571     {
572         if (mi->cabinet[0] == '#')
573         {
574             r = writeout_cabinet_stream(package, &mi->cabinet[1], mi->source);
575             if (r != ERROR_SUCCESS)
576             {
577                 ERR("Failed to extract cabinet stream\n");
578                 return ERROR_FUNCTION_FAILED;
579             }
580         }
581         else
582             lstrcatW(mi->source, mi->cabinet);
583     }
584
585     options = MSICODE_PRODUCT;
586     if (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE)
587     {
588         source = source_dir;
589         options |= MSISOURCETYPE_MEDIA;
590     }
591     else if (package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
592     {
593         source = package->BaseURL;
594         options |= MSISOURCETYPE_URL;
595     }
596     else
597     {
598         source = mi->source;
599         options |= MSISOURCETYPE_NETWORK;
600     }
601
602     msi_package_add_media_disk(package, package->Context,
603                                MSICODE_PRODUCT, mi->disk_id,
604                                mi->volume_label, mi->disk_prompt);
605
606     msi_package_add_info(package, package->Context,
607                          options, INSTALLPROPERTY_LASTUSEDSOURCEW, source);
608
609     msi_free(source_dir);
610     return ERROR_SUCCESS;
611 }
612
613 /* FIXME: search NETWORK and URL sources as well */
614 static UINT find_published_source(MSIPACKAGE *package, MSIMEDIAINFO *mi)
615 {
616     WCHAR source[MAX_PATH];
617     WCHAR volume[MAX_PATH];
618     WCHAR prompt[MAX_PATH];
619     DWORD volumesz, promptsz;
620     DWORD index, size, id;
621     UINT r;
622
623     size = MAX_PATH;
624     r = MsiSourceListGetInfoW(package->ProductCode, NULL,
625                               package->Context, MSICODE_PRODUCT,
626                               INSTALLPROPERTY_LASTUSEDSOURCEW, source, &size);
627     if (r != ERROR_SUCCESS)
628         return r;
629
630     index = 0;
631     volumesz = MAX_PATH;
632     promptsz = MAX_PATH;
633     while (MsiSourceListEnumMediaDisksW(package->ProductCode, NULL,
634                                         package->Context,
635                                         MSICODE_PRODUCT, index++, &id,
636                                         volume, &volumesz, prompt, &promptsz) == ERROR_SUCCESS)
637     {
638         mi->disk_id = id;
639         mi->volume_label = msi_realloc(mi->volume_label, ++volumesz * sizeof(WCHAR));
640         lstrcpyW(mi->volume_label, volume);
641         mi->disk_prompt = msi_realloc(mi->disk_prompt, ++promptsz * sizeof(WCHAR));
642         lstrcpyW(mi->disk_prompt, prompt);
643
644         if (source_matches_volume(mi, source))
645         {
646             /* FIXME: what about SourceDir */
647             lstrcpyW(mi->source, source);
648             return ERROR_SUCCESS;
649         }
650     }
651
652     return ERROR_FUNCTION_FAILED;
653 }
654
655 UINT ready_media(MSIPACKAGE *package, MSIFILE *file, MSIMEDIAINFO *mi)
656 {
657     UINT rc = ERROR_SUCCESS;
658
659     /* media info for continuous cabinet is already loaded */
660     if (mi->is_continuous)
661         return ERROR_SUCCESS;
662
663     rc = msi_load_media_info(package, file, mi);
664     if (rc != ERROR_SUCCESS)
665     {
666         ERR("Unable to load media info\n");
667         return ERROR_FUNCTION_FAILED;
668     }
669
670     /* cabinet is internal, no checks needed */
671     if (!mi->cabinet || mi->cabinet[0] == '#')
672         return ERROR_SUCCESS;
673
674     /* package should be downloaded */
675     if (file->IsCompressed &&
676         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES &&
677         package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
678     {
679         WCHAR temppath[MAX_PATH];
680
681         msi_download_file(mi->source, temppath);
682         lstrcpyW(mi->source, temppath);
683         return ERROR_SUCCESS;
684     }
685
686     /* check volume matches, change media if not */
687     if (mi->volume_label && mi->disk_id > 1 &&
688         lstrcmpW(mi->first_volume, mi->volume_label))
689     {
690         LPWSTR source = msi_dup_property(package, cszSourceDir);
691         BOOL matches;
692
693         matches = source_matches_volume(mi, source);
694         msi_free(source);
695
696         if ((mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE) && !matches)
697         {
698             rc = msi_change_media(package, mi);
699             if (rc != ERROR_SUCCESS)
700                 return rc;
701         }
702     }
703
704     if (file->IsCompressed &&
705         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
706     {
707         rc = find_published_source(package, mi);
708         if (rc != ERROR_SUCCESS)
709         {
710             ERR("Cabinet not found: %s\n", debugstr_w(mi->source));
711             return ERROR_INSTALL_FAILURE;
712         }
713     }
714
715     return ERROR_SUCCESS;
716 }