Added MIME type checking to ResProtocol::Start.
[wine] / dlls / msi / files.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart for CodeWeavers
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21
22 /*
23  * Actions dealing with files These are
24  *
25  * InstallFiles
26  * DuplicateFiles
27  * MoveFiles (TODO)
28  * PatchFiles (TODO)
29  * RemoveDuplicateFiles(TODO)
30  * RemoveFiles(TODO)
31  */
32
33 #include <stdarg.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "winerror.h"
38 #include "wine/debug.h"
39 #include "fdi.h"
40 #include "msi.h"
41 #include "msidefs.h"
42 #include "msvcrt/fcntl.h"
43 #include "msipriv.h"
44 #include "winuser.h"
45 #include "wine/unicode.h"
46 #include "action.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
49
50 extern const WCHAR szInstallFiles[];
51 extern const WCHAR szDuplicateFiles[];
52 extern const WCHAR szMoveFiles[];
53 extern const WCHAR szPatchFiles[];
54 extern const WCHAR szRemoveDuplicateFiles[];
55 extern const WCHAR szRemoveFiles[];
56
57 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
58
59 static UINT create_component_directory( MSIPACKAGE* package, MSICOMPONENT *comp )
60 {
61     UINT rc = ERROR_SUCCESS;
62     MSIFOLDER *folder;
63     LPWSTR install_path;
64
65     install_path = resolve_folder(package, comp->Directory, FALSE, FALSE, &folder);
66     if (!install_path)
67         return ERROR_FUNCTION_FAILED; 
68
69     /* create the path */
70     if (folder->State == 0)
71     {
72         create_full_pathW(install_path);
73         folder->State = 2;
74     }
75     HeapFree(GetProcessHeap(), 0, install_path);
76
77     return rc;
78 }
79
80 /*
81  * This is a helper function for handling embedded cabinet media
82  */
83 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
84                                     WCHAR* source)
85 {
86     UINT rc;
87     USHORT* data;
88     UINT    size;
89     DWORD   write;
90     HANDLE  the_file;
91     WCHAR tmp[MAX_PATH];
92
93     rc = read_raw_stream_data(package->db,stream_name,&data,&size); 
94     if (rc != ERROR_SUCCESS)
95         return rc;
96
97     write = MAX_PATH;
98     if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
99         GetTempPathW(MAX_PATH,tmp);
100
101     GetTempFileNameW(tmp,stream_name,0,source);
102
103     track_tempfile(package,strrchrW(source,'\\'), source);
104     the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
105                            FILE_ATTRIBUTE_NORMAL, NULL);
106
107     if (the_file == INVALID_HANDLE_VALUE)
108     {
109         ERR("Unable to create file %s\n",debugstr_w(source));
110         rc = ERROR_FUNCTION_FAILED;
111         goto end;
112     }
113
114     WriteFile(the_file,data,size,&write,NULL);
115     CloseHandle(the_file);
116     TRACE("wrote %li bytes to %s\n",write,debugstr_w(source));
117 end:
118     HeapFree(GetProcessHeap(),0,data);
119     return rc;
120 }
121
122
123 /* Support functions for FDI functions */
124 typedef struct
125 {
126     MSIPACKAGE* package;
127     LPCSTR cab_path;
128 } CabData;
129
130 static void * cabinet_alloc(ULONG cb)
131 {
132     return HeapAlloc(GetProcessHeap(), 0, cb);
133 }
134
135 static void cabinet_free(void *pv)
136 {
137     HeapFree(GetProcessHeap(), 0, pv);
138 }
139
140 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
141 {
142     DWORD dwAccess = 0;
143     DWORD dwShareMode = 0;
144     DWORD dwCreateDisposition = OPEN_EXISTING;
145     switch (oflag & _O_ACCMODE)
146     {
147     case _O_RDONLY:
148         dwAccess = GENERIC_READ;
149         dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
150         break;
151     case _O_WRONLY:
152         dwAccess = GENERIC_WRITE;
153         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
154         break;
155     case _O_RDWR:
156         dwAccess = GENERIC_READ | GENERIC_WRITE;
157         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
158         break;
159     }
160     if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
161         dwCreateDisposition = CREATE_NEW;
162     else if (oflag & _O_CREAT)
163         dwCreateDisposition = CREATE_ALWAYS;
164     return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL, 
165                                 dwCreateDisposition, 0, NULL);
166 }
167
168 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
169 {
170     DWORD dwRead;
171     if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL))
172         return dwRead;
173     return 0;
174 }
175
176 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
177 {
178     DWORD dwWritten;
179     if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL))
180         return dwWritten;
181     return 0;
182 }
183
184 static int cabinet_close(INT_PTR hf)
185 {
186     return CloseHandle((HANDLE)hf) ? 0 : -1;
187 }
188
189 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
190 {
191     /* flags are compatible and so are passed straight through */
192     return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
193 }
194
195 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
196 {
197     switch (fdint)
198     {
199     case fdintCOPY_FILE:
200     {
201         CabData *data = (CabData*) pfdin->pv;
202         ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1);
203         char *file;
204
205         LPWSTR trackname;
206         LPWSTR trackpath;
207         LPWSTR tracknametmp;
208         static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0};
209         LPWSTR given_file;
210
211         MSIRECORD * uirow;
212         LPWSTR uipath;
213         MSIFILE *f;
214
215         given_file = strdupAtoW(pfdin->psz1);
216         f = get_loaded_file(data->package, given_file);
217
218         if (!f)
219         {
220             ERR("Unknown File in Cabinent (%s)\n",debugstr_w(given_file));
221             HeapFree(GetProcessHeap(),0,given_file);
222             return 0;
223         }
224
225         if (!((f->State == 1 || f->State == 2)))
226         {
227             TRACE("Skipping extraction of %s\n",debugstr_w(given_file));
228             HeapFree(GetProcessHeap(),0,given_file);
229             return 0;
230         }
231
232         file = cabinet_alloc((len+1)*sizeof(char));
233         strcpy(file, data->cab_path);
234         strcat(file, pfdin->psz1);
235
236         TRACE("file: %s\n", debugstr_a(file));
237
238         /* track this file so it can be deleted if not installed */
239         trackpath=strdupAtoW(file);
240         tracknametmp=strdupAtoW(strrchr(file,'\\')+1);
241         trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) + 
242                                   strlenW(tmpprefix)+1) * sizeof(WCHAR));
243
244         strcpyW(trackname,tmpprefix);
245         strcatW(trackname,tracknametmp);
246
247         track_tempfile(data->package, trackname, trackpath);
248
249         HeapFree(GetProcessHeap(),0,trackpath);
250         HeapFree(GetProcessHeap(),0,trackname);
251         HeapFree(GetProcessHeap(),0,tracknametmp);
252
253         /* the UI chunk */
254         uirow=MSI_CreateRecord(9);
255         MSI_RecordSetStringW( uirow, 1, f->FileName );
256         uipath = strdupW( f->TargetPath );
257         *(strrchrW(uipath,'\\')+1)=0;
258         MSI_RecordSetStringW(uirow,9,uipath);
259         MSI_RecordSetInteger( uirow, 6, f->FileSize );
260         ui_actiondata(data->package,szInstallFiles,uirow);
261         msiobj_release( &uirow->hdr );
262         HeapFree(GetProcessHeap(),0,uipath);
263
264         ui_progress( data->package, 2, f->FileSize, 0, 0);
265
266         return cabinet_open(file, _O_WRONLY | _O_CREAT, 0);
267     }
268     case fdintCLOSE_FILE_INFO:
269     {
270         FILETIME ft;
271             FILETIME ftLocal;
272         if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
273             return -1;
274         if (!LocalFileTimeToFileTime(&ft, &ftLocal))
275             return -1;
276         if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal))
277             return -1;
278
279         cabinet_close(pfdin->hf);
280         return 1;
281     }
282     default:
283         return 0;
284     }
285 }
286
287 /***********************************************************************
288  *            extract_cabinet_file
289  *
290  * Extract files from a cab file.
291  */
292 static BOOL extract_cabinet_file(MSIPACKAGE* package, LPCWSTR source, 
293                                  LPCWSTR path)
294 {
295     HFDI hfdi;
296     ERF erf;
297     BOOL ret;
298     char *cabinet;
299     char *cab_path;
300     CabData data;
301
302     TRACE("Extracting %s to %s\n",debugstr_w(source), debugstr_w(path));
303
304     hfdi = FDICreate(cabinet_alloc,
305                      cabinet_free,
306                      cabinet_open,
307                      cabinet_read,
308                      cabinet_write,
309                      cabinet_close,
310                      cabinet_seek,
311                      0,
312                      &erf);
313     if (!hfdi)
314     {
315         ERR("FDICreate failed\n");
316         return FALSE;
317     }
318
319     if (!(cabinet = strdupWtoA( source )))
320     {
321         FDIDestroy(hfdi);
322         return FALSE;
323     }
324     if (!(cab_path = strdupWtoA( path )))
325     {
326         FDIDestroy(hfdi);
327         HeapFree(GetProcessHeap(), 0, cabinet);
328         return FALSE;
329     }
330
331     data.package = package;
332     data.cab_path = cab_path;
333
334     ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data);
335
336     if (!ret)
337         ERR("FDICopy failed\n");
338
339     FDIDestroy(hfdi);
340
341     HeapFree(GetProcessHeap(), 0, cabinet);
342     HeapFree(GetProcessHeap(), 0, cab_path);
343
344     return ret;
345 }
346
347 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, MSICOMPONENT*
348         comp, LPCWSTR path)
349 {
350     if (file->Attributes & msidbFileAttributesNoncompressed)
351     {
352         LPWSTR p;
353         p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
354         file->SourcePath = build_directory_name(2, p, file->ShortName);
355         HeapFree(GetProcessHeap(),0,p);
356     }
357     else
358         file->SourcePath = build_directory_name(2, path, file->File);
359 }
360
361 static BOOL check_volume(LPCWSTR path, LPCWSTR want_volume, LPWSTR volume, 
362         UINT *intype)
363 {
364     WCHAR drive[4];
365     WCHAR name[MAX_PATH];
366     UINT type;
367
368     if (!(path[0] && path[1] == ':'))
369         return TRUE;
370
371     drive[0] = path[0];
372     drive[1] = path[1];
373     drive[2] = '\\';
374     drive[3] = 0;
375     TRACE("Checking volume %s .. (%s)\n",debugstr_w(drive), debugstr_w(want_volume));
376     type = GetDriveTypeW(drive);
377     TRACE("drive is of type %x\n",type);
378
379     if (type == DRIVE_UNKNOWN || type == DRIVE_NO_ROOT_DIR || 
380             type == DRIVE_FIXED || type == DRIVE_RAMDISK)
381         return TRUE;
382
383     GetVolumeInformationW(drive, name, MAX_PATH, NULL, NULL, NULL, NULL, 0);
384     TRACE("Drive contains %s\n", debugstr_w(name));
385     volume = strdupW(name);
386     if (*intype)
387         *intype=type;
388     return (strcmpiW(want_volume,name)==0);
389 }
390
391 static BOOL check_for_sourcefile(LPCWSTR source)
392 {
393     DWORD attrib = GetFileAttributesW(source);
394     return (!(attrib == INVALID_FILE_ATTRIBUTES));
395 }
396
397 static UINT ready_volume(MSIPACKAGE* package, LPCWSTR path, LPWSTR last_volume, 
398                          MSIRECORD *row,UINT *type )
399 {
400     LPWSTR volume = NULL; 
401     LPCWSTR want_volume = MSI_RecordGetString(row, 5);
402     BOOL ok = check_volume(path, want_volume, volume, type);
403
404     TRACE("Readying Volume for %s (%s, %s)\n",debugstr_w(path), debugstr_w(want_volume), debugstr_w(last_volume));
405
406     if (check_for_sourcefile(path) && !ok)
407     {
408         FIXME("Found the Sourcefile but not on the correct volume.(%s,%s,%s)\n",
409                 debugstr_w(path),debugstr_w(want_volume), debugstr_w(volume));
410         return ERROR_SUCCESS;
411     }
412
413     while (!ok)
414     {
415         INT rc;
416         LPCWSTR prompt;
417         LPWSTR msg;
418       
419         prompt = MSI_RecordGetString(row,3);
420         msg = generate_error_string(package, 1302, 1, prompt);
421         rc = MessageBoxW(NULL,msg,NULL,MB_OKCANCEL);
422         HeapFree(GetProcessHeap(),0,volume);
423         HeapFree(GetProcessHeap(),0,msg);
424         if (rc == IDOK)
425             ok = check_for_sourcefile(path);
426         else
427             return ERROR_INSTALL_USEREXIT;
428     }
429
430     HeapFree(GetProcessHeap(),0,last_volume);
431     last_volume = strdupW(volume);
432     return ERROR_SUCCESS;
433 }
434
435 static UINT ready_media_for_file(MSIPACKAGE *package, MSIFILE *file,
436                                  MSICOMPONENT* comp)
437 {
438     UINT rc = ERROR_SUCCESS;
439     MSIRECORD * row = 0;
440     static WCHAR source[MAX_PATH];
441     static const WCHAR ExecSeqQuery[] =
442         {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
443          '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
444          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
445          ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
446          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
447     LPCWSTR cab, volume;
448     DWORD sz;
449     INT seq;
450     static UINT last_sequence = 0; 
451     static LPWSTR last_volume = NULL;
452     static LPWSTR last_path = NULL;
453     UINT type;
454     LPCWSTR prompt;
455     static DWORD count = 0;
456
457     /* cleanup signal */
458     if (!package)
459     {
460         HeapFree(GetProcessHeap(),0,last_path);
461         HeapFree(GetProcessHeap(),0,last_volume);
462         last_sequence = 0;
463         last_path = NULL;
464         last_volume = NULL;
465         count = 0;
466         memset(source,0,sizeof(source));
467         return ERROR_SUCCESS;
468     }
469
470     if (file->Sequence <= last_sequence)
471     {
472         set_file_source(package,file,comp,last_path);
473         TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
474         return ERROR_SUCCESS;
475     }
476
477     count ++;
478     row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
479     if (!row)
480     {
481         TRACE("Unable to query row\n");
482         return ERROR_FUNCTION_FAILED;
483     }
484
485     seq = MSI_RecordGetInteger(row,2);
486     last_sequence = seq;
487
488     volume = MSI_RecordGetString(row, 5);
489     prompt = MSI_RecordGetString(row, 3);
490
491     HeapFree(GetProcessHeap(),0,last_path);
492     last_path = NULL;
493
494     if (file->Attributes & msidbFileAttributesNoncompressed)
495     {
496         last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
497         set_file_source(package,file,comp,last_path);
498         rc = ready_volume(package, file->SourcePath, last_volume, row,&type);
499
500         MsiSourceListAddMediaDiskW(package->ProductCode, NULL, 
501             MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
502             prompt);
503
504         if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
505                 type == DRIVE_RAMDISK)
506             MsiSourceListSetInfoW(package->ProductCode, NULL, 
507                 MSIINSTALLCONTEXT_USERMANAGED, 
508                 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
509                 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
510         else
511             MsiSourceListSetInfoW(package->ProductCode, NULL, 
512                 MSIINSTALLCONTEXT_USERMANAGED, 
513                 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
514                 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
515         msiobj_release(&row->hdr);
516         return rc;
517     }
518
519     cab = MSI_RecordGetString(row,4);
520     if (cab)
521     {
522         TRACE("Source is CAB %s\n",debugstr_w(cab));
523         /* the stream does not contain the # character */
524         if (cab[0]=='#')
525         {
526             LPWSTR path;
527
528             writeout_cabinet_stream(package,&cab[1],source);
529             last_path = strdupW(source);
530             *(strrchrW(last_path,'\\')+1)=0;
531
532             path = load_dynamic_property(package,cszSourceDir,NULL);
533
534             MsiSourceListAddMediaDiskW(package->ProductCode, NULL, 
535                 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count,
536                 volume, prompt);
537
538             MsiSourceListSetInfoW(package->ProductCode, NULL,
539                 MSIINSTALLCONTEXT_USERMANAGED,
540                 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
541                 INSTALLPROPERTY_LASTUSEDSOURCEW, path);
542
543             HeapFree(GetProcessHeap(),0,path);
544         }
545         else
546         {
547             sz = MAX_PATH;
548             last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
549             if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
550             {
551                 ERR("No Source dir defined \n");
552                 rc = ERROR_FUNCTION_FAILED;
553             }
554             else
555             {
556                 strcpyW(last_path,source);
557                 strcatW(source,cab);
558
559                 rc = ready_volume(package, source, last_volume, row, &type);
560                 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
561                         type == DRIVE_RAMDISK)
562                     MsiSourceListSetInfoW(package->ProductCode, NULL,
563                             MSIINSTALLCONTEXT_USERMANAGED,
564                             MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
565                             INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
566                 else
567                     MsiSourceListSetInfoW(package->ProductCode, NULL,
568                             MSIINSTALLCONTEXT_USERMANAGED,
569                             MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
570                             INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
571
572                 /* extract the cab file into a folder in the temp folder */
573                 sz = MAX_PATH;
574                 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz) 
575                                     != ERROR_SUCCESS)
576                     GetTempPathW(MAX_PATH,last_path);
577             }
578         }
579         rc = !extract_cabinet_file(package, source, last_path);
580     }
581     else
582     {
583         sz = MAX_PATH;
584         last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
585         MSI_GetPropertyW(package,cszSourceDir,source,&sz);
586         strcpyW(last_path,source);
587         rc = ready_volume(package, last_path, last_volume, row, &type);
588
589         if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
590                 type == DRIVE_RAMDISK)
591             MsiSourceListSetInfoW(package->ProductCode, NULL,
592                     MSIINSTALLCONTEXT_USERMANAGED,
593                     MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
594                     INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
595         else
596             MsiSourceListSetInfoW(package->ProductCode, NULL,
597                     MSIINSTALLCONTEXT_USERMANAGED,
598                     MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
599                     INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
600     }
601     set_file_source(package, file, comp, last_path);
602
603     MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
604             MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
605             prompt);
606
607     msiobj_release(&row->hdr);
608
609     return rc;
610 }
611
612 static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
613                                    LPWSTR* file_source)
614 {
615     MSIFILE *file;
616
617     if (!package)
618         return ERROR_INVALID_HANDLE;
619
620     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
621     {
622         if (lstrcmpW( file_key, file->File )==0)
623         {
624             if (file->State >= 2)
625             {
626                 *file_source = strdupW( file->TargetPath );
627                 return ERROR_SUCCESS;
628             }
629             else
630                 return ERROR_FILE_NOT_FOUND;
631         }
632     }
633
634     return ERROR_FUNCTION_FAILED;
635 }
636
637 /*
638  * In order to make this work more effeciencly I am going to do this in 2
639  * passes.
640  * Pass 1) Correct all the TargetPaths and determin what files are to be
641  * installed.
642  * Pass 2) Extract Cabinents and copy files.
643  */
644 UINT ACTION_InstallFiles(MSIPACKAGE *package)
645 {
646     UINT rc = ERROR_SUCCESS;
647     LPWSTR ptr;
648     MSIFILE *file;
649
650     if (!package)
651         return ERROR_INVALID_HANDLE;
652
653     /* increment progress bar each time action data is sent */
654     ui_progress(package,1,1,0,0);
655
656     /* handle the keys for the SouceList */
657     ptr = strrchrW(package->PackagePath,'\\');
658     if (ptr)
659     {
660         ptr ++;
661         MsiSourceListSetInfoW(package->ProductCode, NULL,
662                 MSIINSTALLCONTEXT_USERMANAGED,
663                 MSICODE_PRODUCT,
664                 INSTALLPROPERTY_PACKAGENAMEW, ptr);
665     }
666     FIXME("Write DiskPrompt\n");
667     
668     /* Pass 1 */
669     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
670     {
671         MSICOMPONENT* comp = NULL;
672
673         if (!ACTION_VerifyComponentForAction(package, file->Component, 
674                                        INSTALLSTATE_LOCAL))
675         {
676             ui_progress(package,2,file->FileSize,0,0);
677             TRACE("File %s is not scheduled for install\n",
678                    debugstr_w(file->File));
679
680             file->State = 5;
681             continue;
682         }
683
684         if ((file->State == 1) || (file->State == 2))
685         {
686             LPWSTR p = NULL;
687
688             TRACE("Pass 1: %s\n",debugstr_w(file->File));
689
690             create_component_directory( package, file->Component );
691
692             /* recalculate file paths because things may have changed */
693
694             comp = file->Component;
695             if (!comp)
696             {
697                 ERR("No Component for file\n");
698                 continue;
699             }
700
701             p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
702             HeapFree(GetProcessHeap(),0,file->TargetPath);
703
704             file->TargetPath = build_directory_name(2, p, file->FileName);
705             HeapFree(GetProcessHeap(),0,p);
706         }
707     }
708
709     /* Pass 2 */
710     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
711     {
712         if ((file->State == 1) || (file->State == 2))
713         {
714             TRACE("Pass 2: %s\n",debugstr_w(file->File));
715
716             rc = ready_media_for_file( package, file, file->Component );
717             if (rc != ERROR_SUCCESS)
718             {
719                 ERR("Unable to ready media\n");
720                 rc = ERROR_FUNCTION_FAILED;
721                 break;
722             }
723
724             TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
725                   debugstr_w(file->TargetPath));
726
727             if (file->Attributes & msidbFileAttributesNoncompressed)
728                 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
729             else
730                 rc = MoveFileW(file->SourcePath, file->TargetPath);
731
732             if (!rc)
733             {
734                 rc = GetLastError();
735                 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
736                      debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
737                       rc);
738                 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
739                 {
740                     if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
741                         ERR("Unable to copy file (%s -> %s) (error %ld)\n",
742                             debugstr_w(file->SourcePath), 
743                             debugstr_w(file->TargetPath), GetLastError());
744                     if (!(file->Attributes & msidbFileAttributesNoncompressed))
745                         DeleteFileW(file->SourcePath);
746                     rc = 0;
747                 }
748                 else if (rc == ERROR_FILE_NOT_FOUND)
749                 {
750                     ERR("Source File Not Found!  Continuing\n");
751                     rc = 0;
752                 }
753                 else if (file->Attributes & msidbFileAttributesVital)
754                 {
755                     ERR("Ignoring Error and continuing (nonvital file)...\n");
756                     rc = 0;
757                 }
758             }
759             else
760             {
761                 file->State = 4;
762                 rc = ERROR_SUCCESS;
763             }
764         }
765     }
766
767     /* cleanup */
768     ready_media_for_file(NULL, NULL, NULL);
769     return rc;
770 }
771
772 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
773 {
774     MSIPACKAGE *package = (MSIPACKAGE*)param;
775     WCHAR *file_source = NULL;
776     WCHAR dest_name[0x100];
777     LPWSTR dest_path, dest;
778     LPCWSTR file_key, component;
779     DWORD sz;
780     DWORD rc;
781     MSICOMPONENT *comp;
782
783     component = MSI_RecordGetString(row,2);
784     comp = get_loaded_component(package,component);
785
786     if (!ACTION_VerifyComponentForAction(package, comp, INSTALLSTATE_LOCAL))
787     {
788         TRACE("Skipping copy due to disabled component %s\n",
789                         debugstr_w(component));
790
791         /* the action taken was the same as the current install state */        
792         comp->Action = comp->Installed;
793
794         return ERROR_SUCCESS;
795     }
796
797     comp->Action = INSTALLSTATE_LOCAL;
798
799     file_key = MSI_RecordGetString(row,3);
800     if (!file_key)
801     {
802         ERR("Unable to get file key\n");
803         return ERROR_FUNCTION_FAILED;
804     }
805
806     rc = get_file_target(package,file_key,&file_source);
807
808     if (rc != ERROR_SUCCESS)
809     {
810         ERR("Original file unknown %s\n",debugstr_w(file_key));
811         HeapFree(GetProcessHeap(),0,file_source);
812         return ERROR_SUCCESS;
813     }
814
815     if (MSI_RecordIsNull(row,4))
816         strcpyW(dest_name,strrchrW(file_source,'\\')+1);
817     else
818     {
819         sz=0x100;
820         MSI_RecordGetStringW(row,4,dest_name,&sz);
821         reduce_to_longfilename(dest_name);
822     }
823
824     if (MSI_RecordIsNull(row,5))
825     {
826         LPWSTR p;
827         dest_path = strdupW(file_source);
828         p = strrchrW(dest_path,'\\');
829         if (p)
830             *p=0;
831     }
832     else
833     {
834         LPCWSTR destkey;
835         destkey = MSI_RecordGetString(row,5);
836         dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
837         if (!dest_path)
838         {
839             /* try a Property */
840             dest_path = load_dynamic_property(package, destkey, NULL);
841             if (!dest_path)
842             {
843                 FIXME("Unable to get destination folder, try AppSearch properties\n");
844                 HeapFree(GetProcessHeap(),0,file_source);
845                 return ERROR_SUCCESS;
846             }
847         }
848     }
849
850     dest = build_directory_name(2, dest_path, dest_name);
851
852     TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
853                     debugstr_w(dest)); 
854
855     if (strcmpW(file_source,dest))
856         rc = !CopyFileW(file_source,dest,TRUE);
857     else
858         rc = ERROR_SUCCESS;
859
860     if (rc != ERROR_SUCCESS)
861         ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
862
863     FIXME("We should track these duplicate files as well\n");   
864
865     HeapFree(GetProcessHeap(),0,dest_path);
866     HeapFree(GetProcessHeap(),0,dest);
867     HeapFree(GetProcessHeap(),0,file_source);
868
869     return ERROR_SUCCESS;
870 }
871
872 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
873 {
874     UINT rc;
875     MSIQUERY * view;
876     static const WCHAR ExecSeqQuery[] =
877         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
878          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
879
880     if (!package)
881         return ERROR_INVALID_HANDLE;
882
883     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
884     if (rc != ERROR_SUCCESS)
885         return ERROR_SUCCESS;
886
887     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
888     msiobj_release(&view->hdr);
889
890     return rc;
891 }