Fix some gcc 4.0 warnings.
[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         INT index;
211
212         MSIRECORD * uirow;
213         LPWSTR uipath;
214
215         given_file = strdupAtoW(pfdin->psz1);
216         index = get_loaded_file(data->package, given_file);
217
218         if (index < 0)
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 (!((data->package->files[index].State == 1 ||
226                data->package->files[index].State == 2)))
227         {
228             TRACE("Skipping extraction of %s\n",debugstr_w(given_file));
229             HeapFree(GetProcessHeap(),0,given_file);
230             return 0;
231         }
232
233         file = cabinet_alloc((len+1)*sizeof(char));
234         strcpy(file, data->cab_path);
235         strcat(file, pfdin->psz1);
236
237         TRACE("file: %s\n", debugstr_a(file));
238
239         /* track this file so it can be deleted if not installed */
240         trackpath=strdupAtoW(file);
241         tracknametmp=strdupAtoW(strrchr(file,'\\')+1);
242         trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) + 
243                                   strlenW(tmpprefix)+1) * sizeof(WCHAR));
244
245         strcpyW(trackname,tmpprefix);
246         strcatW(trackname,tracknametmp);
247
248         track_tempfile(data->package, trackname, trackpath);
249
250         HeapFree(GetProcessHeap(),0,trackpath);
251         HeapFree(GetProcessHeap(),0,trackname);
252         HeapFree(GetProcessHeap(),0,tracknametmp);
253
254         /* the UI chunk */
255         uirow=MSI_CreateRecord(9);
256         MSI_RecordSetStringW(uirow,1,data->package->files[index].File);
257         uipath = strdupW(data->package->files[index].TargetPath);
258         *(strrchrW(uipath,'\\')+1)=0;
259         MSI_RecordSetStringW(uirow,9,uipath);
260         MSI_RecordSetInteger(uirow,6,data->package->files[index].FileSize);
261         ui_actiondata(data->package,szInstallFiles,uirow);
262         msiobj_release( &uirow->hdr );
263         HeapFree(GetProcessHeap(),0,uipath);
264
265         ui_progress(data->package,2,data->package->files[index].FileSize,0,0);
266
267         return cabinet_open(file, _O_WRONLY | _O_CREAT, 0);
268     }
269     case fdintCLOSE_FILE_INFO:
270     {
271         FILETIME ft;
272             FILETIME ftLocal;
273         if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
274             return -1;
275         if (!LocalFileTimeToFileTime(&ft, &ftLocal))
276             return -1;
277         if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal))
278             return -1;
279
280         cabinet_close(pfdin->hf);
281         return 1;
282     }
283     default:
284         return 0;
285     }
286 }
287
288 /***********************************************************************
289  *            extract_cabinet_file
290  *
291  * Extract files from a cab file.
292  */
293 static BOOL extract_cabinet_file(MSIPACKAGE* package, LPCWSTR source, 
294                                  LPCWSTR path)
295 {
296     HFDI hfdi;
297     ERF erf;
298     BOOL ret;
299     char *cabinet;
300     char *cab_path;
301     CabData data;
302
303     TRACE("Extracting %s to %s\n",debugstr_w(source), debugstr_w(path));
304
305     hfdi = FDICreate(cabinet_alloc,
306                      cabinet_free,
307                      cabinet_open,
308                      cabinet_read,
309                      cabinet_write,
310                      cabinet_close,
311                      cabinet_seek,
312                      0,
313                      &erf);
314     if (!hfdi)
315     {
316         ERR("FDICreate failed\n");
317         return FALSE;
318     }
319
320     if (!(cabinet = strdupWtoA( source )))
321     {
322         FDIDestroy(hfdi);
323         return FALSE;
324     }
325     if (!(cab_path = strdupWtoA( path )))
326     {
327         FDIDestroy(hfdi);
328         HeapFree(GetProcessHeap(), 0, cabinet);
329         return FALSE;
330     }
331
332     data.package = package;
333     data.cab_path = cab_path;
334
335     ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data);
336
337     if (!ret)
338         ERR("FDICopy failed\n");
339
340     FDIDestroy(hfdi);
341
342     HeapFree(GetProcessHeap(), 0, cabinet);
343     HeapFree(GetProcessHeap(), 0, cab_path);
344
345     return ret;
346 }
347
348 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, MSICOMPONENT*
349         comp, LPCWSTR path)
350 {
351     if (file->Attributes & msidbFileAttributesNoncompressed)
352     {
353         LPWSTR p;
354         p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
355         file->SourcePath = build_directory_name(2, p, file->ShortName);
356         HeapFree(GetProcessHeap(),0,p);
357     }
358     else
359         file->SourcePath = build_directory_name(2, path, file->File);
360 }
361
362 static BOOL check_volume(LPCWSTR path, LPCWSTR want_volume, LPWSTR volume, 
363         UINT *intype)
364 {
365     WCHAR drive[4];
366     WCHAR name[MAX_PATH];
367     UINT type;
368
369     if (!(path[0] && path[1] == ':'))
370         return TRUE;
371
372     drive[0] = path[0];
373     drive[1] = path[1];
374     drive[2] = '\\';
375     drive[3] = 0;
376     TRACE("Checking volume %s .. (%s)\n",debugstr_w(drive), debugstr_w(want_volume));
377     type = GetDriveTypeW(drive);
378     TRACE("drive is of type %x\n",type);
379
380     if (type == DRIVE_UNKNOWN || type == DRIVE_NO_ROOT_DIR || 
381             type == DRIVE_FIXED || type == DRIVE_RAMDISK)
382         return TRUE;
383
384     GetVolumeInformationW(drive, name, MAX_PATH, NULL, NULL, NULL, NULL, 0);
385     TRACE("Drive contains %s\n", debugstr_w(name));
386     volume = strdupW(name);
387     if (*intype)
388         *intype=type;
389     return (strcmpiW(want_volume,name)==0);
390 }
391
392 static BOOL check_for_sourcefile(LPCWSTR source)
393 {
394     DWORD attrib = GetFileAttributesW(source);
395     return (!(attrib == INVALID_FILE_ATTRIBUTES));
396 }
397
398 static UINT ready_volume(MSIPACKAGE* package, LPCWSTR path, LPWSTR last_volume, 
399                          MSIRECORD *row,UINT *type )
400 {
401     LPWSTR volume = NULL; 
402     LPCWSTR want_volume = MSI_RecordGetString(row, 5);
403     BOOL ok = check_volume(path, want_volume, volume, type);
404
405     TRACE("Readying Volume for %s (%s, %s)\n",debugstr_w(path), debugstr_w(want_volume), debugstr_w(last_volume));
406
407     if (check_for_sourcefile(path) && !ok)
408     {
409         FIXME("Found the Sourcefile but not on the correct volume.(%s,%s,%s)\n",
410                 debugstr_w(path),debugstr_w(want_volume), debugstr_w(volume));
411         return ERROR_SUCCESS;
412     }
413
414     while (!ok)
415     {
416         INT rc;
417         LPCWSTR prompt;
418         LPWSTR msg;
419       
420         prompt = MSI_RecordGetString(row,3);
421         msg = generate_error_string(package, 1302, 1, prompt);
422         rc = MessageBoxW(NULL,msg,NULL,MB_OKCANCEL);
423         HeapFree(GetProcessHeap(),0,volume);
424         HeapFree(GetProcessHeap(),0,msg);
425         if (rc == IDOK)
426             ok = check_for_sourcefile(path);
427         else
428             return ERROR_INSTALL_USEREXIT;
429     }
430
431     HeapFree(GetProcessHeap(),0,last_volume);
432     last_volume = strdupW(volume);
433     return ERROR_SUCCESS;
434 }
435
436 static UINT ready_media_for_file(MSIPACKAGE *package, int fileindex, 
437                                  MSICOMPONENT* comp)
438 {
439     UINT rc = ERROR_SUCCESS;
440     MSIRECORD * row = 0;
441     static WCHAR source[MAX_PATH];
442     static const WCHAR ExecSeqQuery[] =
443         {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
444          '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
445          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
446          ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
447          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
448     LPCWSTR cab, volume;
449     DWORD sz;
450     INT seq;
451     static UINT last_sequence = 0; 
452     static LPWSTR last_volume = NULL;
453     static LPWSTR last_path = NULL;
454     MSIFILE* file = NULL;
455     UINT type;
456     LPCWSTR prompt;
457     static DWORD count = 0;
458
459     /* cleanup signal */
460     if (!package)
461     {
462         HeapFree(GetProcessHeap(),0,last_path);
463         HeapFree(GetProcessHeap(),0,last_volume);
464         last_sequence = 0;
465         last_path = NULL;
466         last_volume = NULL;
467         count = 0;
468         memset(source,0,sizeof(source));
469         return ERROR_SUCCESS;
470     }
471
472     file = &package->files[fileindex];
473
474     if (file->Sequence <= last_sequence)
475     {
476         set_file_source(package,file,comp,last_path);
477         TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
478         return ERROR_SUCCESS;
479     }
480
481     count ++;
482     row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
483     if (!row)
484     {
485         TRACE("Unable to query row\n");
486         return ERROR_FUNCTION_FAILED;
487     }
488
489     seq = MSI_RecordGetInteger(row,2);
490     last_sequence = seq;
491
492     volume = MSI_RecordGetString(row, 5);
493     prompt = MSI_RecordGetString(row, 3);
494
495     HeapFree(GetProcessHeap(),0,last_path);
496     last_path = NULL;
497
498     if (file->Attributes & msidbFileAttributesNoncompressed)
499     {
500         last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
501         set_file_source(package,file,comp,last_path);
502         rc = ready_volume(package, file->SourcePath, last_volume, row,&type);
503
504         MsiSourceListAddMediaDiskW(package->ProductCode, NULL, 
505             MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
506             prompt);
507
508         if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
509                 type == DRIVE_RAMDISK)
510             MsiSourceListSetInfoW(package->ProductCode, NULL, 
511                 MSIINSTALLCONTEXT_USERMANAGED, 
512                 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
513                 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
514         else
515             MsiSourceListSetInfoW(package->ProductCode, NULL, 
516                 MSIINSTALLCONTEXT_USERMANAGED, 
517                 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
518                 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
519         msiobj_release(&row->hdr);
520         return rc;
521     }
522
523     cab = MSI_RecordGetString(row,4);
524     if (cab)
525     {
526         TRACE("Source is CAB %s\n",debugstr_w(cab));
527         /* the stream does not contain the # character */
528         if (cab[0]=='#')
529         {
530             LPWSTR path;
531
532             writeout_cabinet_stream(package,&cab[1],source);
533             last_path = strdupW(source);
534             *(strrchrW(last_path,'\\')+1)=0;
535
536             path = load_dynamic_property(package,cszSourceDir,NULL);
537
538             MsiSourceListAddMediaDiskW(package->ProductCode, NULL, 
539                 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count,
540                 volume, prompt);
541
542             MsiSourceListSetInfoW(package->ProductCode, NULL,
543                 MSIINSTALLCONTEXT_USERMANAGED,
544                 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
545                 INSTALLPROPERTY_LASTUSEDSOURCEW, path);
546
547             HeapFree(GetProcessHeap(),0,path);
548         }
549         else
550         {
551             sz = MAX_PATH;
552             last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
553             if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
554             {
555                 ERR("No Source dir defined \n");
556                 rc = ERROR_FUNCTION_FAILED;
557             }
558             else
559             {
560                 strcpyW(last_path,source);
561                 strcatW(source,cab);
562
563                 rc = ready_volume(package, source, last_volume, row, &type);
564                 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
565                         type == DRIVE_RAMDISK)
566                     MsiSourceListSetInfoW(package->ProductCode, NULL,
567                             MSIINSTALLCONTEXT_USERMANAGED,
568                             MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
569                             INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
570                 else
571                     MsiSourceListSetInfoW(package->ProductCode, NULL,
572                             MSIINSTALLCONTEXT_USERMANAGED,
573                             MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
574                             INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
575
576                 /* extract the cab file into a folder in the temp folder */
577                 sz = MAX_PATH;
578                 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz) 
579                                     != ERROR_SUCCESS)
580                     GetTempPathW(MAX_PATH,last_path);
581             }
582         }
583         rc = !extract_cabinet_file(package, source, last_path);
584         /* reaquire file ptr */
585         file = &package->files[fileindex];
586     }
587     else
588     {
589         sz = MAX_PATH;
590         last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
591         MSI_GetPropertyW(package,cszSourceDir,source,&sz);
592         strcpyW(last_path,source);
593         rc = ready_volume(package, last_path, last_volume, row, &type);
594
595         if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM || 
596                 type == DRIVE_RAMDISK)
597             MsiSourceListSetInfoW(package->ProductCode, NULL,
598                     MSIINSTALLCONTEXT_USERMANAGED,
599                     MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
600                     INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
601         else
602             MsiSourceListSetInfoW(package->ProductCode, NULL,
603                     MSIINSTALLCONTEXT_USERMANAGED,
604                     MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
605                     INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
606     }
607     set_file_source(package, file, comp, last_path);
608
609     MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
610             MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
611             prompt);
612
613     msiobj_release(&row->hdr);
614
615     return rc;
616 }
617
618 inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
619                                    LPWSTR* file_source)
620 {
621     DWORD index;
622
623     if (!package)
624         return ERROR_INVALID_HANDLE;
625
626     for (index = 0; index < package->loaded_files; index ++)
627     {
628         if (strcmpW(file_key,package->files[index].File)==0)
629         {
630             if (package->files[index].State >= 2)
631             {
632                 *file_source = strdupW(package->files[index].TargetPath);
633                 return ERROR_SUCCESS;
634             }
635             else
636                 return ERROR_FILE_NOT_FOUND;
637         }
638     }
639
640     return ERROR_FUNCTION_FAILED;
641 }
642
643 /*
644  * In order to make this work more effeciencly I am going to do this in 2
645  * passes.
646  * Pass 1) Correct all the TargetPaths and determin what files are to be
647  * installed.
648  * Pass 2) Extract Cabinents and copy files.
649  */
650 UINT ACTION_InstallFiles(MSIPACKAGE *package)
651 {
652     UINT rc = ERROR_SUCCESS;
653     DWORD index;
654     LPWSTR ptr;
655
656     if (!package)
657         return ERROR_INVALID_HANDLE;
658
659     /* increment progress bar each time action data is sent */
660     ui_progress(package,1,1,0,0);
661
662     /* handle the keys for the SouceList */
663     ptr = strrchrW(package->PackagePath,'\\');
664     if (ptr)
665     {
666         ptr ++;
667         MsiSourceListSetInfoW(package->ProductCode, NULL,
668                 MSIINSTALLCONTEXT_USERMANAGED,
669                 MSICODE_PRODUCT,
670                 INSTALLPROPERTY_PACKAGENAMEW, ptr);
671     }
672     FIXME("Write DiskPrompt\n");
673     
674     /* Pass 1 */
675     for (index = 0; index < package->loaded_files; index++)
676     {
677         MSIFILE *file;
678         MSICOMPONENT* comp = NULL;
679
680         file = &package->files[index];
681
682         if (file->Temporary)
683             continue;
684
685         if (!ACTION_VerifyComponentForAction(package, file->Component, 
686                                        INSTALLSTATE_LOCAL))
687         {
688             ui_progress(package,2,file->FileSize,0,0);
689             TRACE("File %s is not scheduled for install\n",
690                    debugstr_w(file->File));
691
692             file->State = 5;
693             continue;
694         }
695
696         if ((file->State == 1) || (file->State == 2))
697         {
698             LPWSTR p = NULL;
699
700             TRACE("Pass 1: %s\n",debugstr_w(file->File));
701
702             create_component_directory( package, file->Component );
703
704             /* recalculate file paths because things may have changed */
705
706             comp = file->Component;
707             if (!comp)
708             {
709                 ERR("No Component for file\n");
710                 continue;
711             }
712
713             p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
714             HeapFree(GetProcessHeap(),0,file->TargetPath);
715
716             file->TargetPath = build_directory_name(2, p, file->FileName);
717             HeapFree(GetProcessHeap(),0,p);
718         }
719     }
720
721     /* Pass 2 */
722     for (index = 0; index < package->loaded_files; index++)
723     {
724         MSIFILE *file;
725
726         file = &package->files[index];
727
728         if (file->Temporary)
729             continue;
730
731         if ((file->State == 1) || (file->State == 2))
732         {
733             TRACE("Pass 2: %s\n",debugstr_w(file->File));
734
735             rc = ready_media_for_file( package, index, file->Component );
736             if (rc != ERROR_SUCCESS)
737             {
738                 ERR("Unable to ready media\n");
739                 rc = ERROR_FUNCTION_FAILED;
740                 break;
741             }
742
743             /*
744              * WARNING!
745              * our file table could change here because a new temp file
746              * may have been created. So reaquire our ptr.
747              */
748             file = &package->files[index];
749
750             TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
751                   debugstr_w(file->TargetPath));
752
753             if (file->Attributes & msidbFileAttributesNoncompressed)
754                 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
755             else
756                 rc = MoveFileW(file->SourcePath, file->TargetPath);
757
758             if (!rc)
759             {
760                 rc = GetLastError();
761                 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
762                      debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
763                       rc);
764                 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
765                 {
766                     if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
767                         ERR("Unable to copy file (%s -> %s) (error %ld)\n",
768                             debugstr_w(file->SourcePath), 
769                             debugstr_w(file->TargetPath), GetLastError());
770                     if (!(file->Attributes & msidbFileAttributesNoncompressed))
771                         DeleteFileW(file->SourcePath);
772                     rc = 0;
773                 }
774                 else if (rc == ERROR_FILE_NOT_FOUND)
775                 {
776                     ERR("Source File Not Found!  Continuing\n");
777                     rc = 0;
778                 }
779                 else if (file->Attributes & msidbFileAttributesVital)
780                 {
781                     ERR("Ignoring Error and continuing (nonvital file)...\n");
782                     rc = 0;
783                 }
784             }
785             else
786             {
787                 file->State = 4;
788                 rc = ERROR_SUCCESS;
789             }
790         }
791     }
792
793     /* cleanup */
794     ready_media_for_file(NULL, 0, NULL);
795     return rc;
796 }
797
798 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
799 {
800     MSIPACKAGE *package = (MSIPACKAGE*)param;
801     WCHAR *file_source = NULL;
802     WCHAR dest_name[0x100];
803     LPWSTR dest_path, dest;
804     LPCWSTR file_key, component;
805     DWORD sz;
806     DWORD rc;
807     MSICOMPONENT *comp;
808
809     component = MSI_RecordGetString(row,2);
810     comp = get_loaded_component(package,component);
811
812     if (!ACTION_VerifyComponentForAction(package, comp, INSTALLSTATE_LOCAL))
813     {
814         TRACE("Skipping copy due to disabled component %s\n",
815                         debugstr_w(component));
816
817         /* the action taken was the same as the current install state */        
818         comp->Action = comp->Installed;
819
820         return ERROR_SUCCESS;
821     }
822
823     comp->Action = INSTALLSTATE_LOCAL;
824
825     file_key = MSI_RecordGetString(row,3);
826     if (!file_key)
827     {
828         ERR("Unable to get file key\n");
829         return ERROR_FUNCTION_FAILED;
830     }
831
832     rc = get_file_target(package,file_key,&file_source);
833
834     if (rc != ERROR_SUCCESS)
835     {
836         ERR("Original file unknown %s\n",debugstr_w(file_key));
837         HeapFree(GetProcessHeap(),0,file_source);
838         return ERROR_SUCCESS;
839     }
840
841     if (MSI_RecordIsNull(row,4))
842         strcpyW(dest_name,strrchrW(file_source,'\\')+1);
843     else
844     {
845         sz=0x100;
846         MSI_RecordGetStringW(row,4,dest_name,&sz);
847         reduce_to_longfilename(dest_name);
848     }
849
850     if (MSI_RecordIsNull(row,5))
851     {
852         LPWSTR p;
853         dest_path = strdupW(file_source);
854         p = strrchrW(dest_path,'\\');
855         if (p)
856             *p=0;
857     }
858     else
859     {
860         LPCWSTR destkey;
861         destkey = MSI_RecordGetString(row,5);
862         dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
863         if (!dest_path)
864         {
865             /* try a Property */
866             dest_path = load_dynamic_property(package, destkey, NULL);
867             if (!dest_path)
868             {
869                 FIXME("Unable to get destination folder, try AppSearch properties\n");
870                 HeapFree(GetProcessHeap(),0,file_source);
871                 return ERROR_SUCCESS;
872             }
873         }
874     }
875
876     dest = build_directory_name(2, dest_path, dest_name);
877
878     TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
879                     debugstr_w(dest)); 
880
881     if (strcmpW(file_source,dest))
882         rc = !CopyFileW(file_source,dest,TRUE);
883     else
884         rc = ERROR_SUCCESS;
885
886     if (rc != ERROR_SUCCESS)
887         ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
888
889     FIXME("We should track these duplicate files as well\n");   
890
891     HeapFree(GetProcessHeap(),0,dest_path);
892     HeapFree(GetProcessHeap(),0,dest);
893     HeapFree(GetProcessHeap(),0,file_source);
894
895     return ERROR_SUCCESS;
896 }
897
898 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
899 {
900     UINT rc;
901     MSIQUERY * view;
902     static const WCHAR ExecSeqQuery[] =
903         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
904          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
905
906     if (!package)
907         return ERROR_INVALID_HANDLE;
908
909     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
910     if (rc != ERROR_SUCCESS)
911         return ERROR_SUCCESS;
912
913     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
914     msiobj_release(&view->hdr);
915
916     return rc;
917 }