/* * Implementation of the Microsoft Installer (msi.dll) * * Copyright 2002,2003,2004,2005 Mike McCormack 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #define COBJMACROS #define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "winreg.h" #include "winnls.h" #include "wine/debug.h" #include "wine/unicode.h" #include "msi.h" #include "msiquery.h" #include "msipriv.h" #include "objidl.h" #include "objbase.h" #include "initguid.h" WINE_DEFAULT_DEBUG_CHANNEL(msi); DEFINE_GUID( CLSID_MsiDatabase, 0x000c1084, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46); DEFINE_GUID( CLSID_MsiPatch, 0x000c1086, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46); /* * .MSI file format * * An .msi file is a structured storage file. * It contains a number of streams. * A stream for each table in the database. * Two streams for the string table in the database. * Any binary data in a table is a reference to a stream. */ static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg ) { MSIDATABASE *db = (MSIDATABASE *) arg; DWORD r; msi_free(db->path); free_cached_tables( db ); msi_free_transforms( db ); msi_destroy_stringtable( db->strings ); r = IStorage_Release( db->storage ); if( r ) ERR("database reference count was not zero (%ld)\n", r); if (db->deletefile) { DeleteFileW( db->deletefile ); msi_free( db->deletefile ); } } UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb) { IStorage *stg = NULL; HRESULT r; MSIDATABASE *db = NULL; UINT ret = ERROR_FUNCTION_FAILED; LPCWSTR szMode, save_path; STATSTG stat; BOOL created = FALSE; WCHAR path[MAX_PATH]; static const WCHAR backslash[] = {'\\',0}; TRACE("%s %s\n",debugstr_w(szDBPath),debugstr_w(szPersist) ); if( !pdb ) return ERROR_INVALID_PARAMETER; save_path = szDBPath; szMode = szPersist; if( HIWORD( szPersist ) ) { if (!CopyFileW( szDBPath, szPersist, FALSE )) return ERROR_OPEN_FAILED; szDBPath = szPersist; szPersist = MSIDBOPEN_TRANSACT; created = TRUE; } if( szPersist == MSIDBOPEN_READONLY ) { r = StgOpenStorage( szDBPath, NULL, STGM_DIRECT|STGM_READ|STGM_SHARE_DENY_WRITE, NULL, 0, &stg); } else if( szPersist == MSIDBOPEN_CREATE || szPersist == MSIDBOPEN_CREATEDIRECT ) { /* FIXME: MSIDBOPEN_CREATE should case STGM_TRANSACTED flag to be * used here: */ r = StgCreateDocfile( szDBPath, STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg); if( r == ERROR_SUCCESS ) { IStorage_SetClass( stg, &CLSID_MsiDatabase ); r = init_string_table( stg ); } created = TRUE; } else if( szPersist == MSIDBOPEN_TRANSACT ) { /* FIXME: MSIDBOPEN_TRANSACT should case STGM_TRANSACTED flag to be * used here: */ r = StgOpenStorage( szDBPath, NULL, STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg); } else if( szPersist == MSIDBOPEN_DIRECT ) { r = StgOpenStorage( szDBPath, NULL, STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg); } else { ERR("unknown flag %p\n",szPersist); return ERROR_INVALID_PARAMETER; } if( FAILED( r ) ) { FIXME("open failed r = %08lx!\n",r); return ERROR_FUNCTION_FAILED; } r = IStorage_Stat( stg, &stat, STATFLAG_NONAME ); if( FAILED( r ) ) { FIXME("Failed to stat storage\n"); goto end; } if ( !IsEqualGUID( &stat.clsid, &CLSID_MsiDatabase ) && !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) ) { ERR("storage GUID is not a MSI database GUID %s\n", debugstr_guid(&stat.clsid) ); goto end; } db = alloc_msiobject( MSIHANDLETYPE_DATABASE, sizeof (MSIDATABASE), MSI_CloseDatabase ); if( !db ) { FIXME("Failed to allocate a handle\n"); goto end; } if (!strchrW( save_path, '\\' )) { GetCurrentDirectoryW( MAX_PATH, path ); lstrcatW( path, backslash ); lstrcatW( path, save_path ); } else lstrcpyW( path, save_path ); db->path = strdupW( path ); if( TRACE_ON( msi ) ) enum_stream_names( stg ); db->storage = stg; db->mode = szMode; if (created) db->deletefile = strdupW( szDBPath ); else db->deletefile = NULL; list_init( &db->tables ); list_init( &db->transforms ); db->strings = load_string_table( stg ); if( !db->strings ) goto end; ret = ERROR_SUCCESS; msiobj_addref( &db->hdr ); IStorage_AddRef( stg ); *pdb = db; end: if( db ) msiobj_release( &db->hdr ); if( stg ) IStorage_Release( stg ); return ret; } UINT WINAPI MsiOpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIHANDLE *phDB) { MSIDATABASE *db; UINT ret; TRACE("%s %s %p\n",debugstr_w(szDBPath),debugstr_w(szPersist), phDB); ret = MSI_OpenDatabaseW( szDBPath, szPersist, &db ); if( ret == ERROR_SUCCESS ) { *phDB = alloc_msihandle( &db->hdr ); if (! *phDB) ret = ERROR_NOT_ENOUGH_MEMORY; msiobj_release( &db->hdr ); } return ret; } UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB) { HRESULT r = ERROR_FUNCTION_FAILED; LPWSTR szwDBPath = NULL, szwPersist = NULL; TRACE("%s %s %p\n", debugstr_a(szDBPath), debugstr_a(szPersist), phDB); if( szDBPath ) { szwDBPath = strdupAtoW( szDBPath ); if( !szwDBPath ) goto end; } if( HIWORD(szPersist) ) { szwPersist = strdupAtoW( szPersist ); if( !szwPersist ) goto end; } else szwPersist = (LPWSTR)(DWORD_PTR)szPersist; r = MsiOpenDatabaseW( szwDBPath, szwPersist, phDB ); end: if( HIWORD(szPersist) ) msi_free( szwPersist ); msi_free( szwDBPath ); return r; } UINT MSI_DatabaseImport( MSIDATABASE *db, LPCWSTR folder, LPCWSTR file ) { FIXME("%p %s %s\n", db, debugstr_w(folder), debugstr_w(file) ); if( folder == NULL || file == NULL ) return ERROR_INVALID_PARAMETER; return ERROR_CALL_NOT_IMPLEMENTED; } UINT WINAPI MsiDatabaseImportW(MSIHANDLE handle, LPCWSTR szFolder, LPCWSTR szFilename) { MSIDATABASE *db; UINT r; TRACE("%lx %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename)); db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE ); if( !db ) return ERROR_INVALID_HANDLE; r = MSI_DatabaseImport( db, szFolder, szFilename ); msiobj_release( &db->hdr ); return r; } UINT WINAPI MsiDatabaseImportA( MSIHANDLE handle, LPCSTR szFolder, LPCSTR szFilename ) { LPWSTR path = NULL, file = NULL; UINT r = ERROR_OUTOFMEMORY; TRACE("%lx %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename)); if( szFolder ) { path = strdupAtoW( szFolder ); if( !path ) goto end; } if( szFilename ) { file = strdupAtoW( szFilename ); if( !file ) goto end; } r = MsiDatabaseImportW( handle, path, file ); end: msi_free( path ); msi_free( file ); return r; } static UINT msi_export_record( HANDLE handle, MSIRECORD *row, UINT start ) { UINT i, count, len, r = ERROR_SUCCESS; const char *sep; char *buffer; DWORD sz; len = 0x100; buffer = msi_alloc( len ); if ( !buffer ) return ERROR_OUTOFMEMORY; count = MSI_RecordGetFieldCount( row ); for ( i=start; i<=count; i++ ) { sz = len; r = MSI_RecordGetStringA( row, i, buffer, &sz ); if (r == ERROR_MORE_DATA) { char *p = msi_realloc( buffer, sz + 1 ); if (!p) break; len = sz + 1; buffer = p; } sz = len; r = MSI_RecordGetStringA( row, i, buffer, &sz ); if (r != ERROR_SUCCESS) break; if (!WriteFile( handle, buffer, sz, &sz, NULL )) { r = ERROR_FUNCTION_FAILED; break; } sep = (i < count) ? "\t" : "\r\n"; if (!WriteFile( handle, sep, strlen(sep), &sz, NULL )) { r = ERROR_FUNCTION_FAILED; break; } } msi_free( buffer ); return r; } static UINT msi_export_row( MSIRECORD *row, void *arg ) { return msi_export_record( arg, row, 1 ); } UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table, LPCWSTR folder, LPCWSTR file ) { static const WCHAR query[] = { 's','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','%','s',0 }; static const WCHAR szbs[] = { '\\', 0 }; MSIRECORD *rec = NULL; MSIQUERY *view = NULL; LPWSTR filename; HANDLE handle; UINT len, r; TRACE("%p %s %s %s\n", db, debugstr_w(table), debugstr_w(folder), debugstr_w(file) ); if( folder == NULL || file == NULL ) return ERROR_INVALID_PARAMETER; len = lstrlenW(folder) + lstrlenW(file) + 2; filename = msi_alloc(len * sizeof (WCHAR)); if (!filename) return ERROR_OUTOFMEMORY; lstrcpyW( filename, folder ); lstrcatW( filename, szbs ); lstrcatW( filename, file ); handle = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); msi_free( filename ); if (handle == INVALID_HANDLE_VALUE) return ERROR_FUNCTION_FAILED; r = MSI_OpenQuery( db, &view, query, table ); if (r == ERROR_SUCCESS) { /* write out row 1, the column names */ r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &rec); if (r == ERROR_SUCCESS) { msi_export_record( handle, rec, 1 ); msiobj_release( &rec->hdr ); } /* write out row 2, the column types */ r = MSI_ViewGetColumnInfo(view, MSICOLINFO_TYPES, &rec); if (r == ERROR_SUCCESS) { msi_export_record( handle, rec, 1 ); msiobj_release( &rec->hdr ); } /* write out row 3, the table name + keys */ r = MSI_DatabaseGetPrimaryKeys( db, table, &rec ); if (r == ERROR_SUCCESS) { MSI_RecordSetStringW( rec, 0, table ); msi_export_record( handle, rec, 0 ); msiobj_release( &rec->hdr ); } /* write out row 4 onwards, the data */ r = MSI_IterateRecords( view, 0, msi_export_row, handle ); msiobj_release( &view->hdr ); } CloseHandle( handle ); return r; } /*********************************************************************** * MsiExportDatabaseW [MSI.@] * * Writes a file containing the table data as tab separated ASCII. * * The format is as follows: * * row1 : colname1 colname2 .... colnameN * row2 : coltype1 coltype2 .... coltypeN * row3 : tablename key1 key2 ... keyM * * Followed by the data, starting at row 1 with one row per line * * row4 : data data data ... data */ UINT WINAPI MsiDatabaseExportW( MSIHANDLE handle, LPCWSTR szTable, LPCWSTR szFolder, LPCWSTR szFilename ) { MSIDATABASE *db; UINT r; TRACE("%lx %s %s %s\n", handle, debugstr_w(szTable), debugstr_w(szFolder), debugstr_w(szFilename)); db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE ); if( !db ) return ERROR_INVALID_HANDLE; r = MSI_DatabaseExport( db, szTable, szFolder, szFilename ); msiobj_release( &db->hdr ); return r; } UINT WINAPI MsiDatabaseExportA( MSIHANDLE handle, LPCSTR szTable, LPCSTR szFolder, LPCSTR szFilename ) { LPWSTR path = NULL, file = NULL, table = NULL; UINT r = ERROR_OUTOFMEMORY; TRACE("%lx %s %s %s\n", handle, debugstr_a(szTable), debugstr_a(szFolder), debugstr_a(szFilename)); if( szTable ) { table = strdupAtoW( szTable ); if( !table ) goto end; } if( szFolder ) { path = strdupAtoW( szFolder ); if( !path ) goto end; } if( szFilename ) { file = strdupAtoW( szFilename ); if( !file ) goto end; } r = MsiDatabaseExportW( handle, table, path, file ); end: msi_free( table ); msi_free( path ); msi_free( file ); return r; } MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle ) { MSIDBSTATE ret = MSIDBSTATE_READ; MSIDATABASE *db; TRACE("%ld\n", handle); db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE ); if (!db) return MSIDBSTATE_ERROR; if (db->mode != MSIDBOPEN_READONLY ) ret = MSIDBSTATE_WRITE; msiobj_release( &db->hdr ); return ret; }