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