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