xref: /reactos/dll/win32/msi/files.c (revision 84344399)
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  * Actions dealing with files:
23  *
24  * InstallFiles
25  * DuplicateFiles
26  * MoveFiles
27  * PatchFiles
28  * RemoveDuplicateFiles
29  * RemoveFiles
30  */
31 
32 #include <stdarg.h>
33 
34 #define COBJMACROS
35 
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winerror.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 "patchapi.h"
47 #include "wine/debug.h"
48 
49 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50 
51 BOOL msi_get_temp_file_name( MSIPACKAGE *package, const WCHAR *tmp_path, const WCHAR *prefix, WCHAR *tmp_filename )
52 {
53     BOOL ret;
54     msi_disable_fs_redirection( package );
55     ret = GetTempFileNameW( tmp_path, prefix, 0, tmp_filename );
56     msi_revert_fs_redirection( package );
57     return ret;
58 }
59 
60 HANDLE msi_create_file( MSIPACKAGE *package, const WCHAR *filename, DWORD access, DWORD sharing, DWORD creation,
61                         DWORD flags )
62 {
63     HANDLE handle;
64     msi_disable_fs_redirection( package );
65     handle = CreateFileW( filename, access, sharing, NULL, creation, flags, NULL );
66     msi_revert_fs_redirection( package );
67     return handle;
68 }
69 
70 static BOOL copy_file( MSIPACKAGE *package, const WCHAR *src, const WCHAR *dst, BOOL fail_if_exists )
71 {
72     BOOL ret;
73     msi_disable_fs_redirection( package );
74     ret = CopyFileW( src, dst, fail_if_exists );
75     msi_revert_fs_redirection( package );
76     return ret;
77 }
78 
79 BOOL msi_delete_file( MSIPACKAGE *package, const WCHAR *filename )
80 {
81     BOOL ret;
82     msi_disable_fs_redirection( package );
83     ret = DeleteFileW( filename );
84     msi_revert_fs_redirection( package );
85     return ret;
86 }
87 
88 static BOOL create_directory( MSIPACKAGE *package, const WCHAR *path )
89 {
90     BOOL ret;
91     msi_disable_fs_redirection( package );
92     ret = CreateDirectoryW( path, NULL );
93     msi_revert_fs_redirection( package );
94     return ret;
95 }
96 
97 BOOL msi_remove_directory( MSIPACKAGE *package, const WCHAR *path )
98 {
99     BOOL ret;
100     msi_disable_fs_redirection( package );
101     ret = RemoveDirectoryW( path );
102     msi_revert_fs_redirection( package );
103     return ret;
104 }
105 
106 BOOL msi_set_file_attributes( MSIPACKAGE *package, const WCHAR *filename, DWORD attrs )
107 {
108     BOOL ret;
109     msi_disable_fs_redirection( package );
110     ret = SetFileAttributesW( filename, attrs );
111     msi_revert_fs_redirection( package );
112     return ret;
113 }
114 
115 DWORD msi_get_file_attributes( MSIPACKAGE *package, const WCHAR *path )
116 {
117     DWORD attrs;
118     msi_disable_fs_redirection( package );
119     attrs = GetFileAttributesW( path );
120     msi_revert_fs_redirection( package );
121     return attrs;
122 }
123 
124 HANDLE msi_find_first_file( MSIPACKAGE *package, const WCHAR *filename, WIN32_FIND_DATAW *data )
125 {
126     HANDLE handle;
127     msi_disable_fs_redirection( package );
128     handle = FindFirstFileW( filename, data );
129     msi_revert_fs_redirection( package );
130     return handle;
131 }
132 
133 BOOL msi_find_next_file( MSIPACKAGE *package, HANDLE handle, WIN32_FIND_DATAW *data )
134 {
135     BOOL ret;
136     msi_disable_fs_redirection( package );
137     ret = FindNextFileW( handle, data );
138     msi_revert_fs_redirection( package );
139     return ret;
140 }
141 
142 BOOL msi_move_file( MSIPACKAGE *package, const WCHAR *from, const WCHAR *to, DWORD flags )
143 {
144     BOOL ret;
145     msi_disable_fs_redirection( package );
146     ret = MoveFileExW( from, to, flags );
147     msi_revert_fs_redirection( package );
148     return ret;
149 }
150 
151 static BOOL apply_filepatch( MSIPACKAGE *package, const WCHAR *patch, const WCHAR *old, const WCHAR *new )
152 {
153     BOOL ret;
154     msi_disable_fs_redirection( package );
155     ret = ApplyPatchToFileW( patch, old, new, 0 );
156     msi_revert_fs_redirection( package );
157     return ret;
158 }
159 
160 DWORD msi_get_file_version_info( MSIPACKAGE *package, const WCHAR *path, DWORD buflen, BYTE *buffer )
161 {
162     DWORD size, handle;
163     msi_disable_fs_redirection( package );
164     if (buffer) size = GetFileVersionInfoW( path, 0, buflen, buffer );
165     else size = GetFileVersionInfoSizeW( path, &handle );
166     msi_revert_fs_redirection( package );
167     return size;
168 }
169 
170 VS_FIXEDFILEINFO *msi_get_disk_file_version( MSIPACKAGE *package, const WCHAR *filename )
171 {
172     VS_FIXEDFILEINFO *ptr, *ret;
173     DWORD version_size;
174     UINT size;
175     void *version;
176 
177     if (!(version_size = msi_get_file_version_info( package, filename, 0, NULL ))) return NULL;
178     if (!(version = malloc( version_size ))) return NULL;
179 
180     msi_get_file_version_info( package, filename, version_size, version );
181 
182     if (!VerQueryValueW( version, L"\\", (void **)&ptr, &size ))
183     {
184         free( version );
185         return NULL;
186     }
187 
188     if (!(ret = malloc( size )))
189     {
190         free( version );
191         return NULL;
192     }
193 
194     memcpy( ret, ptr, size );
195     free( version );
196     return ret;
197 }
198 
199 DWORD msi_get_disk_file_size( MSIPACKAGE *package, const WCHAR *filename )
200 {
201     DWORD size;
202     HANDLE file;
203     file = msi_create_file( package, filename, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, 0 );
204     if (file == INVALID_HANDLE_VALUE) return INVALID_FILE_SIZE;
205     size = GetFileSize( file, NULL );
206     CloseHandle( file );
207     return size;
208 }
209 
210 /* Recursively create all directories in the path. */
211 BOOL msi_create_full_path( MSIPACKAGE *package, const WCHAR *path )
212 {
213     BOOL ret = TRUE;
214     WCHAR *new_path;
215     int len;
216 
217     if (!(new_path = malloc( (wcslen( path ) + 1) * sizeof(WCHAR) ))) return FALSE;
218     lstrcpyW( new_path, path );
219 
220     while ((len = lstrlenW( new_path )) && new_path[len - 1] == '\\')
221     new_path[len - 1] = 0;
222 
223     while (!create_directory( package, new_path ))
224     {
225         WCHAR *slash;
226         DWORD last_error = GetLastError();
227         if (last_error == ERROR_ALREADY_EXISTS) break;
228         if (last_error != ERROR_PATH_NOT_FOUND)
229         {
230             ret = FALSE;
231             break;
232         }
233         if (!(slash = wcsrchr( new_path, '\\' )))
234         {
235             ret = FALSE;
236             break;
237         }
238         len = slash - new_path;
239         new_path[len] = 0;
240         if (!msi_create_full_path( package, new_path ))
241         {
242             ret = FALSE;
243             break;
244         }
245         new_path[len] = '\\';
246     }
247     free( new_path );
248     return ret;
249 }
250 
251 static void file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
252 {
253     MSIRECORD *uirow;
254 
255     uirow = MSI_CreateRecord( 9 );
256     MSI_RecordSetStringW( uirow, 1, f->FileName );
257     MSI_RecordSetStringW( uirow, 9, f->Component->Directory );
258     MSI_RecordSetInteger( uirow, 6, f->FileSize );
259     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
260     msiobj_release( &uirow->hdr );
261     msi_ui_progress( package, 2, f->FileSize, 0, 0 );
262 }
263 
264 static BOOL is_registered_patch_media( MSIPACKAGE *package, UINT disk_id )
265 {
266     MSIPATCHINFO *patch;
267 
268     LIST_FOR_EACH_ENTRY( patch, &package->patches, MSIPATCHINFO, entry )
269     {
270         if (patch->disk_id == disk_id && patch->registered) return TRUE;
271     }
272     return FALSE;
273 }
274 
275 static BOOL is_obsoleted_by_patch( MSIPACKAGE *package, MSIFILE *file )
276 {
277     if (!list_empty( &package->patches ) && file->disk_id < MSI_INITIAL_MEDIA_TRANSFORM_DISKID)
278     {
279         if (!msi_get_property_int( package->db, L"Installed", 0 )) return FALSE;
280         return TRUE;
281     }
282     if (is_registered_patch_media( package, file->disk_id )) return TRUE;
283     return FALSE;
284 }
285 
286 static BOOL file_hash_matches( MSIPACKAGE *package, MSIFILE *file )
287 {
288     UINT r;
289     MSIFILEHASHINFO hash;
290 
291     hash.dwFileHashInfoSize = sizeof(hash);
292     r = msi_get_filehash( package, file->TargetPath, &hash );
293     if (r != ERROR_SUCCESS)
294         return FALSE;
295 
296     return !memcmp( &hash, &file->hash, sizeof(hash) );
297 }
298 
299 static msi_file_state calculate_install_state( MSIPACKAGE *package, MSIFILE *file )
300 {
301     MSICOMPONENT *comp = file->Component;
302     VS_FIXEDFILEINFO *file_version;
303     WCHAR *font_version;
304     msi_file_state state;
305     DWORD size;
306 
307     comp->Action = msi_get_component_action( package, comp );
308     if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL)
309     {
310         TRACE("skipping %s (not scheduled for install)\n", debugstr_w(file->File));
311         return msifs_skipped;
312     }
313     if (is_obsoleted_by_patch( package, file ))
314     {
315         TRACE("skipping %s (obsoleted by patch)\n", debugstr_w(file->File));
316         return msifs_skipped;
317     }
318     if (msi_get_file_attributes( package, file->TargetPath ) == INVALID_FILE_ATTRIBUTES)
319     {
320         TRACE("installing %s (missing)\n", debugstr_w(file->File));
321         return msifs_missing;
322     }
323     if (file->Version)
324     {
325         if ((file_version = msi_get_disk_file_version( package, file->TargetPath )))
326         {
327             if (msi_compare_file_versions( file_version, file->Version ) < 0)
328             {
329                 TRACE("overwriting %s (new version %s old version %u.%u.%u.%u)\n",
330                       debugstr_w(file->File), debugstr_w(file->Version),
331                       HIWORD(file_version->dwFileVersionMS), LOWORD(file_version->dwFileVersionMS),
332                       HIWORD(file_version->dwFileVersionLS), LOWORD(file_version->dwFileVersionLS));
333                 state = msifs_overwrite;
334             }
335             else
336             {
337                 TRACE("keeping %s (new version %s old version %u.%u.%u.%u)\n",
338                       debugstr_w(file->File), debugstr_w(file->Version),
339                       HIWORD(file_version->dwFileVersionMS), LOWORD(file_version->dwFileVersionMS),
340                       HIWORD(file_version->dwFileVersionLS), LOWORD(file_version->dwFileVersionLS));
341                 state = msifs_present;
342             }
343             free( file_version );
344             return state;
345         }
346         else if ((font_version = msi_get_font_file_version( package, file->TargetPath )))
347         {
348             if (msi_compare_font_versions( font_version, file->Version ) < 0)
349             {
350                 TRACE("overwriting %s (new version %s old version %s)\n",
351                       debugstr_w(file->File), debugstr_w(file->Version), debugstr_w(font_version));
352                 state = msifs_overwrite;
353             }
354             else
355             {
356                 TRACE("keeping %s (new version %s old version %s)\n",
357                       debugstr_w(file->File), debugstr_w(file->Version), debugstr_w(font_version));
358                 state = msifs_present;
359             }
360             free( font_version );
361             return state;
362         }
363     }
364     if ((size = msi_get_disk_file_size( package, file->TargetPath )) != file->FileSize)
365     {
366         TRACE("overwriting %s (old size %lu new size %d)\n", debugstr_w(file->File), size, file->FileSize);
367         return msifs_overwrite;
368     }
369     if (file->hash.dwFileHashInfoSize)
370     {
371         if (file_hash_matches( package, file ))
372         {
373             TRACE("keeping %s (hash match)\n", debugstr_w(file->File));
374             return msifs_hashmatch;
375         }
376         else
377         {
378             TRACE("overwriting %s (hash mismatch)\n", debugstr_w(file->File));
379             return msifs_overwrite;
380         }
381     }
382     /* assume present */
383     TRACE("keeping %s\n", debugstr_w(file->File));
384     return msifs_present;
385 }
386 
387 static void schedule_install_files(MSIPACKAGE *package)
388 {
389     MSIFILE *file;
390 
391     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
392     {
393         MSICOMPONENT *comp = file->Component;
394 
395         file->state = calculate_install_state( package, file );
396         if (file->state == msifs_overwrite && (comp->Attributes & msidbComponentAttributesNeverOverwrite))
397         {
398             TRACE("not overwriting %s\n", debugstr_w(file->TargetPath));
399             file->state = msifs_skipped;
400         }
401     }
402 }
403 
404 static UINT copy_file_attributes( MSIPACKAGE *package, MSIFILE *file, WCHAR *source )
405 {
406     BOOL ret;
407 
408     ret = copy_file( package, source, file->TargetPath, FALSE );
409     if (!ret)
410         return GetLastError();
411 
412     msi_set_file_attributes( package, file->TargetPath, FILE_ATTRIBUTE_NORMAL );
413     return ERROR_SUCCESS;
414 }
415 
416 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
417 {
418     UINT gle;
419 
420     TRACE("Copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
421 
422     gle = copy_file_attributes( package, file, source );
423     if (gle == ERROR_SUCCESS)
424         return gle;
425 
426     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
427     {
428         TRACE("overwriting existing file\n");
429         return ERROR_SUCCESS;
430     }
431     else if (gle == ERROR_ACCESS_DENIED)
432     {
433         msi_set_file_attributes( package, file->TargetPath, FILE_ATTRIBUTE_NORMAL );
434 
435         gle = copy_file_attributes( package, file, source );
436         TRACE("Overwriting existing file: %d\n", gle);
437     }
438     if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
439     {
440         WCHAR *tmpfileW, *pathW, *p;
441         DWORD len;
442 
443         TRACE("file in use, scheduling rename operation\n");
444 
445         if (!(pathW = wcsdup( file->TargetPath ))) return ERROR_OUTOFMEMORY;
446         if ((p = wcsrchr(pathW, '\\'))) *p = 0;
447         len = lstrlenW( pathW ) + 16;
448         if (!(tmpfileW = malloc(len * sizeof(WCHAR))))
449         {
450             free( pathW );
451             return ERROR_OUTOFMEMORY;
452         }
453         if (!GetTempFileNameW( pathW, L"msi", 0, tmpfileW )) tmpfileW[0] = 0;
454         free( pathW );
455 
456         if (copy_file( package, source, tmpfileW, FALSE ) &&
457             msi_move_file( package, file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT ) &&
458             msi_move_file( package, tmpfileW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT ))
459         {
460             package->need_reboot_at_end = 1;
461             gle = ERROR_SUCCESS;
462         }
463         else
464         {
465             gle = GetLastError();
466             WARN("failed to schedule rename operation: %d)\n", gle);
467             DeleteFileW( tmpfileW );
468         }
469         free(tmpfileW);
470     }
471 
472     return gle;
473 }
474 
475 static UINT create_folder( MSIPACKAGE *package, const WCHAR *dir )
476 {
477     MSIFOLDER *folder;
478     const WCHAR *install_path;
479 
480     install_path = msi_get_target_folder( package, dir );
481     if (!install_path) return ERROR_FUNCTION_FAILED;
482 
483     folder = msi_get_loaded_folder( package, dir );
484     if (folder->State == FOLDER_STATE_UNINITIALIZED)
485     {
486         msi_create_full_path( package, install_path );
487         folder->State = FOLDER_STATE_CREATED;
488     }
489     return ERROR_SUCCESS;
490 }
491 
492 static MSIFILE *find_file( MSIPACKAGE *package, UINT disk_id, const WCHAR *filename )
493 {
494     MSIFILE *file;
495 
496     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
497     {
498         if (file->disk_id == disk_id &&
499             file->state != msifs_installed &&
500             !wcsicmp( filename, file->File )) return file;
501     }
502     return NULL;
503 }
504 
505 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR filename, DWORD action,
506                             LPWSTR *path, DWORD *attrs, PVOID user)
507 {
508     MSIFILE *file = *(MSIFILE **)user;
509 
510     if (action == MSICABEXTRACT_BEGINEXTRACT)
511     {
512         if (!(file = find_file( package, file->disk_id, filename )))
513         {
514             TRACE("unknown file in cabinet (%s)\n", debugstr_w(filename));
515             return FALSE;
516         }
517         if (file->state != msifs_missing && file->state != msifs_overwrite)
518             return FALSE;
519 
520         if (!msi_is_global_assembly( file->Component ))
521         {
522             create_folder( package, file->Component->Directory );
523         }
524         *path = wcsdup( file->TargetPath );
525         *attrs = file->Attributes;
526         *(MSIFILE **)user = file;
527     }
528     else if (action == MSICABEXTRACT_FILEEXTRACTED)
529     {
530         if (!msi_is_global_assembly( file->Component )) file->state = msifs_installed;
531     }
532 
533     return TRUE;
534 }
535 
536 WCHAR *msi_resolve_file_source( MSIPACKAGE *package, MSIFILE *file )
537 {
538     WCHAR *p, *path;
539 
540     TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
541 
542     if (file->IsCompressed) return NULL;
543 
544     p = msi_resolve_source_folder( package, file->Component->Directory, NULL );
545     path = msi_build_directory_name( 2, p, file->ShortName );
546 
547     if (file->LongName && msi_get_file_attributes( package, path ) == INVALID_FILE_ATTRIBUTES)
548     {
549         free( path );
550         path = msi_build_directory_name( 2, p, file->LongName );
551     }
552     free( p );
553     TRACE("file %s source resolves to %s\n", debugstr_w(file->File), debugstr_w(path));
554     return path;
555 }
556 
557 /*
558  * ACTION_InstallFiles()
559  *
560  * For efficiency, this is done in two passes:
561  * 1) Correct all the TargetPaths and determine what files are to be installed.
562  * 2) Extract Cabinets and copy files.
563  */
564 UINT ACTION_InstallFiles(MSIPACKAGE *package)
565 {
566     MSIMEDIAINFO *mi;
567     UINT rc = ERROR_SUCCESS;
568     MSIFILE *file;
569 
570     msi_set_sourcedir_props(package, FALSE);
571 
572     if (package->script == SCRIPT_NONE)
573         return msi_schedule_action(package, SCRIPT_INSTALL, L"InstallFiles");
574 
575     schedule_install_files(package);
576     mi = calloc(1, sizeof(MSIMEDIAINFO));
577 
578     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
579     {
580         BOOL is_global_assembly = msi_is_global_assembly( file->Component );
581 
582         file_update_ui( package, file, L"InstallFiles" );
583 
584         rc = msi_load_media_info( package, file->Sequence, mi );
585         if (rc != ERROR_SUCCESS)
586         {
587             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
588             rc = ERROR_FUNCTION_FAILED;
589             goto done;
590         }
591 
592         if (file->state != msifs_hashmatch &&
593             file->state != msifs_skipped &&
594             (file->state != msifs_present || !msi_get_property_int( package->db, L"Installed", 0 )) &&
595             (rc = ready_media( package, file->IsCompressed, mi )))
596         {
597             ERR("Failed to ready media for %s\n", debugstr_w(file->File));
598             goto done;
599         }
600 
601         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
602             continue;
603 
604         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
605             (file->IsCompressed && !mi->is_extracted))
606         {
607             MSICABDATA data;
608             MSIFILE *cursor = file;
609 
610             data.mi = mi;
611             data.package = package;
612             data.cb = installfiles_cb;
613             data.user = &cursor;
614 
615             if (file->IsCompressed && !msi_cabextract(package, mi, &data))
616             {
617                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
618                 rc = ERROR_INSTALL_FAILURE;
619                 goto done;
620             }
621         }
622 
623         if (!file->IsCompressed)
624         {
625             WCHAR *source = msi_resolve_file_source(package, file);
626 
627             TRACE("copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
628 
629             if (!is_global_assembly)
630             {
631                 create_folder(package, file->Component->Directory);
632             }
633             rc = copy_install_file(package, file, source);
634             if (rc != ERROR_SUCCESS)
635             {
636                 ERR("Failed to copy %s to %s (%u)\n", debugstr_w(source), debugstr_w(file->TargetPath), rc);
637                 rc = ERROR_INSTALL_FAILURE;
638                 free(source);
639                 goto done;
640             }
641             if (!is_global_assembly) file->state = msifs_installed;
642             free(source);
643         }
644         else if (!is_global_assembly && file->state != msifs_installed &&
645                  !(file->Attributes & msidbFileAttributesPatchAdded))
646         {
647             ERR("compressed file wasn't installed (%s)\n", debugstr_w(file->File));
648             rc = ERROR_INSTALL_FAILURE;
649             goto done;
650         }
651     }
652 
653 done:
654     msi_free_media_info(mi);
655     return rc;
656 }
657 
658 static MSIFILEPATCH *find_filepatch( MSIPACKAGE *package, UINT disk_id, const WCHAR *key )
659 {
660     MSIFILEPATCH *patch;
661 
662     LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
663     {
664         if (!patch->extracted && patch->disk_id == disk_id && !wcscmp( key, patch->File->File ))
665             return patch;
666     }
667     return NULL;
668 }
669 
670 static BOOL patchfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
671                           LPWSTR *path, DWORD *attrs, PVOID user)
672 {
673     MSIFILEPATCH *patch = *(MSIFILEPATCH **)user;
674 
675     if (action == MSICABEXTRACT_BEGINEXTRACT)
676     {
677         MSICOMPONENT *comp;
678 
679         if (is_registered_patch_media( package, patch->disk_id ) ||
680             !(patch = find_filepatch( package, patch->disk_id, file ))) return FALSE;
681 
682         comp = patch->File->Component;
683         comp->Action = msi_get_component_action( package, comp );
684         if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL)
685         {
686             TRACE("file %s component %s not installed or disabled\n",
687                   debugstr_w(patch->File->File), debugstr_w(comp->Component));
688             return FALSE;
689         }
690 
691         patch->path = msi_create_temp_file( package->db );
692         *path = wcsdup( patch->path );
693         *attrs = patch->File->Attributes;
694         *(MSIFILEPATCH **)user = patch;
695     }
696     else if (action == MSICABEXTRACT_FILEEXTRACTED)
697     {
698         patch->extracted = TRUE;
699     }
700 
701     return TRUE;
702 }
703 
704 static UINT patch_file( MSIPACKAGE *package, MSIFILEPATCH *patch )
705 {
706     UINT r = ERROR_SUCCESS;
707     WCHAR *tmpfile = msi_create_temp_file( package->db );
708 
709     if (!tmpfile) return ERROR_INSTALL_FAILURE;
710     if (apply_filepatch( package, patch->path, patch->File->TargetPath, tmpfile ))
711     {
712         msi_delete_file( package, patch->File->TargetPath );
713         msi_move_file( package, tmpfile, patch->File->TargetPath, 0 );
714     }
715     else
716     {
717         WARN( "failed to patch %s: %#lx\n", debugstr_w(patch->File->TargetPath), GetLastError() );
718         r = ERROR_INSTALL_FAILURE;
719     }
720     DeleteFileW( patch->path );
721     DeleteFileW( tmpfile );
722     free( tmpfile );
723     return r;
724 }
725 
726 UINT msi_patch_assembly( MSIPACKAGE *package, MSIASSEMBLY *assembly, MSIFILEPATCH *patch )
727 {
728     UINT r = ERROR_FUNCTION_FAILED;
729     IAssemblyName *name;
730     IAssemblyEnum *iter;
731 
732     if (!(iter = msi_create_assembly_enum( package, assembly->display_name )))
733         return ERROR_FUNCTION_FAILED;
734 
735     while ((IAssemblyEnum_GetNextAssembly( iter, NULL, &name, 0 ) == S_OK))
736     {
737         WCHAR *displayname, *path;
738         DWORD len = 0;
739         HRESULT hr;
740 
741         hr = IAssemblyName_GetDisplayName( name, NULL, &len, 0 );
742         if (hr != E_NOT_SUFFICIENT_BUFFER || !(displayname = malloc( len * sizeof(WCHAR) )))
743             break;
744 
745         hr = IAssemblyName_GetDisplayName( name, displayname, &len, 0 );
746         if (FAILED( hr ))
747         {
748             free( displayname );
749             break;
750         }
751 
752         if ((path = msi_get_assembly_path( package, displayname )))
753         {
754             if (!copy_file( package, path, patch->File->TargetPath, FALSE ))
755             {
756                 ERR( "failed to copy file %s -> %s (%lu)\n", debugstr_w(path),
757                      debugstr_w(patch->File->TargetPath), GetLastError() );
758                 free( path );
759                 free( displayname );
760                 IAssemblyName_Release( name );
761                 break;
762             }
763             r = patch_file( package, patch );
764             free( path );
765         }
766 
767         free( displayname );
768         IAssemblyName_Release( name );
769         if (r == ERROR_SUCCESS) break;
770     }
771 
772     IAssemblyEnum_Release( iter );
773     return r;
774 }
775 
776 UINT ACTION_PatchFiles( MSIPACKAGE *package )
777 {
778     MSIFILEPATCH *patch;
779     MSIMEDIAINFO *mi;
780     UINT rc = ERROR_SUCCESS;
781 
782     TRACE("%p\n", package);
783 
784     if (package->script == SCRIPT_NONE)
785         return msi_schedule_action(package, SCRIPT_INSTALL, L"PatchFiles");
786 
787     mi = calloc( 1, sizeof(MSIMEDIAINFO) );
788 
789     TRACE("extracting files\n");
790 
791     LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
792     {
793         MSIFILE *file = patch->File;
794         MSICOMPONENT *comp = file->Component;
795 
796         rc = msi_load_media_info( package, patch->Sequence, mi );
797         if (rc != ERROR_SUCCESS)
798         {
799             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
800             rc = ERROR_FUNCTION_FAILED;
801             goto done;
802         }
803         comp->Action = msi_get_component_action( package, comp );
804         if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL) continue;
805 
806         if (!patch->extracted)
807         {
808             MSICABDATA data;
809             MSIFILEPATCH *cursor = patch;
810 
811             rc = ready_media( package, TRUE, mi );
812             if (rc != ERROR_SUCCESS)
813             {
814                 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
815                 goto done;
816             }
817             data.mi      = mi;
818             data.package = package;
819             data.cb      = patchfiles_cb;
820             data.user    = &cursor;
821 
822             if (!msi_cabextract( package, mi, &data ))
823             {
824                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
825                 rc = ERROR_INSTALL_FAILURE;
826                 goto done;
827             }
828         }
829     }
830 
831     TRACE("applying patches\n");
832 
833     LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
834     {
835         MSICOMPONENT *comp = patch->File->Component;
836 
837         if (msi_is_global_assembly( comp ) || !patch->path) continue;
838 
839         rc = patch_file( package, patch );
840         if (rc && !(patch->Attributes & msidbPatchAttributesNonVital))
841         {
842             ERR("Failed to apply patch to file: %s\n", debugstr_w(patch->File->File));
843             break;
844         }
845     }
846 
847 done:
848     msi_free_media_info(mi);
849     return rc;
850 }
851 
852 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
853 
854 struct file_list
855 {
856     struct list entry;
857     LPWSTR sourcename;
858     LPWSTR destname;
859     LPWSTR source;
860     LPWSTR dest;
861 };
862 
863 static BOOL move_file( MSIPACKAGE *package, const WCHAR *source, const WCHAR *dest, int options )
864 {
865     BOOL ret;
866 
867     if (msi_get_file_attributes( package, source ) == FILE_ATTRIBUTE_DIRECTORY ||
868         msi_get_file_attributes( package, dest ) == FILE_ATTRIBUTE_DIRECTORY)
869     {
870         WARN("Source or dest is directory, not moving\n");
871         return FALSE;
872     }
873 
874     if (options == msidbMoveFileOptionsMove)
875     {
876         TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
877         ret = msi_move_file( package, source, dest, MOVEFILE_REPLACE_EXISTING );
878         if (!ret)
879         {
880             WARN( "msi_move_file failed: %lu\n", GetLastError() );
881             return FALSE;
882         }
883     }
884     else
885     {
886         TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
887         ret = copy_file( package, source, dest, FALSE );
888         if (!ret)
889         {
890             WARN( "copy_file failed: %lu\n", GetLastError() );
891             return FALSE;
892         }
893     }
894 
895     return TRUE;
896 }
897 
898 static WCHAR *wildcard_to_file( const WCHAR *wildcard, const WCHAR *filename )
899 {
900     const WCHAR *ptr;
901     WCHAR *path;
902     DWORD dirlen, pathlen;
903 
904     ptr = wcsrchr(wildcard, '\\');
905     dirlen = ptr - wildcard + 1;
906 
907     pathlen = dirlen + lstrlenW(filename) + 1;
908     if (!(path = malloc(pathlen * sizeof(WCHAR)))) return NULL;
909 
910     lstrcpynW(path, wildcard, dirlen + 1);
911     lstrcatW(path, filename);
912 
913     return path;
914 }
915 
916 static void free_file_entry(struct file_list *file)
917 {
918     free(file->source);
919     free(file->dest);
920     free(file);
921 }
922 
923 static void free_list(struct file_list *list)
924 {
925     while (!list_empty(&list->entry))
926     {
927         struct file_list *file = LIST_ENTRY(list_head(&list->entry), struct file_list, entry);
928 
929         list_remove(&file->entry);
930         free_file_entry(file);
931     }
932 }
933 
934 static BOOL add_wildcard( struct file_list *files, const WCHAR *source, WCHAR *dest )
935 {
936     struct file_list *new, *file;
937     WCHAR *ptr, *filename;
938     DWORD size;
939 
940     new = calloc(1, sizeof(*new));
941     if (!new)
942         return FALSE;
943 
944     new->source = wcsdup(source);
945     ptr = wcsrchr(dest, '\\') + 1;
946     filename = wcsrchr(new->source, '\\') + 1;
947 
948     new->sourcename = filename;
949 
950     if (*ptr)
951         new->destname = ptr;
952     else
953         new->destname = new->sourcename;
954 
955     size = (ptr - dest) + lstrlenW(filename) + 1;
956     new->dest = malloc(size * sizeof(WCHAR));
957     if (!new->dest)
958     {
959         free_file_entry(new);
960         return FALSE;
961     }
962 
963     lstrcpynW(new->dest, dest, ptr - dest + 1);
964     lstrcatW(new->dest, filename);
965 
966     if (list_empty(&files->entry))
967     {
968         list_add_head(&files->entry, &new->entry);
969         return TRUE;
970     }
971 
972     LIST_FOR_EACH_ENTRY(file, &files->entry, struct file_list, entry)
973     {
974         if (wcscmp( source, file->source ) < 0)
975         {
976             list_add_before(&file->entry, &new->entry);
977             return TRUE;
978         }
979     }
980 
981     list_add_after(&file->entry, &new->entry);
982     return TRUE;
983 }
984 
985 static BOOL move_files_wildcard( MSIPACKAGE *package, const WCHAR *source, WCHAR *dest, int options )
986 {
987     WIN32_FIND_DATAW wfd;
988     HANDLE hfile;
989     LPWSTR path;
990     BOOL res;
991     struct file_list files, *file;
992     DWORD size;
993 
994     hfile = msi_find_first_file( package, source, &wfd );
995     if (hfile == INVALID_HANDLE_VALUE) return FALSE;
996 
997     list_init(&files.entry);
998 
999     for (res = TRUE; res; res = msi_find_next_file( package, hfile, &wfd ))
1000     {
1001         if (is_dot_dir(wfd.cFileName)) continue;
1002 
1003         path = wildcard_to_file( source, wfd.cFileName );
1004         if (!path)
1005         {
1006             res = FALSE;
1007             goto done;
1008         }
1009 
1010         add_wildcard(&files, path, dest);
1011         free(path);
1012     }
1013 
1014     /* no files match the wildcard */
1015     if (list_empty(&files.entry))
1016         goto done;
1017 
1018     /* only the first wildcard match gets renamed to dest */
1019     file = LIST_ENTRY(list_head(&files.entry), struct file_list, entry);
1020     size = (wcsrchr(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
1021     file->dest = realloc(file->dest, size * sizeof(WCHAR));
1022     if (!file->dest)
1023     {
1024         res = FALSE;
1025         goto done;
1026     }
1027 
1028     /* file->dest may be shorter after the reallocation, so add a NULL
1029      * terminator.  This is needed for the call to wcsrchr, as there will no
1030      * longer be a NULL terminator within the bounds of the allocation in this case.
1031      */
1032     file->dest[size - 1] = '\0';
1033     lstrcpyW(wcsrchr(file->dest, '\\') + 1, file->destname);
1034 
1035     while (!list_empty(&files.entry))
1036     {
1037         file = LIST_ENTRY(list_head(&files.entry), struct file_list, entry);
1038 
1039         move_file( package, file->source, file->dest, options );
1040 
1041         list_remove(&file->entry);
1042         free_file_entry(file);
1043     }
1044 
1045     res = TRUE;
1046 
1047 done:
1048     free_list(&files);
1049     FindClose(hfile);
1050     return res;
1051 }
1052 
1053 void msi_reduce_to_long_filename( WCHAR *filename )
1054 {
1055     WCHAR *p = wcschr( filename, '|' );
1056     if (p) memmove( filename, p + 1, (lstrlenW( p + 1 ) + 1) * sizeof(WCHAR) );
1057 }
1058 
1059 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
1060 {
1061     MSIPACKAGE *package = param;
1062     MSIRECORD *uirow;
1063     MSICOMPONENT *comp;
1064     LPCWSTR sourcename, component;
1065     LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
1066     int options;
1067     DWORD size;
1068     BOOL wildcards;
1069 
1070     component = MSI_RecordGetString(rec, 2);
1071     comp = msi_get_loaded_component(package, component);
1072     if (!comp)
1073         return ERROR_SUCCESS;
1074 
1075     comp->Action = msi_get_component_action( package, comp );
1076     if (comp->Action != INSTALLSTATE_LOCAL)
1077     {
1078         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
1079         return ERROR_SUCCESS;
1080     }
1081 
1082     sourcename = MSI_RecordGetString(rec, 3);
1083     options = MSI_RecordGetInteger(rec, 7);
1084 
1085     sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
1086     if (!sourcedir)
1087         goto done;
1088 
1089     destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
1090     if (!destdir)
1091         goto done;
1092 
1093     if (!sourcename)
1094     {
1095         if (msi_get_file_attributes( package, sourcedir ) == INVALID_FILE_ATTRIBUTES)
1096             goto done;
1097 
1098         source = wcsdup(sourcedir);
1099         if (!source)
1100             goto done;
1101     }
1102     else
1103     {
1104         size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
1105         source = malloc(size * sizeof(WCHAR));
1106         if (!source)
1107             goto done;
1108 
1109         lstrcpyW(source, sourcedir);
1110         if (source[lstrlenW(source) - 1] != '\\')
1111             lstrcatW(source, L"\\");
1112         lstrcatW(source, sourcename);
1113     }
1114 
1115     wildcards = wcschr(source, '*') || wcschr(source, '?');
1116 
1117     if (MSI_RecordIsNull(rec, 4))
1118     {
1119         if (!wildcards)
1120         {
1121             WCHAR *p;
1122             if (sourcename)
1123                 destname = wcsdup(sourcename);
1124             else if ((p = wcsrchr(sourcedir, '\\')))
1125                 destname = wcsdup(p + 1);
1126             else
1127                 destname = wcsdup(sourcedir);
1128             if (!destname)
1129                 goto done;
1130         }
1131     }
1132     else
1133     {
1134         destname = wcsdup(MSI_RecordGetString(rec, 4));
1135         if (destname) msi_reduce_to_long_filename(destname);
1136     }
1137 
1138     size = 0;
1139     if (destname)
1140         size = lstrlenW(destname);
1141 
1142     size += lstrlenW(destdir) + 2;
1143     dest = malloc(size * sizeof(WCHAR));
1144     if (!dest)
1145         goto done;
1146 
1147     lstrcpyW(dest, destdir);
1148     if (dest[lstrlenW(dest) - 1] != '\\')
1149         lstrcatW(dest, L"\\");
1150 
1151     if (destname)
1152         lstrcatW(dest, destname);
1153 
1154     if (msi_get_file_attributes( package, destdir ) == INVALID_FILE_ATTRIBUTES)
1155     {
1156         if (!msi_create_full_path( package, destdir ))
1157         {
1158             WARN( "failed to create directory %lu\n", GetLastError() );
1159             goto done;
1160         }
1161     }
1162 
1163     if (!wildcards)
1164         move_file( package, source, dest, options );
1165     else
1166         move_files_wildcard( package, source, dest, options );
1167 
1168 done:
1169     uirow = MSI_CreateRecord( 9 );
1170     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
1171     MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
1172     MSI_RecordSetStringW( uirow, 9, destdir );
1173     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1174     msiobj_release( &uirow->hdr );
1175 
1176     free(sourcedir);
1177     free(destdir);
1178     free(destname);
1179     free(source);
1180     free(dest);
1181 
1182     return ERROR_SUCCESS;
1183 }
1184 
1185 UINT ACTION_MoveFiles( MSIPACKAGE *package )
1186 {
1187     MSIQUERY *view;
1188     UINT rc;
1189 
1190     if (package->script == SCRIPT_NONE)
1191         return msi_schedule_action(package, SCRIPT_INSTALL, L"MoveFiles");
1192 
1193     rc = MSI_DatabaseOpenViewW(package->db, L"SELECT * FROM `MoveFile`", &view);
1194     if (rc != ERROR_SUCCESS)
1195         return ERROR_SUCCESS;
1196 
1197     rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
1198     msiobj_release(&view->hdr);
1199     return rc;
1200 }
1201 
1202 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
1203 {
1204     DWORD len;
1205     WCHAR *dst_name, *dst_path, *dst;
1206 
1207     if (MSI_RecordIsNull( row, 4 ))
1208     {
1209         len = lstrlenW( src ) + 1;
1210         if (!(dst_name = malloc( len * sizeof(WCHAR)))) return NULL;
1211         lstrcpyW( dst_name, wcsrchr( src, '\\' ) + 1 );
1212     }
1213     else
1214     {
1215         MSI_RecordGetStringW( row, 4, NULL, &len );
1216         if (!(dst_name = malloc( ++len * sizeof(WCHAR) ))) return NULL;
1217         MSI_RecordGetStringW( row, 4, dst_name, &len );
1218         msi_reduce_to_long_filename( dst_name );
1219     }
1220 
1221     if (MSI_RecordIsNull( row, 5 ))
1222     {
1223         WCHAR *p;
1224         dst_path = wcsdup( src );
1225         p = wcsrchr( dst_path, '\\' );
1226         if (p) *p = 0;
1227     }
1228     else
1229     {
1230         const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
1231 
1232         dst_path = wcsdup( msi_get_target_folder( package, dst_key ) );
1233         if (!dst_path)
1234         {
1235             /* try a property */
1236             dst_path = msi_dup_property( package->db, dst_key );
1237             if (!dst_path)
1238             {
1239                 FIXME("Unable to get destination folder, try AppSearch properties\n");
1240                 free( dst_name );
1241                 return NULL;
1242             }
1243         }
1244     }
1245 
1246     dst = msi_build_directory_name( 2, dst_path, dst_name );
1247     msi_create_full_path( package, dst_path );
1248 
1249     free( dst_name );
1250     free( dst_path );
1251     return dst;
1252 }
1253 
1254 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
1255 {
1256     MSIPACKAGE *package = param;
1257     LPWSTR dest;
1258     LPCWSTR file_key, component;
1259     MSICOMPONENT *comp;
1260     MSIRECORD *uirow;
1261     MSIFILE *file;
1262 
1263     component = MSI_RecordGetString(row,2);
1264     comp = msi_get_loaded_component(package, component);
1265     if (!comp)
1266         return ERROR_SUCCESS;
1267 
1268     comp->Action = msi_get_component_action( package, comp );
1269     if (comp->Action != INSTALLSTATE_LOCAL)
1270     {
1271         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
1272         return ERROR_SUCCESS;
1273     }
1274 
1275     file_key = MSI_RecordGetString(row,3);
1276     if (!file_key)
1277     {
1278         ERR("Unable to get file key\n");
1279         return ERROR_FUNCTION_FAILED;
1280     }
1281 
1282     file = msi_get_loaded_file( package, file_key );
1283     if (!file)
1284     {
1285         ERR("Original file unknown %s\n", debugstr_w(file_key));
1286         return ERROR_SUCCESS;
1287     }
1288 
1289     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1290     if (!dest)
1291     {
1292         WARN("Unable to get duplicate filename\n");
1293         return ERROR_SUCCESS;
1294     }
1295 
1296     TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
1297     if (!copy_file( package, file->TargetPath, dest, TRUE ))
1298     {
1299         WARN( "failed to copy file %s -> %s (%lu)\n",
1300               debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError() );
1301     }
1302     FIXME("We should track these duplicate files as well\n");
1303 
1304     uirow = MSI_CreateRecord( 9 );
1305     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1306     MSI_RecordSetInteger( uirow, 6, file->FileSize );
1307     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1308     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1309     msiobj_release( &uirow->hdr );
1310 
1311     free(dest);
1312     return ERROR_SUCCESS;
1313 }
1314 
1315 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
1316 {
1317     MSIQUERY *view;
1318     UINT rc;
1319 
1320     if (package->script == SCRIPT_NONE)
1321         return msi_schedule_action(package, SCRIPT_INSTALL, L"DuplicateFiles");
1322 
1323     rc = MSI_DatabaseOpenViewW(package->db, L"SELECT * FROM `DuplicateFile`", &view);
1324     if (rc != ERROR_SUCCESS)
1325         return ERROR_SUCCESS;
1326 
1327     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
1328     msiobj_release(&view->hdr);
1329     return rc;
1330 }
1331 
1332 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
1333 {
1334     MSIPACKAGE *package = param;
1335     LPWSTR dest;
1336     LPCWSTR file_key, component;
1337     MSICOMPONENT *comp;
1338     MSIRECORD *uirow;
1339     MSIFILE *file;
1340 
1341     component = MSI_RecordGetString( row, 2 );
1342     comp = msi_get_loaded_component( package, component );
1343     if (!comp)
1344         return ERROR_SUCCESS;
1345 
1346     comp->Action = msi_get_component_action( package, comp );
1347     if (comp->Action != INSTALLSTATE_ABSENT)
1348     {
1349         TRACE("component not scheduled for removal %s\n", debugstr_w(component));
1350         return ERROR_SUCCESS;
1351     }
1352 
1353     file_key = MSI_RecordGetString( row, 3 );
1354     if (!file_key)
1355     {
1356         ERR("Unable to get file key\n");
1357         return ERROR_FUNCTION_FAILED;
1358     }
1359 
1360     file = msi_get_loaded_file( package, file_key );
1361     if (!file)
1362     {
1363         ERR("Original file unknown %s\n", debugstr_w(file_key));
1364         return ERROR_SUCCESS;
1365     }
1366 
1367     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1368     if (!dest)
1369     {
1370         WARN("Unable to get duplicate filename\n");
1371         return ERROR_SUCCESS;
1372     }
1373 
1374     TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
1375     if (!msi_delete_file( package, dest ))
1376     {
1377         WARN( "failed to delete duplicate file %s (%lu)\n", debugstr_w(dest), GetLastError() );
1378     }
1379 
1380     uirow = MSI_CreateRecord( 9 );
1381     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1382     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1383     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1384     msiobj_release( &uirow->hdr );
1385 
1386     free(dest);
1387     return ERROR_SUCCESS;
1388 }
1389 
1390 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
1391 {
1392     MSIQUERY *view;
1393     UINT rc;
1394 
1395     if (package->script == SCRIPT_NONE)
1396         return msi_schedule_action(package, SCRIPT_INSTALL, L"RemoveDuplicateFiles");
1397 
1398     rc = MSI_DatabaseOpenViewW( package->db, L"SELECT * FROM `DuplicateFile`", &view );
1399     if (rc != ERROR_SUCCESS)
1400         return ERROR_SUCCESS;
1401 
1402     rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
1403     msiobj_release( &view->hdr );
1404     return rc;
1405 }
1406 
1407 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
1408 {
1409     /* special case */
1410     if (comp->Action != INSTALLSTATE_SOURCE &&
1411         comp->Attributes & msidbComponentAttributesSourceOnly &&
1412         (install_mode == msidbRemoveFileInstallModeOnRemove ||
1413          install_mode == msidbRemoveFileInstallModeOnBoth)) return TRUE;
1414 
1415     switch (comp->Action)
1416     {
1417     case INSTALLSTATE_LOCAL:
1418     case INSTALLSTATE_SOURCE:
1419         if (install_mode == msidbRemoveFileInstallModeOnInstall ||
1420             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1421         break;
1422     case INSTALLSTATE_ABSENT:
1423         if (install_mode == msidbRemoveFileInstallModeOnRemove ||
1424             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1425         break;
1426     default: break;
1427     }
1428     return FALSE;
1429 }
1430 
1431 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
1432 {
1433     MSIPACKAGE *package = param;
1434     MSICOMPONENT *comp;
1435     MSIRECORD *uirow;
1436     LPCWSTR component, dirprop;
1437     UINT install_mode;
1438     LPWSTR dir = NULL, path = NULL, filename = NULL;
1439     DWORD size;
1440     UINT ret = ERROR_SUCCESS;
1441 
1442     component = MSI_RecordGetString(row, 2);
1443     dirprop = MSI_RecordGetString(row, 4);
1444     install_mode = MSI_RecordGetInteger(row, 5);
1445 
1446     comp = msi_get_loaded_component(package, component);
1447     if (!comp)
1448         return ERROR_SUCCESS;
1449 
1450     comp->Action = msi_get_component_action( package, comp );
1451     if (!verify_comp_for_removal(comp, install_mode))
1452     {
1453         TRACE("Skipping removal due to install mode\n");
1454         return ERROR_SUCCESS;
1455     }
1456     if (comp->assembly && !comp->assembly->application)
1457     {
1458         return ERROR_SUCCESS;
1459     }
1460     if (comp->Attributes & msidbComponentAttributesPermanent)
1461     {
1462         TRACE("permanent component, not removing file\n");
1463         return ERROR_SUCCESS;
1464     }
1465 
1466     dir = msi_dup_property(package->db, dirprop);
1467     if (!dir)
1468     {
1469         WARN("directory property has no value\n");
1470         return ERROR_SUCCESS;
1471     }
1472     size = 0;
1473     if ((filename = wcsdup( MSI_RecordGetString(row, 3) )))
1474     {
1475         msi_reduce_to_long_filename( filename );
1476         size = lstrlenW( filename );
1477     }
1478     size += lstrlenW(dir) + 2;
1479     path = malloc(size * sizeof(WCHAR));
1480     if (!path)
1481     {
1482         ret = ERROR_OUTOFMEMORY;
1483         goto done;
1484     }
1485 
1486     if (filename)
1487     {
1488         lstrcpyW(path, dir);
1489         PathAddBackslashW(path);
1490         lstrcatW(path, filename);
1491 
1492         TRACE("Deleting misc file: %s\n", debugstr_w(path));
1493         msi_delete_file( package, path );
1494     }
1495     else
1496     {
1497         TRACE("Removing misc directory: %s\n", debugstr_w(dir));
1498         msi_remove_directory( package, dir );
1499     }
1500 
1501 done:
1502     uirow = MSI_CreateRecord( 9 );
1503     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
1504     MSI_RecordSetStringW( uirow, 9, dir );
1505     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1506     msiobj_release( &uirow->hdr );
1507 
1508     free(filename);
1509     free(path);
1510     free(dir);
1511     return ret;
1512 }
1513 
1514 static void remove_folder( MSIFOLDER *folder )
1515 {
1516     FolderList *fl;
1517 
1518     LIST_FOR_EACH_ENTRY( fl, &folder->children, FolderList, entry )
1519     {
1520         remove_folder( fl->folder );
1521     }
1522     if (!folder->persistent && folder->State != FOLDER_STATE_REMOVED)
1523     {
1524         if (RemoveDirectoryW( folder->ResolvedTarget )) folder->State = FOLDER_STATE_REMOVED;
1525     }
1526 }
1527 
1528 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
1529 {
1530     MSIQUERY *view;
1531     MSICOMPONENT *comp;
1532     MSIFILE *file;
1533     UINT r;
1534 
1535     if (package->script == SCRIPT_NONE)
1536         return msi_schedule_action(package, SCRIPT_INSTALL, L"RemoveFiles");
1537 
1538     r = MSI_DatabaseOpenViewW(package->db, L"SELECT * FROM `RemoveFile`", &view);
1539     if (r == ERROR_SUCCESS)
1540     {
1541         r = MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1542         msiobj_release(&view->hdr);
1543         if (r != ERROR_SUCCESS)
1544             return r;
1545     }
1546 
1547     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1548     {
1549         MSIRECORD *uirow;
1550         VS_FIXEDFILEINFO *ver;
1551 
1552         comp = file->Component;
1553         file_update_ui( package, file, L"RemoveFiles" );
1554 
1555         comp->Action = msi_get_component_action( package, comp );
1556         if (comp->Action != INSTALLSTATE_ABSENT || comp->Installed == INSTALLSTATE_SOURCE)
1557             continue;
1558 
1559         if (comp->assembly && !comp->assembly->application)
1560             continue;
1561 
1562         if (comp->Attributes & msidbComponentAttributesPermanent)
1563         {
1564             TRACE("permanent component, not removing file\n");
1565             continue;
1566         }
1567 
1568         if (file->Version)
1569         {
1570             ver = msi_get_disk_file_version( package, file->TargetPath );
1571             if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1572             {
1573                 TRACE("newer version detected, not removing file\n");
1574                 free( ver );
1575                 continue;
1576             }
1577             free( ver );
1578         }
1579 
1580         if (file->state == msifs_installed)
1581             WARN("removing installed file %s\n", debugstr_w(file->TargetPath));
1582 
1583         TRACE("removing %s\n", debugstr_w(file->File) );
1584 
1585         msi_set_file_attributes( package, file->TargetPath, FILE_ATTRIBUTE_NORMAL );
1586         if (!msi_delete_file( package, file->TargetPath ))
1587         {
1588             WARN( "failed to delete %s (%lu)\n",  debugstr_w(file->TargetPath), GetLastError() );
1589         }
1590         file->state = msifs_missing;
1591 
1592         uirow = MSI_CreateRecord( 9 );
1593         MSI_RecordSetStringW( uirow, 1, file->FileName );
1594         MSI_RecordSetStringW( uirow, 9, comp->Directory );
1595         MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
1596         msiobj_release( &uirow->hdr );
1597     }
1598 
1599     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
1600     {
1601         comp->Action = msi_get_component_action( package, comp );
1602         if (comp->Action != INSTALLSTATE_ABSENT) continue;
1603 
1604         if (comp->Attributes & msidbComponentAttributesPermanent)
1605         {
1606             TRACE("permanent component, not removing directory\n");
1607             continue;
1608         }
1609         if (comp->assembly && !comp->assembly->application)
1610             msi_uninstall_assembly( package, comp );
1611         else
1612         {
1613             MSIFOLDER *folder = msi_get_loaded_folder( package, comp->Directory );
1614             if (folder) remove_folder( folder );
1615         }
1616     }
1617     return ERROR_SUCCESS;
1618 }
1619