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