ntdll: Guard debugging code by #ifdef instead of plain #if.
[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 These are
24  *
25  * InstallFiles
26  * DuplicateFiles
27  * MoveFiles (TODO)
28  * PatchFiles (TODO)
29  * RemoveDuplicateFiles(TODO)
30  * RemoveFiles(TODO)
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 void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
51 {
52     MSIRECORD *uirow;
53     LPWSTR uipath, p;
54
55     /* the UI chunk */
56     uirow = MSI_CreateRecord( 9 );
57     MSI_RecordSetStringW( uirow, 1, f->FileName );
58     uipath = strdupW( f->TargetPath );
59     p = strrchrW(uipath,'\\');
60     if (p)
61         p[1]=0;
62     MSI_RecordSetStringW( uirow, 9, uipath);
63     MSI_RecordSetInteger( uirow, 6, f->FileSize );
64     ui_actiondata( package, action, uirow);
65     msiobj_release( &uirow->hdr );
66     msi_free( uipath );
67     ui_progress( package, 2, f->FileSize, 0, 0);
68 }
69
70 /* compares the version of a file read from the filesystem and
71  * the version specified in the File table
72  */
73 static int msi_compare_file_version(MSIFILE *file)
74 {
75     WCHAR version[MAX_PATH];
76     DWORD size;
77     UINT r;
78
79     size = MAX_PATH;
80     version[0] = '\0';
81     r = MsiGetFileVersionW(file->TargetPath, version, &size, NULL, NULL);
82     if (r != ERROR_SUCCESS)
83         return 0;
84
85     return lstrcmpW(version, file->Version);
86 }
87
88 static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, 
89                             MSIFILE** file)
90 {
91     LIST_FOR_EACH_ENTRY( *file, &package->files, MSIFILE, entry )
92     {
93         if (lstrcmpW( file_key, (*file)->File )==0)
94         {
95             if ((*file)->state >= msifs_overwrite)
96                 return ERROR_SUCCESS;
97             else
98                 return ERROR_FILE_NOT_FOUND;
99         }
100     }
101
102     return ERROR_FUNCTION_FAILED;
103 }
104
105 static void schedule_install_files(MSIPACKAGE *package)
106 {
107     MSIFILE *file;
108
109     LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
110     {
111         if (!ACTION_VerifyComponentForAction(file->Component, INSTALLSTATE_LOCAL))
112         {
113             TRACE("File %s is not scheduled for install\n", debugstr_w(file->File));
114
115             ui_progress(package,2,file->FileSize,0,0);
116             file->state = msifs_skipped;
117         }
118     }
119 }
120
121 static UINT copy_file(MSIFILE *file, LPWSTR source)
122 {
123     BOOL ret;
124
125     ret = CopyFileW(source, file->TargetPath, FALSE);
126     if (!ret)
127         return GetLastError();
128
129     SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
130
131     file->state = msifs_installed;
132     return ERROR_SUCCESS;
133 }
134
135 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
136 {
137     UINT gle;
138
139     TRACE("Copying %s to %s\n", debugstr_w(source),
140           debugstr_w(file->TargetPath));
141
142     gle = copy_file(file, source);
143     if (gle == ERROR_SUCCESS)
144         return gle;
145
146     if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
147     {
148         TRACE("overwriting existing file\n");
149         return ERROR_SUCCESS;
150     }
151     else if (gle == ERROR_ACCESS_DENIED)
152     {
153         SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
154
155         gle = copy_file(file, source);
156         TRACE("Overwriting existing file: %d\n", gle);
157     }
158     if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
159     {
160         WCHAR tmpfileW[MAX_PATH], *pathW, *p;
161         DWORD len;
162
163         TRACE("file in use, scheduling rename operation\n");
164
165         GetTempFileNameW(szBackSlash, szMsi, 0, tmpfileW);
166         len = strlenW(file->TargetPath) + strlenW(tmpfileW) + 1;
167         if (!(pathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
168             return ERROR_OUTOFMEMORY;
169
170         strcpyW(pathW, file->TargetPath);
171         if ((p = strrchrW(pathW, '\\'))) *p = 0;
172         strcatW(pathW, tmpfileW);
173
174         if (CopyFileW(source, pathW, FALSE) &&
175             MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
176             MoveFileExW(pathW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
177         {
178             file->state = msifs_installed;
179             package->need_reboot = 1;
180             gle = ERROR_SUCCESS;
181         }
182         else
183         {
184             gle = GetLastError();
185             WARN("failed to schedule rename operation: %d)\n", gle);
186         }
187         HeapFree(GetProcessHeap(), 0, pathW);
188     }
189
190     return gle;
191 }
192
193 static BOOL check_dest_hash_matches(MSIFILE *file)
194 {
195     MSIFILEHASHINFO hash;
196     UINT r;
197
198     if (!file->hash.dwFileHashInfoSize)
199         return FALSE;
200
201     hash.dwFileHashInfoSize = sizeof(MSIFILEHASHINFO);
202     r = MsiGetFileHashW(file->TargetPath, 0, &hash);
203     if (r != ERROR_SUCCESS)
204         return FALSE;
205
206     return !memcmp(&hash, &file->hash, sizeof(MSIFILEHASHINFO));
207 }
208
209 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
210                             LPWSTR *path, DWORD *attrs, PVOID user)
211 {
212     static MSIFILE *f = NULL;
213
214     if (action == MSICABEXTRACT_BEGINEXTRACT)
215     {
216         f = get_loaded_file(package, file);
217         if (!f)
218         {
219             WARN("unknown file in cabinet (%s)\n", debugstr_w(file));
220             return FALSE;
221         }
222
223         if (f->state != msifs_missing && f->state != msifs_overwrite)
224         {
225             TRACE("Skipping extraction of %s\n", debugstr_w(file));
226             return FALSE;
227         }
228
229         msi_file_update_ui(package, f, szInstallFiles);
230
231         *path = strdupW(f->TargetPath);
232         *attrs = f->Attributes;
233     }
234     else if (action == MSICABEXTRACT_FILEEXTRACTED)
235     {
236         f->state = msifs_installed;
237         f = NULL;
238     }
239
240     return TRUE;
241 }
242
243 /*
244  * ACTION_InstallFiles()
245  * 
246  * For efficiency, this is done in two passes:
247  * 1) Correct all the TargetPaths and determine what files are to be installed.
248  * 2) Extract Cabinets and copy files.
249  */
250 UINT ACTION_InstallFiles(MSIPACKAGE *package)
251 {
252     MSIMEDIAINFO *mi;
253     UINT rc = ERROR_SUCCESS;
254     MSIFILE *file;
255
256     /* increment progress bar each time action data is sent */
257     ui_progress(package,1,1,0,0);
258
259     schedule_install_files(package);
260
261     /*
262      * Despite MSDN specifying that the CreateFolders action
263      * should be called before InstallFiles, some installers don't
264      * do that, and they seem to work correctly.  We need to create
265      * directories here to make sure that the files can be copied.
266      */
267     msi_create_component_directories( package );
268
269     mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
270
271     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
272     {
273         if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
274             continue;
275
276         if (check_dest_hash_matches(file))
277         {
278             TRACE("File hashes match, not overwriting\n");
279             continue;
280         }
281
282         if (MsiGetFileVersionW(file->TargetPath, NULL, NULL, NULL, NULL) == ERROR_SUCCESS &&
283             msi_compare_file_version(file) >= 0)
284         {
285             TRACE("Destination file version greater, not overwriting\n");
286             continue;
287         }
288
289         if (file->Sequence > mi->last_sequence || mi->is_continuous ||
290             (file->IsCompressed && !mi->is_extracted))
291         {
292             MSICABDATA data;
293
294             rc = ready_media(package, file, mi);
295             if (rc != ERROR_SUCCESS)
296             {
297                 ERR("Failed to ready media\n");
298                 break;
299             }
300
301             data.mi = mi;
302             data.package = package;
303             data.cb = installfiles_cb;
304             data.user = NULL;
305
306             if (file->IsCompressed &&
307                 !msi_cabextract(package, mi, &data))
308             {
309                 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
310                 rc = ERROR_FUNCTION_FAILED;
311                 break;
312             }
313         }
314
315         if (!file->IsCompressed)
316         {
317             LPWSTR source = resolve_file_source(package, file);
318
319             TRACE("file paths %s to %s\n", debugstr_w(source),
320                   debugstr_w(file->TargetPath));
321
322             msi_file_update_ui(package, file, szInstallFiles);
323             rc = copy_install_file(package, file, source);
324             if (rc != ERROR_SUCCESS)
325             {
326                 ERR("Failed to copy %s to %s (%d)\n", debugstr_w(source),
327                     debugstr_w(file->TargetPath), rc);
328                 rc = ERROR_INSTALL_FAILURE;
329                 msi_free(source);
330                 break;
331             }
332
333             msi_free(source);
334         }
335         else if (file->state != msifs_installed)
336         {
337             ERR("compressed file wasn't extracted (%s)\n",
338                 debugstr_w(file->TargetPath));
339             rc = ERROR_INSTALL_FAILURE;
340             break;
341         }
342     }
343
344     msi_free_media_info(mi);
345     return rc;
346 }
347
348 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
349 {
350     MSIPACKAGE *package = param;
351     WCHAR dest_name[0x100];
352     LPWSTR dest_path, dest;
353     LPCWSTR file_key, component;
354     DWORD sz;
355     DWORD rc;
356     MSICOMPONENT *comp;
357     MSIFILE *file;
358
359     component = MSI_RecordGetString(row,2);
360     comp = get_loaded_component(package,component);
361
362     if (!ACTION_VerifyComponentForAction( comp, INSTALLSTATE_LOCAL ))
363     {
364         TRACE("Skipping copy due to disabled component %s\n",
365                         debugstr_w(component));
366
367         /* the action taken was the same as the current install state */        
368         comp->Action = comp->Installed;
369
370         return ERROR_SUCCESS;
371     }
372
373     comp->Action = INSTALLSTATE_LOCAL;
374
375     file_key = MSI_RecordGetString(row,3);
376     if (!file_key)
377     {
378         ERR("Unable to get file key\n");
379         return ERROR_FUNCTION_FAILED;
380     }
381
382     rc = get_file_target(package,file_key,&file);
383
384     if (rc != ERROR_SUCCESS)
385     {
386         ERR("Original file unknown %s\n",debugstr_w(file_key));
387         return ERROR_SUCCESS;
388     }
389
390     if (MSI_RecordIsNull(row,4))
391         strcpyW(dest_name,strrchrW(file->TargetPath,'\\')+1);
392     else
393     {
394         sz=0x100;
395         MSI_RecordGetStringW(row,4,dest_name,&sz);
396         reduce_to_longfilename(dest_name);
397     }
398
399     if (MSI_RecordIsNull(row,5))
400     {
401         LPWSTR p;
402         dest_path = strdupW(file->TargetPath);
403         p = strrchrW(dest_path,'\\');
404         if (p)
405             *p=0;
406     }
407     else
408     {
409         LPCWSTR destkey;
410         destkey = MSI_RecordGetString(row,5);
411         dest_path = resolve_folder(package, destkey, FALSE, FALSE, TRUE, NULL);
412         if (!dest_path)
413         {
414             /* try a Property */
415             dest_path = msi_dup_property( package, destkey );
416             if (!dest_path)
417             {
418                 FIXME("Unable to get destination folder, try AppSearch properties\n");
419                 return ERROR_SUCCESS;
420             }
421         }
422     }
423
424     dest = build_directory_name(2, dest_path, dest_name);
425     create_full_pathW(dest_path);
426
427     TRACE("Duplicating file %s to %s\n",debugstr_w(file->TargetPath),
428                     debugstr_w(dest)); 
429
430     if (strcmpW(file->TargetPath,dest))
431         rc = !CopyFileW(file->TargetPath,dest,TRUE);
432     else
433         rc = ERROR_SUCCESS;
434
435     if (rc != ERROR_SUCCESS)
436         ERR("Failed to copy file %s -> %s, last error %d\n",
437             debugstr_w(file->TargetPath), debugstr_w(dest_path), GetLastError());
438
439     FIXME("We should track these duplicate files as well\n");   
440
441     msi_free(dest_path);
442     msi_free(dest);
443
444     msi_file_update_ui(package, file, szDuplicateFiles);
445
446     return ERROR_SUCCESS;
447 }
448
449 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
450 {
451     UINT rc;
452     MSIQUERY * view;
453     static const WCHAR ExecSeqQuery[] =
454         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
455          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
456
457     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
458     if (rc != ERROR_SUCCESS)
459         return ERROR_SUCCESS;
460
461     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
462     msiobj_release(&view->hdr);
463
464     return rc;
465 }
466
467 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
468 {
469     INSTALLSTATE request = comp->ActionRequest;
470
471     if (request == INSTALLSTATE_UNKNOWN)
472         return FALSE;
473
474     if (install_mode == msidbRemoveFileInstallModeOnInstall &&
475         (request == INSTALLSTATE_LOCAL || request == INSTALLSTATE_SOURCE))
476         return TRUE;
477
478     if (request == INSTALLSTATE_ABSENT)
479     {
480         if (!comp->ComponentId)
481             return FALSE;
482
483         if (install_mode == msidbRemoveFileInstallModeOnRemove)
484             return TRUE;
485     }
486
487     if (install_mode == msidbRemoveFileInstallModeOnBoth)
488         return TRUE;
489
490     return FALSE;
491 }
492
493 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
494 {
495     MSIPACKAGE *package = param;
496     MSICOMPONENT *comp;
497     LPCWSTR component, filename, dirprop;
498     UINT install_mode;
499     LPWSTR dir = NULL, path = NULL;
500     DWORD size;
501     UINT r;
502
503     component = MSI_RecordGetString(row, 2);
504     filename = MSI_RecordGetString(row, 3);
505     dirprop = MSI_RecordGetString(row, 4);
506     install_mode = MSI_RecordGetInteger(row, 5);
507
508     comp = get_loaded_component(package, component);
509     if (!comp)
510     {
511         ERR("Invalid component: %s\n", debugstr_w(component));
512         return ERROR_FUNCTION_FAILED;
513     }
514
515     if (!verify_comp_for_removal(comp, install_mode))
516     {
517         TRACE("Skipping removal due to missing conditions\n");
518         comp->Action = comp->Installed;
519         return ERROR_SUCCESS;
520     }
521
522     dir = msi_dup_property(package, dirprop);
523     if (!dir)
524         return ERROR_OUTOFMEMORY;
525
526     size = (filename != NULL) ? lstrlenW(filename) : 0;
527     size += lstrlenW(dir) + 2;
528     path = msi_alloc(size * sizeof(WCHAR));
529     if (!path)
530     {
531         r = ERROR_OUTOFMEMORY;
532         goto done;
533     }
534
535     lstrcpyW(path, dir);
536     PathAddBackslashW(path);
537
538     if (filename)
539     {
540         lstrcatW(path, filename);
541
542         TRACE("Deleting misc file: %s\n", debugstr_w(path));
543         DeleteFileW(path);
544     }
545     else
546     {
547         TRACE("Removing misc directory: %s\n", debugstr_w(path));
548         RemoveDirectoryW(path);
549     }
550
551 done:
552     msi_free(path);
553     msi_free(dir);
554     return ERROR_SUCCESS;
555 }
556
557 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
558 {
559     MSIQUERY *view;
560     MSIFILE *file;
561     UINT r;
562
563     static const WCHAR query[] = {
564         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
565         '`','R','e','m','o','v','e','F','i','l','e','`',0};
566
567     r = MSI_DatabaseOpenViewW(package->db, query, &view);
568     if (r == ERROR_SUCCESS)
569     {
570         MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
571         msiobj_release(&view->hdr);
572     }
573
574     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
575     {
576         MSIRECORD *uirow;
577         LPWSTR uipath, p;
578
579         if ( file->state == msifs_installed )
580             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
581
582         if ( file->Component->ActionRequest != INSTALLSTATE_ABSENT ||
583              file->Component->Installed == INSTALLSTATE_SOURCE )
584             continue;
585
586         /* don't remove a file if the old file
587          * is strictly newer than the version to be installed
588          */
589         if ( msi_compare_file_version( file ) < 0 )
590             continue;
591
592         TRACE("removing %s\n", debugstr_w(file->File) );
593         if ( !DeleteFileW( file->TargetPath ) )
594             TRACE("failed to delete %s\n",  debugstr_w(file->TargetPath));
595         file->state = msifs_missing;
596
597         /* the UI chunk */
598         uirow = MSI_CreateRecord( 9 );
599         MSI_RecordSetStringW( uirow, 1, file->FileName );
600         uipath = strdupW( file->TargetPath );
601         p = strrchrW(uipath,'\\');
602         if (p)
603             p[1]=0;
604         MSI_RecordSetStringW( uirow, 9, uipath);
605         ui_actiondata( package, szRemoveFiles, uirow);
606         msiobj_release( &uirow->hdr );
607         msi_free( uipath );
608         /* FIXME: call ui_progress here? */
609     }
610
611     return ERROR_SUCCESS;
612 }