msi: Fix a typo.
[wine] / dlls / msi / files.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21
22 /*
23  * Actions dealing with files These are
24  *
25  * InstallFiles
26  * DuplicateFiles
27  * MoveFiles (TODO)
28  * PatchFiles (TODO)
29  * RemoveDuplicateFiles(TODO)
30  * RemoveFiles(TODO)
31  */
32
33 #include <stdarg.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "winerror.h"
38 #include "wine/debug.h"
39 #include "fdi.h"
40 #include "msi.h"
41 #include "msidefs.h"
42 #include "msvcrt/fcntl.h"
43 #include "msipriv.h"
44 #include "winuser.h"
45 #include "winreg.h"
46 #include "shlwapi.h"
47 #include "wine/unicode.h"
48
49 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50
51 extern const WCHAR szInstallFiles[];
52 extern const WCHAR szDuplicateFiles[];
53 extern const WCHAR szMoveFiles[];
54 extern const WCHAR szPatchFiles[];
55 extern const WCHAR szRemoveDuplicateFiles[];
56 extern const WCHAR szRemoveFiles[];
57
58 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
59
60 struct media_info {
61     UINT disk_id;
62     UINT type;
63     UINT last_sequence;
64     LPWSTR disk_prompt;
65     LPWSTR cabinet;
66     LPWSTR first_volume;
67     LPWSTR volume_label;
68     BOOL is_continuous;
69     BOOL is_extracted;
70     WCHAR source[MAX_PATH];
71 };
72
73 static BOOL source_matches_volume(struct media_info *mi, LPWSTR source_root)
74 {
75     WCHAR volume_name[MAX_PATH + 1];
76
77     if (!GetVolumeInformationW(source_root, volume_name, MAX_PATH + 1,
78                                NULL, NULL, NULL, NULL, 0))
79     {
80         ERR("Failed to get volume information\n");
81         return FALSE;
82     }
83
84     return !lstrcmpW(mi->volume_label, volume_name);
85 }
86
87 static UINT msi_change_media( MSIPACKAGE *package, struct media_info *mi )
88 {
89     LPSTR msg;
90     LPWSTR error, error_dialog;
91     LPWSTR source_dir;
92     UINT r = ERROR_SUCCESS;
93
94     static const WCHAR szUILevel[] = {'U','I','L','e','v','e','l',0};
95     static const WCHAR error_prop[] = {'E','r','r','o','r','D','i','a','l','o','g',0};
96
97     if ( (msi_get_property_int(package, szUILevel, 0) & INSTALLUILEVEL_MASK) == INSTALLUILEVEL_NONE && !gUIHandlerA )
98         return ERROR_SUCCESS;
99
100     error = generate_error_string( package, 1302, 1, mi->disk_prompt );
101     error_dialog = msi_dup_property( package, error_prop );
102     source_dir = msi_dup_property( package, cszSourceDir );
103     PathStripToRootW(source_dir);
104
105     while ( r == ERROR_SUCCESS &&
106             !source_matches_volume(mi, source_dir) )
107     {
108         r = msi_spawn_error_dialog( package, error_dialog, error );
109
110         if (gUIHandlerA)
111         {
112             msg = strdupWtoA( error );
113             gUIHandlerA( gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, msg );
114             msi_free(msg);
115         }
116     }
117
118     msi_free( error );
119     msi_free( error_dialog );
120     msi_free( source_dir );
121
122     return r;
123 }
124
125 /*
126  * This is a helper function for handling embedded cabinet media
127  */
128 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
129                                     WCHAR* source)
130 {
131     UINT rc;
132     USHORT* data;
133     UINT    size;
134     DWORD   write;
135     HANDLE  the_file;
136     WCHAR tmp[MAX_PATH];
137
138     rc = read_raw_stream_data(package->db,stream_name,&data,&size); 
139     if (rc != ERROR_SUCCESS)
140         return rc;
141
142     write = MAX_PATH;
143     if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
144         GetTempPathW(MAX_PATH,tmp);
145
146     GetTempFileNameW(tmp,stream_name,0,source);
147
148     track_tempfile(package, source);
149     the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
150                            FILE_ATTRIBUTE_NORMAL, NULL);
151
152     if (the_file == INVALID_HANDLE_VALUE)
153     {
154         ERR("Unable to create file %s\n",debugstr_w(source));
155         rc = ERROR_FUNCTION_FAILED;
156         goto end;
157     }
158
159     WriteFile(the_file,data,size,&write,NULL);
160     CloseHandle(the_file);
161     TRACE("wrote %i bytes to %s\n",write,debugstr_w(source));
162 end:
163     msi_free(data);
164     return rc;
165 }
166
167
168 /* Support functions for FDI functions */
169 typedef struct
170 {
171     MSIPACKAGE* package;
172     struct media_info *mi;
173 } CabData;
174
175 static void * cabinet_alloc(ULONG cb)
176 {
177     return msi_alloc(cb);
178 }
179
180 static void cabinet_free(void *pv)
181 {
182     msi_free(pv);
183 }
184
185 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
186 {
187     HANDLE handle;
188     DWORD dwAccess = 0;
189     DWORD dwShareMode = 0;
190     DWORD dwCreateDisposition = OPEN_EXISTING;
191     switch (oflag & _O_ACCMODE)
192     {
193     case _O_RDONLY:
194         dwAccess = GENERIC_READ;
195         dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
196         break;
197     case _O_WRONLY:
198         dwAccess = GENERIC_WRITE;
199         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
200         break;
201     case _O_RDWR:
202         dwAccess = GENERIC_READ | GENERIC_WRITE;
203         dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
204         break;
205     }
206     if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
207         dwCreateDisposition = CREATE_NEW;
208     else if (oflag & _O_CREAT)
209         dwCreateDisposition = CREATE_ALWAYS;
210     handle = CreateFileA( pszFile, dwAccess, dwShareMode, NULL, 
211                           dwCreateDisposition, 0, NULL );
212     if (handle == INVALID_HANDLE_VALUE)
213         return 0;
214     return (INT_PTR) handle;
215 }
216
217 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
218 {
219     HANDLE handle = (HANDLE) hf;
220     DWORD dwRead;
221     if (ReadFile(handle, pv, cb, &dwRead, NULL))
222         return dwRead;
223     return 0;
224 }
225
226 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
227 {
228     HANDLE handle = (HANDLE) hf;
229     DWORD dwWritten;
230     if (WriteFile(handle, pv, cb, &dwWritten, NULL))
231         return dwWritten;
232     return 0;
233 }
234
235 static int cabinet_close(INT_PTR hf)
236 {
237     HANDLE handle = (HANDLE) hf;
238     return CloseHandle(handle) ? 0 : -1;
239 }
240
241 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
242 {
243     HANDLE handle = (HANDLE) hf;
244     /* flags are compatible and so are passed straight through */
245     return SetFilePointer(handle, dist, NULL, seektype);
246 }
247
248 static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
249 {
250     MSIRECORD *uirow;
251     LPWSTR uipath, p;
252
253     /* the UI chunk */
254     uirow = MSI_CreateRecord( 9 );
255     MSI_RecordSetStringW( uirow, 1, f->FileName );
256     uipath = strdupW( f->TargetPath );
257     p = strrchrW(uipath,'\\');
258     if (p)
259         p[1]=0;
260     MSI_RecordSetStringW( uirow, 9, uipath);
261     MSI_RecordSetInteger( uirow, 6, f->FileSize );
262     ui_actiondata( package, action, uirow);
263     msiobj_release( &uirow->hdr );
264     msi_free( uipath );
265     ui_progress( package, 2, f->FileSize, 0, 0);
266 }
267
268 static UINT msi_media_get_disk_info( MSIPACKAGE *package, struct media_info *mi )
269 {
270     MSIRECORD *row;
271     LPWSTR ptr;
272
273     static const WCHAR query[] =
274         {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
275          '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
276          '`','D','i','s','k','I','d','`',' ','=',' ','%','i',0};
277
278     row = MSI_QueryGetRecord(package->db, query, mi->disk_id);
279     if (!row)
280     {
281         TRACE("Unable to query row\n");
282         return ERROR_FUNCTION_FAILED;
283     }
284
285     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
286     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
287     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
288
289     if (!mi->first_volume)
290         mi->first_volume = strdupW(mi->volume_label);
291
292     ptr = strrchrW(mi->source, '\\') + 1;
293     lstrcpyW(ptr, mi->cabinet);
294     msiobj_release(&row->hdr);
295
296     return ERROR_SUCCESS;
297 }
298
299 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
300 {
301     TRACE("(%d)\n", fdint);
302
303     switch (fdint)
304     {
305     case fdintPARTIAL_FILE:
306     {
307         CabData *data = (CabData *)pfdin->pv;
308         data->mi->is_continuous = FALSE;
309         return 0;
310     }
311     case fdintNEXT_CABINET:
312     {
313         CabData *data = (CabData *)pfdin->pv;
314         struct media_info *mi = data->mi;
315         LPWSTR cab = strdupAtoW(pfdin->psz1);
316         UINT rc;
317
318         msi_free(mi->disk_prompt);
319         msi_free(mi->cabinet);
320         msi_free(mi->volume_label);
321         mi->disk_prompt = NULL;
322         mi->cabinet = NULL;
323         mi->volume_label = NULL;
324
325         mi->disk_id++;
326         mi->is_continuous = TRUE;
327
328         rc = msi_media_get_disk_info(data->package, mi);
329         if (rc != ERROR_SUCCESS)
330         {
331             msi_free(cab);
332             ERR("Failed to get next cabinet information: %d\n", rc);
333             return -1;
334         }
335
336         if (lstrcmpiW(mi->cabinet, cab))
337         {
338             msi_free(cab);
339             ERR("Continuous cabinet does not match the next cabinet in the Media table\n");
340             return -1;
341         }
342
343         msi_free(cab);
344
345         TRACE("Searching for %s\n", debugstr_w(mi->source));
346
347         if (GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
348             rc = msi_change_media(data->package, mi);
349
350         if (rc != ERROR_SUCCESS)
351             return -1;
352
353         return 0;
354     }
355     case fdintCOPY_FILE:
356     {
357         CabData *data = (CabData*) pfdin->pv;
358         HANDLE handle;
359         LPWSTR file;
360         MSIFILE *f;
361         DWORD attrs;
362
363         file = strdupAtoW(pfdin->psz1);
364         f = get_loaded_file(data->package, file);
365         msi_free(file);
366
367         if (!f)
368         {
369             WARN("unknown file in cabinet (%s)\n",debugstr_a(pfdin->psz1));
370             return 0;
371         }
372
373         if (f->state != msifs_missing && f->state != msifs_overwrite)
374         {
375             TRACE("Skipping extraction of %s\n",debugstr_a(pfdin->psz1));
376             return 0;
377         }
378
379         msi_file_update_ui( data->package, f, szInstallFiles );
380
381         TRACE("extracting %s\n", debugstr_w(f->TargetPath) );
382
383         attrs = f->Attributes & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
384         if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL;
385
386         handle = CreateFileW( f->TargetPath, GENERIC_READ | GENERIC_WRITE, 0,
387                               NULL, CREATE_ALWAYS, attrs, NULL );
388         if ( handle == INVALID_HANDLE_VALUE )
389         {
390             if ( GetFileAttributesW( f->TargetPath ) != INVALID_FILE_ATTRIBUTES )
391                 f->state = msifs_installed;
392             else
393                 ERR("failed to create %s (error %d)\n",
394                     debugstr_w( f->TargetPath ), GetLastError() );
395
396             return 0;
397         }
398
399         f->state = msifs_installed;
400         return (INT_PTR) handle;
401     }
402     case fdintCLOSE_FILE_INFO:
403     {
404         CabData *data = (CabData*) pfdin->pv;
405         FILETIME ft;
406         FILETIME ftLocal;
407         HANDLE handle = (HANDLE) pfdin->hf;
408
409         data->mi->is_continuous = FALSE;
410
411         if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
412             return -1;
413         if (!LocalFileTimeToFileTime(&ft, &ftLocal))
414             return -1;
415         if (!SetFileTime(handle, &ftLocal, 0, &ftLocal))
416             return -1;
417         CloseHandle(handle);
418         return 1;
419     }
420     default:
421         return 0;
422     }
423 }
424
425 /***********************************************************************
426  *            extract_cabinet_file
427  *
428  * Extract files from a cab file.
429  */
430 static BOOL extract_cabinet_file(MSIPACKAGE* package, struct media_info *mi)
431 {
432     LPSTR cabinet, cab_path = NULL;
433     LPWSTR ptr;
434     HFDI hfdi;
435     ERF erf;
436     BOOL ret = FALSE;
437     CabData data;
438
439     TRACE("Extracting %s\n", debugstr_w(mi->source));
440
441     hfdi = FDICreate(cabinet_alloc, cabinet_free, cabinet_open, cabinet_read,
442                      cabinet_write, cabinet_close, cabinet_seek, 0, &erf);
443     if (!hfdi)
444     {
445         ERR("FDICreate failed\n");
446         return FALSE;
447     }
448
449     ptr = strrchrW(mi->source, '\\') + 1;
450     cabinet = strdupWtoA(ptr);
451     if (!cabinet)
452         goto done;
453
454     cab_path = strdupWtoA(mi->source);
455     if (!cab_path)
456         goto done;
457
458     cab_path[ptr - mi->source] = '\0';
459
460     data.package = package;
461     data.mi = mi;
462
463     ret = FDICopy(hfdi, cabinet, cab_path, 0, cabinet_notify, NULL, &data);
464     if (!ret)
465         ERR("FDICopy failed\n");
466
467 done:
468     FDIDestroy(hfdi);
469     msi_free(cabinet);
470     msi_free(cab_path);
471
472     if (ret)
473         mi->is_extracted = TRUE;
474
475     return ret;
476 }
477
478 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, LPCWSTR path)
479 {
480     if (!file->IsCompressed)
481     {
482         LPWSTR p, path;
483         p = resolve_folder(package, file->Component->Directory, TRUE, FALSE, TRUE, NULL);
484         path = build_directory_name(2, p, file->ShortName);
485         if (file->LongName &&
486             INVALID_FILE_ATTRIBUTES == GetFileAttributesW( path ))
487         {
488             msi_free(path);
489             path = build_directory_name(2, p, file->LongName);
490         }
491         file->SourcePath = path;
492         msi_free(p);
493     }
494     else
495         file->SourcePath = build_directory_name(2, path, file->File);
496 }
497
498 static void free_media_info( struct media_info *mi )
499 {
500     msi_free( mi->disk_prompt );
501     msi_free( mi->cabinet );
502     msi_free( mi->volume_label );
503     msi_free( mi->first_volume );
504     msi_free( mi );
505 }
506
507 static UINT load_media_info(MSIPACKAGE *package, MSIFILE *file, struct media_info *mi)
508 {
509     MSIRECORD *row;
510     LPWSTR source_dir;
511     LPWSTR source;
512     DWORD options;
513     UINT r;
514
515     static const WCHAR query[] = {
516         'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
517         '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
518         '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
519         ' ','%','i',' ','A','N','D',' ','`','D','i','s','k','I','d','`',' ','>','=',
520         ' ','%','i',' ','O','R','D','E','R',' ','B','Y',' ',
521         '`','D','i','s','k','I','d','`',0
522     };
523
524     row = MSI_QueryGetRecord(package->db, query, file->Sequence, mi->disk_id);
525     if (!row)
526     {
527         TRACE("Unable to query row\n");
528         return ERROR_FUNCTION_FAILED;
529     }
530
531     mi->is_extracted = FALSE;
532     mi->disk_id = MSI_RecordGetInteger(row, 1);
533     mi->last_sequence = MSI_RecordGetInteger(row, 2);
534     msi_free(mi->disk_prompt);
535     mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3));
536     msi_free(mi->cabinet);
537     mi->cabinet = strdupW(MSI_RecordGetString(row, 4));
538     msi_free(mi->volume_label);
539     mi->volume_label = strdupW(MSI_RecordGetString(row, 5));
540     msiobj_release(&row->hdr);
541
542     if (!mi->first_volume)
543         mi->first_volume = strdupW(mi->volume_label);
544
545     source_dir = msi_dup_property(package, cszSourceDir);
546     lstrcpyW(mi->source, source_dir);
547
548     PathStripToRootW(source_dir);
549     mi->type = GetDriveTypeW(source_dir);
550
551     if (file->IsCompressed && mi->cabinet)
552     {
553         if (mi->cabinet[0] == '#')
554         {
555             r = writeout_cabinet_stream(package, &mi->cabinet[1], mi->source);
556             if (r != ERROR_SUCCESS)
557             {
558                 ERR("Failed to extract cabinet stream\n");
559                 return ERROR_FUNCTION_FAILED;
560             }
561         }
562         else
563             lstrcatW(mi->source, mi->cabinet);
564     }
565
566     options = MSICODE_PRODUCT;
567     if (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE)
568     {
569         source = source_dir;
570         options |= MSISOURCETYPE_MEDIA;
571     }
572     else if (package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
573     {
574         source = package->BaseURL;
575         options |= MSISOURCETYPE_URL;
576     }
577     else
578     {
579         source = mi->source;
580         options |= MSISOURCETYPE_NETWORK;
581     }
582
583     if (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE)
584         msi_package_add_media_disk(package, MSIINSTALLCONTEXT_USERUNMANAGED,
585                                    MSICODE_PRODUCT, mi->disk_id,
586                                    mi->volume_label, mi->disk_prompt);
587
588     msi_package_add_info(package, MSIINSTALLCONTEXT_USERUNMANAGED,
589                          options, INSTALLPROPERTY_LASTUSEDSOURCEW, source);
590
591     msi_free(source_dir);
592     return ERROR_SUCCESS;
593 }
594
595 /* FIXME: search NETWORK and URL sources as well */
596 static UINT find_published_source(MSIPACKAGE *package, struct media_info *mi)
597 {
598     WCHAR source[MAX_PATH];
599     WCHAR volume[MAX_PATH];
600     WCHAR prompt[MAX_PATH];
601     DWORD volumesz, promptsz;
602     DWORD index, size, id;
603     UINT r;
604
605     r = MsiSourceListGetInfoW(package->ProductCode, NULL,
606                               MSIINSTALLCONTEXT_USERUNMANAGED, MSICODE_PRODUCT,
607                               INSTALLPROPERTY_LASTUSEDSOURCEW, source, &size);
608     if (r != ERROR_SUCCESS)
609         return r;
610
611     index = 0;
612     volumesz = MAX_PATH;
613     promptsz = MAX_PATH;
614     while (MsiSourceListEnumMediaDisksW(package->ProductCode, NULL,
615                                         MSIINSTALLCONTEXT_USERUNMANAGED,
616                                         MSICODE_PRODUCT, index++, &id,
617                                         volume, &volumesz, prompt, &promptsz) == ERROR_SUCCESS)
618     {
619         mi->disk_id = id;
620         mi->volume_label = msi_realloc(mi->volume_label, ++volumesz * sizeof(WCHAR));
621         lstrcpyW(mi->volume_label, volume);
622         mi->disk_prompt = msi_realloc(mi->disk_prompt, ++promptsz * sizeof(WCHAR));
623         lstrcpyW(mi->disk_prompt, prompt);
624
625         if (source_matches_volume(mi, source))
626         {
627             /* FIXME: what about SourceDir */
628             lstrcpyW(mi->source, source);
629             lstrcatW(mi->source, mi->cabinet);
630             return ERROR_SUCCESS;
631         }
632     }
633
634     return ERROR_FUNCTION_FAILED;
635 }
636
637 static UINT ready_media(MSIPACKAGE *package, MSIFILE *file, struct media_info *mi)
638 {
639     UINT rc = ERROR_SUCCESS;
640
641     /* media info for continuous cabinet is already loaded */
642     if (mi->is_continuous)
643         return ERROR_SUCCESS;
644
645     rc = load_media_info(package, file, mi);
646     if (rc != ERROR_SUCCESS)
647     {
648         ERR("Unable to load media info\n");
649         return ERROR_FUNCTION_FAILED;
650     }
651
652     /* cabinet is internal, no checks needed */
653     if (!mi->cabinet || mi->cabinet[0] == '#')
654         return ERROR_SUCCESS;
655
656     /* package should be downloaded */
657     if (file->IsCompressed &&
658         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES &&
659         package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL))
660     {
661         WCHAR temppath[MAX_PATH];
662
663         msi_download_file(mi->source, temppath);
664         lstrcpyW(mi->source, temppath);
665         return ERROR_SUCCESS;
666     }
667
668     /* check volume matches, change media if not */
669     if (mi->volume_label && mi->disk_id > 1 &&
670         lstrcmpW(mi->first_volume, mi->volume_label))
671     {
672         LPWSTR source = msi_dup_property(package, cszSourceDir);
673         BOOL matches;
674
675         matches = source_matches_volume(mi, source);
676         msi_free(source);
677
678         if ((mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE) && !matches)
679         {
680             rc = msi_change_media(package, mi);
681             if (rc != ERROR_SUCCESS)
682                 return rc;
683         }
684     }
685
686     if (file->IsCompressed &&
687         GetFileAttributesW(mi->source) == INVALID_FILE_ATTRIBUTES)
688     {
689         /* FIXME: this might be done earlier in the install process */
690         rc = find_published_source(package, mi);
691         if (rc != ERROR_SUCCESS)
692         {
693             ERR("Cabinet not found: %s\n", debugstr_w(mi->source));
694             return ERROR_INSTALL_FAILURE;
695         }
696     }
697
698     return ERROR_SUCCESS;
699 }
700
701 static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
702                             MSIFILE** file)
703 {
704     LIST_FOR_EACH_ENTRY( *file, &package->files, MSIFILE, entry )
705     {
706         if (lstrcmpW( file_key, (*file)->File )==0)
707         {
708             if ((*file)->state >= msifs_overwrite)
709                 return ERROR_SUCCESS;
710             else
711                 return ERROR_FILE_NOT_FOUND;
712         }
713     }
714
715     return ERROR_FUNCTION_FAILED;
716 }
717
718 static void schedule_install_files(MSIPACKAGE *package)
719 {
720     MSIFILE *file;
721
722     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
723     {
724         if (!ACTION_VerifyComponentForAction(file->Component, INSTALLSTATE_LOCAL))
725         {
726             TRACE("File %s is not scheduled for install\n", debugstr_w(file->File));
727
728             ui_progress(package,2,file->FileSize,0,0);
729             file->state = msifs_skipped;
730         }
731     }
732 }
733
734 static UINT copy_file(MSIFILE *file)
735 {
736     BOOL ret;
737
738     ret = CopyFileW(file->SourcePath, file->TargetPath, FALSE);
739     if (ret)
740     {
741         file->state = msifs_installed;
742         return ERROR_SUCCESS;
743     }
744
745     return GetLastError();
746 }
747
748 static UINT copy_install_file(MSIFILE *file)
749 {
750     UINT gle;
751
752     TRACE("Copying %s to %s\n", debugstr_w(file->SourcePath),
753           debugstr_w(file->TargetPath));
754
755     gle = copy_file(file);
756     if (gle == ERROR_SUCCESS)
757         return gle;
758
759     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
760     {
761         TRACE("overwriting existing file\n");
762         gle = ERROR_SUCCESS;
763     }
764     else if (gle == ERROR_FILE_NOT_FOUND)
765     {
766         /* FIXME: this needs to be tested, I'm pretty sure it fails */
767         TRACE("Source file not found\n");
768         gle = ERROR_SUCCESS;
769     }
770     else if (gle == ERROR_ACCESS_DENIED)
771     {
772         SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
773
774         gle = copy_file(file);
775         TRACE("Overwriting existing file: %d\n", gle);
776     }
777     else if (!(file->Attributes & msidbFileAttributesVital))
778     {
779         TRACE("Ignoring error for nonvital\n");
780         gle = ERROR_SUCCESS;
781     }
782
783     return gle;
784 }
785
786 static BOOL check_dest_hash_matches(MSIFILE *file)
787 {
788     MSIFILEHASHINFO hash;
789     UINT r;
790
791     if (!file->hash.dwFileHashInfoSize)
792         return FALSE;
793
794     hash.dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
795     r = MsiGetFileHashW(file->TargetPath, 0, &hash);
796     if (r != ERROR_SUCCESS)
797         return FALSE;
798
799     return !memcmp(&hash, &file->hash, sizeof(MSIFILEHASHINFO));
800 }
801
802 /*
803  * ACTION_InstallFiles()
804  * 
805  * For efficiency, this is done in two passes:
806  * 1) Correct all the TargetPaths and determine what files are to be installed.
807  * 2) Extract Cabinets and copy files.
808  */
809 UINT ACTION_InstallFiles(MSIPACKAGE *package)
810 {
811     struct media_info *mi;
812     UINT rc = ERROR_SUCCESS;
813     MSIFILE *file;
814
815     /* increment progress bar each time action data is sent */
816     ui_progress(package,1,1,0,0);
817
818     schedule_install_files(package);
819
820     /*
821      * Despite MSDN specifying that the CreateFolders action
822      * should be called before InstallFiles, some installers don't
823      * do that, and they seem to work correctly.  We need to create
824      * directories here to make sure that the files can be copied.
825      */
826     msi_create_component_directories( package );
827
828     mi = msi_alloc_zero( sizeof(struct media_info) );
829
830     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
831     {
832         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
833             continue;
834
835         if (check_dest_hash_matches(file))
836         {
837             TRACE("File hashes match, not overwriting\n");
838             continue;
839         }
840
841         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
842             (file->IsCompressed && !mi->is_extracted))
843         {
844             rc = ready_media(package, file, mi);
845             if (rc != ERROR_SUCCESS)
846             {
847                 ERR("Failed to ready media\n");
848                 break;
849             }
850
851             if (file->IsCompressed && !extract_cabinet_file(package, mi))
852             {
853                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
854                 rc = ERROR_FUNCTION_FAILED;
855                 break;
856             }
857         }
858
859         set_file_source(package, file, mi->source);
860
861         TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
862               debugstr_w(file->TargetPath));
863
864         if (!file->IsCompressed)
865         {
866             msi_file_update_ui(package, file, szInstallFiles);
867             rc = copy_install_file(file);
868             if (rc != ERROR_SUCCESS)
869             {
870                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(file->SourcePath),
871                     debugstr_w(file->TargetPath), rc);
872                 rc = ERROR_INSTALL_FAILURE;
873                 break;
874             }
875         }
876         else if (file->state != msifs_installed)
877         {
878             ERR("compressed file wasn't extracted (%s)\n", debugstr_w(file->TargetPath));
879             rc = ERROR_INSTALL_FAILURE;
880             break;
881         }
882     }
883
884     free_media_info( mi );
885     return rc;
886 }
887
888 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
889 {
890     MSIPACKAGE *package = (MSIPACKAGE*)param;
891     WCHAR dest_name[0x100];
892     LPWSTR dest_path, dest;
893     LPCWSTR file_key, component;
894     DWORD sz;
895     DWORD rc;
896     MSICOMPONENT *comp;
897     MSIFILE *file;
898
899     component = MSI_RecordGetString(row,2);
900     comp = get_loaded_component(package,component);
901
902     if (!ACTION_VerifyComponentForAction( comp, INSTALLSTATE_LOCAL ))
903     {
904         TRACE("Skipping copy due to disabled component %s\n",
905                         debugstr_w(component));
906
907         /* the action taken was the same as the current install state */        
908         comp->Action = comp->Installed;
909
910         return ERROR_SUCCESS;
911     }
912
913     comp->Action = INSTALLSTATE_LOCAL;
914
915     file_key = MSI_RecordGetString(row,3);
916     if (!file_key)
917     {
918         ERR("Unable to get file key\n");
919         return ERROR_FUNCTION_FAILED;
920     }
921
922     rc = get_file_target(package,file_key,&file);
923
924     if (rc != ERROR_SUCCESS)
925     {
926         ERR("Original file unknown %s\n",debugstr_w(file_key));
927         return ERROR_SUCCESS;
928     }
929
930     if (MSI_RecordIsNull(row,4))
931         strcpyW(dest_name,strrchrW(file->TargetPath,'\\')+1);
932     else
933     {
934         sz=0x100;
935         MSI_RecordGetStringW(row,4,dest_name,&sz);
936         reduce_to_longfilename(dest_name);
937     }
938
939     if (MSI_RecordIsNull(row,5))
940     {
941         LPWSTR p;
942         dest_path = strdupW(file->TargetPath);
943         p = strrchrW(dest_path,'\\');
944         if (p)
945             *p=0;
946     }
947     else
948     {
949         LPCWSTR destkey;
950         destkey = MSI_RecordGetString(row,5);
951         dest_path = resolve_folder(package, destkey, FALSE, FALSE, TRUE, NULL);
952         if (!dest_path)
953         {
954             /* try a Property */
955             dest_path = msi_dup_property( package, destkey );
956             if (!dest_path)
957             {
958                 FIXME("Unable to get destination folder, try AppSearch properties\n");
959                 return ERROR_SUCCESS;
960             }
961         }
962     }
963
964     dest = build_directory_name(2, dest_path, dest_name);
965     create_full_pathW(dest_path);
966
967     TRACE("Duplicating file %s to %s\n",debugstr_w(file->TargetPath),
968                     debugstr_w(dest)); 
969
970     if (strcmpW(file->TargetPath,dest))
971         rc = !CopyFileW(file->TargetPath,dest,TRUE);
972     else
973         rc = ERROR_SUCCESS;
974
975     if (rc != ERROR_SUCCESS)
976         ERR("Failed to copy file %s -> %s, last error %d\n",
977             debugstr_w(file->TargetPath), debugstr_w(dest_path), GetLastError());
978
979     FIXME("We should track these duplicate files as well\n");   
980
981     msi_free(dest_path);
982     msi_free(dest);
983
984     msi_file_update_ui(package, file, szDuplicateFiles);
985
986     return ERROR_SUCCESS;
987 }
988
989 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
990 {
991     UINT rc;
992     MSIQUERY * view;
993     static const WCHAR ExecSeqQuery[] =
994         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
995          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
996
997     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
998     if (rc != ERROR_SUCCESS)
999         return ERROR_SUCCESS;
1000
1001     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
1002     msiobj_release(&view->hdr);
1003
1004     return rc;
1005 }
1006
1007 /* compares the version of a file read from the filesystem and
1008  * the version specified in the File table
1009  */
1010 static int msi_compare_file_version( MSIFILE *file )
1011 {
1012     WCHAR version[MAX_PATH];
1013     DWORD size;
1014     UINT r;
1015
1016     size = MAX_PATH;
1017     version[0] = '\0';
1018     r = MsiGetFileVersionW( file->TargetPath, version, &size, NULL, NULL );
1019     if ( r != ERROR_SUCCESS )
1020         return 0;
1021
1022     return lstrcmpW( version, file->Version );
1023 }
1024
1025 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
1026 {
1027     MSIFILE *file;
1028
1029     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1030     {
1031         MSIRECORD *uirow;
1032         LPWSTR uipath, p;
1033
1034         if ( !file->Component )
1035             continue;
1036         if ( file->Component->Installed == INSTALLSTATE_LOCAL )
1037             continue;
1038
1039         if ( file->state == msifs_installed )
1040             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
1041
1042         if ( file->state != msifs_present )
1043             continue;
1044
1045         /* only remove a file if the version to be installed
1046          * is strictly newer than the old file
1047          */
1048         if ( msi_compare_file_version( file ) >= 0 )
1049             continue;
1050
1051         TRACE("removing %s\n", debugstr_w(file->File) );
1052         if ( !DeleteFileW( file->TargetPath ) )
1053             ERR("failed to delete %s\n",  debugstr_w(file->TargetPath) );
1054         file->state = msifs_missing;
1055
1056         /* the UI chunk */
1057         uirow = MSI_CreateRecord( 9 );
1058         MSI_RecordSetStringW( uirow, 1, file->FileName );
1059         uipath = strdupW( file->TargetPath );
1060         p = strrchrW(uipath,'\\');
1061         if (p)
1062             p[1]=0;
1063         MSI_RecordSetStringW( uirow, 9, uipath);
1064         ui_actiondata( package, szRemoveFiles, uirow);
1065         msiobj_release( &uirow->hdr );
1066         msi_free( uipath );
1067         /* FIXME: call ui_progress here? */
1068     }
1069
1070     return ERROR_SUCCESS;
1071 }