diff --git a/dlls/msi/database.c b/dlls/msi/database.c index d4a965561d5..6af8b475a75 100644 --- a/dlls/msi/database.c +++ b/dlls/msi/database.c @@ -67,6 +67,7 @@ static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg ) DWORD r; free_cached_tables( db ); + msi_free_transforms( db ); msi_destroy_stringtable( db->strings ); r = IStorage_Release( db->storage ); if( r ) @@ -156,6 +157,7 @@ UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb) db->storage = stg; db->mode = szMode; list_init( &db->tables ); + list_init( &db->transforms ); db->strings = load_string_table( stg ); if( !db->strings ) diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h index 19b39f09b0d..76a9e461a21 100644 --- a/dlls/msi/msipriv.h +++ b/dlls/msi/msipriv.h @@ -67,6 +67,7 @@ typedef struct tagMSIDATABASE string_table *strings; LPCWSTR mode; struct list tables; + struct list transforms; } MSIDATABASE; typedef struct tagMSIVIEW MSIVIEW; @@ -279,6 +280,7 @@ extern void msiobj_lock(MSIOBJECTHDR *); extern void msiobj_unlock(MSIOBJECTHDR *); extern void free_cached_tables( MSIDATABASE *db ); +extern void msi_free_transforms( MSIDATABASE *db ); extern string_table *load_string_table( IStorage *stg ); extern UINT MSI_CommitTables( MSIDATABASE *db ); extern HRESULT init_string_table( IStorage *stg ); diff --git a/dlls/msi/table.c b/dlls/msi/table.c index 6e76e04ffda..02530ecfc8d 100644 --- a/dlls/msi/table.c +++ b/dlls/msi/table.c @@ -56,6 +56,11 @@ struct tagMSITABLE WCHAR name[1]; }; +typedef struct tagMSITRANSFORM { + struct list entry; + IStorage *stg; +} MSITRANSFORM; + #define MAX_STREAM_NAME 0x1f static UINT table_get_column_info( MSIDATABASE *db, LPCWSTR name, @@ -276,14 +281,23 @@ UINT db_get_raw_stream( MSIDATABASE *db, LPCWSTR stname, IStream **stm ) r = IStorage_OpenStream(db->storage, encname, NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, stm); - msi_free( encname ); if( FAILED( r ) ) { - WARN("open stream failed r = %08lx - empty table?\n",r); - return ERROR_FUNCTION_FAILED; + MSITRANSFORM *transform; + + LIST_FOR_EACH_ENTRY( transform, &db->transforms, MSITRANSFORM, entry ) + { + TRACE("looking for %s in transform storage\n", debugstr_w(stname) ); + r = IStorage_OpenStream( transform->stg, encname, NULL, + STGM_READ | STGM_SHARE_EXCLUSIVE, 0, stm ); + if (SUCCEEDED(r)) + break; + } } - return ERROR_SUCCESS; + msi_free( encname ); + + return SUCCEEDED(r) ? ERROR_SUCCESS : ERROR_FUNCTION_FAILED; } UINT read_raw_stream_data( MSIDATABASE *db, LPCWSTR stname, @@ -1288,7 +1302,8 @@ static UINT table_validate_new( MSITABLEVIEW *tv, MSIRECORD *rec ) debugstr_w(tv->columns[i].colname) ); val = 0; - if( tv->columns[i].type & MSITYPE_STRING ) + if( (tv->columns[i].type & MSITYPE_STRING ) && + (tv->columns[i].type & 0xff ) ) { /* keys can't be null */ str = MSI_RecordGetString( rec, i+1 ); @@ -1321,38 +1336,32 @@ static UINT table_validate_new( MSITABLEVIEW *tv, MSIRECORD *rec ) return ERROR_SUCCESS; } -static UINT TABLE_insert_row( struct tagMSIVIEW *view, MSIRECORD *rec ) +static UINT msi_table_modify_row( MSITABLEVIEW *tv, MSIRECORD *rec, + UINT row, UINT mask ) { - MSITABLEVIEW *tv = (MSITABLEVIEW*)view; - UINT n, type, val, r, row, col_count = 0; + UINT i, val, r = ERROR_SUCCESS; - r = TABLE_get_dimensions( view, NULL, &col_count ); - if( r ) - return r; + TRACE("%p %p %u %08x\n", tv, rec, row, mask ); - row = -1; - r = table_create_new_row( view, &row ); - TRACE("insert_row returned %08x\n", r); - if( r ) - return r; - - for( n = 1; n <= col_count; n++ ) + for( i = 0; i < tv->num_cols; i++ ) { - r = TABLE_get_column_info( view, n, NULL, &type ); - if( r ) - break; + /* set keys or values specified in the mask */ + if( (~tv->columns[i].type & MSITYPE_KEY) && (~mask & (1<columns[i].type & MSITYPE_STRING) && + ! MSITYPE_IS_BINARY(tv->columns[i].type) ) { - const WCHAR *str = MSI_RecordGetString( rec, n ); + const WCHAR *str = MSI_RecordGetString( rec, i+1 ); val = msi_addstringW( tv->db->strings, 0, str, -1, 1 ); } else { - val = MSI_RecordGetInteger( rec, n ); - val ^= 0x8000; + val = MSI_RecordGetInteger( rec, i+1 ); + if ( 2 == bytes_per_column( &tv->columns[i] ) ) + val ^= 0x8000; } - r = TABLE_set_int( view, row, n, val ); + r = TABLE_set_int( &tv->view, row, i+1, val ); if( r ) break; } @@ -1360,6 +1369,21 @@ static UINT TABLE_insert_row( struct tagMSIVIEW *view, MSIRECORD *rec ) return r; } +static UINT TABLE_insert_row( struct tagMSIVIEW *view, MSIRECORD *rec ) +{ + MSITABLEVIEW *tv = (MSITABLEVIEW*)view; + UINT r, row = -1; + + TRACE("%p %p\n", tv, rec ); + + r = table_create_new_row( view, &row ); + TRACE("insert_row returned %08x\n", r); + if( r != ERROR_SUCCESS ) + return r; + + return msi_table_modify_row( tv, rec, row, ~0 ); +} + static UINT TABLE_modify( struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec) { @@ -1533,10 +1557,297 @@ UINT MSI_CommitTables( MSIDATABASE *db ) return ERROR_SUCCESS; } +static MSIRECORD *msi_get_transform_record( MSITABLEVIEW *tv, string_table *st, USHORT *rawdata ) +{ + UINT i, val, ofs = 0; + USHORT mask = *rawdata++; + MSICOLUMNINFO *columns = tv->columns; + MSIRECORD *rec; + const int debug_transform = 0; + + rec = MSI_CreateRecord( tv->num_cols ); + if( !rec ) + return rec; + + if( debug_transform ) MESSAGE("row -> "); + for( i=0; inum_cols; i++ ) + { + UINT n = bytes_per_column( &columns[i] ); + + if ( (mask&1) && (i>=(mask>>8)) ) + break; + /* all keys must be present */ + if ( (~mask&1) && (~columns[i].type & MSITYPE_KEY) && ((1<columns[i].type) ) + { + LPCWSTR sval = msi_string_lookup_id( st, val ); + MSI_RecordSetStringW( rec, i+1, sval ); + if( debug_transform ) MESSAGE("[%s]", debugstr_w(sval)); + } + else + { + val ^= 0x8000; + MSI_RecordSetInteger( rec, i+1, val ); + if( debug_transform) MESSAGE("[0x%04x]", val ); + } + break; + case 4: + val = rawdata[ofs] + (rawdata[ofs + 1]<<16); + /* val ^= 0x80000000; */ + MSI_RecordSetInteger( rec, i+1, val ); + if( debug_transform ) MESSAGE("[0x%08x]", val ); + break; + default: + ERR("oops - unknown column width %d\n", n); + break; + } + ofs += n/2; + } + if( debug_transform) MESSAGE("\n"); + return rec; +} + +static void dump_record( MSIRECORD *rec ) +{ + UINT i, n; + + MESSAGE("row -> "); + n = MSI_RecordGetFieldCount( rec ); + for( i=1; i<=n; i++ ) + { + LPCWSTR sval = MSI_RecordGetString( rec, i ); + + if( MSI_RecordIsNull( rec, i ) ) + MESSAGE("[]"); + else if( (sval = MSI_RecordGetString( rec, i )) ) + MESSAGE("[%s]", debugstr_w(sval)); + else + MESSAGE("[0x%08x]", MSI_RecordGetInteger( rec, i ) ); + } + MESSAGE("\n"); +} + +static void dump_table( string_table *st, USHORT *rawdata, UINT rawsize ) +{ + LPCWSTR sval; + UINT i; + + for( i=0; i<(rawsize/2); i++ ) + { + sval = msi_string_lookup_id( st, rawdata[i] ); + if( !sval ) sval = (WCHAR[]) {0}; + MESSAGE(" %04x %s\n", rawdata[i], debugstr_w(sval) ); + } +} + +static UINT* msi_record_to_row( MSITABLEVIEW *tv, MSIRECORD *rec ) +{ + LPCWSTR str; + UINT i, r, *data; + + data = msi_alloc( tv->num_cols *sizeof (UINT) ); + for( i=0; inum_cols; i++ ) + { + data[i] = 0; + + if ( ~tv->columns[i].type & MSITYPE_KEY ) + continue; + + /* turn the transform column value into a row value */ + if ( ( tv->columns[i].type & MSITYPE_STRING ) && + ! MSITYPE_IS_BINARY(tv->columns[i].type) ) + { + str = MSI_RecordGetString( rec, i+1 ); + r = msi_string2idW( tv->db->strings, str, &data[i] ); + + /* if there's no matching string in the string table, + these keys can't match any record, so fail now. */ + if( ERROR_SUCCESS != r ) + { + msi_free( data ); + return NULL; + } + } + else + data[i] = MSI_RecordGetInteger( rec, i+1 ); + } + return data; +} + +static UINT msi_row_matches( MSITABLEVIEW *tv, UINT row, UINT *data ) +{ + UINT i, r, x, ret = ERROR_FUNCTION_FAILED; + + for( i=0; inum_cols; i++ ) + { + if ( ~tv->columns[i].type & MSITYPE_KEY ) + continue; + + /* turn the transform column value into a row value */ + r = TABLE_fetch_int( &tv->view, row, i+1, &x ); + if ( r != ERROR_SUCCESS ) + { + ERR("TABLE_fetch_int shouldn't fail here\n"); + break; + } + + /* if this key matches, move to the next column */ + if ( x != data[i] ) + { + ret = ERROR_FUNCTION_FAILED; + break; + } + + ret = ERROR_SUCCESS; + } + + return ret; +} + +static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row ) +{ + UINT i, r = ERROR_FUNCTION_FAILED, *data; + + data = msi_record_to_row( tv, rec ); + if( !data ) + return r; + for( i=0; itable->row_count; i++ ) + { + r = msi_row_matches( tv, i, data ); + if( r == ERROR_SUCCESS ) + { + *row = i; + break; + } + } + msi_free( data ); + return r; +} + +static UINT msi_delete_row( MSITABLEVIEW *tv, UINT row ) +{ + UINT i; + for( i=1; i<=tv->num_cols; i++ ) + tv->view.ops->set_int( &tv->view, row, i, 0 ); + return ERROR_SUCCESS; +} + static UINT msi_table_load_transform( MSIDATABASE *db, IStorage *stg, string_table *st, LPCWSTR name ) { - FIXME("%p %p %p %s\n", db, stg, st, debugstr_w(name) ); + UINT rawsize = 0; + USHORT *rawdata = NULL; + MSITABLEVIEW *tv = NULL; + UINT r, n, sz, i, mask; + MSIRECORD *rec = NULL; + const int debug_transform = 0; + + TRACE("%p %p %p %s\n", db, stg, st, debugstr_w(name) ); + + /* create a table view */ + r = TABLE_CreateView( db, name, (MSIVIEW**) &tv ); + if( r != ERROR_SUCCESS ) + goto err; + + r = tv->view.ops->execute( &tv->view, NULL ); + if( r != ERROR_SUCCESS ) + goto err; + + /* read the transform data */ + r = ERROR_FUNCTION_FAILED; + read_stream_data( stg, name, &rawdata, &rawsize ); + if( !rawdata || (rawsize < 2) ) + { + ERR("odd sized transform for table %s\n", debugstr_w(name)); + goto err; + } + + TRACE("name = %s columns = %u row_size = %u raw size = %u\n", + debugstr_w(name), tv->num_cols, tv->row_size, rawsize ); + + /* interpret the data */ + r = ERROR_SUCCESS; + for( n=0; n < (rawsize/2); ) + { + mask = rawdata[n]; + + if (mask&1) + { + /* + * if the low bit is set, columns are continuous and + * the number of columns is specified in the high byte + */ + sz = 2 + tv->row_size; + } + else + { + /* + * If the low bit is not set, rowdata[n] is a bitmask. + * Excepting for key fields, which are always present, + * each bit indicates that a field is present in the transform record. + * + * rawdata[n] == 0 is a special case ... only the keys will be present + * and it means that this row should be deleted. + */ + sz = 2; + for( i=0; inum_cols; i++ ) + { + if( (tv->columns[i].type & MSITYPE_KEY) || ((1<columns[i] ); + } + } + + /* check we didn't run of the end of the table */ + if ( (n+sz) > rawsize ) + { + ERR("borked.\n"); + dump_table( st, rawdata, rawsize ); + break; + } + + rec = msi_get_transform_record( tv, st, &rawdata[n] ); + if (rec) + { + UINT row = 0; + + r = msi_table_find_row( tv, rec, &row ); + + if( rawdata[n] & 1) + { + if( debug_transform ) MESSAGE("insert [%d]: ", row); + TABLE_insert_row( &tv->view, rec ); + } + else if( mask & 0xff ) + { + if( debug_transform ) MESSAGE("modify [%d]: ", row); + msi_table_modify_row( tv, rec, row, mask ); + } + else + { + if( debug_transform ) MESSAGE("delete [%d]: ", row); + msi_delete_row( tv, row ); + } + if( debug_transform ) dump_record( rec ); + msiobj_release( &rec->hdr ); + } + + n += sz/2; + + } + +err: + /* no need to free the table, it's associated with the database */ + msi_free( rawdata ); + if( tv ) + tv->view.ops->delete( &tv->view ); + return ERROR_SUCCESS; } @@ -1555,6 +1866,8 @@ UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg ) string_table *strings; UINT ret = ERROR_FUNCTION_FAILED; + TRACE("%p %p\n", db, stg ); + strings = load_string_table( stg ); if( !strings ) goto end; @@ -1574,12 +1887,22 @@ UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg ) break; decode_streamname( stat.pwcsName, name ); if( ( name[0] == 0x4840 ) && ( name[1] != '_' ) ) - r = msi_table_load_transform( db, stg, strings, name+1 ); + ret = msi_table_load_transform( db, stg, strings, name+1 ); else - TRACE("non-table stream %s\n", debugstr_w(name) ); + TRACE("transform contains stream %s\n", debugstr_w(name)); n++; } + if ( ret == ERROR_SUCCESS ) + { + MSITRANSFORM *t; + + t = msi_alloc( sizeof *t ); + t->stg = stg; + IStorage_AddRef( stg ); + list_add_tail( &db->transforms, &t->entry ); + } + end: if ( stgenum ) IEnumSTATSTG_Release( stgenum ); @@ -1588,3 +1911,15 @@ end: return ret; } + +void msi_free_transforms( MSIDATABASE *db ) +{ + while( !list_empty( &db->transforms ) ) + { + MSITRANSFORM *t = LIST_ENTRY( list_head( &db->transforms ), + MSITRANSFORM, entry ); + list_remove( &t->entry ); + IStorage_Release( t->stg ); + msi_free( t ); + } +}