gdi32: Return a new path from PATH_FlattenPath instead of replacing the DC path.
[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 = msi_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     }
157 }
158
159 static UINT copy_file(MSIFILE *file, LPWSTR source)
160 {
161     BOOL ret;
162
163     ret = CopyFileW(source, file->TargetPath, FALSE);
164     if (!ret)
165         return GetLastError();
166
167     SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
168
169     file->state = msifs_installed;
170     return ERROR_SUCCESS;
171 }
172
173 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
174 {
175     UINT gle;
176
177     TRACE("Copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
178
179     gle = copy_file(file, source);
180     if (gle == ERROR_SUCCESS)
181         return gle;
182
183     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
184     {
185         TRACE("overwriting existing file\n");
186         return ERROR_SUCCESS;
187     }
188     else if (gle == ERROR_ACCESS_DENIED)
189     {
190         SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
191
192         gle = copy_file(file, source);
193         TRACE("Overwriting existing file: %d\n", gle);
194     }
195     if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
196     {
197         WCHAR *tmpfileW, *pathW, *p;
198         DWORD len;
199
200         TRACE("file in use, scheduling rename operation\n");
201
202         if (!(pathW = strdupW( file->TargetPath ))) return ERROR_OUTOFMEMORY;
203         if ((p = strrchrW(pathW, '\\'))) *p = 0;
204         len = strlenW( pathW ) + 16;
205         if (!(tmpfileW = msi_alloc(len * sizeof(WCHAR))))
206         {
207             msi_free( pathW );
208             return ERROR_OUTOFMEMORY;
209         }
210         if (!GetTempFileNameW( pathW, szMsi, 0, tmpfileW )) tmpfileW[0] = 0;
211         msi_free( pathW );
212
213         if (CopyFileW(source, tmpfileW, FALSE) &&
214             MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
215             MoveFileExW(tmpfileW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
216         {
217             file->state = msifs_installed;
218             package->need_reboot = 1;
219             gle = ERROR_SUCCESS;
220         }
221         else
222         {
223             gle = GetLastError();
224             WARN("failed to schedule rename operation: %d)\n", gle);
225             DeleteFileW( tmpfileW );
226         }
227         msi_free(tmpfileW);
228     }
229
230     return gle;
231 }
232
233 static UINT msi_create_directory( MSIPACKAGE *package, const WCHAR *dir )
234 {
235     MSIFOLDER *folder;
236     const WCHAR *install_path;
237
238     install_path = msi_get_target_folder( package, dir );
239     if (!install_path) return ERROR_FUNCTION_FAILED;
240
241     folder = msi_get_loaded_folder( package, dir );
242     if (folder->State == FOLDER_STATE_UNINITIALIZED)
243     {
244         msi_create_full_path( install_path );
245         folder->State = FOLDER_STATE_CREATED;
246     }
247     return ERROR_SUCCESS;
248 }
249
250 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
251                             LPWSTR *path, DWORD *attrs, PVOID user)
252 {
253     static MSIFILE *f = NULL;
254     UINT_PTR disk_id = (UINT_PTR)user;
255
256     if (action == MSICABEXTRACT_BEGINEXTRACT)
257     {
258         f = msi_get_loaded_file(package, file);
259         if (!f)
260         {
261             TRACE("unknown file in cabinet (%s)\n", debugstr_w(file));
262             return FALSE;
263         }
264         if (f->disk_id != disk_id || (f->state != msifs_missing && f->state != msifs_overwrite))
265             return FALSE;
266
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     schedule_install_files(package);
319     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
320
321     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
322     {
323         msi_file_update_ui( package, file, szInstallFiles );
324
325         rc = msi_load_media_info( package, file->Sequence, mi );
326         if (rc != ERROR_SUCCESS)
327         {
328             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
329             return ERROR_FUNCTION_FAILED;
330         }
331         if (!file->Component->Enabled) continue;
332
333         if (file->state != msifs_hashmatch &&
334             (rc = ready_media( package, file->Sequence, file->IsCompressed, mi )))
335         {
336             ERR("Failed to ready media for %s\n", debugstr_w(file->File));
337             goto done;
338         }
339
340         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
341             continue;
342
343         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
344             (file->IsCompressed && !mi->is_extracted))
345         {
346             MSICABDATA data;
347
348             data.mi = mi;
349             data.package = package;
350             data.cb = installfiles_cb;
351             data.user = (PVOID)(UINT_PTR)mi->disk_id;
352
353             if (file->IsCompressed &&
354                 !msi_cabextract(package, mi, &data))
355             {
356                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
357                 rc = ERROR_INSTALL_FAILURE;
358                 goto done;
359             }
360         }
361
362         if (!file->IsCompressed)
363         {
364             WCHAR *source = msi_resolve_file_source(package, file);
365
366             TRACE("copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
367
368             if (!file->Component->assembly || file->Component->assembly->application)
369             {
370                 msi_create_directory(package, file->Component->Directory);
371             }
372             rc = copy_install_file(package, file, source);
373             if (rc != ERROR_SUCCESS)
374             {
375                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
376                     debugstr_w(file->TargetPath), rc);
377                 rc = ERROR_INSTALL_FAILURE;
378                 msi_free(source);
379                 goto done;
380             }
381             msi_free(source);
382         }
383         else if (file->state != msifs_installed && !(file->Attributes & msidbFileAttributesPatchAdded))
384         {
385             ERR("compressed file wasn't installed (%s)\n", debugstr_w(file->TargetPath));
386             rc = ERROR_INSTALL_FAILURE;
387             goto done;
388         }
389     }
390     msi_init_assembly_caches( package );
391     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
392     {
393         comp->Action = msi_get_component_action( package, comp );
394         if (comp->Action == INSTALLSTATE_LOCAL && comp->assembly && !comp->assembly->installed)
395         {
396             rc = msi_install_assembly( package, comp );
397             if (rc != ERROR_SUCCESS)
398             {
399                 ERR("Failed to install assembly\n");
400                 rc = ERROR_INSTALL_FAILURE;
401                 break;
402             }
403         }
404     }
405     msi_destroy_assembly_caches( package );
406
407 done:
408     msi_free_media_info(mi);
409     return rc;
410 }
411
412 static BOOL load_mspatcha(void)
413 {
414     hmspatcha = LoadLibraryA("mspatcha.dll");
415     if (!hmspatcha)
416     {
417         ERR("Failed to load mspatcha.dll: %d\n", GetLastError());
418         return FALSE;
419     }
420
421     ApplyPatchToFileW = (void*)GetProcAddress(hmspatcha, "ApplyPatchToFileW");
422     if(!ApplyPatchToFileW)
423     {
424         ERR("GetProcAddress(ApplyPatchToFileW) failed: %d.\n", GetLastError());
425         return FALSE;
426     }
427
428     return TRUE;
429 }
430
431 static void unload_mspatch(void)
432 {
433     FreeLibrary(hmspatcha);
434 }
435
436 static BOOL patchfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
437                           LPWSTR *path, DWORD *attrs, PVOID user)
438 {
439     static MSIFILEPATCH *p = NULL;
440     static WCHAR patch_path[MAX_PATH] = {0};
441     static WCHAR temp_folder[MAX_PATH] = {0};
442
443     if (action == MSICABEXTRACT_BEGINEXTRACT)
444     {
445         if (temp_folder[0] == '\0')
446             GetTempPathW(MAX_PATH, temp_folder);
447
448         p = msi_get_loaded_filepatch(package, file);
449         if (!p)
450         {
451             TRACE("unknown file in cabinet (%s)\n", debugstr_w(file));
452             return FALSE;
453         }
454         GetTempFileNameW(temp_folder, NULL, 0, patch_path);
455
456         *path = strdupW(patch_path);
457         *attrs = p->File->Attributes;
458     }
459     else if (action == MSICABEXTRACT_FILEEXTRACTED)
460     {
461         WCHAR patched_file[MAX_PATH];
462         BOOL br;
463
464         GetTempFileNameW(temp_folder, NULL, 0, patched_file);
465
466         br = ApplyPatchToFileW(patch_path, p->File->TargetPath, patched_file, 0);
467         if (br)
468         {
469             /* FIXME: baseline cache */
470
471             DeleteFileW( p->File->TargetPath );
472             MoveFileW( patched_file, p->File->TargetPath );
473
474             p->IsApplied = TRUE;
475         }
476         else
477             ERR("Failed patch %s: %d.\n", debugstr_w(p->File->TargetPath), GetLastError());
478
479         DeleteFileW(patch_path);
480         p = NULL;
481     }
482
483     return TRUE;
484 }
485
486 UINT ACTION_PatchFiles( MSIPACKAGE *package )
487 {
488     MSIFILEPATCH *patch;
489     MSIMEDIAINFO *mi;
490     UINT rc = ERROR_SUCCESS;
491     BOOL mspatcha_loaded = FALSE;
492
493     TRACE("%p\n", package);
494
495     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
496
497     LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
498     {
499         MSIFILE *file = patch->File;
500         MSICOMPONENT *comp = file->Component;
501
502         rc = msi_load_media_info( package, patch->Sequence, mi );
503         if (rc != ERROR_SUCCESS)
504         {
505             ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
506             return ERROR_FUNCTION_FAILED;
507         }
508         comp->Action = msi_get_component_action( package, comp );
509         if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL) continue;
510
511         if (!patch->IsApplied)
512         {
513             MSICABDATA data;
514
515             rc = ready_media( package, patch->Sequence, TRUE, mi );
516             if (rc != ERROR_SUCCESS)
517             {
518                 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
519                 goto done;
520             }
521
522             if (!mspatcha_loaded && !load_mspatcha())
523             {
524                 rc = ERROR_FUNCTION_FAILED;
525                 goto done;
526             }
527             mspatcha_loaded = TRUE;
528
529             data.mi = mi;
530             data.package = package;
531             data.cb = patchfiles_cb;
532             data.user = (PVOID)(UINT_PTR)mi->disk_id;
533
534             if (!msi_cabextract(package, mi, &data))
535             {
536                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
537                 rc = ERROR_INSTALL_FAILURE;
538                 goto done;
539             }
540         }
541
542         if (!patch->IsApplied && !(patch->Attributes & msidbPatchAttributesNonVital))
543         {
544             ERR("Failed to apply patch to file: %s\n", debugstr_w(file->File));
545             rc = ERROR_INSTALL_FAILURE;
546             goto done;
547         }
548     }
549
550 done:
551     msi_free_media_info(mi);
552     if (mspatcha_loaded)
553         unload_mspatch();
554     return rc;
555 }
556
557 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
558
559 typedef struct
560 {
561     struct list entry;
562     LPWSTR sourcename;
563     LPWSTR destname;
564     LPWSTR source;
565     LPWSTR dest;
566 } FILE_LIST;
567
568 static BOOL msi_move_file(LPCWSTR source, LPCWSTR dest, int options)
569 {
570     BOOL ret;
571
572     if (GetFileAttributesW(source) == FILE_ATTRIBUTE_DIRECTORY ||
573         GetFileAttributesW(dest) == FILE_ATTRIBUTE_DIRECTORY)
574     {
575         WARN("Source or dest is directory, not moving\n");
576         return FALSE;
577     }
578
579     if (options == msidbMoveFileOptionsMove)
580     {
581         TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
582         ret = MoveFileExW(source, dest, MOVEFILE_REPLACE_EXISTING);
583         if (!ret)
584         {
585             WARN("MoveFile failed: %d\n", GetLastError());
586             return FALSE;
587         }
588     }
589     else
590     {
591         TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
592         ret = CopyFileW(source, dest, FALSE);
593         if (!ret)
594         {
595             WARN("CopyFile failed: %d\n", GetLastError());
596             return FALSE;
597         }
598     }
599
600     return TRUE;
601 }
602
603 static LPWSTR wildcard_to_file(LPWSTR wildcard, LPWSTR filename)
604 {
605     LPWSTR path, ptr;
606     DWORD dirlen, pathlen;
607
608     ptr = strrchrW(wildcard, '\\');
609     dirlen = ptr - wildcard + 1;
610
611     pathlen = dirlen + lstrlenW(filename) + 1;
612     path = msi_alloc(pathlen * sizeof(WCHAR));
613
614     lstrcpynW(path, wildcard, dirlen + 1);
615     lstrcatW(path, filename);
616
617     return path;
618 }
619
620 static void free_file_entry(FILE_LIST *file)
621 {
622     msi_free(file->source);
623     msi_free(file->dest);
624     msi_free(file);
625 }
626
627 static void free_list(FILE_LIST *list)
628 {
629     while (!list_empty(&list->entry))
630     {
631         FILE_LIST *file = LIST_ENTRY(list_head(&list->entry), FILE_LIST, entry);
632
633         list_remove(&file->entry);
634         free_file_entry(file);
635     }
636 }
637
638 static BOOL add_wildcard(FILE_LIST *files, LPWSTR source, LPWSTR dest)
639 {
640     FILE_LIST *new, *file;
641     LPWSTR ptr, filename;
642     DWORD size;
643
644     new = msi_alloc_zero(sizeof(FILE_LIST));
645     if (!new)
646         return FALSE;
647
648     new->source = strdupW(source);
649     ptr = strrchrW(dest, '\\') + 1;
650     filename = strrchrW(new->source, '\\') + 1;
651
652     new->sourcename = filename;
653
654     if (*ptr)
655         new->destname = ptr;
656     else
657         new->destname = new->sourcename;
658
659     size = (ptr - dest) + lstrlenW(filename) + 1;
660     new->dest = msi_alloc(size * sizeof(WCHAR));
661     if (!new->dest)
662     {
663         free_file_entry(new);
664         return FALSE;
665     }
666
667     lstrcpynW(new->dest, dest, ptr - dest + 1);
668     lstrcatW(new->dest, filename);
669
670     if (list_empty(&files->entry))
671     {
672         list_add_head(&files->entry, &new->entry);
673         return TRUE;
674     }
675
676     LIST_FOR_EACH_ENTRY(file, &files->entry, FILE_LIST, entry)
677     {
678         if (strcmpW( source, file->source ) < 0)
679         {
680             list_add_before(&file->entry, &new->entry);
681             return TRUE;
682         }
683     }
684
685     list_add_after(&file->entry, &new->entry);
686     return TRUE;
687 }
688
689 static BOOL move_files_wildcard(LPWSTR source, LPWSTR dest, int options)
690 {
691     WIN32_FIND_DATAW wfd;
692     HANDLE hfile;
693     LPWSTR path;
694     BOOL res;
695     FILE_LIST files, *file;
696     DWORD size;
697
698     hfile = FindFirstFileW(source, &wfd);
699     if (hfile == INVALID_HANDLE_VALUE) return FALSE;
700
701     list_init(&files.entry);
702
703     for (res = TRUE; res; res = FindNextFileW(hfile, &wfd))
704     {
705         if (is_dot_dir(wfd.cFileName)) continue;
706
707         path = wildcard_to_file(source, wfd.cFileName);
708         if (!path)
709         {
710             res = FALSE;
711             goto done;
712         }
713
714         add_wildcard(&files, path, dest);
715         msi_free(path);
716     }
717
718     /* no files match the wildcard */
719     if (list_empty(&files.entry))
720         goto done;
721
722     /* only the first wildcard match gets renamed to dest */
723     file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
724     size = (strrchrW(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
725     file->dest = msi_realloc(file->dest, size * sizeof(WCHAR));
726     if (!file->dest)
727     {
728         res = FALSE;
729         goto done;
730     }
731
732     /* file->dest may be shorter after the reallocation, so add a NULL
733      * terminator.  This is needed for the call to strrchrW, as there will no
734      * longer be a NULL terminator within the bounds of the allocation in this case.
735      */
736     file->dest[size - 1] = '\0';
737     lstrcpyW(strrchrW(file->dest, '\\') + 1, file->destname);
738
739     while (!list_empty(&files.entry))
740     {
741         file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
742
743         msi_move_file(file->source, file->dest, options);
744
745         list_remove(&file->entry);
746         free_file_entry(file);
747     }
748
749     res = TRUE;
750
751 done:
752     free_list(&files);
753     FindClose(hfile);
754     return res;
755 }
756
757 void msi_reduce_to_long_filename( WCHAR *filename )
758 {
759     WCHAR *p = strchrW( filename, '|' );
760     if (p) memmove( filename, p + 1, (strlenW( p + 1 ) + 1) * sizeof(WCHAR) );
761 }
762
763 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
764 {
765     MSIPACKAGE *package = param;
766     MSIRECORD *uirow;
767     MSICOMPONENT *comp;
768     LPCWSTR sourcename, component;
769     LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
770     int options;
771     DWORD size;
772     BOOL ret, wildcards;
773
774     component = MSI_RecordGetString(rec, 2);
775     comp = msi_get_loaded_component(package, component);
776     if (!comp)
777         return ERROR_SUCCESS;
778
779     comp->Action = msi_get_component_action( package, comp );
780     if (comp->Action != INSTALLSTATE_LOCAL)
781     {
782         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
783         return ERROR_SUCCESS;
784     }
785
786     sourcename = MSI_RecordGetString(rec, 3);
787     options = MSI_RecordGetInteger(rec, 7);
788
789     sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
790     if (!sourcedir)
791         goto done;
792
793     destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
794     if (!destdir)
795         goto done;
796
797     if (!sourcename)
798     {
799         if (GetFileAttributesW(sourcedir) == INVALID_FILE_ATTRIBUTES)
800             goto done;
801
802         source = strdupW(sourcedir);
803         if (!source)
804             goto done;
805     }
806     else
807     {
808         size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
809         source = msi_alloc(size * sizeof(WCHAR));
810         if (!source)
811             goto done;
812
813         lstrcpyW(source, sourcedir);
814         if (source[lstrlenW(source) - 1] != '\\')
815             lstrcatW(source, szBackSlash);
816         lstrcatW(source, sourcename);
817     }
818
819     wildcards = strchrW(source, '*') || strchrW(source, '?');
820
821     if (MSI_RecordIsNull(rec, 4))
822     {
823         if (!wildcards)
824         {
825             destname = strdupW(sourcename);
826             if (!destname)
827                 goto done;
828         }
829     }
830     else
831     {
832         destname = strdupW(MSI_RecordGetString(rec, 4));
833         if (destname) msi_reduce_to_long_filename(destname);
834     }
835
836     size = 0;
837     if (destname)
838         size = lstrlenW(destname);
839
840     size += lstrlenW(destdir) + 2;
841     dest = msi_alloc(size * sizeof(WCHAR));
842     if (!dest)
843         goto done;
844
845     lstrcpyW(dest, destdir);
846     if (dest[lstrlenW(dest) - 1] != '\\')
847         lstrcatW(dest, szBackSlash);
848
849     if (destname)
850         lstrcatW(dest, destname);
851
852     if (GetFileAttributesW(destdir) == INVALID_FILE_ATTRIBUTES)
853     {
854         if (!(ret = msi_create_full_path(destdir)))
855         {
856             WARN("failed to create directory %u\n", GetLastError());
857             goto done;
858         }
859     }
860
861     if (!wildcards)
862         msi_move_file(source, dest, options);
863     else
864         move_files_wildcard(source, dest, options);
865
866 done:
867     uirow = MSI_CreateRecord( 9 );
868     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
869     MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
870     MSI_RecordSetStringW( uirow, 9, destdir );
871     msi_ui_actiondata( package, szMoveFiles, uirow );
872     msiobj_release( &uirow->hdr );
873
874     msi_free(sourcedir);
875     msi_free(destdir);
876     msi_free(destname);
877     msi_free(source);
878     msi_free(dest);
879
880     return ERROR_SUCCESS;
881 }
882
883 UINT ACTION_MoveFiles( MSIPACKAGE *package )
884 {
885     static const WCHAR query[] = {
886         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
887         '`','M','o','v','e','F','i','l','e','`',0};
888     MSIQUERY *view;
889     UINT rc;
890
891     rc = MSI_DatabaseOpenViewW(package->db, query, &view);
892     if (rc != ERROR_SUCCESS)
893         return ERROR_SUCCESS;
894
895     rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
896     msiobj_release(&view->hdr);
897     return rc;
898 }
899
900 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
901 {
902     DWORD len;
903     WCHAR *dst_name, *dst_path, *dst;
904
905     if (MSI_RecordIsNull( row, 4 ))
906     {
907         len = strlenW( src ) + 1;
908         if (!(dst_name = msi_alloc( len * sizeof(WCHAR)))) return NULL;
909         strcpyW( dst_name, strrchrW( src, '\\' ) + 1 );
910     }
911     else
912     {
913         MSI_RecordGetStringW( row, 4, NULL, &len );
914         if (!(dst_name = msi_alloc( ++len * sizeof(WCHAR) ))) return NULL;
915         MSI_RecordGetStringW( row, 4, dst_name, &len );
916         msi_reduce_to_long_filename( dst_name );
917     }
918
919     if (MSI_RecordIsNull( row, 5 ))
920     {
921         WCHAR *p;
922         dst_path = strdupW( src );
923         p = strrchrW( dst_path, '\\' );
924         if (p) *p = 0;
925     }
926     else
927     {
928         const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
929
930         dst_path = strdupW( msi_get_target_folder( package, dst_key ) );
931         if (!dst_path)
932         {
933             /* try a property */
934             dst_path = msi_dup_property( package->db, dst_key );
935             if (!dst_path)
936             {
937                 FIXME("Unable to get destination folder, try AppSearch properties\n");
938                 msi_free( dst_name );
939                 return NULL;
940             }
941         }
942     }
943
944     dst = msi_build_directory_name( 2, dst_path, dst_name );
945     msi_create_full_path( dst_path );
946
947     msi_free( dst_name );
948     msi_free( dst_path );
949     return dst;
950 }
951
952 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
953 {
954     MSIPACKAGE *package = param;
955     LPWSTR dest;
956     LPCWSTR file_key, component;
957     MSICOMPONENT *comp;
958     MSIRECORD *uirow;
959     MSIFILE *file;
960
961     component = MSI_RecordGetString(row,2);
962     comp = msi_get_loaded_component(package, component);
963     if (!comp)
964         return ERROR_SUCCESS;
965
966     comp->Action = msi_get_component_action( package, comp );
967     if (comp->Action != INSTALLSTATE_LOCAL)
968     {
969         TRACE("component not scheduled for installation %s\n", debugstr_w(component));
970         return ERROR_SUCCESS;
971     }
972
973     file_key = MSI_RecordGetString(row,3);
974     if (!file_key)
975     {
976         ERR("Unable to get file key\n");
977         return ERROR_FUNCTION_FAILED;
978     }
979
980     file = msi_get_loaded_file( package, file_key );
981     if (!file)
982     {
983         ERR("Original file unknown %s\n", debugstr_w(file_key));
984         return ERROR_SUCCESS;
985     }
986
987     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
988     if (!dest)
989     {
990         WARN("Unable to get duplicate filename\n");
991         return ERROR_SUCCESS;
992     }
993
994     TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
995
996     if (!CopyFileW( file->TargetPath, dest, TRUE ))
997     {
998         WARN("Failed to copy file %s -> %s (%u)\n",
999              debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError());
1000     }
1001
1002     FIXME("We should track these duplicate files as well\n");   
1003
1004     uirow = MSI_CreateRecord( 9 );
1005     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1006     MSI_RecordSetInteger( uirow, 6, file->FileSize );
1007     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1008     msi_ui_actiondata( package, szDuplicateFiles, uirow );
1009     msiobj_release( &uirow->hdr );
1010
1011     msi_free(dest);
1012     return ERROR_SUCCESS;
1013 }
1014
1015 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
1016 {
1017     static const WCHAR query[] = {
1018         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1019         '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1020     MSIQUERY *view;
1021     UINT rc;
1022
1023     rc = MSI_DatabaseOpenViewW(package->db, query, &view);
1024     if (rc != ERROR_SUCCESS)
1025         return ERROR_SUCCESS;
1026
1027     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
1028     msiobj_release(&view->hdr);
1029     return rc;
1030 }
1031
1032 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
1033 {
1034     MSIPACKAGE *package = param;
1035     LPWSTR dest;
1036     LPCWSTR file_key, component;
1037     MSICOMPONENT *comp;
1038     MSIRECORD *uirow;
1039     MSIFILE *file;
1040
1041     component = MSI_RecordGetString( row, 2 );
1042     comp = msi_get_loaded_component( package, component );
1043     if (!comp)
1044         return ERROR_SUCCESS;
1045
1046     comp->Action = msi_get_component_action( package, comp );
1047     if (comp->Action != INSTALLSTATE_ABSENT)
1048     {
1049         TRACE("component not scheduled for removal %s\n", debugstr_w(component));
1050         return ERROR_SUCCESS;
1051     }
1052
1053     file_key = MSI_RecordGetString( row, 3 );
1054     if (!file_key)
1055     {
1056         ERR("Unable to get file key\n");
1057         return ERROR_FUNCTION_FAILED;
1058     }
1059
1060     file = msi_get_loaded_file( package, file_key );
1061     if (!file)
1062     {
1063         ERR("Original file unknown %s\n", debugstr_w(file_key));
1064         return ERROR_SUCCESS;
1065     }
1066
1067     dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1068     if (!dest)
1069     {
1070         WARN("Unable to get duplicate filename\n");
1071         return ERROR_SUCCESS;
1072     }
1073
1074     TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
1075
1076     if (!DeleteFileW( dest ))
1077     {
1078         WARN("Failed to delete duplicate file %s (%u)\n", debugstr_w(dest), GetLastError());
1079     }
1080
1081     uirow = MSI_CreateRecord( 9 );
1082     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1083     MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1084     msi_ui_actiondata( package, szRemoveDuplicateFiles, uirow );
1085     msiobj_release( &uirow->hdr );
1086
1087     msi_free(dest);
1088     return ERROR_SUCCESS;
1089 }
1090
1091 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
1092 {
1093     static const WCHAR query[] = {
1094         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1095         '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1096     MSIQUERY *view;
1097     UINT rc;
1098
1099     rc = MSI_DatabaseOpenViewW( package->db, query, &view );
1100     if (rc != ERROR_SUCCESS)
1101         return ERROR_SUCCESS;
1102
1103     rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
1104     msiobj_release( &view->hdr );
1105     return rc;
1106 }
1107
1108 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
1109 {
1110     /* special case */
1111     if (comp->Action != INSTALLSTATE_SOURCE &&
1112         comp->Attributes & msidbComponentAttributesSourceOnly &&
1113         (install_mode == msidbRemoveFileInstallModeOnRemove ||
1114          install_mode == msidbRemoveFileInstallModeOnBoth)) return TRUE;
1115
1116     switch (comp->Action)
1117     {
1118     case INSTALLSTATE_LOCAL:
1119     case INSTALLSTATE_SOURCE:
1120         if (install_mode == msidbRemoveFileInstallModeOnInstall ||
1121             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1122         break;
1123     case INSTALLSTATE_ABSENT:
1124         if (install_mode == msidbRemoveFileInstallModeOnRemove ||
1125             install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1126         break;
1127     default: break;
1128     }
1129     return FALSE;
1130 }
1131
1132 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
1133 {
1134     MSIPACKAGE *package = param;
1135     MSICOMPONENT *comp;
1136     MSIRECORD *uirow;
1137     LPCWSTR component, dirprop;
1138     UINT install_mode;
1139     LPWSTR dir = NULL, path = NULL, filename = NULL;
1140     DWORD size;
1141     UINT ret = ERROR_SUCCESS;
1142
1143     component = MSI_RecordGetString(row, 2);
1144     dirprop = MSI_RecordGetString(row, 4);
1145     install_mode = MSI_RecordGetInteger(row, 5);
1146
1147     comp = msi_get_loaded_component(package, component);
1148     if (!comp)
1149         return ERROR_SUCCESS;
1150
1151     comp->Action = msi_get_component_action( package, comp );
1152     if (!verify_comp_for_removal(comp, install_mode))
1153     {
1154         TRACE("Skipping removal due to install mode\n");
1155         return ERROR_SUCCESS;
1156     }
1157     if (comp->assembly && !comp->assembly->application)
1158     {
1159         return ERROR_SUCCESS;
1160     }
1161     if (comp->Attributes & msidbComponentAttributesPermanent)
1162     {
1163         TRACE("permanent component, not removing file\n");
1164         return ERROR_SUCCESS;
1165     }
1166
1167     dir = msi_dup_property(package->db, dirprop);
1168     if (!dir)
1169     {
1170         WARN("directory property has no value\n");
1171         return ERROR_SUCCESS;
1172     }
1173     size = 0;
1174     if ((filename = strdupW( MSI_RecordGetString(row, 3) )))
1175     {
1176         msi_reduce_to_long_filename( filename );
1177         size = lstrlenW( filename );
1178     }
1179     size += lstrlenW(dir) + 2;
1180     path = msi_alloc(size * sizeof(WCHAR));
1181     if (!path)
1182     {
1183         ret = ERROR_OUTOFMEMORY;
1184         goto done;
1185     }
1186
1187     if (filename)
1188     {
1189         lstrcpyW(path, dir);
1190         PathAddBackslashW(path);
1191         lstrcatW(path, filename);
1192
1193         TRACE("Deleting misc file: %s\n", debugstr_w(path));
1194         DeleteFileW(path);
1195     }
1196     else
1197     {
1198         TRACE("Removing misc directory: %s\n", debugstr_w(dir));
1199         RemoveDirectoryW(dir);
1200     }
1201
1202 done:
1203     uirow = MSI_CreateRecord( 9 );
1204     MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
1205     MSI_RecordSetStringW( uirow, 9, dir );
1206     msi_ui_actiondata( package, szRemoveFiles, uirow );
1207     msiobj_release( &uirow->hdr );
1208
1209     msi_free(filename);
1210     msi_free(path);
1211     msi_free(dir);
1212     return ret;
1213 }
1214
1215 static void remove_folder( MSIFOLDER *folder )
1216 {
1217     FolderList *fl;
1218
1219     LIST_FOR_EACH_ENTRY( fl, &folder->children, FolderList, entry )
1220     {
1221         remove_folder( fl->folder );
1222     }
1223     if (!folder->persistent && folder->State != FOLDER_STATE_REMOVED)
1224     {
1225         if (RemoveDirectoryW( folder->ResolvedTarget )) folder->State = FOLDER_STATE_REMOVED;
1226     }
1227 }
1228
1229 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
1230 {
1231     static const WCHAR query[] = {
1232         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1233         '`','R','e','m','o','v','e','F','i','l','e','`',0};
1234     MSIQUERY *view;
1235     MSICOMPONENT *comp;
1236     MSIFILE *file;
1237     UINT r;
1238
1239     r = MSI_DatabaseOpenViewW(package->db, query, &view);
1240     if (r == ERROR_SUCCESS)
1241     {
1242         r = MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1243         msiobj_release(&view->hdr);
1244         if (r != ERROR_SUCCESS)
1245             return r;
1246     }
1247
1248     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1249     {
1250         MSIRECORD *uirow;
1251         VS_FIXEDFILEINFO *ver;
1252
1253         comp = file->Component;
1254         msi_file_update_ui( package, file, szRemoveFiles );
1255
1256         comp->Action = msi_get_component_action( package, comp );
1257         if (comp->Action != INSTALLSTATE_ABSENT || comp->Installed == INSTALLSTATE_SOURCE)
1258             continue;
1259
1260         if (comp->assembly && !comp->assembly->application)
1261             continue;
1262
1263         if (comp->Attributes & msidbComponentAttributesPermanent)
1264         {
1265             TRACE("permanent component, not removing file\n");
1266             continue;
1267         }
1268
1269         if (file->Version)
1270         {
1271             ver = msi_get_disk_file_version( file->TargetPath );
1272             if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1273             {
1274                 TRACE("newer version detected, not removing file\n");
1275                 msi_free( ver );
1276                 continue;
1277             }
1278             msi_free( ver );
1279         }
1280
1281         if (file->state == msifs_installed)
1282             WARN("removing installed file %s\n", debugstr_w(file->TargetPath));
1283
1284         TRACE("removing %s\n", debugstr_w(file->File) );
1285
1286         SetFileAttributesW( file->TargetPath, FILE_ATTRIBUTE_NORMAL );
1287         if (!DeleteFileW( file->TargetPath ))
1288         {
1289             WARN("failed to delete %s (%u)\n",  debugstr_w(file->TargetPath), GetLastError());
1290         }
1291         file->state = msifs_missing;
1292
1293         uirow = MSI_CreateRecord( 9 );
1294         MSI_RecordSetStringW( uirow, 1, file->FileName );
1295         MSI_RecordSetStringW( uirow, 9, comp->Directory );
1296         msi_ui_actiondata( package, szRemoveFiles, uirow );
1297         msiobj_release( &uirow->hdr );
1298     }
1299     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
1300     {
1301         MSIFOLDER *folder;
1302
1303         comp->Action = msi_get_component_action( package, comp );
1304         if (comp->Action != INSTALLSTATE_ABSENT) continue;
1305
1306         if (comp->assembly && !comp->assembly->application) continue;
1307
1308         if (comp->Attributes & msidbComponentAttributesPermanent)
1309         {
1310             TRACE("permanent component, not removing directory\n");
1311             continue;
1312         }
1313         folder = msi_get_loaded_folder( package, comp->Directory );
1314         remove_folder( folder );
1315     }
1316     return ERROR_SUCCESS;
1317 }