msi: Fix handling of an empty language id list in msi_parse_summary.
[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->Enabled && comp->assembly && !comp->assembly->installed)
297         {
298             rc = install_assembly( package, comp );
299             if (rc != ERROR_SUCCESS)
300             {
301                 ERR("Failed to install assembly\n");
302                 rc = ERROR_INSTALL_FAILURE;
303                 break;
304             }
305         }
306     }
307
308 done:
309     msi_free_media_info(mi);
310     return rc;
311 }
312
313 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
314
315 typedef struct
316 {
317     struct list entry;
318     LPWSTR sourcename;
319     LPWSTR destname;
320     LPWSTR source;
321     LPWSTR dest;
322 } FILE_LIST;
323
324 static BOOL msi_move_file(LPCWSTR source, LPCWSTR dest, int options)
325 {
326     BOOL ret;
327
328     if (GetFileAttributesW(source) == FILE_ATTRIBUTE_DIRECTORY ||
329         GetFileAttributesW(dest) == FILE_ATTRIBUTE_DIRECTORY)
330     {
331         WARN("Source or dest is directory, not moving\n");
332         return FALSE;
333     }
334
335     if (options == msidbMoveFileOptionsMove)
336     {
337         TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
338         ret = MoveFileExW(source, dest, MOVEFILE_REPLACE_EXISTING);
339         if (!ret)
340         {
341             WARN("MoveFile failed: %d\n", GetLastError());
342             return FALSE;
343         }
344     }
345     else
346     {
347         TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
348         ret = CopyFileW(source, dest, FALSE);
349         if (!ret)
350         {
351             WARN("CopyFile failed: %d\n", GetLastError());
352             return FALSE;
353         }
354     }
355
356     return TRUE;
357 }
358
359 static LPWSTR wildcard_to_file(LPWSTR wildcard, LPWSTR filename)
360 {
361     LPWSTR path, ptr;
362     DWORD dirlen, pathlen;
363
364     ptr = strrchrW(wildcard, '\\');
365     dirlen = ptr - wildcard + 1;
366
367     pathlen = dirlen + lstrlenW(filename) + 1;
368     path = msi_alloc(pathlen * sizeof(WCHAR));
369
370     lstrcpynW(path, wildcard, dirlen + 1);
371     lstrcatW(path, filename);
372
373     return path;
374 }
375
376 static void free_file_entry(FILE_LIST *file)
377 {
378     msi_free(file->source);
379     msi_free(file->dest);
380     msi_free(file);
381 }
382
383 static void free_list(FILE_LIST *list)
384 {
385     while (!list_empty(&list->entry))
386     {
387         FILE_LIST *file = LIST_ENTRY(list_head(&list->entry), FILE_LIST, entry);
388
389         list_remove(&file->entry);
390         free_file_entry(file);
391     }
392 }
393
394 static BOOL add_wildcard(FILE_LIST *files, LPWSTR source, LPWSTR dest)
395 {
396     FILE_LIST *new, *file;
397     LPWSTR ptr, filename;
398     DWORD size;
399
400     new = msi_alloc_zero(sizeof(FILE_LIST));
401     if (!new)
402         return FALSE;
403
404     new->source = strdupW(source);
405     ptr = strrchrW(dest, '\\') + 1;
406     filename = strrchrW(new->source, '\\') + 1;
407
408     new->sourcename = filename;
409
410     if (*ptr)
411         new->destname = ptr;
412     else
413         new->destname = new->sourcename;
414
415     size = (ptr - dest) + lstrlenW(filename) + 1;
416     new->dest = msi_alloc(size * sizeof(WCHAR));
417     if (!new->dest)
418     {
419         free_file_entry(new);
420         return FALSE;
421     }
422
423     lstrcpynW(new->dest, dest, ptr - dest + 1);
424     lstrcatW(new->dest, filename);
425
426     if (list_empty(&files->entry))
427     {
428         list_add_head(&files->entry, &new->entry);
429         return TRUE;
430     }
431
432     LIST_FOR_EACH_ENTRY(file, &files->entry, FILE_LIST, entry)
433     {
434         if (strcmpW( source, file->source ) < 0)
435         {
436             list_add_before(&file->entry, &new->entry);
437             return TRUE;
438         }
439     }
440
441     list_add_after(&file->entry, &new->entry);
442     return TRUE;
443 }
444
445 static BOOL move_files_wildcard(LPWSTR source, LPWSTR dest, int options)
446 {
447     WIN32_FIND_DATAW wfd;
448     HANDLE hfile;
449     LPWSTR path;
450     BOOL res;
451     FILE_LIST files, *file;
452     DWORD size;
453
454     hfile = FindFirstFileW(source, &wfd);
455     if (hfile == INVALID_HANDLE_VALUE) return FALSE;
456
457     list_init(&files.entry);
458
459     for (res = TRUE; res; res = FindNextFileW(hfile, &wfd))
460     {
461         if (is_dot_dir(wfd.cFileName)) continue;
462
463         path = wildcard_to_file(source, wfd.cFileName);
464         if (!path)
465         {
466             res = FALSE;
467             goto done;
468         }
469
470         add_wildcard(&files, path, dest);
471         msi_free(path);
472     }
473
474     /* no files match the wildcard */
475     if (list_empty(&files.entry))
476         goto done;
477
478     /* only the first wildcard match gets renamed to dest */
479     file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
480     size = (strrchrW(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
481     file->dest = msi_realloc(file->dest, size * sizeof(WCHAR));
482     if (!file->dest)
483     {
484         res = FALSE;
485         goto done;
486     }
487
488     /* file->dest may be shorter after the reallocation, so add a NULL
489      * terminator.  This is needed for the call to strrchrW, as there will no
490      * longer be a NULL terminator within the bounds of the allocation in this case.
491      */
492     file->dest[size - 1] = '\0';
493     lstrcpyW(strrchrW(file->dest, '\\') + 1, file->destname);
494
495     while (!list_empty(&files.entry))
496     {
497         file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
498
499         msi_move_file(file->source, file->dest, options);
500
501         list_remove(&file->entry);
502         free_file_entry(file);
503     }
504
505     res = TRUE;
506
507 done:
508     free_list(&files);
509     FindClose(hfile);
510     return res;
511 }
512
513 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
514 {
515     MSIPACKAGE *package = param;
516     MSIRECORD *uirow;
517     MSICOMPONENT *comp;
518     LPCWSTR sourcename, component;
519     LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
520     int options;
521     DWORD size;
522     BOOL ret, wildcards;
523
524     component = MSI_RecordGetString(rec, 2);
525     comp = get_loaded_component(package, component);
526     if (!comp)
527         return ERROR_SUCCESS;
528
529     if (!comp->Enabled)
530     {
531         TRACE("component is disabled\n");
532         return ERROR_SUCCESS;
533     }
534
535     if (comp->ActionRequest != INSTALLSTATE_LOCAL && comp->ActionRequest != INSTALLSTATE_SOURCE)
536     {
537         TRACE("Component not scheduled for installation: %s\n", debugstr_w(component));
538         comp->Action = comp->Installed;
539         return ERROR_SUCCESS;
540     }
541     comp->Action = comp->ActionRequest;
542
543     sourcename = MSI_RecordGetString(rec, 3);
544     options = MSI_RecordGetInteger(rec, 7);
545
546     sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
547     if (!sourcedir)
548         goto done;
549
550     destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
551     if (!destdir)
552         goto done;
553
554     if (!sourcename)
555     {
556         if (GetFileAttributesW(sourcedir) == INVALID_FILE_ATTRIBUTES)
557             goto done;
558
559         source = strdupW(sourcedir);
560         if (!source)
561             goto done;
562     }
563     else
564     {
565         size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
566         source = msi_alloc(size * sizeof(WCHAR));
567         if (!source)
568             goto done;
569
570         lstrcpyW(source, sourcedir);
571         if (source[lstrlenW(source) - 1] != '\\')
572             lstrcatW(source, szBackSlash);
573         lstrcatW(source, sourcename);
574     }
575
576     wildcards = strchrW(source, '*') || strchrW(source, '?');
577
578     if (MSI_RecordIsNull(rec, 4))
579     {
580         if (!wildcards)
581         {
582             destname = strdupW(sourcename);
583             if (!destname)
584                 goto done;
585         }
586     }
587     else
588     {
589         destname = strdupW(MSI_RecordGetString(rec, 4));
590         if (destname)
591             reduce_to_longfilename(destname);
592     }
593
594     size = 0;
595     if (destname)
596         size = lstrlenW(destname);
597
598     size += lstrlenW(destdir) + 2;
599     dest = msi_alloc(size * sizeof(WCHAR));
600     if (!dest)
601         goto done;
602
603     lstrcpyW(dest, destdir);
604     if (dest[lstrlenW(dest) - 1] != '\\')
605         lstrcatW(dest, szBackSlash);
606
607     if (destname)
608         lstrcatW(dest, destname);
609
610     if (GetFileAttributesW(destdir) == INVALID_FILE_ATTRIBUTES)
611     {
612         ret = CreateDirectoryW(destdir, NULL);
613         if (!ret)
614         {
615             WARN("CreateDirectory failed: %d\n", GetLastError());
616             goto done;
617         }
618     }
619
620     if (!wildcards)
621         msi_move_file(source, dest, options);
622     else
623         move_files_wildcard(source, dest, options);
624
625 done:
626     uirow = MSI_CreateRecord( 9 );
627     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
628     MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
629     MSI_RecordSetStringW( uirow, 9, destdir );
630     ui_actiondata( package, szMoveFiles, uirow );
631     msiobj_release( &uirow->hdr );
632
633     msi_free(sourcedir);
634     msi_free(destdir);
635     msi_free(destname);
636     msi_free(source);
637     msi_free(dest);
638
639     return ERROR_SUCCESS;
640 }
641
642 UINT ACTION_MoveFiles( MSIPACKAGE *package )
643 {
644     UINT rc;
645     MSIQUERY *view;
646
647     static const WCHAR ExecSeqQuery[] =
648         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
649          '`','M','o','v','e','F','i','l','e','`',0};
650
651     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
652     if (rc != ERROR_SUCCESS)
653         return ERROR_SUCCESS;
654
655     rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
656     msiobj_release(&view->hdr);
657
658     return rc;
659 }
660
661 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
662 {
663     DWORD len;
664     WCHAR *dst_name, *dst_path, *dst;
665
666     if (MSI_RecordIsNull( row, 4 ))
667     {
668         len = strlenW( src ) + 1;
669         if (!(dst_name = msi_alloc( len * sizeof(WCHAR)))) return NULL;
670         strcpyW( dst_name, strrchrW( src, '\\' ) + 1 );
671     }
672     else
673     {
674         MSI_RecordGetStringW( row, 4, NULL, &len );
675         if (!(dst_name = msi_alloc( ++len * sizeof(WCHAR) ))) return NULL;
676         MSI_RecordGetStringW( row, 4, dst_name, &len );
677         reduce_to_longfilename( dst_name );
678     }
679
680     if (MSI_RecordIsNull( row, 5 ))
681     {
682         WCHAR *p;
683         dst_path = strdupW( src );
684         p = strrchrW( dst_path, '\\' );
685         if (p) *p = 0;
686     }
687     else
688     {
689         const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
690
691         dst_path = resolve_folder( package, dst_key, FALSE, FALSE, TRUE, NULL );
692         if (!dst_path)
693         {
694             /* try a property */
695             dst_path = msi_dup_property( package->db, dst_key );
696             if (!dst_path)
697             {
698                 FIXME("Unable to get destination folder, try AppSearch properties\n");
699                 msi_free( dst_name );
700                 return NULL;
701             }
702         }
703     }
704
705     dst = build_directory_name( 2, dst_path, dst_name );
706     create_full_pathW( dst_path );
707
708     msi_free( dst_name );
709     msi_free( dst_path );
710     return dst;
711 }
712
713 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
714 {
715     MSIPACKAGE *package = param;
716     LPWSTR dest;
717     LPCWSTR file_key, component;
718     MSICOMPONENT *comp;
719     MSIRECORD *uirow;
720     MSIFILE *file;
721
722     component = MSI_RecordGetString(row,2);
723     comp = get_loaded_component(package,component);
724     if (!comp)
725         return ERROR_SUCCESS;
726
727     if (!comp->Enabled)
728     {
729         TRACE("component is disabled\n");
730         return ERROR_SUCCESS;
731     }
732
733     if (comp->ActionRequest != INSTALLSTATE_LOCAL)
734     {
735         TRACE("Component not scheduled for installation %s\n", debugstr_w(component));
736         comp->Action = comp->Installed;
737         return ERROR_SUCCESS;
738     }
739     comp->Action = INSTALLSTATE_LOCAL;
740
741     file_key = MSI_RecordGetString(row,3);
742     if (!file_key)
743     {
744         ERR("Unable to get file key\n");
745         return ERROR_FUNCTION_FAILED;
746     }
747
748     file = get_loaded_file( package, file_key );
749     if (!file)
750     {
751         ERR("Original file unknown %s\n", debugstr_w(file_key));
752         return ERROR_SUCCESS;
753     }
754
755     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
756     if (!dest)
757     {
758         WARN("Unable to get duplicate filename\n");
759         return ERROR_SUCCESS;
760     }
761
762     TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
763
764     if (!CopyFileW( file->TargetPath, dest, TRUE ))
765     {
766         WARN("Failed to copy file %s -> %s (%u)\n",
767              debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError());
768     }
769
770     FIXME("We should track these duplicate files as well\n");   
771
772     uirow = MSI_CreateRecord( 9 );
773     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
774     MSI_RecordSetInteger( uirow, 6, file->FileSize );
775     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
776     ui_actiondata( package, szDuplicateFiles, uirow );
777     msiobj_release( &uirow->hdr );
778
779     msi_free(dest);
780     return ERROR_SUCCESS;
781 }
782
783 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
784 {
785     UINT rc;
786     MSIQUERY * view;
787     static const WCHAR ExecSeqQuery[] =
788         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
789          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
790
791     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
792     if (rc != ERROR_SUCCESS)
793         return ERROR_SUCCESS;
794
795     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
796     msiobj_release(&view->hdr);
797
798     return rc;
799 }
800
801 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
802 {
803     MSIPACKAGE *package = param;
804     LPWSTR dest;
805     LPCWSTR file_key, component;
806     MSICOMPONENT *comp;
807     MSIRECORD *uirow;
808     MSIFILE *file;
809
810     component = MSI_RecordGetString( row, 2 );
811     comp = get_loaded_component( package, component );
812     if (!comp)
813         return ERROR_SUCCESS;
814
815     if (!comp->Enabled)
816     {
817         TRACE("component is disabled\n");
818         return ERROR_SUCCESS;
819     }
820
821     if (comp->ActionRequest != INSTALLSTATE_ABSENT)
822     {
823         TRACE("Component not scheduled for removal %s\n", debugstr_w(component));
824         comp->Action = comp->Installed;
825         return ERROR_SUCCESS;
826     }
827     comp->Action = INSTALLSTATE_ABSENT;
828
829     file_key = MSI_RecordGetString( row, 3 );
830     if (!file_key)
831     {
832         ERR("Unable to get file key\n");
833         return ERROR_FUNCTION_FAILED;
834     }
835
836     file = get_loaded_file( package, file_key );
837     if (!file)
838     {
839         ERR("Original file unknown %s\n", debugstr_w(file_key));
840         return ERROR_SUCCESS;
841     }
842
843     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
844     if (!dest)
845     {
846         WARN("Unable to get duplicate filename\n");
847         return ERROR_SUCCESS;
848     }
849
850     TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
851
852     if (!DeleteFileW( dest ))
853     {
854         WARN("Failed to delete duplicate file %s (%u)\n", debugstr_w(dest), GetLastError());
855     }
856
857     uirow = MSI_CreateRecord( 9 );
858     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
859     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
860     ui_actiondata( package, szRemoveDuplicateFiles, uirow );
861     msiobj_release( &uirow->hdr );
862
863     msi_free(dest);
864     return ERROR_SUCCESS;
865 }
866
867 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
868 {
869     UINT rc;
870     MSIQUERY *view;
871     static const WCHAR query[] =
872         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
873          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
874
875     rc = MSI_DatabaseOpenViewW( package->db, query, &view );
876     if (rc != ERROR_SUCCESS)
877         return ERROR_SUCCESS;
878
879     rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
880     msiobj_release( &view->hdr );
881
882     return rc;
883 }
884
885 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
886 {
887     INSTALLSTATE request = comp->ActionRequest;
888
889     if (request == INSTALLSTATE_UNKNOWN)
890         return FALSE;
891
892     if (install_mode == msidbRemoveFileInstallModeOnInstall &&
893         (request == INSTALLSTATE_LOCAL || request == INSTALLSTATE_SOURCE))
894         return TRUE;
895
896     if (request == INSTALLSTATE_ABSENT)
897     {
898         if (!comp->ComponentId)
899             return FALSE;
900
901         if (install_mode == msidbRemoveFileInstallModeOnRemove)
902             return TRUE;
903     }
904
905     if (install_mode == msidbRemoveFileInstallModeOnBoth)
906         return TRUE;
907
908     return FALSE;
909 }
910
911 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
912 {
913     MSIPACKAGE *package = param;
914     MSICOMPONENT *comp;
915     MSIRECORD *uirow;
916     LPCWSTR component, filename, dirprop;
917     UINT install_mode;
918     LPWSTR dir = NULL, path = NULL;
919     DWORD size;
920     UINT ret = ERROR_SUCCESS;
921
922     component = MSI_RecordGetString(row, 2);
923     filename = MSI_RecordGetString(row, 3);
924     dirprop = MSI_RecordGetString(row, 4);
925     install_mode = MSI_RecordGetInteger(row, 5);
926
927     comp = get_loaded_component(package, component);
928     if (!comp->Enabled)
929     {
930         TRACE("component is disabled\n");
931         return ERROR_SUCCESS;
932     }
933
934     if (!verify_comp_for_removal(comp, install_mode))
935     {
936         TRACE("Skipping removal due to missing conditions\n");
937         comp->Action = comp->Installed;
938         return ERROR_SUCCESS;
939     }
940
941     if (comp->Attributes & msidbComponentAttributesPermanent)
942     {
943         TRACE("permanent component, not removing file\n");
944         return ERROR_SUCCESS;
945     }
946
947     dir = msi_dup_property(package->db, dirprop);
948     if (!dir)
949         return ERROR_OUTOFMEMORY;
950
951     size = (filename != NULL) ? lstrlenW(filename) : 0;
952     size += lstrlenW(dir) + 2;
953     path = msi_alloc(size * sizeof(WCHAR));
954     if (!path)
955     {
956         ret = ERROR_OUTOFMEMORY;
957         goto done;
958     }
959
960     if (filename)
961     {
962         lstrcpyW(path, dir);
963         PathAddBackslashW(path);
964         lstrcatW(path, filename);
965
966         TRACE("Deleting misc file: %s\n", debugstr_w(path));
967         DeleteFileW(path);
968     }
969     else
970     {
971         TRACE("Removing misc directory: %s\n", debugstr_w(dir));
972         RemoveDirectoryW(dir);
973     }
974
975 done:
976     uirow = MSI_CreateRecord( 9 );
977     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
978     MSI_RecordSetStringW( uirow, 9, dir );
979     ui_actiondata( package, szRemoveFiles, uirow );
980     msiobj_release( &uirow->hdr );
981
982     msi_free(path);
983     msi_free(dir);
984     return ret;
985 }
986
987 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
988 {
989     MSIQUERY *view;
990     MSIFILE *file;
991     UINT r;
992
993     static const WCHAR query[] = {
994         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
995         '`','R','e','m','o','v','e','F','i','l','e','`',0};
996     static const WCHAR folder_query[] = {
997         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
998         '`','C','r','e','a','t','e','F','o','l','d','e','r','`',0};
999
1000     r = MSI_DatabaseOpenViewW(package->db, query, &view);
1001     if (r == ERROR_SUCCESS)
1002     {
1003         MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1004         msiobj_release(&view->hdr);
1005     }
1006
1007     r = MSI_DatabaseOpenViewW(package->db, folder_query, &view);
1008     if (r == ERROR_SUCCESS)
1009         msiobj_release(&view->hdr);
1010
1011     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1012     {
1013         MSIRECORD *uirow;
1014         LPWSTR dir, p;
1015         VS_FIXEDFILEINFO *ver;
1016
1017         if ( file->state == msifs_installed )
1018             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
1019
1020         if ( file->Component->ActionRequest != INSTALLSTATE_ABSENT ||
1021              file->Component->Installed == INSTALLSTATE_SOURCE )
1022             continue;
1023
1024         if (!file->Component->Enabled)
1025         {
1026             TRACE("component is disabled\n");
1027             continue;
1028         }
1029
1030         if (file->Component->Attributes & msidbComponentAttributesPermanent)
1031         {
1032             TRACE("permanent component, not removing file\n");
1033             continue;
1034         }
1035
1036         if (file->Version)
1037         {
1038             ver = msi_get_disk_file_version( file->TargetPath );
1039             if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1040             {
1041                 TRACE("newer version detected, not removing file\n");
1042                 msi_free( ver );
1043                 continue;
1044             }
1045             msi_free( ver );
1046         }
1047
1048         TRACE("removing %s\n", debugstr_w(file->File) );
1049         if (!DeleteFileW( file->TargetPath ))
1050         {
1051             WARN("failed to delete %s\n",  debugstr_w(file->TargetPath));
1052         }
1053         /* FIXME: check persistence for each directory */
1054         else if (r && (dir = strdupW( file->TargetPath )))
1055         {
1056             if ((p = strrchrW( dir, '\\' ))) *p = 0;
1057             RemoveDirectoryW( dir );
1058             msi_free( dir );
1059         }
1060         file->state = msifs_missing;
1061
1062         uirow = MSI_CreateRecord( 9 );
1063         MSI_RecordSetStringW( uirow, 1, file->FileName );
1064         MSI_RecordSetStringW( uirow, 9, file->Component->Directory );
1065         ui_actiondata( package, szRemoveFiles, uirow );
1066         msiobj_release( &uirow->hdr );
1067         /* FIXME: call ui_progress here? */
1068     }
1069
1070     return ERROR_SUCCESS;
1071 }