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