| /* |
| * Implementation of the Microsoft Installer (msi.dll) |
| * |
| * Copyright 2007 James Hawkins |
| * Copyright 2015 Hans Leidekker 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 <stdarg.h> |
| |
| #define COBJMACROS |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "winerror.h" |
| #include "msi.h" |
| #include "msiquery.h" |
| #include "objbase.h" |
| #include "msipriv.h" |
| #include "query.h" |
| |
| #include "wine/debug.h" |
| #include "wine/unicode.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(msidb); |
| |
| #define NUM_STREAMS_COLS 2 |
| |
| typedef struct tagMSISTREAMSVIEW |
| { |
| MSIVIEW view; |
| MSIDATABASE *db; |
| UINT num_cols; |
| } MSISTREAMSVIEW; |
| |
| static BOOL streams_resize_table( MSIDATABASE *db, UINT size ) |
| { |
| if (!db->num_streams_allocated) |
| { |
| if (!(db->streams = msi_alloc_zero( size * sizeof(MSISTREAM) ))) return FALSE; |
| db->num_streams_allocated = size; |
| return TRUE; |
| } |
| while (size >= db->num_streams_allocated) |
| { |
| MSISTREAM *tmp; |
| UINT new_size = db->num_streams_allocated * 2; |
| if (!(tmp = msi_realloc_zero( db->streams, new_size * sizeof(MSISTREAM) ))) return FALSE; |
| db->streams = tmp; |
| db->num_streams_allocated = new_size; |
| } |
| return TRUE; |
| } |
| |
| static UINT STREAMS_fetch_int(struct tagMSIVIEW *view, UINT row, UINT col, UINT *val) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("(%p, %d, %d, %p)\n", view, row, col, val); |
| |
| if (col != 1) |
| return ERROR_INVALID_PARAMETER; |
| |
| if (row >= sv->db->num_streams) |
| return ERROR_NO_MORE_ITEMS; |
| |
| *val = sv->db->streams[row].str_index; |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_fetch_stream(struct tagMSIVIEW *view, UINT row, UINT col, IStream **stm) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| LARGE_INTEGER pos; |
| HRESULT hr; |
| |
| TRACE("(%p, %d, %d, %p)\n", view, row, col, stm); |
| |
| if (row >= sv->db->num_streams) |
| return ERROR_FUNCTION_FAILED; |
| |
| pos.QuadPart = 0; |
| hr = IStream_Seek( sv->db->streams[row].stream, pos, STREAM_SEEK_SET, NULL ); |
| if (FAILED( hr )) |
| return ERROR_FUNCTION_FAILED; |
| |
| *stm = sv->db->streams[row].stream; |
| IStream_AddRef( *stm ); |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_get_row( struct tagMSIVIEW *view, UINT row, MSIRECORD **rec ) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("%p %d %p\n", sv, row, rec); |
| |
| return msi_view_get_row( sv->db, view, row, rec ); |
| } |
| |
| static UINT STREAMS_set_row(struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("(%p, %d, %p, %08x)\n", view, row, rec, mask); |
| |
| if (row > sv->db->num_streams || mask >= (1 << sv->num_cols)) |
| return ERROR_INVALID_PARAMETER; |
| |
| if (mask & 1) |
| { |
| const WCHAR *name = MSI_RecordGetString( rec, 1 ); |
| |
| if (!name) return ERROR_INVALID_PARAMETER; |
| sv->db->streams[row].str_index = msi_add_string( sv->db->strings, name, -1, StringNonPersistent ); |
| } |
| if (mask & 2) |
| { |
| IStream *old, *new; |
| HRESULT hr; |
| UINT r; |
| |
| r = MSI_RecordGetIStream( rec, 2, &new ); |
| if (r != ERROR_SUCCESS) |
| return r; |
| |
| old = sv->db->streams[row].stream; |
| hr = IStream_QueryInterface( new, &IID_IStream, (void **)&sv->db->streams[row].stream ); |
| if (FAILED( hr )) |
| { |
| IStream_Release( new ); |
| return ERROR_FUNCTION_FAILED; |
| } |
| if (old) IStream_Release( old ); |
| } |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT streams_find_row( MSISTREAMSVIEW *sv, MSIRECORD *rec, UINT *row ) |
| { |
| const WCHAR *str; |
| UINT r, i, id, val; |
| |
| str = MSI_RecordGetString( rec, 1 ); |
| r = msi_string2id( sv->db->strings, str, -1, &id ); |
| if (r != ERROR_SUCCESS) |
| return r; |
| |
| for (i = 0; i < sv->db->num_streams; i++) |
| { |
| STREAMS_fetch_int( &sv->view, i, 1, &val ); |
| |
| if (val == id) |
| { |
| if (row) *row = i; |
| return ERROR_SUCCESS; |
| } |
| } |
| |
| return ERROR_FUNCTION_FAILED; |
| } |
| |
| static UINT STREAMS_insert_row(struct tagMSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| UINT i, r, num_rows = sv->db->num_streams + 1; |
| |
| TRACE("(%p, %p, %d, %d)\n", view, rec, row, temporary); |
| |
| r = streams_find_row( sv, rec, NULL ); |
| if (r == ERROR_SUCCESS) |
| return ERROR_FUNCTION_FAILED; |
| |
| if (!streams_resize_table( sv->db, num_rows )) |
| return ERROR_FUNCTION_FAILED; |
| |
| if (row == -1) |
| row = num_rows - 1; |
| |
| /* shift the rows to make room for the new row */ |
| for (i = num_rows - 1; i > row; i--) |
| { |
| sv->db->streams[i] = sv->db->streams[i - 1]; |
| } |
| |
| r = STREAMS_set_row( view, row, rec, (1 << sv->num_cols) - 1 ); |
| if (r == ERROR_SUCCESS) |
| sv->db->num_streams = num_rows; |
| |
| return r; |
| } |
| |
| static UINT STREAMS_delete_row(struct tagMSIVIEW *view, UINT row) |
| { |
| FIXME("(%p %d): stub!\n", view, row); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_execute(struct tagMSIVIEW *view, MSIRECORD *record) |
| { |
| TRACE("(%p, %p)\n", view, record); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_close(struct tagMSIVIEW *view) |
| { |
| TRACE("(%p)\n", view); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_get_dimensions(struct tagMSIVIEW *view, UINT *rows, UINT *cols) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("(%p, %p, %p)\n", view, rows, cols); |
| |
| if (cols) *cols = sv->num_cols; |
| if (rows) *rows = sv->db->num_streams; |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_get_column_info( struct tagMSIVIEW *view, UINT n, LPCWSTR *name, |
| UINT *type, BOOL *temporary, LPCWSTR *table_name ) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("(%p, %d, %p, %p, %p, %p)\n", view, n, name, type, temporary, table_name); |
| |
| if (!n || n > sv->num_cols) |
| return ERROR_INVALID_PARAMETER; |
| |
| switch (n) |
| { |
| case 1: |
| if (name) *name = szName; |
| if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MAX_STREAM_NAME_LEN; |
| break; |
| |
| case 2: |
| if (name) *name = szData; |
| if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE; |
| break; |
| } |
| if (table_name) *table_name = szStreams; |
| if (temporary) *temporary = FALSE; |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT streams_modify_update(struct tagMSIVIEW *view, MSIRECORD *rec) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| UINT r, row; |
| |
| r = streams_find_row(sv, rec, &row); |
| if (r != ERROR_SUCCESS) |
| return ERROR_FUNCTION_FAILED; |
| |
| return STREAMS_set_row( view, row, rec, (1 << sv->num_cols) - 1 ); |
| } |
| |
| static UINT streams_modify_assign(struct tagMSIVIEW *view, MSIRECORD *rec) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| UINT r; |
| |
| r = streams_find_row( sv, rec, NULL ); |
| if (r == ERROR_SUCCESS) |
| return streams_modify_update(view, rec); |
| |
| return STREAMS_insert_row(view, rec, -1, FALSE); |
| } |
| |
| static UINT STREAMS_modify(struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec, UINT row) |
| { |
| UINT r; |
| |
| TRACE("%p %d %p\n", view, eModifyMode, rec); |
| |
| switch (eModifyMode) |
| { |
| case MSIMODIFY_ASSIGN: |
| r = streams_modify_assign(view, rec); |
| break; |
| |
| case MSIMODIFY_INSERT: |
| r = STREAMS_insert_row(view, rec, -1, FALSE); |
| break; |
| |
| case MSIMODIFY_UPDATE: |
| r = streams_modify_update(view, rec); |
| break; |
| |
| case MSIMODIFY_VALIDATE_NEW: |
| case MSIMODIFY_INSERT_TEMPORARY: |
| case MSIMODIFY_REFRESH: |
| case MSIMODIFY_REPLACE: |
| case MSIMODIFY_MERGE: |
| case MSIMODIFY_DELETE: |
| case MSIMODIFY_VALIDATE: |
| case MSIMODIFY_VALIDATE_FIELD: |
| case MSIMODIFY_VALIDATE_DELETE: |
| FIXME("%p %d %p - mode not implemented\n", view, eModifyMode, rec ); |
| r = ERROR_CALL_NOT_IMPLEMENTED; |
| break; |
| |
| default: |
| r = ERROR_INVALID_DATA; |
| } |
| |
| return r; |
| } |
| |
| static UINT STREAMS_delete(struct tagMSIVIEW *view) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| |
| TRACE("(%p)\n", view); |
| |
| msi_free(sv); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT STREAMS_find_matching_rows(struct tagMSIVIEW *view, UINT col, |
| UINT val, UINT *row, MSIITERHANDLE *handle) |
| { |
| MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; |
| UINT index = PtrToUlong(*handle); |
| |
| TRACE("(%p, %d, %d, %p, %p)\n", view, col, val, row, handle); |
| |
| if (!col || col > sv->num_cols) |
| return ERROR_INVALID_PARAMETER; |
| |
| while (index < sv->db->num_streams) |
| { |
| if (sv->db->streams[index].str_index == val) |
| { |
| *row = index; |
| break; |
| } |
| index++; |
| } |
| |
| *handle = UlongToPtr(++index); |
| |
| if (index > sv->db->num_streams) |
| return ERROR_NO_MORE_ITEMS; |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static const MSIVIEWOPS streams_ops = |
| { |
| STREAMS_fetch_int, |
| STREAMS_fetch_stream, |
| STREAMS_get_row, |
| STREAMS_set_row, |
| STREAMS_insert_row, |
| STREAMS_delete_row, |
| STREAMS_execute, |
| STREAMS_close, |
| STREAMS_get_dimensions, |
| STREAMS_get_column_info, |
| STREAMS_modify, |
| STREAMS_delete, |
| STREAMS_find_matching_rows, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| }; |
| |
| static HRESULT open_stream( MSIDATABASE *db, const WCHAR *name, IStream **stream ) |
| { |
| HRESULT hr; |
| |
| hr = IStorage_OpenStream( db->storage, name, NULL, STGM_READ|STGM_SHARE_EXCLUSIVE, 0, stream ); |
| if (FAILED( hr )) |
| { |
| MSITRANSFORM *transform; |
| |
| LIST_FOR_EACH_ENTRY( transform, &db->transforms, MSITRANSFORM, entry ) |
| { |
| hr = IStorage_OpenStream( transform->stg, name, NULL, STGM_READ|STGM_SHARE_EXCLUSIVE, 0, stream ); |
| if (SUCCEEDED( hr )) |
| break; |
| } |
| } |
| return hr; |
| } |
| |
| static MSISTREAM *find_stream( MSIDATABASE *db, const WCHAR *name ) |
| { |
| UINT r, id, i; |
| |
| r = msi_string2id( db->strings, name, -1, &id ); |
| if (r != ERROR_SUCCESS) |
| return NULL; |
| |
| for (i = 0; i < db->num_streams; i++) |
| { |
| if (db->streams[i].str_index == id) return &db->streams[i]; |
| } |
| return NULL; |
| } |
| |
| static UINT append_stream( MSIDATABASE *db, const WCHAR *name, IStream *stream ) |
| { |
| UINT i = db->num_streams; |
| |
| if (!streams_resize_table( db, db->num_streams + 1 )) |
| return ERROR_OUTOFMEMORY; |
| |
| db->streams[i].str_index = msi_add_string( db->strings, name, -1, StringNonPersistent ); |
| db->streams[i].stream = stream; |
| db->num_streams++; |
| |
| TRACE("added %s\n", debugstr_w( name )); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT load_streams( MSIDATABASE *db ) |
| { |
| WCHAR decoded[MAX_STREAM_NAME_LEN + 1]; |
| IEnumSTATSTG *stgenum; |
| STATSTG stat; |
| HRESULT hr; |
| UINT count, r = ERROR_SUCCESS; |
| IStream *stream; |
| |
| hr = IStorage_EnumElements( db->storage, 0, NULL, 0, &stgenum ); |
| if (FAILED( hr )) |
| return ERROR_FUNCTION_FAILED; |
| |
| for (;;) |
| { |
| count = 0; |
| hr = IEnumSTATSTG_Next( stgenum, 1, &stat, &count ); |
| if (FAILED( hr ) || !count) |
| break; |
| |
| /* table streams are not in the _Streams table */ |
| if (stat.type != STGTY_STREAM || *stat.pwcsName == 0x4840) |
| { |
| CoTaskMemFree( stat.pwcsName ); |
| continue; |
| } |
| decode_streamname( stat.pwcsName, decoded ); |
| if (find_stream( db, decoded )) |
| { |
| CoTaskMemFree( stat.pwcsName ); |
| continue; |
| } |
| TRACE("found new stream %s\n", debugstr_w( decoded )); |
| |
| hr = open_stream( db, stat.pwcsName, &stream ); |
| CoTaskMemFree( stat.pwcsName ); |
| if (FAILED( hr )) |
| { |
| ERR("unable to open stream %08x\n", hr); |
| r = ERROR_FUNCTION_FAILED; |
| break; |
| } |
| |
| r = append_stream( db, decoded, stream ); |
| if (r != ERROR_SUCCESS) |
| break; |
| } |
| |
| TRACE("loaded %u streams\n", db->num_streams); |
| IEnumSTATSTG_Release( stgenum ); |
| return r; |
| } |
| |
| UINT msi_get_stream( MSIDATABASE *db, const WCHAR *name, IStream **ret ) |
| { |
| MSISTREAM *stream; |
| WCHAR *encname; |
| HRESULT hr; |
| UINT r; |
| |
| if ((stream = find_stream( db, name ))) |
| { |
| LARGE_INTEGER pos; |
| |
| pos.QuadPart = 0; |
| hr = IStream_Seek( stream->stream, pos, STREAM_SEEK_SET, NULL ); |
| if (FAILED( hr )) |
| return ERROR_FUNCTION_FAILED; |
| |
| *ret = stream->stream; |
| IStream_AddRef( *ret ); |
| return ERROR_SUCCESS; |
| } |
| |
| if (!(encname = encode_streamname( FALSE, name ))) |
| return ERROR_OUTOFMEMORY; |
| |
| hr = open_stream( db, encname, ret ); |
| msi_free( encname ); |
| if (FAILED( hr )) |
| return ERROR_FUNCTION_FAILED; |
| |
| r = append_stream( db, name, *ret ); |
| if (r != ERROR_SUCCESS) |
| { |
| IStream_Release( *ret ); |
| return r; |
| } |
| |
| IStream_AddRef( *ret ); |
| return ERROR_SUCCESS; |
| } |
| |
| UINT STREAMS_CreateView(MSIDATABASE *db, MSIVIEW **view) |
| { |
| MSISTREAMSVIEW *sv; |
| UINT r; |
| |
| TRACE("(%p, %p)\n", db, view); |
| |
| r = load_streams( db ); |
| if (r != ERROR_SUCCESS) |
| return r; |
| |
| if (!(sv = msi_alloc_zero( sizeof(MSISTREAMSVIEW) ))) |
| return ERROR_OUTOFMEMORY; |
| |
| sv->view.ops = &streams_ops; |
| sv->num_cols = NUM_STREAMS_COLS; |
| sv->db = db; |
| |
| *view = (MSIVIEW *)sv; |
| |
| return ERROR_SUCCESS; |
| } |
| |
| static HRESULT write_stream( IStream *dst, IStream *src ) |
| { |
| HRESULT hr; |
| char buf[4096]; |
| STATSTG stat; |
| LARGE_INTEGER pos; |
| UINT count, size; |
| |
| hr = IStream_Stat( src, &stat, STATFLAG_NONAME ); |
| if (FAILED( hr )) return hr; |
| |
| hr = IStream_SetSize( dst, stat.cbSize ); |
| if (FAILED( hr )) return hr; |
| |
| pos.QuadPart = 0; |
| hr = IStream_Seek( dst, pos, STREAM_SEEK_SET, NULL ); |
| if (FAILED( hr )) return hr; |
| |
| for (;;) |
| { |
| size = min( sizeof(buf), stat.cbSize.QuadPart ); |
| hr = IStream_Read( src, buf, size, &count ); |
| if (FAILED( hr ) || count != size) |
| { |
| WARN("failed to read stream: %08x\n", hr); |
| return E_INVALIDARG; |
| } |
| stat.cbSize.QuadPart -= count; |
| if (count) |
| { |
| size = count; |
| hr = IStream_Write( dst, buf, size, &count ); |
| if (FAILED( hr ) || count != size) |
| { |
| WARN("failed to write stream: %08x\n", hr); |
| return E_INVALIDARG; |
| } |
| } |
| if (!stat.cbSize.QuadPart) break; |
| } |
| |
| return S_OK; |
| } |
| |
| UINT msi_commit_streams( MSIDATABASE *db ) |
| { |
| UINT i; |
| const WCHAR *name; |
| WCHAR *encname; |
| IStream *stream; |
| HRESULT hr; |
| |
| TRACE("got %u streams\n", db->num_streams); |
| |
| for (i = 0; i < db->num_streams; i++) |
| { |
| name = msi_string_lookup( db->strings, db->streams[i].str_index, NULL ); |
| if (!(encname = encode_streamname( FALSE, name ))) return ERROR_OUTOFMEMORY; |
| |
| hr = IStorage_CreateStream( db->storage, encname, STGM_WRITE|STGM_SHARE_EXCLUSIVE, 0, 0, &stream ); |
| if (SUCCEEDED( hr )) |
| { |
| hr = write_stream( stream, db->streams[i].stream ); |
| if (FAILED( hr )) |
| { |
| ERR("failed to write stream %s (hr = %08x)\n", debugstr_w(encname), hr); |
| msi_free( encname ); |
| IStream_Release( stream ); |
| return ERROR_FUNCTION_FAILED; |
| } |
| hr = IStream_Commit( stream, 0 ); |
| IStream_Release( stream ); |
| if (FAILED( hr )) |
| { |
| ERR("failed to commit stream %s (hr = %08x)\n", debugstr_w(encname), hr); |
| msi_free( encname ); |
| return ERROR_FUNCTION_FAILED; |
| } |
| } |
| else if (hr != STG_E_FILEALREADYEXISTS) |
| { |
| ERR("failed to create stream %s (hr = %08x)\n", debugstr_w(encname), hr); |
| msi_free( encname ); |
| return ERROR_FUNCTION_FAILED; |
| } |
| msi_free( encname ); |
| } |
| |
| return ERROR_SUCCESS; |
| } |