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