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