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