msi: Print a warning instead of an error if we're going to remove an installed file.
[wine] / dlls / msi / files.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21
22 /*
23  * Actions dealing with files:
24  *
25  * InstallFiles
26  * DuplicateFiles
27  * MoveFiles
28  * PatchFiles
29  * RemoveDuplicateFiles
30  * RemoveFiles
31  */
32
33 #include <stdarg.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "winerror.h"
38 #include "wine/debug.h"
39 #include "fdi.h"
40 #include "msi.h"
41 #include "msidefs.h"
42 #include "msipriv.h"
43 #include "winuser.h"
44 #include "winreg.h"
45 #include "shlwapi.h"
46 #include "wine/unicode.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
49
50 static HMODULE hmspatcha;
51 static BOOL (WINAPI *ApplyPatchToFileW)(LPCWSTR, LPCWSTR, LPCWSTR, ULONG);
52
53 static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
54 {
55     MSIRECORD *uirow;
56
57     uirow = MSI_CreateRecord( 9 );
58     MSI_RecordSetStringW( uirow, 1, f->FileName );
59     MSI_RecordSetStringW( uirow, 9, f->Component->Directory );
60     MSI_RecordSetInteger( uirow, 6, f->FileSize );
61     msi_ui_actiondata( package, action, uirow );
62     msiobj_release( &uirow->hdr );
63     msi_ui_progress( package, 2, f->FileSize, 0, 0 );
64 }
65
66 static msi_file_state calculate_install_state( MSIPACKAGE *package, MSIFILE *file )
67 {
68     MSICOMPONENT *comp = file->Component;
69     VS_FIXEDFILEINFO *file_version;
70     WCHAR *font_version;
71     msi_file_state state;
72     DWORD file_size;
73
74     comp->Action = msi_get_component_action( package, comp );
75     if (comp->Action != INSTALLSTATE_LOCAL || (comp->assembly && comp->assembly->installed))
76     {
77         TRACE("file %s is not scheduled for install\n", debugstr_w(file->File));
78         return msifs_skipped;
79     }
80     if ((comp->assembly && !comp->assembly->application && !comp->assembly->installed) ||
81         GetFileAttributesW( file->TargetPath ) == INVALID_FILE_ATTRIBUTES)
82     {
83         TRACE("file %s is missing\n", debugstr_w(file->File));
84         return msifs_missing;
85     }
86     if (file->Version)
87     {
88         if ((file_version = msi_get_disk_file_version( file->TargetPath )))
89         {
90             TRACE("new %s old %u.%u.%u.%u\n", debugstr_w(file->Version),
91                   HIWORD(file_version->dwFileVersionMS),
92                   LOWORD(file_version->dwFileVersionMS),
93                   HIWORD(file_version->dwFileVersionLS),
94                   LOWORD(file_version->dwFileVersionLS));
95
96             if (msi_compare_file_versions( file_version, file->Version ) < 0)
97                 state = msifs_overwrite;
98             else
99             {
100                 TRACE("destination file version equal or greater, not overwriting\n");
101                 state = msifs_present;
102             }
103             msi_free( file_version );
104             return state;
105         }
106         else if ((font_version = font_version_from_file( file->TargetPath )))
107         {
108             TRACE("new %s old %s\n", debugstr_w(file->Version), debugstr_w(font_version));
109
110             if (msi_compare_font_versions( font_version, file->Version ) < 0)
111                 state = msifs_overwrite;
112             else
113             {
114                 TRACE("destination file version equal or greater, not overwriting\n");
115                 state = msifs_present;
116             }
117             msi_free( font_version );
118             return state;
119         }
120     }
121     if ((file_size = msi_get_disk_file_size( file->TargetPath )) != file->FileSize)
122     {
123         return msifs_overwrite;
124     }
125     if (file->hash.dwFileHashInfoSize)
126     {
127         if (msi_file_hash_matches( file ))
128         {
129             TRACE("file hashes match, not overwriting\n");
130             return msifs_hashmatch;
131         }
132         else
133         {
134             TRACE("file hashes do not match, overwriting\n");
135             return msifs_overwrite;
136         }
137     }
138     /* assume present */
139     return msifs_present;
140 }
141
142 static void schedule_install_files(MSIPACKAGE *package)
143 {
144     MSIFILE *file;
145
146     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
147     {
148         MSICOMPONENT *comp = file->Component;
149
150         file->state = calculate_install_state( package, file );
151         if (file->state == msifs_overwrite && (comp->Attributes & msidbComponentAttributesNeverOverwrite))
152         {
153             TRACE("not overwriting %s\n", debugstr_w(file->TargetPath));
154             file->state = msifs_skipped;
155         }
156         msi_ui_progress( package, 2, file->FileSize, 0, 0 );
157     }
158 }
159
160 static UINT copy_file(MSIFILE *file, LPWSTR source)
161 {
162     BOOL ret;
163
164     ret = CopyFileW(source, file->TargetPath, FALSE);
165     if (!ret)
166         return GetLastError();
167
168     SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
169
170     file->state = msifs_installed;
171     return ERROR_SUCCESS;
172 }
173
174 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
175 {
176     UINT gle;
177
178     TRACE("Copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
179
180     gle = copy_file(file, source);
181     if (gle == ERROR_SUCCESS)
182         return gle;
183
184     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
185     {
186         TRACE("overwriting existing file\n");
187         return ERROR_SUCCESS;
188     }
189     else if (gle == ERROR_ACCESS_DENIED)
190     {
191         SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
192
193         gle = copy_file(file, source);
194         TRACE("Overwriting existing file: %d\n", gle);
195     }
196     if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
197     {
198         WCHAR tmpfileW[MAX_PATH], *pathW, *p;
199         DWORD len;
200
201         TRACE("file in use, scheduling rename operation\n");
202
203         GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
204         len = strlenW(file->TargetPath) + strlenW(tmpfileW) + 1;
205         if (!(pathW = msi_alloc(len * sizeof(WCHAR))))
206             return ERROR_OUTOFMEMORY;
207
208         strcpyW(pathW, file->TargetPath);
209         if ((p = strrchrW(pathW, '\\'))) *p = 0;
210         strcatW(pathW, tmpfileW);
211
212         if (CopyFileW(source, pathW, FALSE) &&
213             MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
214             MoveFileExW(pathW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
215         {
216             file->state = msifs_installed;
217             package->need_reboot = 1;
218             gle = ERROR_SUCCESS;
219         }
220         else
221         {
222             gle = GetLastError();
223             WARN("failed to schedule rename operation: %d)\n", gle);
224         }
225         msi_free(pathW);
226     }
227
228     return gle;
229 }
230
231 static UINT msi_create_directory( MSIPACKAGE *package, const WCHAR *dir )
232 {
233     MSIFOLDER *folder;
234     const WCHAR *install_path;
235
236     install_path = msi_get_target_folder( package, dir );
237     if (!install_path) return ERROR_FUNCTION_FAILED;
238
239     folder = msi_get_loaded_folder( package, dir );
240     if (folder->State == 0)
241     {
242         msi_create_full_path( install_path );
243         folder->State = 2;
244     }
245     return ERROR_SUCCESS;
246 }
247
248 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
249                             LPWSTR *path, DWORD *attrs, PVOID user)
250 {
251     static MSIFILE *f = NULL;
252     UINT_PTR disk_id = (UINT_PTR)user;
253
254     if (action == MSICABEXTRACT_BEGINEXTRACT)
255     {
256         f = msi_get_loaded_file(package, file);
257         if (!f)
258         {
259             TRACE("unknown file in cabinet (%s)\n", debugstr_w(file));
260             return FALSE;
261         }
262
263         if (f->disk_id != disk_id || (f->state != msifs_missing && f->state != msifs_overwrite))
264             return FALSE;
265
266         msi_file_update_ui(package, f, szInstallFiles);
267         if (!f->Component->assembly || f->Component->assembly->application)
268         {
269             msi_create_directory(package, f->Component->Directory);
270         }
271         *path = strdupW(f->TargetPath);
272         *attrs = f->Attributes;
273     }
274     else if (action == MSICABEXTRACT_FILEEXTRACTED)
275     {
276         f->state = msifs_installed;
277         f = NULL;
278     }
279
280     return TRUE;
281 }
282
283 WCHAR *msi_resolve_file_source( MSIPACKAGE *package, MSIFILE *file )
284 {
285     WCHAR *p, *path;
286
287     TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
288
289     if (file->IsCompressed) return NULL;
290
291     p = msi_resolve_source_folder( package, file->Component->Directory, NULL );
292     path = msi_build_directory_name( 2, p, file->ShortName );
293
294     if (file->LongName && GetFileAttributesW( path ) == INVALID_FILE_ATTRIBUTES)
295     {
296         msi_free( path );
297         path = msi_build_directory_name( 2, p, file->LongName );
298     }
299     msi_free( p );
300     TRACE("file %s source resolves to %s\n", debugstr_w(file->File), debugstr_w(path));
301     return path;
302 }
303
304 /*
305  * ACTION_InstallFiles()
306  * 
307  * For efficiency, this is done in two passes:
308  * 1) Correct all the TargetPaths and determine what files are to be installed.
309  * 2) Extract Cabinets and copy files.
310  */
311 UINT ACTION_InstallFiles(MSIPACKAGE *package)
312 {
313     MSIMEDIAINFO *mi;
314     MSICOMPONENT *comp;
315     UINT rc = ERROR_SUCCESS;
316     MSIFILE *file;
317
318     /* increment progress bar each time action data is sent */
319     msi_ui_progress( package, 1, 1, 0, 0 );
320
321     schedule_install_files(package);
322     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
323
324     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
325     {
326         rc = msi_load_media_info( package, file->Sequence, mi );
327         if (rc != ERROR_SUCCESS)
328         {
329             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
330             return ERROR_FUNCTION_FAILED;
331         }
332         if (!file->Component->Enabled) continue;
333
334         if (file->state != msifs_hashmatch &&
335             (rc = ready_media( package, file->Sequence, file->IsCompressed, mi )))
336         {
337             ERR("Failed to ready media for %s\n", debugstr_w(file->File));
338             goto done;
339         }
340
341         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
342             continue;
343
344         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
345             (file->IsCompressed && !mi->is_extracted))
346         {
347             MSICABDATA data;
348
349             data.mi = mi;
350             data.package = package;
351             data.cb = installfiles_cb;
352             data.user = (PVOID)(UINT_PTR)mi->disk_id;
353
354             if (file->IsCompressed &&
355                 !msi_cabextract(package, mi, &data))
356             {
357                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
358                 rc = ERROR_INSTALL_FAILURE;
359                 goto done;
360             }
361         }
362
363         if (!file->IsCompressed)
364         {
365             WCHAR *source = msi_resolve_file_source(package, file);
366
367             TRACE("copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
368
369             msi_file_update_ui(package, file, szInstallFiles);
370             if (!file->Component->assembly || file->Component->assembly->application)
371             {
372                 msi_create_directory(package, file->Component->Directory);
373             }
374             rc = copy_install_file(package, file, source);
375             if (rc != ERROR_SUCCESS)
376             {
377                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
378                     debugstr_w(file->TargetPath), rc);
379                 rc = ERROR_INSTALL_FAILURE;
380                 msi_free(source);
381                 goto done;
382             }
383             msi_free(source);
384         }
385         else if (file->state != msifs_installed && !(file->Attributes & msidbFileAttributesPatchAdded))
386         {
387             ERR("compressed file wasn't installed (%s)\n", debugstr_w(file->TargetPath));
388             rc = ERROR_INSTALL_FAILURE;
389             goto done;
390         }
391     }
392     msi_init_assembly_caches( package );
393     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
394     {
395         comp->Action = msi_get_component_action( package, comp );
396         if (comp->Action == INSTALLSTATE_LOCAL && comp->assembly && !comp->assembly->installed)
397         {
398             rc = msi_install_assembly( package, comp );
399             if (rc != ERROR_SUCCESS)
400             {
401                 ERR("Failed to install assembly\n");
402                 rc = ERROR_INSTALL_FAILURE;
403                 break;
404             }
405         }
406     }
407     msi_destroy_assembly_caches( package );
408
409 done:
410     msi_free_media_info(mi);
411     return rc;
412 }
413
414 static BOOL load_mspatcha(void)
415 {
416     hmspatcha = LoadLibraryA("mspatcha.dll");
417     if (!hmspatcha)
418     {
419         ERR("Failed to load mspatcha.dll: %d\n", GetLastError());
420         return FALSE;
421     }
422
423     ApplyPatchToFileW = (void*)GetProcAddress(hmspatcha, "ApplyPatchToFileW");
424     if(!ApplyPatchToFileW)
425     {
426         ERR("GetProcAddress(ApplyPatchToFileW) failed: %d.\n", GetLastError());
427         return FALSE;
428     }
429
430     return TRUE;
431 }
432
433 static void unload_mspatch(void)
434 {
435     FreeLibrary(hmspatcha);
436 }
437
438 static BOOL patchfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
439                           LPWSTR *path, DWORD *attrs, PVOID user)
440 {
441     static MSIFILEPATCH *p = NULL;
442     static WCHAR patch_path[MAX_PATH] = {0};
443     static WCHAR temp_folder[MAX_PATH] = {0};
444
445     if (action == MSICABEXTRACT_BEGINEXTRACT)
446     {
447         if (temp_folder[0] == '\0')
448             GetTempPathW(MAX_PATH, temp_folder);
449
450         p = msi_get_loaded_filepatch(package, file);
451         if (!p)
452         {
453             TRACE("unknown file in cabinet (%s)\n", debugstr_w(file));
454             return FALSE;
455         }
456
457         msi_file_update_ui(package, p->File, szPatchFiles);
458
459         GetTempFileNameW(temp_folder, NULL, 0, patch_path);
460
461         *path = strdupW(patch_path);
462         *attrs = p->File->Attributes;
463     }
464     else if (action == MSICABEXTRACT_FILEEXTRACTED)
465     {
466         WCHAR patched_file[MAX_PATH];
467         BOOL br;
468
469         GetTempFileNameW(temp_folder, NULL, 0, patched_file);
470
471         br = ApplyPatchToFileW(patch_path, p->File->TargetPath, patched_file, 0);
472         if (br)
473         {
474             /* FIXME: baseline cache */
475
476             DeleteFileW( p->File->TargetPath );
477             MoveFileW( patched_file, p->File->TargetPath );
478
479             p->IsApplied = TRUE;
480         }
481         else
482             ERR("Failed patch %s: %d.\n", debugstr_w(p->File->TargetPath), GetLastError());
483
484         DeleteFileW(patch_path);
485         p = NULL;
486     }
487
488     return TRUE;
489 }
490
491 UINT ACTION_PatchFiles( MSIPACKAGE *package )
492 {
493     MSIFILEPATCH *patch;
494     MSIMEDIAINFO *mi;
495     UINT rc = ERROR_SUCCESS;
496     BOOL mspatcha_loaded = FALSE;
497
498     TRACE("%p\n", package);
499
500     /* increment progress bar each time action data is sent */
501     msi_ui_progress( package, 1, 1, 0, 0 );
502
503     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
504
505     LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
506     {
507         MSIFILE *file = patch->File;
508
509         rc = msi_load_media_info( package, patch->Sequence, mi );
510         if (rc != ERROR_SUCCESS)
511         {
512             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
513             return ERROR_FUNCTION_FAILED;
514         }
515         if (!file->Component->Enabled) continue;
516
517         if (!patch->IsApplied)
518         {
519             MSICABDATA data;
520
521             rc = ready_media( package, patch->Sequence, TRUE, mi );
522             if (rc != ERROR_SUCCESS)
523             {
524                 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
525                 goto done;
526             }
527
528             if (!mspatcha_loaded && !load_mspatcha())
529             {
530                 rc = ERROR_FUNCTION_FAILED;
531                 goto done;
532             }
533             mspatcha_loaded = TRUE;
534
535             data.mi = mi;
536             data.package = package;
537             data.cb = patchfiles_cb;
538             data.user = (PVOID)(UINT_PTR)mi->disk_id;
539
540             if (!msi_cabextract(package, mi, &data))
541             {
542                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
543                 rc = ERROR_INSTALL_FAILURE;
544                 goto done;
545             }
546         }
547
548         if (!patch->IsApplied && !(patch->Attributes & msidbPatchAttributesNonVital))
549         {
550             ERR("Failed to apply patch to file: %s\n", debugstr_w(file->File));
551             rc = ERROR_INSTALL_FAILURE;
552             goto done;
553         }
554     }
555
556 done:
557     msi_free_media_info(mi);
558     if (mspatcha_loaded)
559         unload_mspatch();
560     return rc;
561 }
562
563 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
564
565 typedef struct
566 {
567     struct list entry;
568     LPWSTR sourcename;
569     LPWSTR destname;
570     LPWSTR source;
571     LPWSTR dest;
572 } FILE_LIST;
573
574 static BOOL msi_move_file(LPCWSTR source, LPCWSTR dest, int options)
575 {
576     BOOL ret;
577
578     if (GetFileAttributesW(source) == FILE_ATTRIBUTE_DIRECTORY ||
579         GetFileAttributesW(dest) == FILE_ATTRIBUTE_DIRECTORY)
580     {
581         WARN("Source or dest is directory, not moving\n");
582         return FALSE;
583     }
584
585     if (options == msidbMoveFileOptionsMove)
586     {
587         TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
588         ret = MoveFileExW(source, dest, MOVEFILE_REPLACE_EXISTING);
589         if (!ret)
590         {
591             WARN("MoveFile failed: %d\n", GetLastError());
592             return FALSE;
593         }
594     }
595     else
596     {
597         TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
598         ret = CopyFileW(source, dest, FALSE);
599         if (!ret)
600         {
601             WARN("CopyFile failed: %d\n", GetLastError());
602             return FALSE;
603         }
604     }
605
606     return TRUE;
607 }
608
609 static LPWSTR wildcard_to_file(LPWSTR wildcard, LPWSTR filename)
610 {
611     LPWSTR path, ptr;
612     DWORD dirlen, pathlen;
613
614     ptr = strrchrW(wildcard, '\\');
615     dirlen = ptr - wildcard + 1;
616
617     pathlen = dirlen + lstrlenW(filename) + 1;
618     path = msi_alloc(pathlen * sizeof(WCHAR));
619
620     lstrcpynW(path, wildcard, dirlen + 1);
621     lstrcatW(path, filename);
622
623     return path;
624 }
625
626 static void free_file_entry(FILE_LIST *file)
627 {
628     msi_free(file->source);
629     msi_free(file->dest);
630     msi_free(file);
631 }
632
633 static void free_list(FILE_LIST *list)
634 {
635     while (!list_empty(&list->entry))
636     {
637         FILE_LIST *file = LIST_ENTRY(list_head(&list->entry), FILE_LIST, entry);
638
639         list_remove(&file->entry);
640         free_file_entry(file);
641     }
642 }
643
644 static BOOL add_wildcard(FILE_LIST *files, LPWSTR source, LPWSTR dest)
645 {
646     FILE_LIST *new, *file;
647     LPWSTR ptr, filename;
648     DWORD size;
649
650     new = msi_alloc_zero(sizeof(FILE_LIST));
651     if (!new)
652         return FALSE;
653
654     new->source = strdupW(source);
655     ptr = strrchrW(dest, '\\') + 1;
656     filename = strrchrW(new->source, '\\') + 1;
657
658     new->sourcename = filename;
659
660     if (*ptr)
661         new->destname = ptr;
662     else
663         new->destname = new->sourcename;
664
665     size = (ptr - dest) + lstrlenW(filename) + 1;
666     new->dest = msi_alloc(size * sizeof(WCHAR));
667     if (!new->dest)
668     {
669         free_file_entry(new);
670         return FALSE;
671     }
672
673     lstrcpynW(new->dest, dest, ptr - dest + 1);
674     lstrcatW(new->dest, filename);
675
676     if (list_empty(&files->entry))
677     {
678         list_add_head(&files->entry, &new->entry);
679         return TRUE;
680     }
681
682     LIST_FOR_EACH_ENTRY(file, &files->entry, FILE_LIST, entry)
683     {
684         if (strcmpW( source, file->source ) < 0)
685         {
686             list_add_before(&file->entry, &new->entry);
687             return TRUE;
688         }
689     }
690
691     list_add_after(&file->entry, &new->entry);
692     return TRUE;
693 }
694
695 static BOOL move_files_wildcard(LPWSTR source, LPWSTR dest, int options)
696 {
697     WIN32_FIND_DATAW wfd;
698     HANDLE hfile;
699     LPWSTR path;
700     BOOL res;
701     FILE_LIST files, *file;
702     DWORD size;
703
704     hfile = FindFirstFileW(source, &wfd);
705     if (hfile == INVALID_HANDLE_VALUE) return FALSE;
706
707     list_init(&files.entry);
708
709     for (res = TRUE; res; res = FindNextFileW(hfile, &wfd))
710     {
711         if (is_dot_dir(wfd.cFileName)) continue;
712
713         path = wildcard_to_file(source, wfd.cFileName);
714         if (!path)
715         {
716             res = FALSE;
717             goto done;
718         }
719
720         add_wildcard(&files, path, dest);
721         msi_free(path);
722     }
723
724     /* no files match the wildcard */
725     if (list_empty(&files.entry))
726         goto done;
727
728     /* only the first wildcard match gets renamed to dest */
729     file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
730     size = (strrchrW(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
731     file->dest = msi_realloc(file->dest, size * sizeof(WCHAR));
732     if (!file->dest)
733     {
734         res = FALSE;
735         goto done;
736     }
737
738     /* file->dest may be shorter after the reallocation, so add a NULL
739      * terminator.  This is needed for the call to strrchrW, as there will no
740      * longer be a NULL terminator within the bounds of the allocation in this case.
741      */
742     file->dest[size - 1] = '\0';
743     lstrcpyW(strrchrW(file->dest, '\\') + 1, file->destname);
744
745     while (!list_empty(&files.entry))
746     {
747         file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
748
749         msi_move_file(file->source, file->dest, options);
750
751         list_remove(&file->entry);
752         free_file_entry(file);
753     }
754
755     res = TRUE;
756
757 done:
758     free_list(&files);
759     FindClose(hfile);
760     return res;
761 }
762
763 void msi_reduce_to_long_filename( WCHAR *filename )
764 {
765     WCHAR *p = strchrW( filename, '|' );
766     if (p) memmove( filename, p + 1, (strlenW( p + 1 ) + 1) * sizeof(WCHAR) );
767 }
768
769 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
770 {
771     MSIPACKAGE *package = param;
772     MSIRECORD *uirow;
773     MSICOMPONENT *comp;
774     LPCWSTR sourcename, component;
775     LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
776     int options;
777     DWORD size;
778     BOOL ret, wildcards;
779
780     component = MSI_RecordGetString(rec, 2);
781     comp = msi_get_loaded_component(package, component);
782     if (!comp)
783         return ERROR_SUCCESS;
784
785     comp->Action = msi_get_component_action( package, comp );
786     if (comp->Action != INSTALLSTATE_LOCAL)
787     {
788         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
789         return ERROR_SUCCESS;
790     }
791
792     sourcename = MSI_RecordGetString(rec, 3);
793     options = MSI_RecordGetInteger(rec, 7);
794
795     sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
796     if (!sourcedir)
797         goto done;
798
799     destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
800     if (!destdir)
801         goto done;
802
803     if (!sourcename)
804     {
805         if (GetFileAttributesW(sourcedir) == INVALID_FILE_ATTRIBUTES)
806             goto done;
807
808         source = strdupW(sourcedir);
809         if (!source)
810             goto done;
811     }
812     else
813     {
814         size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
815         source = msi_alloc(size * sizeof(WCHAR));
816         if (!source)
817             goto done;
818
819         lstrcpyW(source, sourcedir);
820         if (source[lstrlenW(source) - 1] != '\\')
821             lstrcatW(source, szBackSlash);
822         lstrcatW(source, sourcename);
823     }
824
825     wildcards = strchrW(source, '*') || strchrW(source, '?');
826
827     if (MSI_RecordIsNull(rec, 4))
828     {
829         if (!wildcards)
830         {
831             destname = strdupW(sourcename);
832             if (!destname)
833                 goto done;
834         }
835     }
836     else
837     {
838         destname = strdupW(MSI_RecordGetString(rec, 4));
839         if (destname) msi_reduce_to_long_filename(destname);
840     }
841
842     size = 0;
843     if (destname)
844         size = lstrlenW(destname);
845
846     size += lstrlenW(destdir) + 2;
847     dest = msi_alloc(size * sizeof(WCHAR));
848     if (!dest)
849         goto done;
850
851     lstrcpyW(dest, destdir);
852     if (dest[lstrlenW(dest) - 1] != '\\')
853         lstrcatW(dest, szBackSlash);
854
855     if (destname)
856         lstrcatW(dest, destname);
857
858     if (GetFileAttributesW(destdir) == INVALID_FILE_ATTRIBUTES)
859     {
860         if (!(ret = msi_create_full_path(destdir)))
861         {
862             WARN("failed to create directory %u\n", GetLastError());
863             goto done;
864         }
865     }
866
867     if (!wildcards)
868         msi_move_file(source, dest, options);
869     else
870         move_files_wildcard(source, dest, options);
871
872 done:
873     uirow = MSI_CreateRecord( 9 );
874     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
875     MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
876     MSI_RecordSetStringW( uirow, 9, destdir );
877     msi_ui_actiondata( package, szMoveFiles, uirow );
878     msiobj_release( &uirow->hdr );
879
880     msi_free(sourcedir);
881     msi_free(destdir);
882     msi_free(destname);
883     msi_free(source);
884     msi_free(dest);
885
886     return ERROR_SUCCESS;
887 }
888
889 UINT ACTION_MoveFiles( MSIPACKAGE *package )
890 {
891     UINT rc;
892     MSIQUERY *view;
893
894     static const WCHAR ExecSeqQuery[] =
895         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
896          '`','M','o','v','e','F','i','l','e','`',0};
897
898     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
899     if (rc != ERROR_SUCCESS)
900         return ERROR_SUCCESS;
901
902     rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
903     msiobj_release(&view->hdr);
904
905     return rc;
906 }
907
908 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
909 {
910     DWORD len;
911     WCHAR *dst_name, *dst_path, *dst;
912
913     if (MSI_RecordIsNull( row, 4 ))
914     {
915         len = strlenW( src ) + 1;
916         if (!(dst_name = msi_alloc( len * sizeof(WCHAR)))) return NULL;
917         strcpyW( dst_name, strrchrW( src, '\\' ) + 1 );
918     }
919     else
920     {
921         MSI_RecordGetStringW( row, 4, NULL, &len );
922         if (!(dst_name = msi_alloc( ++len * sizeof(WCHAR) ))) return NULL;
923         MSI_RecordGetStringW( row, 4, dst_name, &len );
924         msi_reduce_to_long_filename( dst_name );
925     }
926
927     if (MSI_RecordIsNull( row, 5 ))
928     {
929         WCHAR *p;
930         dst_path = strdupW( src );
931         p = strrchrW( dst_path, '\\' );
932         if (p) *p = 0;
933     }
934     else
935     {
936         const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
937
938         dst_path = strdupW( msi_get_target_folder( package, dst_key ) );
939         if (!dst_path)
940         {
941             /* try a property */
942             dst_path = msi_dup_property( package->db, dst_key );
943             if (!dst_path)
944             {
945                 FIXME("Unable to get destination folder, try AppSearch properties\n");
946                 msi_free( dst_name );
947                 return NULL;
948             }
949         }
950     }
951
952     dst = msi_build_directory_name( 2, dst_path, dst_name );
953     msi_create_full_path( dst_path );
954
955     msi_free( dst_name );
956     msi_free( dst_path );
957     return dst;
958 }
959
960 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
961 {
962     MSIPACKAGE *package = param;
963     LPWSTR dest;
964     LPCWSTR file_key, component;
965     MSICOMPONENT *comp;
966     MSIRECORD *uirow;
967     MSIFILE *file;
968
969     component = MSI_RecordGetString(row,2);
970     comp = msi_get_loaded_component(package, component);
971     if (!comp)
972         return ERROR_SUCCESS;
973
974     comp->Action = msi_get_component_action( package, comp );
975     if (comp->Action != INSTALLSTATE_LOCAL)
976     {
977         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
978         return ERROR_SUCCESS;
979     }
980
981     file_key = MSI_RecordGetString(row,3);
982     if (!file_key)
983     {
984         ERR("Unable to get file key\n");
985         return ERROR_FUNCTION_FAILED;
986     }
987
988     file = msi_get_loaded_file( package, file_key );
989     if (!file)
990     {
991         ERR("Original file unknown %s\n", debugstr_w(file_key));
992         return ERROR_SUCCESS;
993     }
994
995     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
996     if (!dest)
997     {
998         WARN("Unable to get duplicate filename\n");
999         return ERROR_SUCCESS;
1000     }
1001
1002     TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
1003
1004     if (!CopyFileW( file->TargetPath, dest, TRUE ))
1005     {
1006         WARN("Failed to copy file %s -> %s (%u)\n",
1007              debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError());
1008     }
1009
1010     FIXME("We should track these duplicate files as well\n");   
1011
1012     uirow = MSI_CreateRecord( 9 );
1013     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1014     MSI_RecordSetInteger( uirow, 6, file->FileSize );
1015     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1016     msi_ui_actiondata( package, szDuplicateFiles, uirow );
1017     msiobj_release( &uirow->hdr );
1018
1019     msi_free(dest);
1020     return ERROR_SUCCESS;
1021 }
1022
1023 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
1024 {
1025     UINT rc;
1026     MSIQUERY * view;
1027     static const WCHAR ExecSeqQuery[] =
1028         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1029          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1030
1031     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
1032     if (rc != ERROR_SUCCESS)
1033         return ERROR_SUCCESS;
1034
1035     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
1036     msiobj_release(&view->hdr);
1037
1038     return rc;
1039 }
1040
1041 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
1042 {
1043     MSIPACKAGE *package = param;
1044     LPWSTR dest;
1045     LPCWSTR file_key, component;
1046     MSICOMPONENT *comp;
1047     MSIRECORD *uirow;
1048     MSIFILE *file;
1049
1050     component = MSI_RecordGetString( row, 2 );
1051     comp = msi_get_loaded_component( package, component );
1052     if (!comp)
1053         return ERROR_SUCCESS;
1054
1055     comp->Action = msi_get_component_action( package, comp );
1056     if (comp->Action != INSTALLSTATE_ABSENT)
1057     {
1058         TRACE("component not scheduled for removal %s\n", debugstr_w(component));
1059         return ERROR_SUCCESS;
1060     }
1061
1062     file_key = MSI_RecordGetString( row, 3 );
1063     if (!file_key)
1064     {
1065         ERR("Unable to get file key\n");
1066         return ERROR_FUNCTION_FAILED;
1067     }
1068
1069     file = msi_get_loaded_file( package, file_key );
1070     if (!file)
1071     {
1072         ERR("Original file unknown %s\n", debugstr_w(file_key));
1073         return ERROR_SUCCESS;
1074     }
1075
1076     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1077     if (!dest)
1078     {
1079         WARN("Unable to get duplicate filename\n");
1080         return ERROR_SUCCESS;
1081     }
1082
1083     TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
1084
1085     if (!DeleteFileW( dest ))
1086     {
1087         WARN("Failed to delete duplicate file %s (%u)\n", debugstr_w(dest), GetLastError());
1088     }
1089
1090     uirow = MSI_CreateRecord( 9 );
1091     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1092     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1093     msi_ui_actiondata( package, szRemoveDuplicateFiles, uirow );
1094     msiobj_release( &uirow->hdr );
1095
1096     msi_free(dest);
1097     return ERROR_SUCCESS;
1098 }
1099
1100 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
1101 {
1102     UINT rc;
1103     MSIQUERY *view;
1104     static const WCHAR query[] =
1105         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1106          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1107
1108     rc = MSI_DatabaseOpenViewW( package->db, query, &view );
1109     if (rc != ERROR_SUCCESS)
1110         return ERROR_SUCCESS;
1111
1112     rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
1113     msiobj_release( &view->hdr );
1114
1115     return rc;
1116 }
1117
1118 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
1119 {
1120     /* special case */
1121     if (comp->Action != INSTALLSTATE_SOURCE &&
1122         comp->Attributes & msidbComponentAttributesSourceOnly &&
1123         (install_mode == msidbRemoveFileInstallModeOnRemove ||
1124          install_mode == msidbRemoveFileInstallModeOnBoth)) return TRUE;
1125
1126     switch (comp->Action)
1127     {
1128     case INSTALLSTATE_LOCAL:
1129     case INSTALLSTATE_SOURCE:
1130         if (install_mode == msidbRemoveFileInstallModeOnInstall ||
1131             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1132         break;
1133     case INSTALLSTATE_ABSENT:
1134         if (install_mode == msidbRemoveFileInstallModeOnRemove ||
1135             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1136         break;
1137     default: break;
1138     }
1139     return FALSE;
1140 }
1141
1142 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
1143 {
1144     MSIPACKAGE *package = param;
1145     MSICOMPONENT *comp;
1146     MSIRECORD *uirow;
1147     LPCWSTR component, dirprop;
1148     UINT install_mode;
1149     LPWSTR dir = NULL, path = NULL, filename = NULL;
1150     DWORD size;
1151     UINT ret = ERROR_SUCCESS;
1152
1153     component = MSI_RecordGetString(row, 2);
1154     dirprop = MSI_RecordGetString(row, 4);
1155     install_mode = MSI_RecordGetInteger(row, 5);
1156
1157     comp = msi_get_loaded_component(package, component);
1158     if (!comp)
1159         return ERROR_SUCCESS;
1160
1161     comp->Action = msi_get_component_action( package, comp );
1162     if (!verify_comp_for_removal(comp, install_mode))
1163     {
1164         TRACE("Skipping removal due to install mode\n");
1165         return ERROR_SUCCESS;
1166     }
1167     if (comp->assembly && !comp->assembly->application)
1168     {
1169         return ERROR_SUCCESS;
1170     }
1171     if (comp->Attributes & msidbComponentAttributesPermanent)
1172     {
1173         TRACE("permanent component, not removing file\n");
1174         return ERROR_SUCCESS;
1175     }
1176
1177     dir = msi_dup_property(package->db, dirprop);
1178     if (!dir)
1179         return ERROR_OUTOFMEMORY;
1180
1181     size = 0;
1182     if ((filename = strdupW( MSI_RecordGetString(row, 3) )))
1183     {
1184         msi_reduce_to_long_filename( filename );
1185         size = lstrlenW( filename );
1186     }
1187     size += lstrlenW(dir) + 2;
1188     path = msi_alloc(size * sizeof(WCHAR));
1189     if (!path)
1190     {
1191         ret = ERROR_OUTOFMEMORY;
1192         goto done;
1193     }
1194
1195     if (filename)
1196     {
1197         lstrcpyW(path, dir);
1198         PathAddBackslashW(path);
1199         lstrcatW(path, filename);
1200
1201         TRACE("Deleting misc file: %s\n", debugstr_w(path));
1202         DeleteFileW(path);
1203     }
1204     else
1205     {
1206         TRACE("Removing misc directory: %s\n", debugstr_w(dir));
1207         RemoveDirectoryW(dir);
1208     }
1209
1210 done:
1211     uirow = MSI_CreateRecord( 9 );
1212     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
1213     MSI_RecordSetStringW( uirow, 9, dir );
1214     msi_ui_actiondata( package, szRemoveFiles, uirow );
1215     msiobj_release( &uirow->hdr );
1216
1217     msi_free(filename);
1218     msi_free(path);
1219     msi_free(dir);
1220     return ret;
1221 }
1222
1223 static BOOL has_persistent_dir( MSIPACKAGE *package, MSICOMPONENT *comp )
1224 {
1225     MSIQUERY *view;
1226     UINT r = ERROR_FUNCTION_FAILED;
1227
1228     static const WCHAR query[] = {
1229         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1230         '`','C','r','e','a','t','e','F','o','l','d','e','r','`',' ','W','H','E','R','E',' ',
1231         '`','C','o','m','p','o','n','e','n','t','_','`',' ','=','\'','%','s','\'',' ','A','N','D',' ',
1232         '`','D','i','r','e','c','t','o','r','y','_','`',' ','=','\'','%','s','\'',0};
1233
1234     if (!MSI_OpenQuery( package->db, &view, query, comp->Component, comp->Directory ))
1235     {
1236         if (!MSI_ViewExecute( view, NULL ))
1237         {
1238             MSIRECORD *rec;
1239             if (!(r = MSI_ViewFetch( view, &rec )))
1240             {
1241                 TRACE("directory %s is persistent\n", debugstr_w(comp->Directory));
1242                 msiobj_release( &rec->hdr );
1243             }
1244         }
1245         msiobj_release( &view->hdr );
1246     }
1247     return (r == ERROR_SUCCESS);
1248 }
1249
1250 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
1251 {
1252     MSIQUERY *view;
1253     MSIFILE *file;
1254     UINT r;
1255
1256     static const WCHAR query[] = {
1257         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1258         '`','R','e','m','o','v','e','F','i','l','e','`',0};
1259
1260     r = MSI_DatabaseOpenViewW(package->db, query, &view);
1261     if (r == ERROR_SUCCESS)
1262     {
1263         MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1264         msiobj_release(&view->hdr);
1265     }
1266
1267     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1268     {
1269         MSIRECORD *uirow;
1270         LPWSTR dir, p;
1271         VS_FIXEDFILEINFO *ver;
1272         MSICOMPONENT *comp = file->Component;
1273
1274         comp->Action = msi_get_component_action( package, comp );
1275         if (comp->Action != INSTALLSTATE_ABSENT || comp->Installed == INSTALLSTATE_SOURCE)
1276             continue;
1277
1278         if (comp->assembly && !comp->assembly->application)
1279             continue;
1280
1281         if (comp->Attributes & msidbComponentAttributesPermanent)
1282         {
1283             TRACE("permanent component, not removing file\n");
1284             continue;
1285         }
1286
1287         if (file->Version)
1288         {
1289             ver = msi_get_disk_file_version( file->TargetPath );
1290             if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1291             {
1292                 TRACE("newer version detected, not removing file\n");
1293                 msi_free( ver );
1294                 continue;
1295             }
1296             msi_free( ver );
1297         }
1298
1299         if (file->state == msifs_installed)
1300             WARN("removing installed file %s\n", debugstr_w(file->TargetPath));
1301
1302         TRACE("removing %s\n", debugstr_w(file->File) );
1303
1304         SetFileAttributesW( file->TargetPath, FILE_ATTRIBUTE_NORMAL );
1305         if (!DeleteFileW( file->TargetPath ))
1306         {
1307             WARN("failed to delete %s (%u)\n",  debugstr_w(file->TargetPath), GetLastError());
1308         }
1309         else if (!has_persistent_dir( package, comp ))
1310         {
1311             if ((dir = strdupW( file->TargetPath )))
1312             {
1313                 if ((p = strrchrW( dir, '\\' ))) *p = 0;
1314                 RemoveDirectoryW( dir );
1315                 msi_free( dir );
1316             }
1317         }
1318         file->state = msifs_missing;
1319
1320         uirow = MSI_CreateRecord( 9 );
1321         MSI_RecordSetStringW( uirow, 1, file->FileName );
1322         MSI_RecordSetStringW( uirow, 9, comp->Directory );
1323         msi_ui_actiondata( package, szRemoveFiles, uirow );
1324         msiobj_release( &uirow->hdr );
1325         /* FIXME: call msi_ui_progress here? */
1326     }
1327
1328     return ERROR_SUCCESS;
1329 }