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