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