msi: Implement the UnregisterFonts standard action.
[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         if (comp)
369             comp->Action = comp->Installed;
370
371         return ERROR_SUCCESS;
372     }
373
374     comp->Action = INSTALLSTATE_LOCAL;
375
376     file_key = MSI_RecordGetString(row,3);
377     if (!file_key)
378     {
379         ERR("Unable to get file key\n");
380         return ERROR_FUNCTION_FAILED;
381     }
382
383     rc = get_file_target(package,file_key,&file);
384
385     if (rc != ERROR_SUCCESS)
386     {
387         ERR("Original file unknown %s\n",debugstr_w(file_key));
388         return ERROR_SUCCESS;
389     }
390
391     if (MSI_RecordIsNull(row,4))
392         strcpyW(dest_name,strrchrW(file->TargetPath,'\\')+1);
393     else
394     {
395         sz=0x100;
396         MSI_RecordGetStringW(row,4,dest_name,&sz);
397         reduce_to_longfilename(dest_name);
398     }
399
400     if (MSI_RecordIsNull(row,5))
401     {
402         LPWSTR p;
403         dest_path = strdupW(file->TargetPath);
404         p = strrchrW(dest_path,'\\');
405         if (p)
406             *p=0;
407     }
408     else
409     {
410         LPCWSTR destkey;
411         destkey = MSI_RecordGetString(row,5);
412         dest_path = resolve_folder(package, destkey, FALSE, FALSE, TRUE, NULL);
413         if (!dest_path)
414         {
415             /* try a Property */
416             dest_path = msi_dup_property( package, destkey );
417             if (!dest_path)
418             {
419                 FIXME("Unable to get destination folder, try AppSearch properties\n");
420                 return ERROR_SUCCESS;
421             }
422         }
423     }
424
425     dest = build_directory_name(2, dest_path, dest_name);
426     create_full_pathW(dest_path);
427
428     TRACE("Duplicating file %s to %s\n",debugstr_w(file->TargetPath),
429                     debugstr_w(dest)); 
430
431     if (strcmpW(file->TargetPath,dest))
432         rc = !CopyFileW(file->TargetPath,dest,TRUE);
433     else
434         rc = ERROR_SUCCESS;
435
436     if (rc != ERROR_SUCCESS)
437         ERR("Failed to copy file %s -> %s, last error %d\n",
438             debugstr_w(file->TargetPath), debugstr_w(dest_path), GetLastError());
439
440     FIXME("We should track these duplicate files as well\n");   
441
442     msi_free(dest_path);
443     msi_free(dest);
444
445     msi_file_update_ui(package, file, szDuplicateFiles);
446
447     return ERROR_SUCCESS;
448 }
449
450 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
451 {
452     UINT rc;
453     MSIQUERY * view;
454     static const WCHAR ExecSeqQuery[] =
455         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
456          '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
457
458     rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
459     if (rc != ERROR_SUCCESS)
460         return ERROR_SUCCESS;
461
462     rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
463     msiobj_release(&view->hdr);
464
465     return rc;
466 }
467
468 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
469 {
470     INSTALLSTATE request = comp->ActionRequest;
471
472     if (request == INSTALLSTATE_UNKNOWN)
473         return FALSE;
474
475     if (install_mode == msidbRemoveFileInstallModeOnInstall &&
476         (request == INSTALLSTATE_LOCAL || request == INSTALLSTATE_SOURCE))
477         return TRUE;
478
479     if (request == INSTALLSTATE_ABSENT)
480     {
481         if (!comp->ComponentId)
482             return FALSE;
483
484         if (install_mode == msidbRemoveFileInstallModeOnRemove)
485             return TRUE;
486     }
487
488     if (install_mode == msidbRemoveFileInstallModeOnBoth)
489         return TRUE;
490
491     return FALSE;
492 }
493
494 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
495 {
496     MSIPACKAGE *package = param;
497     MSICOMPONENT *comp;
498     LPCWSTR component, filename, dirprop;
499     UINT install_mode;
500     LPWSTR dir = NULL, path = NULL;
501     DWORD size;
502     UINT r;
503
504     component = MSI_RecordGetString(row, 2);
505     filename = MSI_RecordGetString(row, 3);
506     dirprop = MSI_RecordGetString(row, 4);
507     install_mode = MSI_RecordGetInteger(row, 5);
508
509     comp = get_loaded_component(package, component);
510     if (!comp)
511     {
512         ERR("Invalid component: %s\n", debugstr_w(component));
513         return ERROR_FUNCTION_FAILED;
514     }
515
516     if (!verify_comp_for_removal(comp, install_mode))
517     {
518         TRACE("Skipping removal due to missing conditions\n");
519         comp->Action = comp->Installed;
520         return ERROR_SUCCESS;
521     }
522
523     dir = msi_dup_property(package, dirprop);
524     if (!dir)
525         return ERROR_OUTOFMEMORY;
526
527     size = (filename != NULL) ? lstrlenW(filename) : 0;
528     size += lstrlenW(dir) + 2;
529     path = msi_alloc(size * sizeof(WCHAR));
530     if (!path)
531     {
532         r = ERROR_OUTOFMEMORY;
533         goto done;
534     }
535
536     lstrcpyW(path, dir);
537     PathAddBackslashW(path);
538
539     if (filename)
540     {
541         lstrcatW(path, filename);
542
543         TRACE("Deleting misc file: %s\n", debugstr_w(path));
544         DeleteFileW(path);
545     }
546     else
547     {
548         TRACE("Removing misc directory: %s\n", debugstr_w(path));
549         RemoveDirectoryW(path);
550     }
551
552 done:
553     msi_free(path);
554     msi_free(dir);
555     return ERROR_SUCCESS;
556 }
557
558 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
559 {
560     MSIQUERY *view;
561     MSIFILE *file;
562     UINT r;
563
564     static const WCHAR query[] = {
565         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
566         '`','R','e','m','o','v','e','F','i','l','e','`',0};
567
568     r = MSI_DatabaseOpenViewW(package->db, query, &view);
569     if (r == ERROR_SUCCESS)
570     {
571         MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
572         msiobj_release(&view->hdr);
573     }
574
575     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
576     {
577         MSIRECORD *uirow;
578         LPWSTR uipath, p;
579
580         if ( file->state == msifs_installed )
581             ERR("removing installed file %s\n", debugstr_w(file->TargetPath));
582
583         if ( file->Component->ActionRequest != INSTALLSTATE_ABSENT ||
584              file->Component->Installed == INSTALLSTATE_SOURCE )
585             continue;
586
587         /* don't remove a file if the old file
588          * is strictly newer than the version to be installed
589          */
590         if ( msi_compare_file_version( file ) < 0 )
591             continue;
592
593         TRACE("removing %s\n", debugstr_w(file->File) );
594         if ( !DeleteFileW( file->TargetPath ) )
595             TRACE("failed to delete %s\n",  debugstr_w(file->TargetPath));
596         file->state = msifs_missing;
597
598         /* the UI chunk */
599         uirow = MSI_CreateRecord( 9 );
600         MSI_RecordSetStringW( uirow, 1, file->FileName );
601         uipath = strdupW( file->TargetPath );
602         p = strrchrW(uipath,'\\');
603         if (p)
604             p[1]=0;
605         MSI_RecordSetStringW( uirow, 9, uipath);
606         ui_actiondata( package, szRemoveFiles, uirow);
607         msiobj_release( &uirow->hdr );
608         msi_free( uipath );
609         /* FIXME: call ui_progress here? */
610     }
611
612     return ERROR_SUCCESS;
613 }