Fix gcc 4.0 -Wpointer-sign 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 "msidefs.h"
41 #include "msvcrt/fcntl.h"
42 #include "msipriv.h"
43 #include "winuser.h"
44 #include "wine/unicode.h"
45 #include "action.h"
46
47 WINE_DEFAULT_DEBUG_CHANNEL(msi);
48
49 extern const WCHAR szInstallFiles[];
50 extern const WCHAR szDuplicateFiles[];
51 extern const WCHAR szMoveFiles[];
52 extern const WCHAR szPatchFiles[];
53 extern const WCHAR szRemoveDuplicateFiles[];
54 extern const WCHAR szRemoveFiles[];
55
56 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
57
58 inline static UINT create_component_directory ( MSIPACKAGE* package, INT component)
59 {
60     UINT rc = ERROR_SUCCESS;
61     MSIFOLDER *folder;
62     LPWSTR install_path;
63
64     install_path = resolve_folder(package, package->components[component].Directory,
65                         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 {
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     return (strcmpiW(want_volume,name)==0);
387 }
388
389 static BOOL check_for_sourcefile(LPCWSTR source)
390 {
391     DWORD attrib = GetFileAttributesW(source);
392     return (!(attrib == INVALID_FILE_ATTRIBUTES));
393 }
394
395 static UINT ready_volume(MSIPACKAGE* package, LPCWSTR path, LPWSTR last_volume, 
396                          MSIRECORD *row)
397 {
398     LPWSTR volume = NULL; 
399     LPCWSTR want_volume = MSI_RecordGetString(row, 5);
400     BOOL ok = check_volume(path, want_volume, volume);
401
402     TRACE("Readying Volume for %s (%s, %s)\n",debugstr_w(path), debugstr_w(want_volume), debugstr_w(last_volume));
403
404     if (check_for_sourcefile(path) && !ok)
405     {
406         FIXME("Found the Sourcefile but not on the correct volume.(%s,%s,%s)\n",
407                 debugstr_w(path),debugstr_w(want_volume), debugstr_w(volume));
408         return ERROR_SUCCESS;
409     }
410
411     while (!ok)
412     {
413         INT rc;
414         LPCWSTR prompt;
415         LPWSTR msg;
416       
417         prompt = MSI_RecordGetString(row,3);
418         msg = generate_error_string(package, 1302, 1, prompt);
419         rc = MessageBoxW(NULL,msg,NULL,MB_OKCANCEL);
420         HeapFree(GetProcessHeap(),0,volume);
421         HeapFree(GetProcessHeap(),0,msg);
422         if (rc == IDOK)
423             ok = check_for_sourcefile(path);
424         else
425             return ERROR_INSTALL_USEREXIT;
426     }
427
428     HeapFree(GetProcessHeap(),0,last_volume);
429     last_volume = strdupW(volume);
430     return ERROR_SUCCESS;
431 }
432
433 static UINT ready_media_for_file(MSIPACKAGE *package, int fileindex, 
434                                  MSICOMPONENT* comp)
435 {
436     UINT rc = ERROR_SUCCESS;
437     MSIRECORD * row = 0;
438     static WCHAR source[MAX_PATH];
439     static const WCHAR ExecSeqQuery[] =
440         {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
441          '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
442          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
443          ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
444          '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
445     LPCWSTR cab, volume;
446     DWORD sz;
447     INT seq;
448     static UINT last_sequence = 0; 
449     static LPWSTR last_volume = NULL;
450     static LPWSTR last_path = NULL;
451     MSIFILE* file = NULL;
452
453     /* cleanup signal */
454     if (!package)
455     {
456         HeapFree(GetProcessHeap(),0,last_path);
457         HeapFree(GetProcessHeap(),0,last_volume);
458         return ERROR_SUCCESS;
459     }
460
461     file = &package->files[fileindex];
462
463     if (file->Sequence <= last_sequence)
464     {
465         set_file_source(package,file,comp,last_path);
466         TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
467         return ERROR_SUCCESS;
468     }
469
470     row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
471     if (!row)
472     {
473         TRACE("Unable to query row\n");
474         return ERROR_FUNCTION_FAILED;
475     }
476
477     seq = MSI_RecordGetInteger(row,2);
478     last_sequence = seq;
479
480     volume = MSI_RecordGetString(row, 5);
481
482     HeapFree(GetProcessHeap(),0,last_path);
483     last_path = NULL;
484
485     if (file->Attributes & msidbFileAttributesNoncompressed)
486     {
487         last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
488         set_file_source(package,file,comp,last_path);
489         rc = ready_volume(package, file->SourcePath, last_volume, row);
490         msiobj_release(&row->hdr);
491         return rc;
492     }
493
494     cab = MSI_RecordGetString(row,4);
495     if (cab)
496     {
497         TRACE("Source is CAB %s\n",debugstr_w(cab));
498         /* the stream does not contain the # character */
499         if (cab[0]=='#')
500         {
501             writeout_cabinet_stream(package,&cab[1],source);
502             last_path = strdupW(source);
503             *(strrchrW(last_path,'\\')+1)=0;
504         }
505         else
506         {
507             sz = MAX_PATH;
508             last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
509             if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
510             {
511                 ERR("No Source dir defined \n");
512                 rc = ERROR_FUNCTION_FAILED;
513             }
514             else
515             {
516                 strcpyW(last_path,source);
517                 strcatW(source,cab);
518                 /* extract the cab file into a folder in the temp folder */
519                 sz = MAX_PATH;
520                 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz) 
521                                     != ERROR_SUCCESS)
522                     GetTempPathW(MAX_PATH,last_path);
523                 rc = ready_volume(package, source, last_volume, row);
524             }
525         }
526         rc = !extract_cabinet_file(package, source, last_path);
527         /* reaquire file ptr */
528         file = &package->files[fileindex];
529     }
530     else
531     {
532         sz = MAX_PATH;
533         last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
534         MSI_GetPropertyW(package,cszSourceDir,source,&sz);
535         strcpyW(last_path,source);
536         rc = ready_volume(package, last_path, last_volume, row);
537     }
538     set_file_source(package, file, comp, last_path);
539     msiobj_release(&row->hdr);
540
541     return rc;
542 }
543
544 inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
545                                    LPWSTR* file_source)
546 {
547     DWORD index;
548
549     if (!package)
550         return ERROR_INVALID_HANDLE;
551
552     for (index = 0; index < package->loaded_files; index ++)
553     {
554         if (strcmpW(file_key,package->files[index].File)==0)
555         {
556             if (package->files[index].State >= 2)
557             {
558                 *file_source = strdupW(package->files[index].TargetPath);
559                 return ERROR_SUCCESS;
560             }
561             else
562                 return ERROR_FILE_NOT_FOUND;
563         }
564     }
565
566     return ERROR_FUNCTION_FAILED;
567 }
568
569 /*
570  * In order to make this work more effeciencly I am going to do this in 2
571  * passes.
572  * Pass 1) Correct all the TargetPaths and determin what files are to be
573  * installed.
574  * Pass 2) Extract Cabinents and copy files.
575  */
576 UINT ACTION_InstallFiles(MSIPACKAGE *package)
577 {
578     UINT rc = ERROR_SUCCESS;
579     DWORD index;
580
581     if (!package)
582         return ERROR_INVALID_HANDLE;
583
584     /* increment progress bar each time action data is sent */
585     ui_progress(package,1,1,0,0);
586
587     /* Pass 1 */
588     for (index = 0; index < package->loaded_files; index++)
589     {
590         MSIFILE *file;
591         MSICOMPONENT* comp = NULL;
592
593         file = &package->files[index];
594
595         if (file->Temporary)
596             continue;
597
598         if (!ACTION_VerifyComponentForAction(package, file->ComponentIndex, 
599                                        INSTALLSTATE_LOCAL))
600         {
601             ui_progress(package,2,file->FileSize,0,0);
602             TRACE("File %s is not scheduled for install\n",
603                    debugstr_w(file->File));
604
605             file->State = 5;
606             continue;
607         }
608
609         if ((file->State == 1) || (file->State == 2))
610         {
611             LPWSTR p = NULL;
612
613             TRACE("Pass 1: %s\n",debugstr_w(file->File));
614
615             create_component_directory( package, file->ComponentIndex);
616
617             /* recalculate file paths because things may have changed */
618
619             if (file->ComponentIndex >= 0)
620                 comp = &package->components[file->ComponentIndex];
621             else
622             {
623                 ERR("No Component for file\n");
624                 continue;
625             }
626
627             p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
628             HeapFree(GetProcessHeap(),0,file->TargetPath);
629
630             file->TargetPath = build_directory_name(2, p, file->FileName);
631             HeapFree(GetProcessHeap(),0,p);
632         }
633     }
634
635     /* Pass 2 */
636     for (index = 0; index < package->loaded_files; index++)
637     {
638         MSIFILE *file;
639         MSICOMPONENT* comp = NULL;
640
641         file = &package->files[index];
642
643         if (file->Temporary)
644             continue;
645
646         if ((file->State == 1) || (file->State == 2))
647         {
648             TRACE("Pass 2: %s\n",debugstr_w(file->File));
649
650             if (file->ComponentIndex >= 0)
651                 comp = &package->components[file->ComponentIndex];
652
653             rc = ready_media_for_file(package, index, comp);
654             if (rc != ERROR_SUCCESS)
655             {
656                 ERR("Unable to ready media\n");
657                 rc = ERROR_FUNCTION_FAILED;
658                 break;
659             }
660
661             /*
662              * WARNING!
663              * our file table could change here because a new temp file
664              * may have been created. So reaquire our ptr.
665              */
666             file = &package->files[index];
667
668             TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
669                   debugstr_w(file->TargetPath));
670
671             if (file->Attributes & msidbFileAttributesNoncompressed)
672                 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
673             else
674                 rc = MoveFileW(file->SourcePath, file->TargetPath);
675
676             if (!rc)
677             {
678                 rc = GetLastError();
679                 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
680                      debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
681                       rc);
682                 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
683                 {
684                     if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
685                         ERR("Unable to copy file (%s -> %s) (error %ld)\n",
686                             debugstr_w(file->SourcePath), 
687                             debugstr_w(file->TargetPath), GetLastError());
688                     if (!(file->Attributes & msidbFileAttributesNoncompressed))
689                         DeleteFileW(file->SourcePath);
690                     rc = 0;
691                 }
692                 else if (rc == ERROR_FILE_NOT_FOUND)
693                 {
694                     ERR("Source File Not Found!  Continuing\n");
695                     rc = 0;
696                 }
697                 else if (file->Attributes & msidbFileAttributesVital)
698                 {
699                     ERR("Ignoring Error and continuing (nonvital file)...\n");
700                     rc = 0;
701                 }
702             }
703             else
704             {
705                 file->State = 4;
706                 rc = ERROR_SUCCESS;
707             }
708         }
709     }
710
711     /* cleanup */
712     ready_media_for_file(NULL, 0, NULL);
713     return rc;
714 }
715
716 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
717 {
718     MSIPACKAGE *package = (MSIPACKAGE*)param;
719     WCHAR *file_source = NULL;
720     WCHAR dest_name[0x100];
721     LPWSTR dest_path, dest;
722     LPCWSTR file_key, component;
723     INT component_index;
724     DWORD sz;
725     DWORD rc;
726
727     component = MSI_RecordGetString(row,2);
728     component_index = get_loaded_component(package,component);
729
730     if (!ACTION_VerifyComponentForAction(package, component_index,
731                             INSTALLSTATE_LOCAL))
732     {
733         TRACE("Skipping copy due to disabled component %s\n",
734                         debugstr_w(component));
735
736         /* the action taken was the same as the current install state */        
737         package->components[component_index].Action =
738                 package->components[component_index].Installed;
739
740         return ERROR_SUCCESS;
741     }
742
743     package->components[component_index].Action = INSTALLSTATE_LOCAL;
744
745     file_key = MSI_RecordGetString(row,3);
746     if (!file_key)
747     {
748         ERR("Unable to get file key\n");
749         return ERROR_FUNCTION_FAILED;
750     }
751
752     rc = get_file_target(package,file_key,&file_source);
753
754     if (rc != ERROR_SUCCESS)
755     {
756         ERR("Original file unknown %s\n",debugstr_w(file_key));
757         HeapFree(GetProcessHeap(),0,file_source);
758         return ERROR_SUCCESS;
759     }
760
761     if (MSI_RecordIsNull(row,4))
762         strcpyW(dest_name,strrchrW(file_source,'\\')+1);
763     else
764     {
765         sz=0x100;
766         MSI_RecordGetStringW(row,4,dest_name,&sz);
767         reduce_to_longfilename(dest_name);
768     }
769
770     if (MSI_RecordIsNull(row,5))
771     {
772         LPWSTR p;
773         dest_path = strdupW(file_source);
774         p = strrchrW(dest_path,'\\');
775         if (p)
776             *p=0;
777     }
778     else
779     {
780         LPCWSTR destkey;
781         destkey = MSI_RecordGetString(row,5);
782         dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
783         if (!dest_path)
784         {
785             /* try a Property */
786             dest_path = load_dynamic_property(package, destkey, NULL);
787             if (!dest_path)
788             {
789                 FIXME("Unable to get destination folder, try AppSearch properties\n");
790                 HeapFree(GetProcessHeap(),0,file_source);
791                 return ERROR_SUCCESS;
792             }
793         }
794     }
795
796     dest = build_directory_name(2, dest_path, dest_name);
797
798     TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
799                     debugstr_w(dest)); 
800
801     if (strcmpW(file_source,dest))
802         rc = !CopyFileW(file_source,dest,TRUE);
803     else
804         rc = ERROR_SUCCESS;
805
806     if (rc != ERROR_SUCCESS)
807         ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
808
809     FIXME("We should track these duplicate files as well\n");   
810
811     HeapFree(GetProcessHeap(),0,dest_path);
812     HeapFree(GetProcessHeap(),0,dest);
813     HeapFree(GetProcessHeap(),0,file_source);
814
815     return ERROR_SUCCESS;
816 }
817
818 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
819 {
820     UINT rc;
821     MSIQUERY * view;
822     static const WCHAR ExecSeqQuery[] =
823         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
824          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
825
826     if (!package)
827         return ERROR_INVALID_HANDLE;
828
829     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
830     if (rc != ERROR_SUCCESS)
831         return ERROR_SUCCESS;
832
833     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
834     msiobj_release(&view->hdr);
835
836     return rc;
837 }