From c668952023f2f4e8cd2e5f76767fceadaf024d7f Mon Sep 17 00:00:00 2001 From: Aric Stewart Date: Fri, 17 Jun 2005 20:56:55 +0000 Subject: [PATCH] Break out all the file related actions and helper functions into files.c --- dlls/msi/Makefile.in | 1 + dlls/msi/action.c | 683 +----------------------------------------- dlls/msi/action.h | 3 + dlls/msi/files.c | 699 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 718 insertions(+), 668 deletions(-) create mode 100644 dlls/msi/files.c diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in index 32e815a9324..1efb19025c2 100644 --- a/dlls/msi/Makefile.in +++ b/dlls/msi/Makefile.in @@ -18,6 +18,7 @@ C_SRCS = \ dialog.c \ distinct.c \ events.c \ + files.c \ format.c \ handle.c \ helpers.c \ diff --git a/dlls/msi/action.c b/dlls/msi/action.c index 4c136e357f1..fc919564544 100644 --- a/dlls/msi/action.c +++ b/dlls/msi/action.c @@ -1,7 +1,7 @@ /* * Implementation of the Microsoft Installer (msi.dll) * - * Copyright 2004 Aric Stewart for CodeWeavers + * Copyright 2004,2005 Aric Stewart for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,7 +27,6 @@ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/stand */ #include -#include #define COBJMACROS @@ -36,15 +35,8 @@ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/stand #include "winerror.h" #include "winreg.h" #include "wine/debug.h" -#include "fdi.h" -#include "msi.h" -#include "msiquery.h" #include "msidefs.h" -#include "msvcrt/fcntl.h" -#include "objbase.h" -#include "objidl.h" #include "msipriv.h" -#include "winnls.h" #include "winuser.h" #include "shlobj.h" #include "wine/unicode.h" @@ -73,8 +65,6 @@ static UINT ACTION_CostInitialize(MSIPACKAGE *package); static UINT ACTION_CreateFolders(MSIPACKAGE *package); static UINT ACTION_CostFinalize(MSIPACKAGE *package); static UINT ACTION_FileCost(MSIPACKAGE *package); -static UINT ACTION_InstallFiles(MSIPACKAGE *package); -static UINT ACTION_DuplicateFiles(MSIPACKAGE *package); static UINT ACTION_WriteRegistryValues(MSIPACKAGE *package); static UINT ACTION_InstallInitialize(MSIPACKAGE *package); static UINT ACTION_InstallValidate(MSIPACKAGE *package); @@ -99,16 +89,15 @@ static UINT ACTION_PublishComponents(MSIPACKAGE *package); /* * consts and values used */ -static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0}; static const WCHAR c_colon[] = {'C',':','\\',0}; -static const WCHAR cszbs[]={'\\',0}; + const static WCHAR szCreateFolders[] = {'C','r','e','a','t','e','F','o','l','d','e','r','s',0}; const static WCHAR szCostFinalize[] = {'C','o','s','t','F','i','n','a','l','i','z','e',0}; -const static WCHAR szInstallFiles[] = +const WCHAR szInstallFiles[] = {'I','n','s','t','a','l','l','F','i','l','e','s',0}; -const static WCHAR szDuplicateFiles[] = +const WCHAR szDuplicateFiles[] = {'D','u','p','l','i','c','a','t','e','F','i','l','e','s',0}; const static WCHAR szWriteRegistryValues[] = {'W','r','i','t','e','R','e','g','i','s','t','r','y', @@ -125,7 +114,7 @@ const static WCHAR szLaunchConditions[] = {'L','a','u','n','c','h','C','o','n','d','i','t','i','o','n','s',0}; const static WCHAR szProcessComponents[] = {'P','r','o','c','e','s','s','C','o','m','p','o','n','e','n','t','s',0}; -const WCHAR szRegisterTypeLibraries[] = +const static WCHAR szRegisterTypeLibraries[] = {'R','e','g','i','s','t','e','r','T','y','p','e', 'L','i','b','r','a','r','i','e','s',0}; const WCHAR szRegisterClassInfo[] = @@ -181,10 +170,10 @@ const static WCHAR szInstallSFPCatalogFile[] = 'F','i','l','e',0}; const static WCHAR szIsolateComponents[] = {'I','s','o','l','a','t','e','C','o','m','p','o','n','e','n','t','s',0}; -const static WCHAR szMigrateFeatureStates[] = +const WCHAR szMigrateFeatureStates[] = {'M','i','g','r','a','t','e','F','e','a','t','u','r','e', 'S','t','a','t','e','s',0}; -const static WCHAR szMoveFiles[] = +const WCHAR szMoveFiles[] = {'M','o','v','e','F','i','l','e','s',0}; const static WCHAR szMsiPublishAssemblies[] = {'M','s','i','P','u','b','l','i','s','h', @@ -196,7 +185,7 @@ const static WCHAR szInstallODBC[] = {'I','n','s','t','a','l','l','O','D','B','C',0}; const static WCHAR szInstallServices[] = {'I','n','s','t','a','l','l','S','e','r','v','i','c','e','s',0}; -const static WCHAR szPatchFiles[] = +const WCHAR szPatchFiles[] = {'P','a','t','c','h','F','i','l','e','s',0}; const static WCHAR szPublishComponents[] = {'P','u','b','l','i','s','h','C','o','m','p','o','n','e','n','t','s',0}; @@ -211,16 +200,16 @@ const WCHAR szRegisterMIMEInfo[] = {'R','e','g','i','s','t','e','r','M','I','M','E','I','n','f','o',0}; const static WCHAR szRegisterUser[] = {'R','e','g','i','s','t','e','r','U','s','e','r',0}; -const static WCHAR szRemoveDuplicateFiles[] = +const WCHAR szRemoveDuplicateFiles[] = {'R','e','m','o','v','e','D','u','p','l','i','c','a','t','e', 'F','i','l','e','s',0}; const static WCHAR szRemoveEnvironmentStrings[] = {'R','e','m','o','v','e','E','n','v','i','r','o','n','m','e','n','t', 'S','t','r','i','n','g','s',0}; -const static WCHAR szRemoveExistingProducts[] = +const WCHAR szRemoveExistingProducts[] = {'R','e','m','o','v','e','E','x','i','s','t','i','n','g', 'P','r','o','d','u','c','t','s',0}; -const static WCHAR szRemoveFiles[] = +const WCHAR szRemoveFiles[] = {'R','e','m','o','v','e','F','i','l','e','s',0}; const static WCHAR szRemoveFolders[] = {'R','e','m','o','v','e','F','o','l','d','e','r','s',0}; @@ -250,19 +239,19 @@ const static WCHAR szUnpublishComponents[] = 'C','o','m','p','o','n','e','n','t','s',0}; const static WCHAR szUnpublishFeatures[] = {'U','n','p','u','b','l','i','s','h','F','e','a','t','u','r','e','s',0}; -const static WCHAR szUnregisterClassInfo[] = +const WCHAR szUnregisterClassInfo[] = {'U','n','r','e','g','i','s','t','e','r','C','l','a','s','s', 'I','n','f','o',0}; const static WCHAR szUnregisterComPlus[] = {'U','n','r','e','g','i','s','t','e','r','C','o','m','P','l','u','s',0}; -const static WCHAR szUnregisterExtensionInfo[] = +const WCHAR szUnregisterExtensionInfo[] = {'U','n','r','e','g','i','s','t','e','r', 'E','x','t','e','n','s','i','o','n','I','n','f','o',0}; const static WCHAR szUnregisterFonts[] = {'U','n','r','e','g','i','s','t','e','r','F','o','n','t','s',0}; -const static WCHAR szUnregisterMIMEInfo[] = +const WCHAR szUnregisterMIMEInfo[] = {'U','n','r','e','g','i','s','t','e','r','M','I','M','E','I','n','f','o',0}; -const static WCHAR szUnregisterProgIdInfo[] = +const WCHAR szUnregisterProgIdInfo[] = {'U','n','r','e','g','i','s','t','e','r','P','r','o','g','I','d', 'I','n','f','o',0}; const static WCHAR szUnregisterTypeLibraries[] = @@ -2028,648 +2017,6 @@ static UINT ACTION_CostFinalize(MSIPACKAGE *package) return SetFeatureStates(package); } -/* - * This is a helper function for handling embedded cabinet media - */ -static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name, - WCHAR* source) -{ - UINT rc; - USHORT* data; - UINT size; - DWORD write; - HANDLE the_file; - WCHAR tmp[MAX_PATH]; - - rc = read_raw_stream_data(package->db,stream_name,&data,&size); - if (rc != ERROR_SUCCESS) - return rc; - - write = MAX_PATH; - if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write)) - GetTempPathW(MAX_PATH,tmp); - - GetTempFileNameW(tmp,stream_name,0,source); - - track_tempfile(package,strrchrW(source,'\\'), source); - the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - - if (the_file == INVALID_HANDLE_VALUE) - { - ERR("Unable to create file %s\n",debugstr_w(source)); - rc = ERROR_FUNCTION_FAILED; - goto end; - } - - WriteFile(the_file,data,size,&write,NULL); - CloseHandle(the_file); - TRACE("wrote %li bytes to %s\n",write,debugstr_w(source)); -end: - HeapFree(GetProcessHeap(),0,data); - return rc; -} - - -/* Support functions for FDI functions */ -typedef struct -{ - MSIPACKAGE* package; - LPCSTR cab_path; - LPCSTR file_name; -} CabData; - -static void * cabinet_alloc(ULONG cb) -{ - return HeapAlloc(GetProcessHeap(), 0, cb); -} - -static void cabinet_free(void *pv) -{ - HeapFree(GetProcessHeap(), 0, pv); -} - -static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode) -{ - DWORD dwAccess = 0; - DWORD dwShareMode = 0; - DWORD dwCreateDisposition = OPEN_EXISTING; - switch (oflag & _O_ACCMODE) - { - case _O_RDONLY: - dwAccess = GENERIC_READ; - dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE; - break; - case _O_WRONLY: - dwAccess = GENERIC_WRITE; - dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; - break; - case _O_RDWR: - dwAccess = GENERIC_READ | GENERIC_WRITE; - dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; - break; - } - if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL)) - dwCreateDisposition = CREATE_NEW; - else if (oflag & _O_CREAT) - dwCreateDisposition = CREATE_ALWAYS; - return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL, - dwCreateDisposition, 0, NULL); -} - -static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb) -{ - DWORD dwRead; - if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL)) - return dwRead; - return 0; -} - -static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb) -{ - DWORD dwWritten; - if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL)) - return dwWritten; - return 0; -} - -static int cabinet_close(INT_PTR hf) -{ - return CloseHandle((HANDLE)hf) ? 0 : -1; -} - -static long cabinet_seek(INT_PTR hf, long dist, int seektype) -{ - /* flags are compatible and so are passed straight through */ - return SetFilePointer((HANDLE)hf, dist, NULL, seektype); -} - -static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin) -{ - /* FIXME: try to do more processing in this function */ - switch (fdint) - { - case fdintCOPY_FILE: - { - CabData *data = (CabData*) pfdin->pv; - ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1); - char *file; - - LPWSTR trackname; - LPWSTR trackpath; - LPWSTR tracknametmp; - static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0}; - - if (data->file_name && lstrcmpiA(data->file_name,pfdin->psz1)) - return 0; - - file = cabinet_alloc((len+1)*sizeof(char)); - strcpy(file, data->cab_path); - strcat(file, pfdin->psz1); - - TRACE("file: %s\n", debugstr_a(file)); - - /* track this file so it can be deleted if not installed */ - trackpath=strdupAtoW(file); - tracknametmp=strdupAtoW(strrchr(file,'\\')+1); - trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) + - strlenW(tmpprefix)+1) * sizeof(WCHAR)); - - strcpyW(trackname,tmpprefix); - strcatW(trackname,tracknametmp); - - track_tempfile(data->package, trackname, trackpath); - - HeapFree(GetProcessHeap(),0,trackpath); - HeapFree(GetProcessHeap(),0,trackname); - HeapFree(GetProcessHeap(),0,tracknametmp); - - return cabinet_open(file, _O_WRONLY | _O_CREAT, 0); - } - case fdintCLOSE_FILE_INFO: - { - FILETIME ft; - FILETIME ftLocal; - if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft)) - return -1; - if (!LocalFileTimeToFileTime(&ft, &ftLocal)) - return -1; - if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal)) - return -1; - - cabinet_close(pfdin->hf); - return 1; - } - default: - return 0; - } -} - -/*********************************************************************** - * extract_cabinet_file - * - * Extract files from a cab file. - */ -static BOOL extract_a_cabinet_file(MSIPACKAGE* package, const WCHAR* source, - const WCHAR* path, const WCHAR* file) -{ - HFDI hfdi; - ERF erf; - BOOL ret; - char *cabinet; - char *cab_path; - char *file_name; - CabData data; - - TRACE("Extracting %s (%s) to %s\n",debugstr_w(source), - debugstr_w(file), debugstr_w(path)); - - hfdi = FDICreate(cabinet_alloc, - cabinet_free, - cabinet_open, - cabinet_read, - cabinet_write, - cabinet_close, - cabinet_seek, - 0, - &erf); - if (!hfdi) - { - ERR("FDICreate failed\n"); - return FALSE; - } - - if (!(cabinet = strdupWtoA( source ))) - { - FDIDestroy(hfdi); - return FALSE; - } - if (!(cab_path = strdupWtoA( path ))) - { - FDIDestroy(hfdi); - HeapFree(GetProcessHeap(), 0, cabinet); - return FALSE; - } - - data.package = package; - data.cab_path = cab_path; - if (file) - file_name = strdupWtoA(file); - else - file_name = NULL; - data.file_name = file_name; - - ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data); - - if (!ret) - ERR("FDICopy failed\n"); - - FDIDestroy(hfdi); - - HeapFree(GetProcessHeap(), 0, cabinet); - HeapFree(GetProcessHeap(), 0, cab_path); - HeapFree(GetProcessHeap(), 0, file_name); - - return ret; -} - -static UINT ready_media_for_file(MSIPACKAGE *package, WCHAR* path, - MSIFILE* file) -{ - UINT rc = ERROR_SUCCESS; - MSIRECORD * row = 0; - static WCHAR source[MAX_PATH]; - static const WCHAR ExecSeqQuery[] = - {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ', - '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ', - '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=', - ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ', - '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0}; - LPCWSTR cab; - DWORD sz; - INT seq; - static UINT last_sequence = 0; - - if (file->Attributes & msidbFileAttributesNoncompressed) - { - TRACE("Uncompressed File, no media to ready.\n"); - return ERROR_SUCCESS; - } - - if (file->Sequence <= last_sequence) - { - TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence); - /*extract_a_cabinet_file(package, source,path,file->File); */ - return ERROR_SUCCESS; - } - - row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence); - if (!row) - return ERROR_FUNCTION_FAILED; - - seq = MSI_RecordGetInteger(row,2); - last_sequence = seq; - - cab = MSI_RecordGetString(row,4); - if (cab) - { - TRACE("Source is CAB %s\n",debugstr_w(cab)); - /* the stream does not contain the # character */ - if (cab[0]=='#') - { - writeout_cabinet_stream(package,&cab[1],source); - strcpyW(path,source); - *(strrchrW(path,'\\')+1)=0; - } - else - { - sz = MAX_PATH; - if (MSI_GetPropertyW(package, cszSourceDir, source, &sz)) - { - ERR("No Source dir defined \n"); - rc = ERROR_FUNCTION_FAILED; - } - else - { - strcpyW(path,source); - strcatW(source,cab); - /* extract the cab file into a folder in the temp folder */ - sz = MAX_PATH; - if (MSI_GetPropertyW(package, cszTempFolder,path, &sz) - != ERROR_SUCCESS) - GetTempPathW(MAX_PATH,path); - } - } - rc = !extract_a_cabinet_file(package, source,path,NULL); - } - else - { - sz = MAX_PATH; - MSI_GetPropertyW(package,cszSourceDir,source,&sz); - strcpyW(path,source); - } - msiobj_release(&row->hdr); - return rc; -} - -inline static UINT create_component_directory ( MSIPACKAGE* package, INT component) -{ - UINT rc = ERROR_SUCCESS; - MSIFOLDER *folder; - LPWSTR install_path; - - install_path = resolve_folder(package, package->components[component].Directory, - FALSE, FALSE, &folder); - if (!install_path) - return ERROR_FUNCTION_FAILED; - - /* create the path */ - if (folder->State == 0) - { - create_full_pathW(install_path); - folder->State = 2; - } - HeapFree(GetProcessHeap(), 0, install_path); - - return rc; -} - -static UINT ACTION_InstallFiles(MSIPACKAGE *package) -{ - UINT rc = ERROR_SUCCESS; - DWORD index; - MSIRECORD * uirow; - WCHAR uipath[MAX_PATH]; - - if (!package) - return ERROR_INVALID_HANDLE; - - /* increment progress bar each time action data is sent */ - ui_progress(package,1,1,0,0); - - for (index = 0; index < package->loaded_files; index++) - { - WCHAR path_to_source[MAX_PATH]; - MSIFILE *file; - - file = &package->files[index]; - - if (file->Temporary) - continue; - - - if (!ACTION_VerifyComponentForAction(package, file->ComponentIndex, - INSTALLSTATE_LOCAL)) - { - ui_progress(package,2,file->FileSize,0,0); - TRACE("File %s is not scheduled for install\n", - debugstr_w(file->File)); - - continue; - } - - if ((file->State == 1) || (file->State == 2)) - { - LPWSTR p; - MSICOMPONENT* comp = NULL; - - TRACE("Installing %s\n",debugstr_w(file->File)); - rc = ready_media_for_file(package, path_to_source, file); - /* - * WARNING! - * our file table could change here because a new temp file - * may have been created - */ - file = &package->files[index]; - if (rc != ERROR_SUCCESS) - { - ERR("Unable to ready media\n"); - rc = ERROR_FUNCTION_FAILED; - break; - } - - create_component_directory( package, file->ComponentIndex); - - /* recalculate file paths because things may have changed */ - - if (file->ComponentIndex >= 0) - comp = &package->components[file->ComponentIndex]; - - p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL); - HeapFree(GetProcessHeap(),0,file->TargetPath); - - file->TargetPath = build_directory_name(2, p, file->FileName); - HeapFree(GetProcessHeap(),0,p); - - if (file->Attributes & msidbFileAttributesNoncompressed) - { - p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL); - file->SourcePath = build_directory_name(2, p, file->ShortName); - HeapFree(GetProcessHeap(),0,p); - } - else - file->SourcePath = build_directory_name(2, path_to_source, - file->File); - - - TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath), - debugstr_w(file->TargetPath)); - - /* the UI chunk */ - uirow=MSI_CreateRecord(9); - MSI_RecordSetStringW(uirow,1,file->File); - strcpyW(uipath,file->TargetPath); - *(strrchrW(uipath,'\\')+1)=0; - MSI_RecordSetStringW(uirow,9,uipath); - MSI_RecordSetInteger(uirow,6,file->FileSize); - ui_actiondata(package,szInstallFiles,uirow); - msiobj_release( &uirow->hdr ); - ui_progress(package,2,file->FileSize,0,0); - - - if (file->Attributes & msidbFileAttributesNoncompressed) - rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE); - else - rc = MoveFileW(file->SourcePath, file->TargetPath); - - if (!rc) - { - rc = GetLastError(); - ERR("Unable to move/copy file (%s -> %s) (error %d)\n", - debugstr_w(file->SourcePath), debugstr_w(file->TargetPath), - rc); - if (rc == ERROR_ALREADY_EXISTS && file->State == 2) - { - if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE)) - ERR("Unable to copy file (%s -> %s) (error %ld)\n", - debugstr_w(file->SourcePath), - debugstr_w(file->TargetPath), GetLastError()); - if (!(file->Attributes & msidbFileAttributesNoncompressed)) - DeleteFileW(file->SourcePath); - rc = 0; - } - else if (rc == ERROR_FILE_NOT_FOUND) - { - ERR("Source File Not Found! Continuing\n"); - rc = 0; - } - else if (file->Attributes & msidbFileAttributesVital) - { - ERR("Ignoring Error and continuing (nonvital file)...\n"); - rc = 0; - } - } - else - { - file->State = 4; - rc = ERROR_SUCCESS; - } - } - } - - return rc; -} - -inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, - LPWSTR* file_source) -{ - DWORD index; - - if (!package) - return ERROR_INVALID_HANDLE; - - for (index = 0; index < package->loaded_files; index ++) - { - if (strcmpW(file_key,package->files[index].File)==0) - { - if (package->files[index].State >= 2) - { - *file_source = strdupW(package->files[index].TargetPath); - return ERROR_SUCCESS; - } - else - return ERROR_FILE_NOT_FOUND; - } - } - - return ERROR_FUNCTION_FAILED; -} - -static UINT ACTION_DuplicateFiles(MSIPACKAGE *package) -{ - UINT rc; - MSIQUERY * view; - MSIRECORD * row = 0; - static const WCHAR ExecSeqQuery[] = - {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', - '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0}; - - if (!package) - return ERROR_INVALID_HANDLE; - - rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view); - if (rc != ERROR_SUCCESS) - return ERROR_SUCCESS; - - rc = MSI_ViewExecute(view, 0); - if (rc != ERROR_SUCCESS) - { - MSI_ViewClose(view); - msiobj_release(&view->hdr); - return rc; - } - - while (1) - { - WCHAR *file_source = NULL; - WCHAR dest_name[0x100]; - LPWSTR dest_path, dest; - LPCWSTR file_key, component; - INT component_index; - - DWORD sz; - - rc = MSI_ViewFetch(view,&row); - if (rc != ERROR_SUCCESS) - { - rc = ERROR_SUCCESS; - break; - } - - component = MSI_RecordGetString(row,2); - component_index = get_loaded_component(package,component); - - if (!ACTION_VerifyComponentForAction(package, component_index, - INSTALLSTATE_LOCAL)) - { - TRACE("Skipping copy due to disabled component %s\n", - debugstr_w(component)); - - /* the action taken was the same as the current install state */ - package->components[component_index].Action = - package->components[component_index].Installed; - - msiobj_release(&row->hdr); - continue; - } - - package->components[component_index].Action = INSTALLSTATE_LOCAL; - - file_key = MSI_RecordGetString(row,3); - if (!file_key) - { - ERR("Unable to get file key\n"); - msiobj_release(&row->hdr); - break; - } - - rc = get_file_target(package,file_key,&file_source); - - if (rc != ERROR_SUCCESS) - { - ERR("Original file unknown %s\n",debugstr_w(file_key)); - msiobj_release(&row->hdr); - HeapFree(GetProcessHeap(),0,file_source); - continue; - } - - if (MSI_RecordIsNull(row,4)) - { - strcpyW(dest_name,strrchrW(file_source,'\\')+1); - } - else - { - sz=0x100; - MSI_RecordGetStringW(row,4,dest_name,&sz); - reduce_to_longfilename(dest_name); - } - - if (MSI_RecordIsNull(row,5)) - { - LPWSTR p; - dest_path = strdupW(file_source); - p = strrchrW(dest_path,'\\'); - if (p) - *p=0; - } - else - { - LPCWSTR destkey; - destkey = MSI_RecordGetString(row,5); - dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL); - if (!dest_path) - { - ERR("Unable to get destination folder\n"); - msiobj_release(&row->hdr); - HeapFree(GetProcessHeap(),0,file_source); - break; - } - } - - dest = build_directory_name(2, dest_path, dest_name); - - TRACE("Duplicating file %s to %s\n",debugstr_w(file_source), - debugstr_w(dest)); - - if (strcmpW(file_source,dest)) - rc = !CopyFileW(file_source,dest,TRUE); - else - rc = ERROR_SUCCESS; - - if (rc != ERROR_SUCCESS) - ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError()); - - FIXME("We should track these duplicate files as well\n"); - - msiobj_release(&row->hdr); - HeapFree(GetProcessHeap(),0,dest_path); - HeapFree(GetProcessHeap(),0,dest); - HeapFree(GetProcessHeap(),0,file_source); - } - MSI_ViewClose(view); - msiobj_release(&view->hdr); - return rc; -} /* OK this value is "interpreted" and then formatted based on the diff --git a/dlls/msi/action.h b/dlls/msi/action.h index e2c7b32f161..9c472a53f15 100644 --- a/dlls/msi/action.h +++ b/dlls/msi/action.h @@ -205,6 +205,8 @@ UINT ACTION_CustomAction(MSIPACKAGE *package,const WCHAR *action, BOOL execute); /* actions in other modules */ UINT ACTION_AppSearch(MSIPACKAGE *package); UINT ACTION_FindRelatedProducts(MSIPACKAGE *package); +UINT ACTION_InstallFiles(MSIPACKAGE *package); +UINT ACTION_DuplicateFiles(MSIPACKAGE *package); UINT ACTION_RegisterClassInfo(MSIPACKAGE *package); UINT ACTION_RegisterProgIdInfo(MSIPACKAGE *package); UINT ACTION_RegisterExtensionInfo(MSIPACKAGE *package); @@ -252,3 +254,4 @@ void ui_actiondata(MSIPACKAGE *, LPCWSTR, MSIRECORD *); extern const WCHAR cszSourceDir[]; extern const WCHAR szProductCode[]; extern const WCHAR cszRootDrive[]; +extern const WCHAR cszbs[]; diff --git a/dlls/msi/files.c b/dlls/msi/files.c new file mode 100644 index 00000000000..6cfce967748 --- /dev/null +++ b/dlls/msi/files.c @@ -0,0 +1,699 @@ +/* + * Implementation of the Microsoft Installer (msi.dll) + * + * Copyright 2005 Aric Stewart for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* + * Actions dealing with files These are + * + * InstallFiles + * DuplicateFiles + * MoveFiles (TODO) + * PatchFiles (TODO) + * RemoveDuplicateFiles(TODO) + * RemoveFiles(TODO) + */ + +#include + +#include "windef.h" +#include "winbase.h" +#include "winerror.h" +#include "wine/debug.h" +#include "fdi.h" +#include "msidefs.h" +#include "msvcrt/fcntl.h" +#include "msipriv.h" +#include "winuser.h" +#include "wine/unicode.h" +#include "action.h" + +WINE_DEFAULT_DEBUG_CHANNEL(msi); + +extern const WCHAR szInstallFiles[]; +extern const WCHAR szDuplicateFiles[]; +extern const WCHAR szMoveFiles[]; +extern const WCHAR szPatchFiles[]; +extern const WCHAR szRemoveDuplicateFiles[]; +extern const WCHAR szRemoveFiles[]; + +static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0}; + +inline static UINT create_component_directory ( MSIPACKAGE* package, INT component) +{ + UINT rc = ERROR_SUCCESS; + MSIFOLDER *folder; + LPWSTR install_path; + + install_path = resolve_folder(package, package->components[component].Directory, + FALSE, FALSE, &folder); + if (!install_path) + return ERROR_FUNCTION_FAILED; + + /* create the path */ + if (folder->State == 0) + { + create_full_pathW(install_path); + folder->State = 2; + } + HeapFree(GetProcessHeap(), 0, install_path); + + return rc; +} + +/* + * This is a helper function for handling embedded cabinet media + */ +static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name, + WCHAR* source) +{ + UINT rc; + USHORT* data; + UINT size; + DWORD write; + HANDLE the_file; + WCHAR tmp[MAX_PATH]; + + rc = read_raw_stream_data(package->db,stream_name,&data,&size); + if (rc != ERROR_SUCCESS) + return rc; + + write = MAX_PATH; + if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write)) + GetTempPathW(MAX_PATH,tmp); + + GetTempFileNameW(tmp,stream_name,0,source); + + track_tempfile(package,strrchrW(source,'\\'), source); + the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + + if (the_file == INVALID_HANDLE_VALUE) + { + ERR("Unable to create file %s\n",debugstr_w(source)); + rc = ERROR_FUNCTION_FAILED; + goto end; + } + + WriteFile(the_file,data,size,&write,NULL); + CloseHandle(the_file); + TRACE("wrote %li bytes to %s\n",write,debugstr_w(source)); +end: + HeapFree(GetProcessHeap(),0,data); + return rc; +} + + +/* Support functions for FDI functions */ +typedef struct +{ + MSIPACKAGE* package; + LPCSTR cab_path; + LPCSTR file_name; +} CabData; + +static void * cabinet_alloc(ULONG cb) +{ + return HeapAlloc(GetProcessHeap(), 0, cb); +} + +static void cabinet_free(void *pv) +{ + HeapFree(GetProcessHeap(), 0, pv); +} + +static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode) +{ + DWORD dwAccess = 0; + DWORD dwShareMode = 0; + DWORD dwCreateDisposition = OPEN_EXISTING; + switch (oflag & _O_ACCMODE) + { + case _O_RDONLY: + dwAccess = GENERIC_READ; + dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE; + break; + case _O_WRONLY: + dwAccess = GENERIC_WRITE; + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + break; + case _O_RDWR: + dwAccess = GENERIC_READ | GENERIC_WRITE; + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + break; + } + if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL)) + dwCreateDisposition = CREATE_NEW; + else if (oflag & _O_CREAT) + dwCreateDisposition = CREATE_ALWAYS; + return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL, + dwCreateDisposition, 0, NULL); +} + +static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb) +{ + DWORD dwRead; + if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL)) + return dwRead; + return 0; +} + +static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb) +{ + DWORD dwWritten; + if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL)) + return dwWritten; + return 0; +} + +static int cabinet_close(INT_PTR hf) +{ + return CloseHandle((HANDLE)hf) ? 0 : -1; +} + +static long cabinet_seek(INT_PTR hf, long dist, int seektype) +{ + /* flags are compatible and so are passed straight through */ + return SetFilePointer((HANDLE)hf, dist, NULL, seektype); +} + +static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin) +{ + /* FIXME: try to do more processing in this function */ + switch (fdint) + { + case fdintCOPY_FILE: + { + CabData *data = (CabData*) pfdin->pv; + ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1); + char *file; + + LPWSTR trackname; + LPWSTR trackpath; + LPWSTR tracknametmp; + static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0}; + + if (data->file_name && lstrcmpiA(data->file_name,pfdin->psz1)) + return 0; + + file = cabinet_alloc((len+1)*sizeof(char)); + strcpy(file, data->cab_path); + strcat(file, pfdin->psz1); + + TRACE("file: %s\n", debugstr_a(file)); + + /* track this file so it can be deleted if not installed */ + trackpath=strdupAtoW(file); + tracknametmp=strdupAtoW(strrchr(file,'\\')+1); + trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) + + strlenW(tmpprefix)+1) * sizeof(WCHAR)); + + strcpyW(trackname,tmpprefix); + strcatW(trackname,tracknametmp); + + track_tempfile(data->package, trackname, trackpath); + + HeapFree(GetProcessHeap(),0,trackpath); + HeapFree(GetProcessHeap(),0,trackname); + HeapFree(GetProcessHeap(),0,tracknametmp); + + return cabinet_open(file, _O_WRONLY | _O_CREAT, 0); + } + case fdintCLOSE_FILE_INFO: + { + FILETIME ft; + FILETIME ftLocal; + if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft)) + return -1; + if (!LocalFileTimeToFileTime(&ft, &ftLocal)) + return -1; + if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal)) + return -1; + + cabinet_close(pfdin->hf); + return 1; + } + default: + return 0; + } +} + +/*********************************************************************** + * extract_cabinet_file + * + * Extract files from a cab file. + */ +static BOOL extract_a_cabinet_file(MSIPACKAGE* package, const WCHAR* source, + const WCHAR* path, const WCHAR* file) +{ + HFDI hfdi; + ERF erf; + BOOL ret; + char *cabinet; + char *cab_path; + char *file_name; + CabData data; + + TRACE("Extracting %s (%s) to %s\n",debugstr_w(source), + debugstr_w(file), debugstr_w(path)); + + hfdi = FDICreate(cabinet_alloc, + cabinet_free, + cabinet_open, + cabinet_read, + cabinet_write, + cabinet_close, + cabinet_seek, + 0, + &erf); + if (!hfdi) + { + ERR("FDICreate failed\n"); + return FALSE; + } + + if (!(cabinet = strdupWtoA( source ))) + { + FDIDestroy(hfdi); + return FALSE; + } + if (!(cab_path = strdupWtoA( path ))) + { + FDIDestroy(hfdi); + HeapFree(GetProcessHeap(), 0, cabinet); + return FALSE; + } + + data.package = package; + data.cab_path = cab_path; + if (file) + file_name = strdupWtoA(file); + else + file_name = NULL; + data.file_name = file_name; + + ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data); + + if (!ret) + ERR("FDICopy failed\n"); + + FDIDestroy(hfdi); + + HeapFree(GetProcessHeap(), 0, cabinet); + HeapFree(GetProcessHeap(), 0, cab_path); + HeapFree(GetProcessHeap(), 0, file_name); + + return ret; +} + +static UINT ready_media_for_file(MSIPACKAGE *package, WCHAR* path, + MSIFILE* file) +{ + UINT rc = ERROR_SUCCESS; + MSIRECORD * row = 0; + static WCHAR source[MAX_PATH]; + static const WCHAR ExecSeqQuery[] = + {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ', + '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ', + '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=', + ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ', + '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0}; + LPCWSTR cab; + DWORD sz; + INT seq; + static UINT last_sequence = 0; + + if (file->Attributes & msidbFileAttributesNoncompressed) + { + TRACE("Uncompressed File, no media to ready.\n"); + return ERROR_SUCCESS; + } + + if (file->Sequence <= last_sequence) + { + TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence); + /*extract_a_cabinet_file(package, source,path,file->File); */ + return ERROR_SUCCESS; + } + + row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence); + if (!row) + return ERROR_FUNCTION_FAILED; + + seq = MSI_RecordGetInteger(row,2); + last_sequence = seq; + + cab = MSI_RecordGetString(row,4); + if (cab) + { + TRACE("Source is CAB %s\n",debugstr_w(cab)); + /* the stream does not contain the # character */ + if (cab[0]=='#') + { + writeout_cabinet_stream(package,&cab[1],source); + strcpyW(path,source); + *(strrchrW(path,'\\')+1)=0; + } + else + { + sz = MAX_PATH; + if (MSI_GetPropertyW(package, cszSourceDir, source, &sz)) + { + ERR("No Source dir defined \n"); + rc = ERROR_FUNCTION_FAILED; + } + else + { + strcpyW(path,source); + strcatW(source,cab); + /* extract the cab file into a folder in the temp folder */ + sz = MAX_PATH; + if (MSI_GetPropertyW(package, cszTempFolder,path, &sz) + != ERROR_SUCCESS) + GetTempPathW(MAX_PATH,path); + } + } + rc = !extract_a_cabinet_file(package, source,path,NULL); + } + else + { + sz = MAX_PATH; + MSI_GetPropertyW(package,cszSourceDir,source,&sz); + strcpyW(path,source); + } + msiobj_release(&row->hdr); + return rc; +} + +inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key, + LPWSTR* file_source) +{ + DWORD index; + + if (!package) + return ERROR_INVALID_HANDLE; + + for (index = 0; index < package->loaded_files; index ++) + { + if (strcmpW(file_key,package->files[index].File)==0) + { + if (package->files[index].State >= 2) + { + *file_source = strdupW(package->files[index].TargetPath); + return ERROR_SUCCESS; + } + else + return ERROR_FILE_NOT_FOUND; + } + } + + return ERROR_FUNCTION_FAILED; +} + +UINT ACTION_InstallFiles(MSIPACKAGE *package) +{ + UINT rc = ERROR_SUCCESS; + DWORD index; + MSIRECORD * uirow; + WCHAR uipath[MAX_PATH]; + + if (!package) + return ERROR_INVALID_HANDLE; + + /* increment progress bar each time action data is sent */ + ui_progress(package,1,1,0,0); + + for (index = 0; index < package->loaded_files; index++) + { + WCHAR path_to_source[MAX_PATH]; + MSIFILE *file; + + file = &package->files[index]; + + if (file->Temporary) + continue; + + + if (!ACTION_VerifyComponentForAction(package, file->ComponentIndex, + INSTALLSTATE_LOCAL)) + { + ui_progress(package,2,file->FileSize,0,0); + TRACE("File %s is not scheduled for install\n", + debugstr_w(file->File)); + + continue; + } + + if ((file->State == 1) || (file->State == 2)) + { + LPWSTR p; + MSICOMPONENT* comp = NULL; + + TRACE("Installing %s\n",debugstr_w(file->File)); + rc = ready_media_for_file(package, path_to_source, file); + /* + * WARNING! + * our file table could change here because a new temp file + * may have been created + */ + file = &package->files[index]; + if (rc != ERROR_SUCCESS) + { + ERR("Unable to ready media\n"); + rc = ERROR_FUNCTION_FAILED; + break; + } + + create_component_directory( package, file->ComponentIndex); + + /* recalculate file paths because things may have changed */ + + if (file->ComponentIndex >= 0) + comp = &package->components[file->ComponentIndex]; + + p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL); + HeapFree(GetProcessHeap(),0,file->TargetPath); + + file->TargetPath = build_directory_name(2, p, file->FileName); + HeapFree(GetProcessHeap(),0,p); + + if (file->Attributes & msidbFileAttributesNoncompressed) + { + p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL); + file->SourcePath = build_directory_name(2, p, file->ShortName); + HeapFree(GetProcessHeap(),0,p); + } + else + file->SourcePath = build_directory_name(2, path_to_source, + file->File); + + + TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath), + debugstr_w(file->TargetPath)); + + /* the UI chunk */ + uirow=MSI_CreateRecord(9); + MSI_RecordSetStringW(uirow,1,file->File); + strcpyW(uipath,file->TargetPath); + *(strrchrW(uipath,'\\')+1)=0; + MSI_RecordSetStringW(uirow,9,uipath); + MSI_RecordSetInteger(uirow,6,file->FileSize); + ui_actiondata(package,szInstallFiles,uirow); + msiobj_release( &uirow->hdr ); + ui_progress(package,2,file->FileSize,0,0); + + + if (file->Attributes & msidbFileAttributesNoncompressed) + rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE); + else + rc = MoveFileW(file->SourcePath, file->TargetPath); + + if (!rc) + { + rc = GetLastError(); + ERR("Unable to move/copy file (%s -> %s) (error %d)\n", + debugstr_w(file->SourcePath), debugstr_w(file->TargetPath), + rc); + if (rc == ERROR_ALREADY_EXISTS && file->State == 2) + { + if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE)) + ERR("Unable to copy file (%s -> %s) (error %ld)\n", + debugstr_w(file->SourcePath), + debugstr_w(file->TargetPath), GetLastError()); + if (!(file->Attributes & msidbFileAttributesNoncompressed)) + DeleteFileW(file->SourcePath); + rc = 0; + } + else if (rc == ERROR_FILE_NOT_FOUND) + { + ERR("Source File Not Found! Continuing\n"); + rc = 0; + } + else if (file->Attributes & msidbFileAttributesVital) + { + ERR("Ignoring Error and continuing (nonvital file)...\n"); + rc = 0; + } + } + else + { + file->State = 4; + rc = ERROR_SUCCESS; + } + } + } + + return rc; +} + +UINT ACTION_DuplicateFiles(MSIPACKAGE *package) +{ + UINT rc; + MSIQUERY * view; + MSIRECORD * row = 0; + static const WCHAR ExecSeqQuery[] = + {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ', + '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0}; + + if (!package) + return ERROR_INVALID_HANDLE; + + rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view); + if (rc != ERROR_SUCCESS) + return ERROR_SUCCESS; + + rc = MSI_ViewExecute(view, 0); + if (rc != ERROR_SUCCESS) + { + MSI_ViewClose(view); + msiobj_release(&view->hdr); + return rc; + } + + while (1) + { + WCHAR *file_source = NULL; + WCHAR dest_name[0x100]; + LPWSTR dest_path, dest; + LPCWSTR file_key, component; + INT component_index; + + DWORD sz; + + rc = MSI_ViewFetch(view,&row); + if (rc != ERROR_SUCCESS) + { + rc = ERROR_SUCCESS; + break; + } + + component = MSI_RecordGetString(row,2); + component_index = get_loaded_component(package,component); + + if (!ACTION_VerifyComponentForAction(package, component_index, + INSTALLSTATE_LOCAL)) + { + TRACE("Skipping copy due to disabled component %s\n", + debugstr_w(component)); + + /* the action taken was the same as the current install state */ + package->components[component_index].Action = + package->components[component_index].Installed; + + msiobj_release(&row->hdr); + continue; + } + + package->components[component_index].Action = INSTALLSTATE_LOCAL; + + file_key = MSI_RecordGetString(row,3); + if (!file_key) + { + ERR("Unable to get file key\n"); + msiobj_release(&row->hdr); + break; + } + + rc = get_file_target(package,file_key,&file_source); + + if (rc != ERROR_SUCCESS) + { + ERR("Original file unknown %s\n",debugstr_w(file_key)); + msiobj_release(&row->hdr); + HeapFree(GetProcessHeap(),0,file_source); + continue; + } + + if (MSI_RecordIsNull(row,4)) + { + strcpyW(dest_name,strrchrW(file_source,'\\')+1); + } + else + { + sz=0x100; + MSI_RecordGetStringW(row,4,dest_name,&sz); + reduce_to_longfilename(dest_name); + } + + if (MSI_RecordIsNull(row,5)) + { + LPWSTR p; + dest_path = strdupW(file_source); + p = strrchrW(dest_path,'\\'); + if (p) + *p=0; + } + else + { + LPCWSTR destkey; + destkey = MSI_RecordGetString(row,5); + dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL); + if (!dest_path) + { + ERR("Unable to get destination folder\n"); + msiobj_release(&row->hdr); + HeapFree(GetProcessHeap(),0,file_source); + break; + } + } + + dest = build_directory_name(2, dest_path, dest_name); + + TRACE("Duplicating file %s to %s\n",debugstr_w(file_source), + debugstr_w(dest)); + + if (strcmpW(file_source,dest)) + rc = !CopyFileW(file_source,dest,TRUE); + else + rc = ERROR_SUCCESS; + + if (rc != ERROR_SUCCESS) + ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError()); + + FIXME("We should track these duplicate files as well\n"); + + msiobj_release(&row->hdr); + HeapFree(GetProcessHeap(),0,dest_path); + HeapFree(GetProcessHeap(),0,dest); + HeapFree(GetProcessHeap(),0,file_source); + } + MSI_ViewClose(view); + msiobj_release(&view->hdr); + return rc; +}