msi: Implement special handling for the _Streams table.
diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in index 5fb0941..e7ba773 100644 --- a/dlls/msi/Makefile.in +++ b/dlls/msi/Makefile.in
@@ -39,6 +39,7 @@ regsvr.c \ select.c \ source.c \ + streams.c \ string.c \ suminfo.c \ table.c \
diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h index e7c0c71..a12a264 100644 --- a/dlls/msi/msipriv.h +++ b/dlls/msi/msipriv.h
@@ -568,7 +568,7 @@ extern UINT read_stream_data( IStorage *stg, LPCWSTR stname, USHORT **pdata, UINT *psz ); extern UINT write_stream_data( IStorage *stg, LPCWSTR stname, - LPVOID data, UINT sz ); + LPVOID data, UINT sz, BOOL bTable ); /* transform functions */ extern UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg ); @@ -606,6 +606,8 @@ extern UINT get_raw_stream( MSIHANDLE hdb, LPCWSTR stname, IStream **stm ); extern UINT db_get_raw_stream( MSIDATABASE *db, LPCWSTR stname, IStream **stm ); extern void enum_stream_names( IStorage *stg ); +extern BOOL decode_streamname(LPWSTR in, LPWSTR out); +extern LPWSTR encode_streamname(BOOL bTable, LPCWSTR in); /* database internals */ extern UINT MSI_OpenDatabaseW( LPCWSTR, LPCWSTR, MSIDATABASE ** );
diff --git a/dlls/msi/query.h b/dlls/msi/query.h index 69d511c..2bf8955 100644 --- a/dlls/msi/query.h +++ b/dlls/msi/query.h
@@ -124,6 +124,8 @@ UINT ALTER_CreateView( MSIDATABASE *db, MSIVIEW **view, LPCWSTR name, int hold ); +UINT STREAMS_CreateView( MSIDATABASE *db, MSIVIEW **view ); + int sqliteGetToken(const WCHAR *z, int *tokenType); MSIRECORD *msi_query_merge_record( UINT fields, column_info *vl, MSIRECORD *rec );
diff --git a/dlls/msi/streams.c b/dlls/msi/streams.c new file mode 100644 index 0000000..611f997 --- /dev/null +++ b/dlls/msi/streams.c
@@ -0,0 +1,404 @@ +/* + * Implementation of the Microsoft Installer (msi.dll) + * + * Copyright 2007 James Hawkins + * + * 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 "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(msidb); + +#define NUM_STREAMS_COLS 2 +#define MAX_STREAM_NAME_LEN 62 + +typedef struct tabSTREAM +{ + int str_index; + LPWSTR name; + IStream *stream; +} STREAM; + +typedef struct tagMSISTREAMSVIEW +{ + MSIVIEW view; + MSIDATABASE *db; + STREAM **streams; + UINT max_streams; + UINT num_rows; + UINT row_size; +} MSISTREAMSVIEW; + +static BOOL add_stream_to_table(MSISTREAMSVIEW *sv, STREAM *stream, int index) +{ + if (index >= sv->max_streams) + { + sv->max_streams *= 2; + sv->streams = msi_realloc(sv->streams, sv->max_streams * sizeof(STREAM *)); + if (!sv->streams) + return FALSE; + } + + sv->streams[index] = stream; + return TRUE; +} + +static STREAM *create_stream(MSISTREAMSVIEW *sv, LPWSTR name, BOOL encoded, IStream *stm) +{ + STREAM *stream; + WCHAR decoded[MAX_STREAM_NAME_LEN]; + LPWSTR ptr = name; + + stream = msi_alloc(sizeof(STREAM)); + if (!stream) + return NULL; + + if (encoded) + { + decode_streamname(name, decoded); + ptr = decoded; + TRACE("stream -> %s %s\n", debugstr_w(name), debugstr_w(decoded)); + } + + stream->name = strdupW(ptr); + if (!stream->name) + { + msi_free(stream); + return NULL; + } + + stream->str_index = msi_addstringW(sv->db->strings, 0, stream->name, -1, 1, StringNonPersistent); + stream->stream = stm; + return stream; +} + +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->num_rows) + return ERROR_NO_MORE_ITEMS; + + *val = sv->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; + + TRACE("(%p, %d, %d, %p)\n", view, row, col, stm); + + if (row >= sv->num_rows) + return ERROR_FUNCTION_FAILED; + + IStream_AddRef(sv->streams[row]->stream); + *stm = sv->streams[row]->stream; + + return ERROR_SUCCESS; +} + +static UINT STREAMS_set_row(struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask) +{ + FIXME("(%p, %d, %p, %d): stub!\n", view, row, rec, mask); + return ERROR_SUCCESS; +} + +static UINT STREAMS_insert_row(struct tagMSIVIEW *view, MSIRECORD *rec, BOOL temporary) +{ + MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; + STREAM *stream; + IStream *stm; + STATSTG stat; + LPWSTR name = NULL; + USHORT *data = NULL; + HRESULT hr; + ULONG count; + UINT r = ERROR_FUNCTION_FAILED; + + TRACE("(%p, %p, %d)\n", view, rec, temporary); + + r = MSI_RecordGetIStream(rec, 2, &stm); + if (r != ERROR_SUCCESS) + return r; + + hr = IStream_Stat(stm, &stat, STATFLAG_NONAME); + if (FAILED(hr)) + { + WARN("failed to stat stream: %08x\n", hr); + goto done; + } + + if (stat.cbSize.QuadPart >> 32) + goto done; + + data = msi_alloc(stat.cbSize.QuadPart); + if (!data) + goto done; + + hr = IStream_Read(stm, data, stat.cbSize.QuadPart, &count); + if (FAILED(hr) || count != stat.cbSize.QuadPart) + { + WARN("failed to read stream: %08x\n", hr); + goto done; + } + + name = strdupW(MSI_RecordGetString(rec, 1)); + if (!name) + goto done; + + r = write_stream_data(sv->db->storage, name, data, count, FALSE); + if (r != ERROR_SUCCESS) + { + WARN("failed to write stream data: %d\n", r); + goto done; + } + + stream = create_stream(sv, name, FALSE, NULL); + if (!stream) + goto done; + + IStorage_OpenStream(sv->db->storage, name, 0, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream->stream); + + if (!add_stream_to_table(sv, stream, sv->num_rows++)) + goto done; + +done: + msi_free(name); + msi_free(data); + + IStream_Release(stm); + + return r; +} + +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 = NUM_STREAMS_COLS; + if (rows) *rows = sv->num_rows; + + return ERROR_SUCCESS; +} + +static UINT STREAMS_get_column_info(struct tagMSIVIEW *view, + UINT n, LPWSTR *name, UINT *type) +{ + LPCWSTR name_ptr = NULL; + + static const WCHAR Name[] = {'N','a','m','e',0}; + static const WCHAR Data[] = {'D','a','t','a',0}; + + TRACE("(%p, %d, %p, %p)\n", view, n, name, type); + + if (n == 0 || n > NUM_STREAMS_COLS) + return ERROR_INVALID_PARAMETER; + + switch (n) + { + case 1: + name_ptr = Name; + if (type) *type = MSITYPE_STRING | MAX_STREAM_NAME_LEN; + break; + + case 2: + name_ptr = Data; + if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE; + break; + } + + if (name) + { + *name = strdupW(name_ptr); + if (!*name) return ERROR_FUNCTION_FAILED; + } + + return ERROR_SUCCESS; +} + +static UINT STREAMS_modify(struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec) +{ + FIXME("(%p, %d, %p): stub!\n", view, eModifyMode, rec); + return ERROR_SUCCESS; +} + +static UINT STREAMS_delete(struct tagMSIVIEW *view) +{ + MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view; + int i; + + TRACE("(%p)\n", view); + + for (i = 0; i < sv->num_rows; i++) + { + if (sv->streams[i]->stream) + IStream_Release(sv->streams[i]->stream); + msi_free(sv->streams[i]); + } + + msi_free(sv->streams); + + 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 = (UINT)*handle; + + TRACE("(%d, %d): %d\n", *row, col, val); + + if (col == 0 || col > NUM_STREAMS_COLS) + return ERROR_INVALID_PARAMETER; + + while (index < sv->num_rows) + { + if (sv->streams[index]->str_index == val) + { + *row = index; + break; + } + + index++; + } + + *handle = (MSIITERHANDLE)++index; + if (index >= sv->num_rows) + return ERROR_NO_MORE_ITEMS; + + return ERROR_SUCCESS; +} + +static const MSIVIEWOPS streams_ops = +{ + STREAMS_fetch_int, + STREAMS_fetch_stream, + STREAMS_set_row, + STREAMS_insert_row, + STREAMS_execute, + STREAMS_close, + STREAMS_get_dimensions, + STREAMS_get_column_info, + STREAMS_modify, + STREAMS_delete, + STREAMS_find_matching_rows +}; + +static UINT add_streams_to_table(MSISTREAMSVIEW *sv) +{ + IEnumSTATSTG *stgenum = NULL; + STATSTG stat; + STREAM *stream = NULL; + HRESULT hr; + UINT count = 0, size; + + hr = IStorage_EnumElements(sv->db->storage, 0, NULL, 0, &stgenum); + if (FAILED(hr)) + return -1; + + sv->max_streams = 1; + sv->streams = msi_alloc(sizeof(STREAM *)); + if (!sv->streams) + return -1; + + while (TRUE) + { + size = 0; + hr = IEnumSTATSTG_Next(stgenum, 1, &stat, &size); + if (FAILED(hr) || !size) + break; + + /* table streams are not in the _Streams table */ + if (*stat.pwcsName == 0x4840) + continue; + + stream = create_stream(sv, stat.pwcsName, TRUE, NULL); + if (!stream) + { + count = -1; + break; + } + + IStorage_OpenStream(sv->db->storage, stat.pwcsName, 0, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stream->stream); + + if (!add_stream_to_table(sv, stream, count++)) + { + count = -1; + break; + } + } + + IEnumSTATSTG_Release(stgenum); + return count; +} + +UINT STREAMS_CreateView(MSIDATABASE *db, MSIVIEW **view) +{ + MSISTREAMSVIEW *sv; + + TRACE("(%p, %p)\n", db, view); + + sv = msi_alloc(sizeof(MSISTREAMSVIEW)); + if (!sv) + return ERROR_FUNCTION_FAILED; + + sv->view.ops = &streams_ops; + sv->db = db; + sv->num_rows = add_streams_to_table(sv); + + if (sv->num_rows < 0) + return ERROR_FUNCTION_FAILED; + + *view = (MSIVIEW *)sv; + + return ERROR_SUCCESS; +}
diff --git a/dlls/msi/string.c b/dlls/msi/string.c index ef6d7c7..b773a35 100644 --- a/dlls/msi/string.c +++ b/dlls/msi/string.c
@@ -495,12 +495,12 @@ UINT ret; /* create the StringPool stream... add the zero string to it*/ - ret = write_stream_data(stg, szStringPool, zero, sizeof zero); + ret = write_stream_data(stg, szStringPool, zero, sizeof zero, TRUE); if (ret != ERROR_SUCCESS) return E_FAIL; /* create the StringData stream... make it zero length */ - ret = write_stream_data(stg, szStringData, NULL, 0); + ret = write_stream_data(stg, szStringData, NULL, 0, TRUE); if (ret != ERROR_SUCCESS) return E_FAIL; @@ -663,11 +663,11 @@ } /* write the streams */ - r = write_stream_data( storage, szStringData, data, datasize ); + r = write_stream_data( storage, szStringData, data, datasize, TRUE ); TRACE("Wrote StringData r=%08x\n", r); if( r ) goto err; - r = write_stream_data( storage, szStringPool, pool, poolsize ); + r = write_stream_data( storage, szStringPool, pool, poolsize, TRUE ); TRACE("Wrote StringPool r=%08x\n", r); if( r ) goto err;
diff --git a/dlls/msi/table.c b/dlls/msi/table.c index 974b3f1..0d96793 100644 --- a/dlls/msi/table.c +++ b/dlls/msi/table.c
@@ -134,7 +134,7 @@ return -1; } -static LPWSTR encode_streamname(BOOL bTable, LPCWSTR in) +LPWSTR encode_streamname(BOOL bTable, LPCWSTR in) { DWORD count = MAX_STREAM_NAME; DWORD ch, next; @@ -193,7 +193,7 @@ return '_'; } -static BOOL decode_streamname(LPWSTR in, LPWSTR out) +BOOL decode_streamname(LPWSTR in, LPWSTR out) { WCHAR ch; DWORD count = 0; @@ -395,7 +395,7 @@ } UINT write_stream_data( IStorage *stg, LPCWSTR stname, - LPVOID data, UINT sz ) + LPVOID data, UINT sz, BOOL bTable ) { HRESULT r; UINT ret = ERROR_FUNCTION_FAILED; @@ -405,7 +405,7 @@ LARGE_INTEGER pos; LPWSTR encname; - encname = encode_streamname(TRUE, stname ); + encname = encode_streamname(bTable, stname ); r = IStorage_OpenStream( stg, encname, NULL, STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &stm); if( FAILED(r) ) @@ -845,7 +845,7 @@ } TRACE("writing %d bytes\n", rawsize); - r = write_stream_data( db->storage, t->name, rawdata, rawsize ); + r = write_stream_data( db->storage, t->name, rawdata, rawsize, TRUE ); err: msi_free( rawdata ); @@ -1642,8 +1642,13 @@ MSITABLEVIEW *tv ; UINT r, sz; + static const WCHAR Streams[] = {'_','S','t','r','e','a','m','s',0}; + TRACE("%p %s %p\n", db, debugstr_w(name), view ); + if ( !lstrcmpW( name, Streams ) ) + return STREAMS_CreateView( db, view ); + sz = sizeof *tv + lstrlenW(name)*sizeof name[0] ; tv = msi_alloc_zero( sz ); if( !tv )
diff --git a/dlls/msi/tests/db.c b/dlls/msi/tests/db.c index 073cf01..1d24f5d 100644 --- a/dlls/msi/tests/db.c +++ b/dlls/msi/tests/db.c
@@ -1194,14 +1194,25 @@ "( `Property` CHAR(255), `Value` CHAR(1) PRIMARY KEY `Property`)" ); ok( r == ERROR_SUCCESS , "Failed to create table\n" ); + r = run_query( hdb, 0, + "INSERT INTO `Properties` " + "( `Value`, `Property` ) VALUES ( 'Prop', 'value' )" ); + ok( r == ERROR_SUCCESS, "Failed to add to table\n" ); + + r = MsiDatabaseCommit( hdb ); + ok( r == ERROR_SUCCESS , "Failed to commit database\n" ); + + MsiCloseHandle( hdb ); + + r = MsiOpenDatabase(msifile, MSIDBOPEN_TRANSACT, &hdb ); + ok( r == ERROR_SUCCESS , "Failed to open database\n" ); + /* check the column types */ rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_TYPES ); ok( rec, "failed to get column info record\n" ); - todo_wine { ok( check_record( rec, 1, "s62"), "wrong record type\n"); ok( check_record( rec, 2, "V0"), "wrong record type\n"); - } MsiCloseHandle( rec ); @@ -1209,10 +1220,8 @@ rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_NAMES ); ok( rec, "failed to get column info record\n" ); - todo_wine { ok( check_record( rec, 1, "Name"), "wrong record type\n"); ok( check_record( rec, 2, "Data"), "wrong record type\n"); - } MsiCloseHandle( rec ); @@ -1229,63 +1238,39 @@ r = MsiDatabaseOpenView( hdb, "INSERT INTO `_Streams` ( `Name`, `Data` ) VALUES ( ?, ? )", &view ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); - } + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); r = MsiViewExecute( view, rec ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); - } + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); MsiCloseHandle( rec ); MsiCloseHandle( view ); r = MsiDatabaseOpenView( hdb, "SELECT `Name`, `Data` FROM `_Streams`", &view ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); - } + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); r = MsiViewExecute( view, 0 ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); - } + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); r = MsiViewFetch( view, &rec ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to fetch record: %d\n", r); - } + ok( r == ERROR_SUCCESS, "Failed to fetch record: %d\n", r); size = MAX_PATH; r = MsiRecordGetString( rec, 1, file, &size ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r); - ok( !lstrcmp(file, "data"), "Expected 'data', got %s\n", file); - } + ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r); + ok( !lstrcmp(file, "data"), "Expected 'data', got %s\n", file); size = MAX_PATH; memset(buf, 0, MAX_PATH); r = MsiRecordReadStream( rec, 2, buf, &size ); - todo_wine - { - ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r); - ok( !lstrcmp(buf, "test.txt\n"), "Expected 'test.txt\\n', got %s\n", buf); - } + ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r); + ok( !lstrcmp(buf, "test.txt\n"), "Expected 'test.txt\\n', got %s", buf); MsiCloseHandle( rec ); r = MsiViewFetch( view, &rec ); - todo_wine - { - ok( r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r); - } + ok( r == ERROR_NO_MORE_ITEMS, "Expected ERROR_NO_MORE_ITEMS, got %d\n", r); MsiCloseHandle( view ); MsiCloseHandle( hdb );