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