msi: Remove track_tempfile()'s unused 2nd parameter.
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 "winreg.h"
46 #include "shlwapi.h"
47 #include "wine/unicode.h"
48
49 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50
51 extern const WCHAR szInstallFiles[];
52 extern const WCHAR szDuplicateFiles[];
53 extern const WCHAR szMoveFiles[];
54 extern const WCHAR szPatchFiles[];
55 extern const WCHAR szRemoveDuplicateFiles[];
56 extern const WCHAR szRemoveFiles[];
57
58 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
59
60 struct media_info {
61     UINT disk_id;
62     UINT last_sequence;
63     LPWSTR disk_prompt;
64     LPWSTR cabinet;
65     LPWSTR volume_label;
66     BOOL is_continuous;
67     WCHAR source[MAX_PATH];
68 };
69
70 static UINT msi_change_media( MSIPACKAGE *package, struct media_info *mi )
71 {
72     LPSTR msg;
73     LPWSTR error, error_dialog;
74     UINT r = ERROR_SUCCESS;
75
76     static const WCHAR szUILevel[] = {'U','I','L','e','v','e','l',0};
77     static const WCHAR error_prop[] = {'E','r','r','o','r','D','i','a','l','o','g',0};
78
79     if ( msi_get_property_int(package, szUILevel, 0) == INSTALLUILEVEL_NONE && !gUIHandlerA )
80         return ERROR_SUCCESS;
81
82     error = generate_error_string( package, 1302, 1, mi->disk_prompt );
83     error_dialog = msi_dup_property( package, error_prop );
84
85     while ( r == ERROR_SUCCESS && GetFileAttributesW( mi->source ) == INVALID_FILE_ATTRIBUTES )
86     {
87         r = msi_spawn_error_dialog( package, error_dialog, error );
88
89         if (gUIHandlerA)
90         {
91             msg = strdupWtoA( error );
92             gUIHandlerA( gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, msg );
93             msi_free(msg);
94         }
95     }
96
97     msi_free( error );
98     msi_free( error_dialog );
99
100     return r;
101 }
102
103 /*
104  * This is a helper function for handling embedded cabinet media
105  */
106 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
107                                     WCHAR* source)
108 {
109     UINT rc;
110     USHORT* data;
111     UINT    size;
112     DWORD   write;
113     HANDLE  the_file;
114     WCHAR tmp[MAX_PATH];
115
116     rc = read_raw_stream_data(package->db,stream_name,&data,&size); 
117     if (rc != ERROR_SUCCESS)
118         return rc;
119
120     write = MAX_PATH;
121     if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
122         GetTempPathW(MAX_PATH,tmp);
123
124     GetTempFileNameW(tmp,stream_name,0,source);
125
126     track_tempfile(package, source);
127     the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
128                            FILE_ATTRIBUTE_NORMAL, NULL);
129
130     if (the_file == INVALID_HANDLE_VALUE)
131     {
132         ERR("Unable to create file %s\n",debugstr_w(source));
133         rc = ERROR_FUNCTION_FAILED;
134         goto end;
135     }
136
137     WriteFile(the_file,data,size,&write,NULL);
138     CloseHandle(the_file);
139     TRACE("wrote %i bytes to %s\n",write,debugstr_w(source));
140 end:
141     msi_free(data);
142     return rc;
143 }
144
145
146 /* Support functions for FDI functions */
147 typedef struct
148 {
149     MSIPACKAGE* package;
150     struct media_info *mi;
151 } CabData;
152
153 static void * cabinet_alloc(ULONG cb)
154 {
155     return msi_alloc(cb);
156 }
157
158 static void cabinet_free(void *pv)
159 {
160     msi_free(pv);
161 }
162
163 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
164 {
165     HANDLE handle;
166     DWORD dwAccess = 0;
167     DWORD dwShareMode = 0;
168     DWORD dwCreateDisposition = OPEN_EXISTING;
169     switch (oflag & _O_ACCMODE)
170     {
171     case _O_RDONLY:
172         dwAccess = GENERIC_READ;
173         dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
174         break;
175     case _O_WRONLY:
176         dwAccess = GENERIC_WRITE;
177         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
178         break;
179     case _O_RDWR:
180         dwAccess = GENERIC_READ | GENERIC_WRITE;
181         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
182         break;
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     handle = CreateFileA( pszFile, dwAccess, dwShareMode, NULL, 
189                           dwCreateDisposition, 0, NULL );
190     if (handle == INVALID_HANDLE_VALUE)
191         return 0;
192     return (INT_PTR) handle;
193 }
194
195 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
196 {
197     HANDLE handle = (HANDLE) hf;
198     DWORD dwRead;
199     if (ReadFile(handle, pv, cb, &dwRead, NULL))
200         return dwRead;
201     return 0;
202 }
203
204 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
205 {
206     HANDLE handle = (HANDLE) hf;
207     DWORD dwWritten;
208     if (WriteFile(handle, pv, cb, &dwWritten, NULL))
209         return dwWritten;
210     return 0;
211 }
212
213 static int cabinet_close(INT_PTR hf)
214 {
215     HANDLE handle = (HANDLE) hf;
216     return CloseHandle(handle) ? 0 : -1;
217 }
218
219 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
220 {
221     HANDLE handle = (HANDLE) hf;
222     /* flags are compatible and so are passed straight through */
223     return SetFilePointer(handle, dist, NULL, seektype);
224 }
225
226 static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
227 {
228     MSIRECORD *uirow;
229     LPWSTR uipath, p;
230
231     /* the UI chunk */
232     uirow = MSI_CreateRecord( 9 );
233     MSI_RecordSetStringW( uirow, 1, f->FileName );
234     uipath = strdupW( f->TargetPath );
235     p = strrchrW(uipath,'\\');
236     if (p)
237         p[1]=0;
238     MSI_RecordSetStringW( uirow, 9, uipath);
239     MSI_RecordSetInteger( uirow, 6, f->FileSize );
240     ui_actiondata( package, action, uirow);
241     msiobj_release( &uirow->hdr );
242     msi_free( uipath );
243     ui_progress( package, 2, f->FileSize, 0, 0);
244 }
245
246 static UINT msi_media_get_disk_info( CabData *data )
247 {
248     MSIPACKAGE *package = data->package;
249     MSIRECORD *row;
250     LPWSTR ptr;
251
252     static const WCHAR query[] =
253         {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
254          '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
255          '`','D','i','s','k','I','d','`',' ','=',' ','%','i',0};
256
257     row = MSI_QueryGetRecord(package->db, query, data->mi->disk_id);
258     if (!row)
259     {
260         TRACE("Unable to query row\n");
261         return ERROR_FUNCTION_FAILED;
262     }
263
264     data->mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
265     data->mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
266
267     ptr = strrchrW(data->mi->source, '\\') + 1;
268     lstrcpyW(ptr, data->mi->cabinet);
269
270     return ERROR_SUCCESS;
271 }
272
273 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
274 {
275     TRACE("(%d)\n", fdint);
276
277     switch (fdint)
278     {
279     case fdintPARTIAL_FILE:
280     {
281         CabData *data = (CabData *)pfdin->pv;
282         data->mi->is_continuous = FALSE;
283         return 0;
284     }
285     case fdintNEXT_CABINET:
286     {
287         CabData *data = (CabData *)pfdin->pv;
288         struct media_info *mi = data->mi;
289         LPWSTR cab = strdupAtoW(pfdin->psz1);
290         UINT rc;
291
292         msi_free(mi->disk_prompt);
293
294         mi->disk_id++;
295         mi->is_continuous = TRUE;
296
297         rc = msi_media_get_disk_info(data);
298         if (rc != ERROR_SUCCESS)
299         {
300             ERR("Failed to get next cabinet information: %d\n", rc);
301             return -1;
302         }
303
304         if (lstrcmpiW(mi->cabinet, cab))
305         {
306             msi_free(cab);
307             ERR("Continuous cabinet does not match the next cabinet in the Media table\n");
308             return -1;
309         }
310
311         msi_free(cab);
312
313         TRACE("Searching for %s\n", debugstr_w(mi->source));
314
315         if (GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
316             rc = msi_change_media(data->package, mi);
317
318         if (rc != ERROR_SUCCESS)
319             return -1;
320
321         return 0;
322     }
323     case fdintCOPY_FILE:
324     {
325         CabData *data = (CabData*) pfdin->pv;
326         HANDLE handle;
327         LPWSTR file;
328         MSIFILE *f;
329         DWORD attrs;
330
331         file = strdupAtoW(pfdin->psz1);
332         f = get_loaded_file(data->package, file);
333         msi_free(file);
334
335         if (!f)
336         {
337             WARN("unknown file in cabinet (%s)\n",debugstr_a(pfdin->psz1));
338             return 0;
339         }
340
341         if (f->state != msifs_missing && f->state != msifs_overwrite)
342         {
343             TRACE("Skipping extraction of %s\n",debugstr_a(pfdin->psz1));
344             return 0;
345         }
346
347         msi_file_update_ui( data->package, f, szInstallFiles );
348
349         TRACE("extracting %s\n", debugstr_w(f->TargetPath) );
350
351         attrs = f->Attributes & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
352         if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL;
353
354         handle = CreateFileW( f->TargetPath, GENERIC_READ | GENERIC_WRITE, 0,
355                               NULL, CREATE_ALWAYS, attrs, NULL );
356         if ( handle == INVALID_HANDLE_VALUE )
357         {
358             ERR("failed to create %s (error %d)\n",
359                 debugstr_w( f->TargetPath ), GetLastError() );
360             return 0;
361         }
362
363         f->state = msifs_installed;
364         return (INT_PTR) handle;
365     }
366     case fdintCLOSE_FILE_INFO:
367     {
368         FILETIME ft;
369         FILETIME ftLocal;
370         HANDLE handle = (HANDLE) pfdin->hf;
371
372         if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
373             return -1;
374         if (!LocalFileTimeToFileTime(&ft, &ftLocal))
375             return -1;
376         if (!SetFileTime(handle, &ftLocal, 0, &ftLocal))
377             return -1;
378         CloseHandle(handle);
379         return 1;
380     }
381     default:
382         return 0;
383     }
384 }
385
386 /***********************************************************************
387  *            extract_cabinet_file
388  *
389  * Extract files from a cab file.
390  */
391 static BOOL extract_cabinet_file(MSIPACKAGE* package, struct media_info *mi)
392 {
393     LPSTR cabinet, cab_path = NULL;
394     LPWSTR ptr;
395     HFDI hfdi;
396     ERF erf;
397     BOOL ret = FALSE;
398     CabData data;
399
400     TRACE("Extracting %s\n", debugstr_w(mi->source));
401
402     hfdi = FDICreate(cabinet_alloc, cabinet_free, cabinet_open, cabinet_read,
403                      cabinet_write, cabinet_close, cabinet_seek, 0, &erf);
404     if (!hfdi)
405     {
406         ERR("FDICreate failed\n");
407         return FALSE;
408     }
409
410     ptr = strrchrW(mi->source, '\\') + 1;
411     cabinet = strdupWtoA(ptr);
412     if (!cabinet)
413         goto done;
414
415     cab_path = strdupWtoA(mi->source);
416     if (!cab_path)
417         goto done;
418
419     cab_path[ptr - mi->source] = '\0';
420
421     data.package = package;
422     data.mi = mi;
423
424     ret = FDICopy(hfdi, cabinet, cab_path, 0, cabinet_notify, NULL, &data);
425     if (!ret)
426         ERR("FDICopy failed\n");
427
428 done:
429     FDIDestroy(hfdi);
430     msi_free(cabinet);
431     msi_free(cab_path);
432
433     return ret;
434 }
435
436 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, LPCWSTR path)
437 {
438     if (!file->IsCompressed)
439     {
440         LPWSTR p, path;
441         p = resolve_folder(package, file->Component->Directory, TRUE, FALSE, NULL);
442         path = build_directory_name(2, p, file->ShortName);
443         if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW( path ))
444         {
445             msi_free(path);
446             path = build_directory_name(2, p, file->LongName);
447         }
448         file->SourcePath = path;
449         msi_free(p);
450     }
451     else
452         file->SourcePath = build_directory_name(2, path, file->File);
453 }
454
455 static void free_media_info( struct media_info *mi )
456 {
457     msi_free( mi->disk_prompt );
458     msi_free( mi->cabinet );
459     msi_free( mi->volume_label );
460     msi_free( mi );
461 }
462
463 static UINT download_remote_cabinet(MSIPACKAGE *package, struct media_info *mi)
464 {
465     WCHAR temppath[MAX_PATH];
466     LPWSTR src, ptr;
467     LPCWSTR cab;
468
469     src = strdupW(package->BaseURL);
470     if (!src)
471         return ERROR_OUTOFMEMORY;
472
473     ptr = strrchrW(src, '/');
474     if (!ptr)
475     {
476         msi_free(src);
477         return ERROR_FUNCTION_FAILED;
478     }
479
480     *(ptr + 1) = '\0';
481     ptr = strrchrW(mi->source, '\\');
482     src = msi_realloc(src, (lstrlenW(src) + lstrlenW(ptr)) * sizeof(WCHAR));
483     if (!src)
484         return ERROR_OUTOFMEMORY;
485
486     lstrcatW(src, ptr + 1);
487
488     temppath[0] = '\0';
489     cab = msi_download_file(src, temppath);
490     lstrcpyW(mi->source, cab);
491
492     msi_free(src);
493     return ERROR_SUCCESS;
494 }
495
496 static UINT load_media_info(MSIPACKAGE *package, MSIFILE *file, struct media_info *mi)
497 {
498     MSIRECORD *row;
499     LPWSTR source_dir;
500     UINT r;
501
502     static const WCHAR query[] = {
503         'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
504         '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
505         '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
506         ' ','%','i',' ','A','N','D',' ','`','D','i','s','k','I','d','`',' ','>','=',
507         ' ','%','i',' ','O','R','D','E','R',' ','B','Y',' ',
508         '`','D','i','s','k','I','d','`',0
509     };
510
511     row = MSI_QueryGetRecord(package->db, query, file->Sequence, mi->disk_id);
512     if (!row)
513     {
514         TRACE("Unable to query row\n");
515         return ERROR_FUNCTION_FAILED;
516     }
517
518     mi->disk_id = MSI_RecordGetInteger(row, 1);
519     mi->last_sequence = MSI_RecordGetInteger(row, 2);
520     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
521     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
522     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
523     msiobj_release(&row->hdr);
524
525     source_dir = msi_dup_property(package, cszSourceDir);
526
527     if (mi->cabinet && mi->cabinet[0] == '#')
528     {
529         r = writeout_cabinet_stream(package, &mi->cabinet[1], mi->source);
530         if (r != ERROR_SUCCESS)
531         {
532             ERR("Failed to extract cabinet stream\n");
533             return ERROR_FUNCTION_FAILED;
534         }
535     }
536     else
537     {
538         lstrcpyW(mi->source, source_dir);
539
540
541         if (mi->cabinet)
542             lstrcatW(mi->source, mi->cabinet);
543     }
544
545     MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
546         MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT,
547         mi->disk_id, mi->volume_label, mi->disk_prompt);
548
549     MsiSourceListSetInfoW(package->ProductCode, NULL,
550         MSIINSTALLCONTEXT_USERMANAGED,
551         MSICODE_PRODUCT | MSISOURCETYPE_MEDIA,
552         INSTALLPROPERTY_LASTUSEDSOURCEW, mi->source);
553
554     msi_free(source_dir);
555     return ERROR_SUCCESS;
556 }
557
558 static UINT ready_media(MSIPACKAGE *package, MSIFILE *file, struct media_info *mi)
559 {
560     UINT rc = ERROR_SUCCESS;
561     BOOL found = FALSE;
562
563     /* media info for continuous cabinet is already loaded */
564     if (mi->is_continuous)
565         return ERROR_SUCCESS;
566
567     rc = load_media_info(package, file, mi);
568     if (rc != ERROR_SUCCESS)
569     {
570         ERR("Unable to load media info\n");
571         return ERROR_FUNCTION_FAILED;
572     }
573
574     if (file->IsCompressed &&
575         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
576     {
577         if (package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
578         {
579             rc = download_remote_cabinet(package, mi);
580             if (rc == ERROR_SUCCESS &&
581                 GetFileAttributesW(mi->source) != INVALID_FILE_ATTRIBUTES)
582             {
583                 found = TRUE;
584             }
585         }
586
587         if (!found)
588             rc = msi_change_media(package, mi);
589     }
590
591     return rc;
592 }
593
594 static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
595                             MSIFILE** file)
596 {
597     LIST_FOR_EACH_ENTRY( *file, &package->files, MSIFILE, entry )
598     {
599         if (lstrcmpW( file_key, (*file)->File )==0)
600         {
601             if ((*file)->state >= msifs_overwrite)
602                 return ERROR_SUCCESS;
603             else
604                 return ERROR_FILE_NOT_FOUND;
605         }
606     }
607
608     return ERROR_FUNCTION_FAILED;
609 }
610
611 static void schedule_install_files(MSIPACKAGE *package)
612 {
613     MSIFILE *file;
614
615     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
616     {
617         if (!ACTION_VerifyComponentForAction(file->Component, INSTALLSTATE_LOCAL))
618         {
619             TRACE("File %s is not scheduled for install\n", debugstr_w(file->File));
620
621             ui_progress(package,2,file->FileSize,0,0);
622             file->state = msifs_skipped;
623         }
624     }
625 }
626
627 static UINT copy_install_file(MSIFILE *file)
628 {
629     BOOL ret;
630     UINT gle;
631
632     TRACE("Copying %s to %s\n", debugstr_w(file->SourcePath),
633           debugstr_w(file->TargetPath));
634
635     ret = CopyFileW(file->SourcePath, file->TargetPath, FALSE);
636     if (ret)
637     {
638         file->state = msifs_installed;
639         return ERROR_SUCCESS;
640     }
641
642     gle = GetLastError();
643     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
644     {
645         TRACE("overwriting existing file\n");
646         gle = ERROR_SUCCESS;
647     }
648     else if (gle == ERROR_FILE_NOT_FOUND)
649     {
650         /* FIXME: this needs to be tested, I'm pretty sure it fails */
651         TRACE("Source file not found\n");
652         gle = ERROR_SUCCESS;
653     }
654     else if (!(file->Attributes & msidbFileAttributesVital))
655     {
656         TRACE("Ignoring error for nonvital\n");
657         gle = ERROR_SUCCESS;
658     }
659
660     return gle;
661 }
662
663 /*
664  * ACTION_InstallFiles()
665  * 
666  * For efficiency, this is done in two passes:
667  * 1) Correct all the TargetPaths and determine what files are to be installed.
668  * 2) Extract Cabinets and copy files.
669  */
670 UINT ACTION_InstallFiles(MSIPACKAGE *package)
671 {
672     struct media_info *mi;
673     UINT rc = ERROR_SUCCESS;
674     LPWSTR ptr;
675     MSIFILE *file;
676
677     /* increment progress bar each time action data is sent */
678     ui_progress(package,1,1,0,0);
679
680     /* handle the keys for the SourceList */
681     ptr = strrchrW(package->PackagePath,'\\');
682     if (ptr)
683     {
684         ptr++;
685         MsiSourceListSetInfoW(package->ProductCode, NULL,
686                 MSIINSTALLCONTEXT_USERMANAGED,
687                 MSICODE_PRODUCT,
688                 INSTALLPROPERTY_PACKAGENAMEW, ptr);
689     }
690
691     schedule_install_files(package);
692
693     /*
694      * Despite MSDN specifying that the CreateFolders action
695      * should be called before InstallFiles, some installers don't
696      * do that, and they seem to work correctly.  We need to create
697      * directories here to make sure that the files can be copied.
698      */
699     msi_create_component_directories( package );
700
701     mi = msi_alloc_zero( sizeof(struct media_info) );
702
703     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
704     {
705         if (file->state != msifs_missing && file->state != msifs_overwrite)
706             continue;
707
708         if (file->Sequence > mi->last_sequence || mi->is_continuous)
709         {
710             rc = ready_media(package, file, mi);
711             if (rc != ERROR_SUCCESS)
712             {
713                 ERR("Failed to ready media\n");
714                 rc = ERROR_FUNCTION_FAILED;
715                 break;
716             }
717
718             if (file->IsCompressed && !extract_cabinet_file(package, mi))
719             {
720                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
721                 rc = ERROR_FUNCTION_FAILED;
722                 break;
723             }
724         }
725
726         set_file_source(package, file, mi->source);
727
728         TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
729               debugstr_w(file->TargetPath));
730
731         if (!file->IsCompressed)
732         {
733             rc = copy_install_file(file);
734             if (rc != ERROR_SUCCESS)
735             {
736                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(file->SourcePath),
737                     debugstr_w(file->TargetPath), rc);
738                 rc = ERROR_INSTALL_FAILURE;
739                 break;
740             }
741         }
742         else if (file->state != msifs_installed)
743         {
744             ERR("compressed file wasn't extracted (%s)\n", debugstr_w(file->TargetPath));
745             rc = ERROR_INSTALL_FAILURE;
746             break;
747         }
748     }
749
750     free_media_info( mi );
751     return rc;
752 }
753
754 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
755 {
756     MSIPACKAGE *package = (MSIPACKAGE*)param;
757     WCHAR dest_name[0x100];
758     LPWSTR dest_path, dest;
759     LPCWSTR file_key, component;
760     DWORD sz;
761     DWORD rc;
762     MSICOMPONENT *comp;
763     MSIFILE *file;
764
765     component = MSI_RecordGetString(row,2);
766     comp = get_loaded_component(package,component);
767
768     if (!ACTION_VerifyComponentForAction( comp, INSTALLSTATE_LOCAL ))
769     {
770         TRACE("Skipping copy due to disabled component %s\n",
771                         debugstr_w(component));
772
773         /* the action taken was the same as the current install state */        
774         comp->Action = comp->Installed;
775
776         return ERROR_SUCCESS;
777     }
778
779     comp->Action = INSTALLSTATE_LOCAL;
780
781     file_key = MSI_RecordGetString(row,3);
782     if (!file_key)
783     {
784         ERR("Unable to get file key\n");
785         return ERROR_FUNCTION_FAILED;
786     }
787
788     rc = get_file_target(package,file_key,&file);
789
790     if (rc != ERROR_SUCCESS)
791     {
792         ERR("Original file unknown %s\n",debugstr_w(file_key));
793         return ERROR_SUCCESS;
794     }
795
796     if (MSI_RecordIsNull(row,4))
797         strcpyW(dest_name,strrchrW(file->TargetPath,'\\')+1);
798     else
799     {
800         sz=0x100;
801         MSI_RecordGetStringW(row,4,dest_name,&sz);
802         reduce_to_longfilename(dest_name);
803     }
804
805     if (MSI_RecordIsNull(row,5))
806     {
807         LPWSTR p;
808         dest_path = strdupW(file->TargetPath);
809         p = strrchrW(dest_path,'\\');
810         if (p)
811             *p=0;
812     }
813     else
814     {
815         LPCWSTR destkey;
816         destkey = MSI_RecordGetString(row,5);
817         dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
818         if (!dest_path)
819         {
820             /* try a Property */
821             dest_path = msi_dup_property( package, destkey );
822             if (!dest_path)
823             {
824                 FIXME("Unable to get destination folder, try AppSearch properties\n");
825                 return ERROR_SUCCESS;
826             }
827         }
828     }
829
830     dest = build_directory_name(2, dest_path, dest_name);
831
832     TRACE("Duplicating file %s to %s\n",debugstr_w(file->TargetPath),
833                     debugstr_w(dest)); 
834
835     if (strcmpW(file->TargetPath,dest))
836         rc = !CopyFileW(file->TargetPath,dest,TRUE);
837     else
838         rc = ERROR_SUCCESS;
839
840     if (rc != ERROR_SUCCESS)
841         ERR("Failed to copy file %s -> %s, last error %d\n",
842             debugstr_w(file->TargetPath), debugstr_w(dest_path), GetLastError());
843
844     FIXME("We should track these duplicate files as well\n");   
845
846     msi_free(dest_path);
847     msi_free(dest);
848
849     msi_file_update_ui(package, file, szDuplicateFiles);
850
851     return ERROR_SUCCESS;
852 }
853
854 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
855 {
856     UINT rc;
857     MSIQUERY * view;
858     static const WCHAR ExecSeqQuery[] =
859         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
860          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
861
862     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
863     if (rc != ERROR_SUCCESS)
864         return ERROR_SUCCESS;
865
866     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
867     msiobj_release(&view->hdr);
868
869     return rc;
870 }
871
872 /* compares the version of a file read from the filesystem and
873  * the version specified in the File table
874  */
875 static int msi_compare_file_version( MSIFILE *file )
876 {
877     WCHAR version[MAX_PATH];
878     DWORD size;
879     UINT r;
880
881     size = MAX_PATH;
882     version[0] = '\0';
883     r = MsiGetFileVersionW( file->TargetPath, version, &size, NULL, NULL );
884     if ( r != ERROR_SUCCESS )
885         return 0;
886
887     return lstrcmpW( version, file->Version );
888 }
889
890 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
891 {
892     MSIFILE *file;
893
894     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
895     {
896         MSIRECORD *uirow;
897         LPWSTR uipath, p;
898
899         if ( !file->Component )
900             continue;
901         if ( file->Component->Installed == INSTALLSTATE_LOCAL )
902             continue;
903
904         if ( file->state == msifs_installed )
905             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
906
907         if ( file->state != msifs_present )
908             continue;
909
910         /* only remove a file if the version to be installed
911          * is strictly newer than the old file
912          */
913         if ( msi_compare_file_version( file ) >= 0 )
914             continue;
915
916         TRACE("removing %s\n", debugstr_w(file->File) );
917         if ( !DeleteFileW( file->TargetPath ) )
918             ERR("failed to delete %s\n",  debugstr_w(file->TargetPath) );
919         file->state = msifs_missing;
920
921         /* the UI chunk */
922         uirow = MSI_CreateRecord( 9 );
923         MSI_RecordSetStringW( uirow, 1, file->FileName );
924         uipath = strdupW( file->TargetPath );
925         p = strrchrW(uipath,'\\');
926         if (p)
927             p[1]=0;
928         MSI_RecordSetStringW( uirow, 9, uipath);
929         ui_actiondata( package, szRemoveFiles, uirow);
930         msiobj_release( &uirow->hdr );
931         msi_free( uipath );
932         /* FIXME: call ui_progress here? */
933     }
934
935     return ERROR_SUCCESS;
936 }