msi/tests: Fix the scope of todo_wine in the tests for MsiApplyMultiplePatches.
[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
28  * PatchFiles (TODO)
29  * RemoveDuplicateFiles
30  * RemoveFiles
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 "msipriv.h"
43 #include "winuser.h"
44 #include "winreg.h"
45 #include "shlwapi.h"
46 #include "wine/unicode.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
49
50 static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
51 {
52     MSIRECORD *uirow;
53
54     uirow = MSI_CreateRecord( 9 );
55     MSI_RecordSetStringW( uirow, 1, f->FileName );
56     MSI_RecordSetStringW( uirow, 9, f->Component->Directory );
57     MSI_RecordSetInteger( uirow, 6, f->FileSize );
58     ui_actiondata( package, action, uirow );
59     msiobj_release( &uirow->hdr );
60     ui_progress( package, 2, f->FileSize, 0, 0 );
61 }
62
63 static void schedule_install_files(MSIPACKAGE *package)
64 {
65     MSIFILE *file;
66
67     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
68     {
69         MSICOMPONENT *comp = file->Component;
70
71         if (comp->ActionRequest != INSTALLSTATE_LOCAL || !comp->Enabled)
72         {
73             TRACE("File %s is not scheduled for install\n", debugstr_w(file->File));
74             file->state = msifs_skipped;
75             continue;
76         }
77         comp->Action = INSTALLSTATE_LOCAL;
78         ui_progress( package, 2, file->FileSize, 0, 0 );
79
80         if (file->state == msifs_overwrite &&
81             (comp->Attributes & msidbComponentAttributesNeverOverwrite))
82         {
83             TRACE("not overwriting %s\n", debugstr_w(file->TargetPath));
84             file->state = msifs_skipped;
85         }
86     }
87 }
88
89 static UINT copy_file(MSIFILE *file, LPWSTR source)
90 {
91     BOOL ret;
92
93     ret = CopyFileW(source, file->TargetPath, FALSE);
94     if (!ret)
95         return GetLastError();
96
97     SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
98
99     file->state = msifs_installed;
100     return ERROR_SUCCESS;
101 }
102
103 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
104 {
105     UINT gle;
106
107     TRACE("Copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
108
109     gle = copy_file(file, source);
110     if (gle == ERROR_SUCCESS)
111         return gle;
112
113     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
114     {
115         TRACE("overwriting existing file\n");
116         return ERROR_SUCCESS;
117     }
118     else if (gle == ERROR_ACCESS_DENIED)
119     {
120         SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
121
122         gle = copy_file(file, source);
123         TRACE("Overwriting existing file: %d\n", gle);
124     }
125     if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
126     {
127         WCHAR tmpfileW[MAX_PATH], *pathW, *p;
128         DWORD len;
129
130         TRACE("file in use, scheduling rename operation\n");
131
132         GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
133         len = strlenW(file->TargetPath) + strlenW(tmpfileW) + 1;
134         if (!(pathW = msi_alloc(len * sizeof(WCHAR))))
135             return ERROR_OUTOFMEMORY;
136
137         strcpyW(pathW, file->TargetPath);
138         if ((p = strrchrW(pathW, '\\'))) *p = 0;
139         strcatW(pathW, tmpfileW);
140
141         if (CopyFileW(source, pathW, FALSE) &&
142             MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
143             MoveFileExW(pathW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
144         {
145             file->state = msifs_installed;
146             package->need_reboot = 1;
147             gle = ERROR_SUCCESS;
148         }
149         else
150         {
151             gle = GetLastError();
152             WARN("failed to schedule rename operation: %d)\n", gle);
153         }
154         msi_free(pathW);
155     }
156
157     return gle;
158 }
159
160 static UINT msi_create_directory( MSIPACKAGE *package, const WCHAR *dir )
161 {
162     MSIFOLDER *folder;
163     WCHAR *install_path;
164
165     install_path = resolve_folder( package, dir, FALSE, FALSE, TRUE, &folder );
166     if (!install_path)
167         return ERROR_FUNCTION_FAILED;
168
169     if (folder->State == 0)
170     {
171         create_full_pathW( install_path );
172         folder->State = 2;
173     }
174     msi_free( install_path );
175     return ERROR_SUCCESS;
176 }
177
178 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
179                             LPWSTR *path, DWORD *attrs, PVOID user)
180 {
181     static MSIFILE *f = NULL;
182     UINT_PTR disk_id = (UINT_PTR)user;
183
184     if (action == MSICABEXTRACT_BEGINEXTRACT)
185     {
186         f = get_loaded_file(package, file);
187         if (!f)
188         {
189             WARN("unknown file in cabinet (%s)\n", debugstr_w(file));
190             return FALSE;
191         }
192
193         if (f->disk_id != disk_id || (f->state != msifs_missing && f->state != msifs_overwrite))
194             return FALSE;
195
196         msi_file_update_ui(package, f, szInstallFiles);
197         if (!f->Component->assembly || f->Component->assembly->application)
198         {
199             msi_create_directory(package, f->Component->Directory);
200         }
201         *path = strdupW(f->TargetPath);
202         *attrs = f->Attributes;
203     }
204     else if (action == MSICABEXTRACT_FILEEXTRACTED)
205     {
206         f->state = msifs_installed;
207         f = NULL;
208     }
209
210     return TRUE;
211 }
212
213 /*
214  * ACTION_InstallFiles()
215  * 
216  * For efficiency, this is done in two passes:
217  * 1) Correct all the TargetPaths and determine what files are to be installed.
218  * 2) Extract Cabinets and copy files.
219  */
220 UINT ACTION_InstallFiles(MSIPACKAGE *package)
221 {
222     MSIMEDIAINFO *mi;
223     MSICOMPONENT *comp;
224     UINT rc = ERROR_SUCCESS;
225     MSIFILE *file;
226
227     /* increment progress bar each time action data is sent */
228     ui_progress(package,1,1,0,0);
229
230     schedule_install_files(package);
231
232     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
233
234     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
235     {
236         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
237             continue;
238
239         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
240             (file->IsCompressed && !mi->is_extracted))
241         {
242             MSICABDATA data;
243
244             rc = ready_media(package, file, mi);
245             if (rc != ERROR_SUCCESS)
246             {
247                 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
248                 goto done;
249             }
250
251             data.mi = mi;
252             data.package = package;
253             data.cb = installfiles_cb;
254             data.user = (PVOID)(UINT_PTR)mi->disk_id;
255
256             if (file->IsCompressed &&
257                 !msi_cabextract(package, mi, &data))
258             {
259                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
260                 rc = ERROR_INSTALL_FAILURE;
261                 goto done;
262             }
263         }
264
265         if (!file->IsCompressed)
266         {
267             LPWSTR source = resolve_file_source(package, file);
268
269             TRACE("copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
270
271             msi_file_update_ui(package, file, szInstallFiles);
272             if (!file->Component->assembly || file->Component->assembly->application)
273             {
274                 msi_create_directory(package, file->Component->Directory);
275             }
276             rc = copy_install_file(package, file, source);
277             if (rc != ERROR_SUCCESS)
278             {
279                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
280                     debugstr_w(file->TargetPath), rc);
281                 rc = ERROR_INSTALL_FAILURE;
282                 msi_free(source);
283                 goto done;
284             }
285             msi_free(source);
286         }
287         else if (file->state != msifs_installed)
288         {
289             ERR("compressed file wasn't installed (%s)\n", debugstr_w(file->TargetPath));
290             rc = ERROR_INSTALL_FAILURE;
291             goto done;
292         }
293     }
294     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
295     {
296         if (comp->ActionRequest == INSTALLSTATE_LOCAL && comp->Enabled &&
297             comp->assembly && !comp->assembly->installed)
298         {
299             rc = install_assembly( package, comp );
300             if (rc != ERROR_SUCCESS)
301             {
302                 ERR("Failed to install assembly\n");
303                 rc = ERROR_INSTALL_FAILURE;
304                 break;
305             }
306         }
307     }
308
309 done:
310     msi_free_media_info(mi);
311     return rc;
312 }
313
314 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
315
316 typedef struct
317 {
318     struct list entry;
319     LPWSTR sourcename;
320     LPWSTR destname;
321     LPWSTR source;
322     LPWSTR dest;
323 } FILE_LIST;
324
325 static BOOL msi_move_file(LPCWSTR source, LPCWSTR dest, int options)
326 {
327     BOOL ret;
328
329     if (GetFileAttributesW(source) == FILE_ATTRIBUTE_DIRECTORY ||
330         GetFileAttributesW(dest) == FILE_ATTRIBUTE_DIRECTORY)
331     {
332         WARN("Source or dest is directory, not moving\n");
333         return FALSE;
334     }
335
336     if (options == msidbMoveFileOptionsMove)
337     {
338         TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
339         ret = MoveFileExW(source, dest, MOVEFILE_REPLACE_EXISTING);
340         if (!ret)
341         {
342             WARN("MoveFile failed: %d\n", GetLastError());
343             return FALSE;
344         }
345     }
346     else
347     {
348         TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
349         ret = CopyFileW(source, dest, FALSE);
350         if (!ret)
351         {
352             WARN("CopyFile failed: %d\n", GetLastError());
353             return FALSE;
354         }
355     }
356
357     return TRUE;
358 }
359
360 static LPWSTR wildcard_to_file(LPWSTR wildcard, LPWSTR filename)
361 {
362     LPWSTR path, ptr;
363     DWORD dirlen, pathlen;
364
365     ptr = strrchrW(wildcard, '\\');
366     dirlen = ptr - wildcard + 1;
367
368     pathlen = dirlen + lstrlenW(filename) + 1;
369     path = msi_alloc(pathlen * sizeof(WCHAR));
370
371     lstrcpynW(path, wildcard, dirlen + 1);
372     lstrcatW(path, filename);
373
374     return path;
375 }
376
377 static void free_file_entry(FILE_LIST *file)
378 {
379     msi_free(file->source);
380     msi_free(file->dest);
381     msi_free(file);
382 }
383
384 static void free_list(FILE_LIST *list)
385 {
386     while (!list_empty(&list->entry))
387     {
388         FILE_LIST *file = LIST_ENTRY(list_head(&list->entry), FILE_LIST, entry);
389
390         list_remove(&file->entry);
391         free_file_entry(file);
392     }
393 }
394
395 static BOOL add_wildcard(FILE_LIST *files, LPWSTR source, LPWSTR dest)
396 {
397     FILE_LIST *new, *file;
398     LPWSTR ptr, filename;
399     DWORD size;
400
401     new = msi_alloc_zero(sizeof(FILE_LIST));
402     if (!new)
403         return FALSE;
404
405     new->source = strdupW(source);
406     ptr = strrchrW(dest, '\\') + 1;
407     filename = strrchrW(new->source, '\\') + 1;
408
409     new->sourcename = filename;
410
411     if (*ptr)
412         new->destname = ptr;
413     else
414         new->destname = new->sourcename;
415
416     size = (ptr - dest) + lstrlenW(filename) + 1;
417     new->dest = msi_alloc(size * sizeof(WCHAR));
418     if (!new->dest)
419     {
420         free_file_entry(new);
421         return FALSE;
422     }
423
424     lstrcpynW(new->dest, dest, ptr - dest + 1);
425     lstrcatW(new->dest, filename);
426
427     if (list_empty(&files->entry))
428     {
429         list_add_head(&files->entry, &new->entry);
430         return TRUE;
431     }
432
433     LIST_FOR_EACH_ENTRY(file, &files->entry, FILE_LIST, entry)
434     {
435         if (strcmpW( source, file->source ) < 0)
436         {
437             list_add_before(&file->entry, &new->entry);
438             return TRUE;
439         }
440     }
441
442     list_add_after(&file->entry, &new->entry);
443     return TRUE;
444 }
445
446 static BOOL move_files_wildcard(LPWSTR source, LPWSTR dest, int options)
447 {
448     WIN32_FIND_DATAW wfd;
449     HANDLE hfile;
450     LPWSTR path;
451     BOOL res;
452     FILE_LIST files, *file;
453     DWORD size;
454
455     hfile = FindFirstFileW(source, &wfd);
456     if (hfile == INVALID_HANDLE_VALUE) return FALSE;
457
458     list_init(&files.entry);
459
460     for (res = TRUE; res; res = FindNextFileW(hfile, &wfd))
461     {
462         if (is_dot_dir(wfd.cFileName)) continue;
463
464         path = wildcard_to_file(source, wfd.cFileName);
465         if (!path)
466         {
467             res = FALSE;
468             goto done;
469         }
470
471         add_wildcard(&files, path, dest);
472         msi_free(path);
473     }
474
475     /* no files match the wildcard */
476     if (list_empty(&files.entry))
477         goto done;
478
479     /* only the first wildcard match gets renamed to dest */
480     file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
481     size = (strrchrW(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
482     file->dest = msi_realloc(file->dest, size * sizeof(WCHAR));
483     if (!file->dest)
484     {
485         res = FALSE;
486         goto done;
487     }
488
489     /* file->dest may be shorter after the reallocation, so add a NULL
490      * terminator.  This is needed for the call to strrchrW, as there will no
491      * longer be a NULL terminator within the bounds of the allocation in this case.
492      */
493     file->dest[size - 1] = '\0';
494     lstrcpyW(strrchrW(file->dest, '\\') + 1, file->destname);
495
496     while (!list_empty(&files.entry))
497     {
498         file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
499
500         msi_move_file(file->source, file->dest, options);
501
502         list_remove(&file->entry);
503         free_file_entry(file);
504     }
505
506     res = TRUE;
507
508 done:
509     free_list(&files);
510     FindClose(hfile);
511     return res;
512 }
513
514 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
515 {
516     MSIPACKAGE *package = param;
517     MSIRECORD *uirow;
518     MSICOMPONENT *comp;
519     LPCWSTR sourcename, component;
520     LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
521     int options;
522     DWORD size;
523     BOOL ret, wildcards;
524
525     component = MSI_RecordGetString(rec, 2);
526     comp = get_loaded_component(package, component);
527     if (!comp)
528         return ERROR_SUCCESS;
529
530     if (!comp->Enabled)
531     {
532         TRACE("component is disabled\n");
533         return ERROR_SUCCESS;
534     }
535
536     if (comp->ActionRequest != INSTALLSTATE_LOCAL && comp->ActionRequest != INSTALLSTATE_SOURCE)
537     {
538         TRACE("Component not scheduled for installation: %s\n", debugstr_w(component));
539         comp->Action = comp->Installed;
540         return ERROR_SUCCESS;
541     }
542     comp->Action = comp->ActionRequest;
543
544     sourcename = MSI_RecordGetString(rec, 3);
545     options = MSI_RecordGetInteger(rec, 7);
546
547     sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
548     if (!sourcedir)
549         goto done;
550
551     destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
552     if (!destdir)
553         goto done;
554
555     if (!sourcename)
556     {
557         if (GetFileAttributesW(sourcedir) == INVALID_FILE_ATTRIBUTES)
558             goto done;
559
560         source = strdupW(sourcedir);
561         if (!source)
562             goto done;
563     }
564     else
565     {
566         size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
567         source = msi_alloc(size * sizeof(WCHAR));
568         if (!source)
569             goto done;
570
571         lstrcpyW(source, sourcedir);
572         if (source[lstrlenW(source) - 1] != '\\')
573             lstrcatW(source, szBackSlash);
574         lstrcatW(source, sourcename);
575     }
576
577     wildcards = strchrW(source, '*') || strchrW(source, '?');
578
579     if (MSI_RecordIsNull(rec, 4))
580     {
581         if (!wildcards)
582         {
583             destname = strdupW(sourcename);
584             if (!destname)
585                 goto done;
586         }
587     }
588     else
589     {
590         destname = strdupW(MSI_RecordGetString(rec, 4));
591         if (destname)
592             reduce_to_longfilename(destname);
593     }
594
595     size = 0;
596     if (destname)
597         size = lstrlenW(destname);
598
599     size += lstrlenW(destdir) + 2;
600     dest = msi_alloc(size * sizeof(WCHAR));
601     if (!dest)
602         goto done;
603
604     lstrcpyW(dest, destdir);
605     if (dest[lstrlenW(dest) - 1] != '\\')
606         lstrcatW(dest, szBackSlash);
607
608     if (destname)
609         lstrcatW(dest, destname);
610
611     if (GetFileAttributesW(destdir) == INVALID_FILE_ATTRIBUTES)
612     {
613         ret = CreateDirectoryW(destdir, NULL);
614         if (!ret)
615         {
616             WARN("CreateDirectory failed: %d\n", GetLastError());
617             goto done;
618         }
619     }
620
621     if (!wildcards)
622         msi_move_file(source, dest, options);
623     else
624         move_files_wildcard(source, dest, options);
625
626 done:
627     uirow = MSI_CreateRecord( 9 );
628     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
629     MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
630     MSI_RecordSetStringW( uirow, 9, destdir );
631     ui_actiondata( package, szMoveFiles, uirow );
632     msiobj_release( &uirow->hdr );
633
634     msi_free(sourcedir);
635     msi_free(destdir);
636     msi_free(destname);
637     msi_free(source);
638     msi_free(dest);
639
640     return ERROR_SUCCESS;
641 }
642
643 UINT ACTION_MoveFiles( MSIPACKAGE *package )
644 {
645     UINT rc;
646     MSIQUERY *view;
647
648     static const WCHAR ExecSeqQuery[] =
649         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
650          '`','M','o','v','e','F','i','l','e','`',0};
651
652     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
653     if (rc != ERROR_SUCCESS)
654         return ERROR_SUCCESS;
655
656     rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
657     msiobj_release(&view->hdr);
658
659     return rc;
660 }
661
662 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
663 {
664     DWORD len;
665     WCHAR *dst_name, *dst_path, *dst;
666
667     if (MSI_RecordIsNull( row, 4 ))
668     {
669         len = strlenW( src ) + 1;
670         if (!(dst_name = msi_alloc( len * sizeof(WCHAR)))) return NULL;
671         strcpyW( dst_name, strrchrW( src, '\\' ) + 1 );
672     }
673     else
674     {
675         MSI_RecordGetStringW( row, 4, NULL, &len );
676         if (!(dst_name = msi_alloc( ++len * sizeof(WCHAR) ))) return NULL;
677         MSI_RecordGetStringW( row, 4, dst_name, &len );
678         reduce_to_longfilename( dst_name );
679     }
680
681     if (MSI_RecordIsNull( row, 5 ))
682     {
683         WCHAR *p;
684         dst_path = strdupW( src );
685         p = strrchrW( dst_path, '\\' );
686         if (p) *p = 0;
687     }
688     else
689     {
690         const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
691
692         dst_path = resolve_folder( package, dst_key, FALSE, FALSE, TRUE, NULL );
693         if (!dst_path)
694         {
695             /* try a property */
696             dst_path = msi_dup_property( package->db, dst_key );
697             if (!dst_path)
698             {
699                 FIXME("Unable to get destination folder, try AppSearch properties\n");
700                 msi_free( dst_name );
701                 return NULL;
702             }
703         }
704     }
705
706     dst = build_directory_name( 2, dst_path, dst_name );
707     create_full_pathW( dst_path );
708
709     msi_free( dst_name );
710     msi_free( dst_path );
711     return dst;
712 }
713
714 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
715 {
716     MSIPACKAGE *package = param;
717     LPWSTR dest;
718     LPCWSTR file_key, component;
719     MSICOMPONENT *comp;
720     MSIRECORD *uirow;
721     MSIFILE *file;
722
723     component = MSI_RecordGetString(row,2);
724     comp = get_loaded_component(package,component);
725     if (!comp)
726         return ERROR_SUCCESS;
727
728     if (!comp->Enabled)
729     {
730         TRACE("component is disabled\n");
731         return ERROR_SUCCESS;
732     }
733
734     if (comp->ActionRequest != INSTALLSTATE_LOCAL)
735     {
736         TRACE("Component not scheduled for installation %s\n", debugstr_w(component));
737         comp->Action = comp->Installed;
738         return ERROR_SUCCESS;
739     }
740     comp->Action = INSTALLSTATE_LOCAL;
741
742     file_key = MSI_RecordGetString(row,3);
743     if (!file_key)
744     {
745         ERR("Unable to get file key\n");
746         return ERROR_FUNCTION_FAILED;
747     }
748
749     file = get_loaded_file( package, file_key );
750     if (!file)
751     {
752         ERR("Original file unknown %s\n", debugstr_w(file_key));
753         return ERROR_SUCCESS;
754     }
755
756     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
757     if (!dest)
758     {
759         WARN("Unable to get duplicate filename\n");
760         return ERROR_SUCCESS;
761     }
762
763     TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
764
765     if (!CopyFileW( file->TargetPath, dest, TRUE ))
766     {
767         WARN("Failed to copy file %s -> %s (%u)\n",
768              debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError());
769     }
770
771     FIXME("We should track these duplicate files as well\n");   
772
773     uirow = MSI_CreateRecord( 9 );
774     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
775     MSI_RecordSetInteger( uirow, 6, file->FileSize );
776     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
777     ui_actiondata( package, szDuplicateFiles, uirow );
778     msiobj_release( &uirow->hdr );
779
780     msi_free(dest);
781     return ERROR_SUCCESS;
782 }
783
784 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
785 {
786     UINT rc;
787     MSIQUERY * view;
788     static const WCHAR ExecSeqQuery[] =
789         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
790          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
791
792     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
793     if (rc != ERROR_SUCCESS)
794         return ERROR_SUCCESS;
795
796     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
797     msiobj_release(&view->hdr);
798
799     return rc;
800 }
801
802 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
803 {
804     MSIPACKAGE *package = param;
805     LPWSTR dest;
806     LPCWSTR file_key, component;
807     MSICOMPONENT *comp;
808     MSIRECORD *uirow;
809     MSIFILE *file;
810
811     component = MSI_RecordGetString( row, 2 );
812     comp = get_loaded_component( package, component );
813     if (!comp)
814         return ERROR_SUCCESS;
815
816     if (!comp->Enabled)
817     {
818         TRACE("component is disabled\n");
819         return ERROR_SUCCESS;
820     }
821
822     if (comp->ActionRequest != INSTALLSTATE_ABSENT)
823     {
824         TRACE("Component not scheduled for removal %s\n", debugstr_w(component));
825         comp->Action = comp->Installed;
826         return ERROR_SUCCESS;
827     }
828     comp->Action = INSTALLSTATE_ABSENT;
829
830     file_key = MSI_RecordGetString( row, 3 );
831     if (!file_key)
832     {
833         ERR("Unable to get file key\n");
834         return ERROR_FUNCTION_FAILED;
835     }
836
837     file = get_loaded_file( package, file_key );
838     if (!file)
839     {
840         ERR("Original file unknown %s\n", debugstr_w(file_key));
841         return ERROR_SUCCESS;
842     }
843
844     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
845     if (!dest)
846     {
847         WARN("Unable to get duplicate filename\n");
848         return ERROR_SUCCESS;
849     }
850
851     TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
852
853     if (!DeleteFileW( dest ))
854     {
855         WARN("Failed to delete duplicate file %s (%u)\n", debugstr_w(dest), GetLastError());
856     }
857
858     uirow = MSI_CreateRecord( 9 );
859     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
860     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
861     ui_actiondata( package, szRemoveDuplicateFiles, uirow );
862     msiobj_release( &uirow->hdr );
863
864     msi_free(dest);
865     return ERROR_SUCCESS;
866 }
867
868 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
869 {
870     UINT rc;
871     MSIQUERY *view;
872     static const WCHAR query[] =
873         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
874          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
875
876     rc = MSI_DatabaseOpenViewW( package->db, query, &view );
877     if (rc != ERROR_SUCCESS)
878         return ERROR_SUCCESS;
879
880     rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
881     msiobj_release( &view->hdr );
882
883     return rc;
884 }
885
886 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
887 {
888     INSTALLSTATE request = comp->ActionRequest;
889
890     if (request == INSTALLSTATE_UNKNOWN)
891         return FALSE;
892
893     if (install_mode == msidbRemoveFileInstallModeOnInstall &&
894         (request == INSTALLSTATE_LOCAL || request == INSTALLSTATE_SOURCE))
895         return TRUE;
896
897     if (request == INSTALLSTATE_ABSENT)
898     {
899         if (!comp->ComponentId)
900             return FALSE;
901
902         if (install_mode == msidbRemoveFileInstallModeOnRemove)
903             return TRUE;
904     }
905
906     if (install_mode == msidbRemoveFileInstallModeOnBoth)
907         return TRUE;
908
909     return FALSE;
910 }
911
912 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
913 {
914     MSIPACKAGE *package = param;
915     MSICOMPONENT *comp;
916     MSIRECORD *uirow;
917     LPCWSTR component, filename, dirprop;
918     UINT install_mode;
919     LPWSTR dir = NULL, path = NULL;
920     DWORD size;
921     UINT ret = ERROR_SUCCESS;
922
923     component = MSI_RecordGetString(row, 2);
924     filename = MSI_RecordGetString(row, 3);
925     dirprop = MSI_RecordGetString(row, 4);
926     install_mode = MSI_RecordGetInteger(row, 5);
927
928     comp = get_loaded_component(package, component);
929     if (!comp->Enabled)
930     {
931         TRACE("component is disabled\n");
932         return ERROR_SUCCESS;
933     }
934
935     if (!verify_comp_for_removal(comp, install_mode))
936     {
937         TRACE("Skipping removal due to missing conditions\n");
938         comp->Action = comp->Installed;
939         return ERROR_SUCCESS;
940     }
941
942     if (comp->Attributes & msidbComponentAttributesPermanent)
943     {
944         TRACE("permanent component, not removing file\n");
945         return ERROR_SUCCESS;
946     }
947
948     dir = msi_dup_property(package->db, dirprop);
949     if (!dir)
950         return ERROR_OUTOFMEMORY;
951
952     size = (filename != NULL) ? lstrlenW(filename) : 0;
953     size += lstrlenW(dir) + 2;
954     path = msi_alloc(size * sizeof(WCHAR));
955     if (!path)
956     {
957         ret = ERROR_OUTOFMEMORY;
958         goto done;
959     }
960
961     if (filename)
962     {
963         lstrcpyW(path, dir);
964         PathAddBackslashW(path);
965         lstrcatW(path, filename);
966
967         TRACE("Deleting misc file: %s\n", debugstr_w(path));
968         DeleteFileW(path);
969     }
970     else
971     {
972         TRACE("Removing misc directory: %s\n", debugstr_w(dir));
973         RemoveDirectoryW(dir);
974     }
975
976 done:
977     uirow = MSI_CreateRecord( 9 );
978     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
979     MSI_RecordSetStringW( uirow, 9, dir );
980     ui_actiondata( package, szRemoveFiles, uirow );
981     msiobj_release( &uirow->hdr );
982
983     msi_free(path);
984     msi_free(dir);
985     return ret;
986 }
987
988 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
989 {
990     MSIQUERY *view;
991     MSIFILE *file;
992     UINT r;
993
994     static const WCHAR query[] = {
995         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
996         '`','R','e','m','o','v','e','F','i','l','e','`',0};
997     static const WCHAR folder_query[] = {
998         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
999         '`','C','r','e','a','t','e','F','o','l','d','e','r','`',0};
1000
1001     r = MSI_DatabaseOpenViewW(package->db, query, &view);
1002     if (r == ERROR_SUCCESS)
1003     {
1004         MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1005         msiobj_release(&view->hdr);
1006     }
1007
1008     r = MSI_DatabaseOpenViewW(package->db, folder_query, &view);
1009     if (r == ERROR_SUCCESS)
1010         msiobj_release(&view->hdr);
1011
1012     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1013     {
1014         MSIRECORD *uirow;
1015         LPWSTR dir, p;
1016         VS_FIXEDFILEINFO *ver;
1017
1018         if ( file->state == msifs_installed )
1019             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
1020
1021         if ( file->Component->ActionRequest != INSTALLSTATE_ABSENT ||
1022              file->Component->Installed == INSTALLSTATE_SOURCE )
1023             continue;
1024
1025         if (!file->Component->Enabled)
1026         {
1027             TRACE("component is disabled\n");
1028             continue;
1029         }
1030
1031         if (file->Component->Attributes & msidbComponentAttributesPermanent)
1032         {
1033             TRACE("permanent component, not removing file\n");
1034             continue;
1035         }
1036
1037         if (file->Version)
1038         {
1039             ver = msi_get_disk_file_version( file->TargetPath );
1040             if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1041             {
1042                 TRACE("newer version detected, not removing file\n");
1043                 msi_free( ver );
1044                 continue;
1045             }
1046             msi_free( ver );
1047         }
1048
1049         TRACE("removing %s\n", debugstr_w(file->File) );
1050         if (!DeleteFileW( file->TargetPath ))
1051         {
1052             WARN("failed to delete %s\n",  debugstr_w(file->TargetPath));
1053         }
1054         /* FIXME: check persistence for each directory */
1055         else if (r && (dir = strdupW( file->TargetPath )))
1056         {
1057             if ((p = strrchrW( dir, '\\' ))) *p = 0;
1058             RemoveDirectoryW( dir );
1059             msi_free( dir );
1060         }
1061         file->state = msifs_missing;
1062
1063         uirow = MSI_CreateRecord( 9 );
1064         MSI_RecordSetStringW( uirow, 1, file->FileName );
1065         MSI_RecordSetStringW( uirow, 9, file->Component->Directory );
1066         ui_actiondata( package, szRemoveFiles, uirow );
1067         msiobj_release( &uirow->hdr );
1068         /* FIXME: call ui_progress here? */
1069     }
1070
1071     return ERROR_SUCCESS;
1072 }