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