2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Aric Stewart for CodeWeavers
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * Actions dealing with files These are
29 * RemoveDuplicateFiles(TODO)
38 #include "wine/debug.h"
41 #include "msvcrt/fcntl.h"
44 #include "wine/unicode.h"
47 WINE_DEFAULT_DEBUG_CHANNEL(msi);
49 extern const WCHAR szInstallFiles[];
50 extern const WCHAR szDuplicateFiles[];
51 extern const WCHAR szMoveFiles[];
52 extern const WCHAR szPatchFiles[];
53 extern const WCHAR szRemoveDuplicateFiles[];
54 extern const WCHAR szRemoveFiles[];
56 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
58 inline static UINT create_component_directory ( MSIPACKAGE* package, INT component)
60 UINT rc = ERROR_SUCCESS;
64 install_path = resolve_folder(package, package->components[component].Directory,
65 FALSE, FALSE, &folder);
67 return ERROR_FUNCTION_FAILED;
70 if (folder->State == 0)
72 create_full_pathW(install_path);
75 HeapFree(GetProcessHeap(), 0, install_path);
81 * This is a helper function for handling embedded cabinet media
83 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
93 rc = read_raw_stream_data(package->db,stream_name,&data,&size);
94 if (rc != ERROR_SUCCESS)
98 if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
99 GetTempPathW(MAX_PATH,tmp);
101 GetTempFileNameW(tmp,stream_name,0,source);
103 track_tempfile(package,strrchrW(source,'\\'), source);
104 the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
105 FILE_ATTRIBUTE_NORMAL, NULL);
107 if (the_file == INVALID_HANDLE_VALUE)
109 ERR("Unable to create file %s\n",debugstr_w(source));
110 rc = ERROR_FUNCTION_FAILED;
114 WriteFile(the_file,data,size,&write,NULL);
115 CloseHandle(the_file);
116 TRACE("wrote %li bytes to %s\n",write,debugstr_w(source));
118 HeapFree(GetProcessHeap(),0,data);
123 /* Support functions for FDI functions */
130 static void * cabinet_alloc(ULONG cb)
132 return HeapAlloc(GetProcessHeap(), 0, cb);
135 static void cabinet_free(void *pv)
137 HeapFree(GetProcessHeap(), 0, pv);
140 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
143 DWORD dwShareMode = 0;
144 DWORD dwCreateDisposition = OPEN_EXISTING;
145 switch (oflag & _O_ACCMODE)
148 dwAccess = GENERIC_READ;
149 dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
152 dwAccess = GENERIC_WRITE;
153 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
156 dwAccess = GENERIC_READ | GENERIC_WRITE;
157 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
160 if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
161 dwCreateDisposition = CREATE_NEW;
162 else if (oflag & _O_CREAT)
163 dwCreateDisposition = CREATE_ALWAYS;
164 return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL,
165 dwCreateDisposition, 0, NULL);
168 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
171 if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL))
176 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
179 if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL))
184 static int cabinet_close(INT_PTR hf)
186 return CloseHandle((HANDLE)hf) ? 0 : -1;
189 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
191 /* flags are compatible and so are passed straight through */
192 return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
195 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
201 CabData *data = (CabData*) pfdin->pv;
202 ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1);
208 static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0};
215 given_file = strdupAtoW(pfdin->psz1);
216 index = get_loaded_file(data->package, given_file);
220 ERR("Unknown File in Cabinent (%s)\n",debugstr_w(given_file));
221 HeapFree(GetProcessHeap(),0,given_file);
225 if (!((data->package->files[index].State == 1 ||
226 data->package->files[index].State == 2)))
228 TRACE("Skipping extraction of %s\n",debugstr_w(given_file));
229 HeapFree(GetProcessHeap(),0,given_file);
233 file = cabinet_alloc((len+1)*sizeof(char));
234 strcpy(file, data->cab_path);
235 strcat(file, pfdin->psz1);
237 TRACE("file: %s\n", debugstr_a(file));
239 /* track this file so it can be deleted if not installed */
240 trackpath=strdupAtoW(file);
241 tracknametmp=strdupAtoW(strrchr(file,'\\')+1);
242 trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) +
243 strlenW(tmpprefix)+1) * sizeof(WCHAR));
245 strcpyW(trackname,tmpprefix);
246 strcatW(trackname,tracknametmp);
248 track_tempfile(data->package, trackname, trackpath);
250 HeapFree(GetProcessHeap(),0,trackpath);
251 HeapFree(GetProcessHeap(),0,trackname);
252 HeapFree(GetProcessHeap(),0,tracknametmp);
255 uirow=MSI_CreateRecord(9);
256 MSI_RecordSetStringW(uirow,1,data->package->files[index].File);
257 uipath = strdupW(data->package->files[index].TargetPath);
258 *(strrchrW(uipath,'\\')+1)=0;
259 MSI_RecordSetStringW(uirow,9,uipath);
260 MSI_RecordSetInteger(uirow,6,data->package->files[index].FileSize);
261 ui_actiondata(data->package,szInstallFiles,uirow);
262 msiobj_release( &uirow->hdr );
263 HeapFree(GetProcessHeap(),0,uipath);
265 ui_progress(data->package,2,data->package->files[index].FileSize,0,0);
267 return cabinet_open(file, _O_WRONLY | _O_CREAT, 0);
269 case fdintCLOSE_FILE_INFO:
273 if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
275 if (!LocalFileTimeToFileTime(&ft, &ftLocal))
277 if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal))
280 cabinet_close(pfdin->hf);
288 /***********************************************************************
289 * extract_cabinet_file
291 * Extract files from a cab file.
293 static BOOL extract_cabinet_file(MSIPACKAGE* package, LPCWSTR source,
303 TRACE("Extracting %s to %s\n",debugstr_w(source), debugstr_w(path));
305 hfdi = FDICreate(cabinet_alloc,
316 ERR("FDICreate failed\n");
320 if (!(cabinet = strdupWtoA( source )))
325 if (!(cab_path = strdupWtoA( path )))
328 HeapFree(GetProcessHeap(), 0, cabinet);
332 data.package = package;
333 data.cab_path = cab_path;
335 ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data);
338 ERR("FDICopy failed\n");
342 HeapFree(GetProcessHeap(), 0, cabinet);
343 HeapFree(GetProcessHeap(), 0, cab_path);
348 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, MSICOMPONENT*
351 if (file->Attributes & msidbFileAttributesNoncompressed)
354 p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
355 file->SourcePath = build_directory_name(2, p, file->ShortName);
356 HeapFree(GetProcessHeap(),0,p);
359 file->SourcePath = build_directory_name(2, path, file->File);
362 static UINT ready_media_for_file(MSIPACKAGE *package, int fileindex,
365 UINT rc = ERROR_SUCCESS;
367 static WCHAR source[MAX_PATH];
368 static const WCHAR ExecSeqQuery[] =
369 {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
370 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
371 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
372 ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
373 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
377 static UINT last_sequence = 0;
378 static LPWSTR last_path = NULL;
379 MSIFILE* file = NULL;
384 HeapFree(GetProcessHeap(),0,last_path);
385 return ERROR_SUCCESS;
388 file = &package->files[fileindex];
390 if (file->Sequence <= last_sequence)
392 set_file_source(package,file,comp,last_path);
393 TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
394 return ERROR_SUCCESS;
397 row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
400 TRACE("Unable to query row\n");
401 return ERROR_FUNCTION_FAILED;
404 seq = MSI_RecordGetInteger(row,2);
407 HeapFree(GetProcessHeap(),0,last_path);
410 if (file->Attributes & msidbFileAttributesNoncompressed)
412 last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
413 set_file_source(package,file,comp,last_path);
414 msiobj_release(&row->hdr);
418 cab = MSI_RecordGetString(row,4);
421 TRACE("Source is CAB %s\n",debugstr_w(cab));
422 /* the stream does not contain the # character */
425 writeout_cabinet_stream(package,&cab[1],source);
426 last_path = strdupW(source);
427 *(strrchrW(last_path,'\\')+1)=0;
432 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
433 if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
435 ERR("No Source dir defined \n");
436 rc = ERROR_FUNCTION_FAILED;
440 strcpyW(last_path,source);
442 /* extract the cab file into a folder in the temp folder */
444 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz)
446 GetTempPathW(MAX_PATH,last_path);
449 rc = !extract_cabinet_file(package, source, last_path);
450 /* reaquire file ptr */
451 file = &package->files[fileindex];
456 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
457 MSI_GetPropertyW(package,cszSourceDir,source,&sz);
458 strcpyW(last_path,source);
460 set_file_source(package, file, comp, last_path);
461 msiobj_release(&row->hdr);
466 inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key,
472 return ERROR_INVALID_HANDLE;
474 for (index = 0; index < package->loaded_files; index ++)
476 if (strcmpW(file_key,package->files[index].File)==0)
478 if (package->files[index].State >= 2)
480 *file_source = strdupW(package->files[index].TargetPath);
481 return ERROR_SUCCESS;
484 return ERROR_FILE_NOT_FOUND;
488 return ERROR_FUNCTION_FAILED;
492 * In order to make this work more effeciencly I am going to do this in 2
494 * Pass 1) Correct all the TargetPaths and determin what files are to be
496 * Pass 2) Extract Cabinents and copy files.
498 UINT ACTION_InstallFiles(MSIPACKAGE *package)
500 UINT rc = ERROR_SUCCESS;
504 return ERROR_INVALID_HANDLE;
506 /* increment progress bar each time action data is sent */
507 ui_progress(package,1,1,0,0);
510 for (index = 0; index < package->loaded_files; index++)
513 MSICOMPONENT* comp = NULL;
515 file = &package->files[index];
520 if (!ACTION_VerifyComponentForAction(package, file->ComponentIndex,
523 ui_progress(package,2,file->FileSize,0,0);
524 TRACE("File %s is not scheduled for install\n",
525 debugstr_w(file->File));
531 if ((file->State == 1) || (file->State == 2))
535 TRACE("Pass 1: %s\n",debugstr_w(file->File));
537 create_component_directory( package, file->ComponentIndex);
539 /* recalculate file paths because things may have changed */
541 if (file->ComponentIndex >= 0)
542 comp = &package->components[file->ComponentIndex];
545 ERR("No Component for file\n");
549 p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
550 HeapFree(GetProcessHeap(),0,file->TargetPath);
552 file->TargetPath = build_directory_name(2, p, file->FileName);
553 HeapFree(GetProcessHeap(),0,p);
558 for (index = 0; index < package->loaded_files; index++)
561 MSICOMPONENT* comp = NULL;
563 file = &package->files[index];
568 if ((file->State == 1) || (file->State == 2))
570 TRACE("Pass 2: %s\n",debugstr_w(file->File));
572 if (file->ComponentIndex >= 0)
573 comp = &package->components[file->ComponentIndex];
575 rc = ready_media_for_file(package, index, comp);
576 if (rc != ERROR_SUCCESS)
578 ERR("Unable to ready media\n");
579 rc = ERROR_FUNCTION_FAILED;
585 * our file table could change here because a new temp file
586 * may have been created. So reaquire our ptr.
588 file = &package->files[index];
590 TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
591 debugstr_w(file->TargetPath));
593 if (file->Attributes & msidbFileAttributesNoncompressed)
594 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
596 rc = MoveFileW(file->SourcePath, file->TargetPath);
601 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
602 debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
604 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
606 if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
607 ERR("Unable to copy file (%s -> %s) (error %ld)\n",
608 debugstr_w(file->SourcePath),
609 debugstr_w(file->TargetPath), GetLastError());
610 if (!(file->Attributes & msidbFileAttributesNoncompressed))
611 DeleteFileW(file->SourcePath);
614 else if (rc == ERROR_FILE_NOT_FOUND)
616 ERR("Source File Not Found! Continuing\n");
619 else if (file->Attributes & msidbFileAttributesVital)
621 ERR("Ignoring Error and continuing (nonvital file)...\n");
634 ready_media_for_file(NULL, 0, NULL);
638 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
640 MSIPACKAGE *package = (MSIPACKAGE*)param;
641 WCHAR *file_source = NULL;
642 WCHAR dest_name[0x100];
643 LPWSTR dest_path, dest;
644 LPCWSTR file_key, component;
649 component = MSI_RecordGetString(row,2);
650 component_index = get_loaded_component(package,component);
652 if (!ACTION_VerifyComponentForAction(package, component_index,
655 TRACE("Skipping copy due to disabled component %s\n",
656 debugstr_w(component));
658 /* the action taken was the same as the current install state */
659 package->components[component_index].Action =
660 package->components[component_index].Installed;
662 return ERROR_SUCCESS;
665 package->components[component_index].Action = INSTALLSTATE_LOCAL;
667 file_key = MSI_RecordGetString(row,3);
670 ERR("Unable to get file key\n");
671 return ERROR_FUNCTION_FAILED;
674 rc = get_file_target(package,file_key,&file_source);
676 if (rc != ERROR_SUCCESS)
678 ERR("Original file unknown %s\n",debugstr_w(file_key));
679 HeapFree(GetProcessHeap(),0,file_source);
680 return ERROR_SUCCESS;
683 if (MSI_RecordIsNull(row,4))
684 strcpyW(dest_name,strrchrW(file_source,'\\')+1);
688 MSI_RecordGetStringW(row,4,dest_name,&sz);
689 reduce_to_longfilename(dest_name);
692 if (MSI_RecordIsNull(row,5))
695 dest_path = strdupW(file_source);
696 p = strrchrW(dest_path,'\\');
703 destkey = MSI_RecordGetString(row,5);
704 dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
708 dest_path = load_dynamic_property(package, destkey, NULL);
711 FIXME("Unable to get destination folder, try AppSearch properties\n");
712 HeapFree(GetProcessHeap(),0,file_source);
713 return ERROR_SUCCESS;
718 dest = build_directory_name(2, dest_path, dest_name);
720 TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
723 if (strcmpW(file_source,dest))
724 rc = !CopyFileW(file_source,dest,TRUE);
728 if (rc != ERROR_SUCCESS)
729 ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
731 FIXME("We should track these duplicate files as well\n");
733 HeapFree(GetProcessHeap(),0,dest_path);
734 HeapFree(GetProcessHeap(),0,dest);
735 HeapFree(GetProcessHeap(),0,file_source);
737 return ERROR_SUCCESS;
740 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
744 static const WCHAR ExecSeqQuery[] =
745 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
746 '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
749 return ERROR_INVALID_HANDLE;
751 rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
752 if (rc != ERROR_SUCCESS)
753 return ERROR_SUCCESS;
755 rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
756 msiobj_release(&view->hdr);