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