1 /* 2 * Implementation of the Microsoft Installer (msi.dll) 3 * 4 * Copyright 2002-2005 Mike McCormack for CodeWeavers 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21 #include <stdarg.h> 22 #include <assert.h> 23 24 #define COBJMACROS 25 26 #include "windef.h" 27 #include "winbase.h" 28 #include "winerror.h" 29 #include "msi.h" 30 #include "msiquery.h" 31 #include "objbase.h" 32 #include "objidl.h" 33 #include "winnls.h" 34 #include "msipriv.h" 35 #include "query.h" 36 37 #include "wine/debug.h" 38 39 WINE_DEFAULT_DEBUG_CHANNEL(msidb); 40 41 #define MSITABLE_HASH_TABLE_SIZE 37 42 43 typedef struct tagMSICOLUMNHASHENTRY 44 { 45 struct tagMSICOLUMNHASHENTRY *next; 46 UINT value; 47 UINT row; 48 } MSICOLUMNHASHENTRY; 49 50 typedef struct tagMSICOLUMNINFO 51 { 52 LPCWSTR tablename; 53 UINT number; 54 LPCWSTR colname; 55 UINT type; 56 UINT offset; 57 INT ref_count; 58 BOOL temporary; 59 MSICOLUMNHASHENTRY **hash_table; 60 } MSICOLUMNINFO; 61 62 struct tagMSITABLE 63 { 64 BYTE **data; 65 BOOL *data_persistent; 66 UINT row_count; 67 struct list entry; 68 MSICOLUMNINFO *colinfo; 69 UINT col_count; 70 MSICONDITION persistent; 71 INT ref_count; 72 WCHAR name[1]; 73 }; 74 75 /* information for default tables */ 76 static const WCHAR szTables[] = {'_','T','a','b','l','e','s',0}; 77 static const WCHAR szTable[] = {'T','a','b','l','e',0}; 78 static const WCHAR szColumns[] = {'_','C','o','l','u','m','n','s',0}; 79 static const WCHAR szNumber[] = {'N','u','m','b','e','r',0}; 80 static const WCHAR szType[] = {'T','y','p','e',0}; 81 82 static const MSICOLUMNINFO _Columns_cols[4] = { 83 { szColumns, 1, szTable, MSITYPE_VALID | MSITYPE_STRING | MSITYPE_KEY | 64, 0, 0, 0, NULL }, 84 { szColumns, 2, szNumber, MSITYPE_VALID | MSITYPE_KEY | 2, 2, 0, 0, NULL }, 85 { szColumns, 3, szName, MSITYPE_VALID | MSITYPE_STRING | 64, 4, 0, 0, NULL }, 86 { szColumns, 4, szType, MSITYPE_VALID | 2, 6, 0, 0, NULL }, 87 }; 88 89 static const MSICOLUMNINFO _Tables_cols[1] = { 90 { szTables, 1, szName, MSITYPE_VALID | MSITYPE_STRING | MSITYPE_KEY | 64, 0, 0, 0, NULL }, 91 }; 92 93 #define MAX_STREAM_NAME 0x1f 94 95 static inline UINT bytes_per_column( MSIDATABASE *db, const MSICOLUMNINFO *col, UINT bytes_per_strref ) 96 { 97 if( MSITYPE_IS_BINARY(col->type) ) 98 return 2; 99 100 if( col->type & MSITYPE_STRING ) 101 return bytes_per_strref; 102 103 if( (col->type & 0xff) <= 2) 104 return 2; 105 106 if( (col->type & 0xff) != 4 ) 107 ERR("Invalid column size %u\n", col->type & 0xff); 108 109 return 4; 110 } 111 112 static int utf2mime(int x) 113 { 114 if( (x>='0') && (x<='9') ) 115 return x-'0'; 116 if( (x>='A') && (x<='Z') ) 117 return x-'A'+10; 118 if( (x>='a') && (x<='z') ) 119 return x-'a'+10+26; 120 if( x=='.' ) 121 return 10+26+26; 122 if( x=='_' ) 123 return 10+26+26+1; 124 return -1; 125 } 126 127 LPWSTR encode_streamname(BOOL bTable, LPCWSTR in) 128 { 129 DWORD count = MAX_STREAM_NAME; 130 DWORD ch, next; 131 LPWSTR out, p; 132 133 if( !bTable ) 134 count = lstrlenW( in )+2; 135 if (!(out = msi_alloc( count*sizeof(WCHAR) ))) return NULL; 136 p = out; 137 138 if( bTable ) 139 { 140 *p++ = 0x4840; 141 count --; 142 } 143 while( count -- ) 144 { 145 ch = *in++; 146 if( !ch ) 147 { 148 *p = ch; 149 return out; 150 } 151 if( ( ch < 0x80 ) && ( utf2mime(ch) >= 0 ) ) 152 { 153 ch = utf2mime(ch) + 0x4800; 154 next = *in; 155 if( next && (next<0x80) ) 156 { 157 next = utf2mime(next); 158 if( next != -1 ) 159 { 160 next += 0x3ffffc0; 161 ch += (next<<6); 162 in++; 163 } 164 } 165 } 166 *p++ = ch; 167 } 168 ERR("Failed to encode stream name (%s)\n",debugstr_w(in)); 169 msi_free( out ); 170 return NULL; 171 } 172 173 static int mime2utf(int x) 174 { 175 if( x<10 ) 176 return x + '0'; 177 if( x<(10+26)) 178 return x - 10 + 'A'; 179 if( x<(10+26+26)) 180 return x - 10 - 26 + 'a'; 181 if( x == (10+26+26) ) 182 return '.'; 183 return '_'; 184 } 185 186 BOOL decode_streamname(LPCWSTR in, LPWSTR out) 187 { 188 WCHAR ch; 189 DWORD count = 0; 190 191 while ( (ch = *in++) ) 192 { 193 if( (ch >= 0x3800 ) && (ch < 0x4840 ) ) 194 { 195 if( ch >= 0x4800 ) 196 ch = mime2utf(ch-0x4800); 197 else 198 { 199 ch -= 0x3800; 200 *out++ = mime2utf(ch&0x3f); 201 count++; 202 ch = mime2utf((ch>>6)&0x3f); 203 } 204 } 205 *out++ = ch; 206 count++; 207 } 208 *out = 0; 209 return count; 210 } 211 212 void enum_stream_names( IStorage *stg ) 213 { 214 IEnumSTATSTG *stgenum = NULL; 215 HRESULT r; 216 STATSTG stat; 217 ULONG n, count; 218 WCHAR name[0x40]; 219 220 r = IStorage_EnumElements( stg, 0, NULL, 0, &stgenum ); 221 if( FAILED( r ) ) 222 return; 223 224 n = 0; 225 while( 1 ) 226 { 227 count = 0; 228 r = IEnumSTATSTG_Next( stgenum, 1, &stat, &count ); 229 if( FAILED( r ) || !count ) 230 break; 231 decode_streamname( stat.pwcsName, name ); 232 TRACE("stream %2d -> %s %s\n", n, 233 debugstr_w(stat.pwcsName), debugstr_w(name) ); 234 CoTaskMemFree( stat.pwcsName ); 235 n++; 236 } 237 238 IEnumSTATSTG_Release( stgenum ); 239 } 240 241 UINT read_stream_data( IStorage *stg, LPCWSTR stname, BOOL table, 242 BYTE **pdata, UINT *psz ) 243 { 244 HRESULT r; 245 UINT ret = ERROR_FUNCTION_FAILED; 246 VOID *data; 247 ULONG sz, count; 248 IStream *stm = NULL; 249 STATSTG stat; 250 LPWSTR encname; 251 252 encname = encode_streamname(table, stname); 253 254 TRACE("%s -> %s\n",debugstr_w(stname),debugstr_w(encname)); 255 256 r = IStorage_OpenStream(stg, encname, NULL, 257 STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm); 258 msi_free( encname ); 259 if( FAILED( r ) ) 260 { 261 WARN("open stream failed r = %08x - empty table?\n", r); 262 return ret; 263 } 264 265 r = IStream_Stat(stm, &stat, STATFLAG_NONAME ); 266 if( FAILED( r ) ) 267 { 268 WARN("open stream failed r = %08x!\n", r); 269 goto end; 270 } 271 272 if( stat.cbSize.QuadPart >> 32 ) 273 { 274 WARN("Too big!\n"); 275 goto end; 276 } 277 278 sz = stat.cbSize.QuadPart; 279 data = msi_alloc( sz ); 280 if( !data ) 281 { 282 WARN("couldn't allocate memory r=%08x!\n", r); 283 ret = ERROR_NOT_ENOUGH_MEMORY; 284 goto end; 285 } 286 287 r = IStream_Read(stm, data, sz, &count ); 288 if( FAILED( r ) || ( count != sz ) ) 289 { 290 msi_free( data ); 291 WARN("read stream failed r = %08x!\n", r); 292 goto end; 293 } 294 295 *pdata = data; 296 *psz = sz; 297 ret = ERROR_SUCCESS; 298 299 end: 300 IStream_Release( stm ); 301 302 return ret; 303 } 304 305 UINT write_stream_data( IStorage *stg, LPCWSTR stname, 306 LPCVOID data, UINT sz, BOOL bTable ) 307 { 308 HRESULT r; 309 UINT ret = ERROR_FUNCTION_FAILED; 310 ULONG count; 311 IStream *stm = NULL; 312 ULARGE_INTEGER size; 313 LARGE_INTEGER pos; 314 LPWSTR encname; 315 316 encname = encode_streamname(bTable, stname ); 317 r = IStorage_OpenStream( stg, encname, NULL, 318 STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &stm); 319 if( FAILED(r) ) 320 { 321 r = IStorage_CreateStream( stg, encname, 322 STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stm); 323 } 324 msi_free( encname ); 325 if( FAILED( r ) ) 326 { 327 WARN("open stream failed r = %08x\n", r); 328 return ret; 329 } 330 331 size.QuadPart = sz; 332 r = IStream_SetSize( stm, size ); 333 if( FAILED( r ) ) 334 { 335 WARN("Failed to SetSize\n"); 336 goto end; 337 } 338 339 pos.QuadPart = 0; 340 r = IStream_Seek( stm, pos, STREAM_SEEK_SET, NULL ); 341 if( FAILED( r ) ) 342 { 343 WARN("Failed to Seek\n"); 344 goto end; 345 } 346 347 if (sz) 348 { 349 r = IStream_Write(stm, data, sz, &count ); 350 if( FAILED( r ) || ( count != sz ) ) 351 { 352 WARN("Failed to Write\n"); 353 goto end; 354 } 355 } 356 357 ret = ERROR_SUCCESS; 358 359 end: 360 IStream_Release( stm ); 361 362 return ret; 363 } 364 365 static void msi_free_colinfo( MSICOLUMNINFO *colinfo, UINT count ) 366 { 367 UINT i; 368 for (i = 0; i < count; i++) msi_free( colinfo[i].hash_table ); 369 } 370 371 static void free_table( MSITABLE *table ) 372 { 373 UINT i; 374 for( i=0; i<table->row_count; i++ ) 375 msi_free( table->data[i] ); 376 msi_free( table->data ); 377 msi_free( table->data_persistent ); 378 msi_free_colinfo( table->colinfo, table->col_count ); 379 msi_free( table->colinfo ); 380 msi_free( table ); 381 } 382 383 static UINT msi_table_get_row_size( MSIDATABASE *db, const MSICOLUMNINFO *cols, UINT count, UINT bytes_per_strref ) 384 { 385 const MSICOLUMNINFO *last_col; 386 387 if (!count) 388 return 0; 389 390 if (bytes_per_strref != LONG_STR_BYTES) 391 { 392 UINT i, size = 0; 393 for (i = 0; i < count; i++) size += bytes_per_column( db, &cols[i], bytes_per_strref ); 394 return size; 395 } 396 last_col = &cols[count - 1]; 397 return last_col->offset + bytes_per_column( db, last_col, bytes_per_strref ); 398 } 399 400 /* add this table to the list of cached tables in the database */ 401 static UINT read_table_from_storage( MSIDATABASE *db, MSITABLE *t, IStorage *stg ) 402 { 403 BYTE *rawdata = NULL; 404 UINT rawsize = 0, i, j, row_size, row_size_mem; 405 406 TRACE("%s\n",debugstr_w(t->name)); 407 408 row_size = msi_table_get_row_size( db, t->colinfo, t->col_count, db->bytes_per_strref ); 409 row_size_mem = msi_table_get_row_size( db, t->colinfo, t->col_count, LONG_STR_BYTES ); 410 411 /* if we can't read the table, just assume that it's empty */ 412 read_stream_data( stg, t->name, TRUE, &rawdata, &rawsize ); 413 if( !rawdata ) 414 return ERROR_SUCCESS; 415 416 TRACE("Read %d bytes\n", rawsize ); 417 418 if( rawsize % row_size ) 419 { 420 WARN("Table size is invalid %d/%d\n", rawsize, row_size ); 421 goto err; 422 } 423 424 if ((t->row_count = rawsize / row_size)) 425 { 426 if (!(t->data = msi_alloc_zero( t->row_count * sizeof(USHORT *) ))) goto err; 427 if (!(t->data_persistent = msi_alloc_zero( t->row_count * sizeof(BOOL) ))) goto err; 428 } 429 430 /* transpose all the data */ 431 TRACE("Transposing data from %d rows\n", t->row_count ); 432 for (i = 0; i < t->row_count; i++) 433 { 434 UINT ofs = 0, ofs_mem = 0; 435 436 t->data[i] = msi_alloc( row_size_mem ); 437 if( !t->data[i] ) 438 goto err; 439 t->data_persistent[i] = TRUE; 440 441 for (j = 0; j < t->col_count; j++) 442 { 443 UINT m = bytes_per_column( db, &t->colinfo[j], LONG_STR_BYTES ); 444 UINT n = bytes_per_column( db, &t->colinfo[j], db->bytes_per_strref ); 445 UINT k; 446 447 if ( n != 2 && n != 3 && n != 4 ) 448 { 449 ERR("oops - unknown column width %d\n", n); 450 goto err; 451 } 452 if (t->colinfo[j].type & MSITYPE_STRING && n < m) 453 { 454 for (k = 0; k < m; k++) 455 { 456 if (k < n) 457 t->data[i][ofs_mem + k] = rawdata[ofs * t->row_count + i * n + k]; 458 else 459 t->data[i][ofs_mem + k] = 0; 460 } 461 } 462 else 463 { 464 for (k = 0; k < n; k++) 465 t->data[i][ofs_mem + k] = rawdata[ofs * t->row_count + i * n + k]; 466 } 467 ofs_mem += m; 468 ofs += n; 469 } 470 } 471 472 msi_free( rawdata ); 473 return ERROR_SUCCESS; 474 err: 475 msi_free( rawdata ); 476 return ERROR_FUNCTION_FAILED; 477 } 478 479 void free_cached_tables( MSIDATABASE *db ) 480 { 481 while( !list_empty( &db->tables ) ) 482 { 483 MSITABLE *t = LIST_ENTRY( list_head( &db->tables ), MSITABLE, entry ); 484 485 list_remove( &t->entry ); 486 free_table( t ); 487 } 488 } 489 490 static MSITABLE *find_cached_table( MSIDATABASE *db, LPCWSTR name ) 491 { 492 MSITABLE *t; 493 494 LIST_FOR_EACH_ENTRY( t, &db->tables, MSITABLE, entry ) 495 if( !wcscmp( name, t->name ) ) 496 return t; 497 498 return NULL; 499 } 500 501 static void table_calc_column_offsets( MSIDATABASE *db, MSICOLUMNINFO *colinfo, DWORD count ) 502 { 503 DWORD i; 504 505 for (i = 0; colinfo && i < count; i++) 506 { 507 assert( i + 1 == colinfo[i].number ); 508 if (i) colinfo[i].offset = colinfo[i - 1].offset + 509 bytes_per_column( db, &colinfo[i - 1], LONG_STR_BYTES ); 510 else colinfo[i].offset = 0; 511 512 TRACE("column %d is [%s] with type %08x ofs %d\n", 513 colinfo[i].number, debugstr_w(colinfo[i].colname), 514 colinfo[i].type, colinfo[i].offset); 515 } 516 } 517 518 static UINT get_defaulttablecolumns( MSIDATABASE *db, LPCWSTR name, MSICOLUMNINFO *colinfo, UINT *sz ) 519 { 520 const MSICOLUMNINFO *p; 521 DWORD i, n; 522 523 TRACE("%s\n", debugstr_w(name)); 524 525 if (!wcscmp( name, szTables )) 526 { 527 p = _Tables_cols; 528 n = 1; 529 } 530 else if (!wcscmp( name, szColumns )) 531 { 532 p = _Columns_cols; 533 n = 4; 534 } 535 else return ERROR_FUNCTION_FAILED; 536 537 for (i = 0; i < n; i++) 538 { 539 if (colinfo && i < *sz) colinfo[i] = p[i]; 540 if (colinfo && i >= *sz) break; 541 } 542 table_calc_column_offsets( db, colinfo, n ); 543 *sz = n; 544 return ERROR_SUCCESS; 545 } 546 547 static UINT get_tablecolumns( MSIDATABASE *db, LPCWSTR szTableName, MSICOLUMNINFO *colinfo, UINT *sz ); 548 549 static UINT table_get_column_info( MSIDATABASE *db, LPCWSTR name, MSICOLUMNINFO **pcols, UINT *pcount ) 550 { 551 UINT r, column_count = 0; 552 MSICOLUMNINFO *columns; 553 554 /* get the number of columns in this table */ 555 column_count = 0; 556 r = get_tablecolumns( db, name, NULL, &column_count ); 557 if (r != ERROR_SUCCESS) 558 return r; 559 560 *pcount = column_count; 561 562 /* if there are no columns, there's no table */ 563 if (!column_count) 564 return ERROR_INVALID_PARAMETER; 565 566 TRACE("table %s found\n", debugstr_w(name)); 567 568 columns = msi_alloc( column_count * sizeof(MSICOLUMNINFO) ); 569 if (!columns) 570 return ERROR_FUNCTION_FAILED; 571 572 r = get_tablecolumns( db, name, columns, &column_count ); 573 if (r != ERROR_SUCCESS) 574 { 575 msi_free( columns ); 576 return ERROR_FUNCTION_FAILED; 577 } 578 *pcols = columns; 579 return r; 580 } 581 582 static UINT get_table( MSIDATABASE *db, LPCWSTR name, MSITABLE **table_ret ) 583 { 584 MSITABLE *table; 585 UINT r; 586 587 /* first, see if the table is cached */ 588 table = find_cached_table( db, name ); 589 if (table) 590 { 591 *table_ret = table; 592 return ERROR_SUCCESS; 593 } 594 595 /* nonexistent tables should be interpreted as empty tables */ 596 table = msi_alloc( sizeof(MSITABLE) + lstrlenW( name ) * sizeof(WCHAR) ); 597 if (!table) 598 return ERROR_FUNCTION_FAILED; 599 600 table->row_count = 0; 601 table->data = NULL; 602 table->data_persistent = NULL; 603 table->colinfo = NULL; 604 table->col_count = 0; 605 table->persistent = MSICONDITION_TRUE; 606 lstrcpyW( table->name, name ); 607 608 if (!wcscmp( name, szTables ) || !wcscmp( name, szColumns )) 609 table->persistent = MSICONDITION_NONE; 610 611 r = table_get_column_info( db, name, &table->colinfo, &table->col_count ); 612 if (r != ERROR_SUCCESS) 613 { 614 free_table( table ); 615 return r; 616 } 617 r = read_table_from_storage( db, table, db->storage ); 618 if (r != ERROR_SUCCESS) 619 { 620 free_table( table ); 621 return r; 622 } 623 list_add_head( &db->tables, &table->entry ); 624 *table_ret = table; 625 return ERROR_SUCCESS; 626 } 627 628 static UINT read_table_int( BYTE *const *data, UINT row, UINT col, UINT bytes ) 629 { 630 UINT ret = 0, i; 631 632 for (i = 0; i < bytes; i++) 633 ret += data[row][col + i] << i * 8; 634 635 return ret; 636 } 637 638 static UINT get_tablecolumns( MSIDATABASE *db, LPCWSTR szTableName, MSICOLUMNINFO *colinfo, UINT *sz ) 639 { 640 UINT r, i, n = 0, table_id, count, maxcount = *sz; 641 MSITABLE *table = NULL; 642 643 TRACE("%s\n", debugstr_w(szTableName)); 644 645 /* first check if there is a default table with that name */ 646 r = get_defaulttablecolumns( db, szTableName, colinfo, sz ); 647 if (r == ERROR_SUCCESS && *sz) 648 return r; 649 650 r = get_table( db, szColumns, &table ); 651 if (r != ERROR_SUCCESS) 652 { 653 ERR("couldn't load _Columns table\n"); 654 return ERROR_FUNCTION_FAILED; 655 } 656 657 /* convert table and column names to IDs from the string table */ 658 r = msi_string2id( db->strings, szTableName, -1, &table_id ); 659 if (r != ERROR_SUCCESS) 660 { 661 WARN("Couldn't find id for %s\n", debugstr_w(szTableName)); 662 return r; 663 } 664 TRACE("Table id is %d, row count is %d\n", table_id, table->row_count); 665 666 /* Note: _Columns table doesn't have non-persistent data */ 667 668 /* if maxcount is non-zero, assume it's exactly right for this table */ 669 if (colinfo) memset( colinfo, 0, maxcount * sizeof(*colinfo) ); 670 count = table->row_count; 671 for (i = 0; i < count; i++) 672 { 673 if (read_table_int( table->data, i, 0, LONG_STR_BYTES) != table_id) continue; 674 if (colinfo) 675 { 676 UINT id = read_table_int( table->data, i, table->colinfo[2].offset, LONG_STR_BYTES ); 677 UINT col = read_table_int( table->data, i, table->colinfo[1].offset, sizeof(USHORT) ) - (1 << 15); 678 679 /* check the column number is in range */ 680 if (col < 1 || col > maxcount) 681 { 682 ERR("column %d out of range (maxcount: %d)\n", col, maxcount); 683 continue; 684 } 685 /* check if this column was already set */ 686 if (colinfo[col - 1].number) 687 { 688 ERR("duplicate column %d\n", col); 689 continue; 690 } 691 colinfo[col - 1].tablename = msi_string_lookup( db->strings, table_id, NULL ); 692 colinfo[col - 1].number = col; 693 colinfo[col - 1].colname = msi_string_lookup( db->strings, id, NULL ); 694 colinfo[col - 1].type = read_table_int( table->data, i, table->colinfo[3].offset, 695 sizeof(USHORT) ) - (1 << 15); 696 colinfo[col - 1].offset = 0; 697 colinfo[col - 1].ref_count = 0; 698 colinfo[col - 1].hash_table = NULL; 699 } 700 n++; 701 } 702 TRACE("%s has %d columns\n", debugstr_w(szTableName), n); 703 704 if (colinfo && n != maxcount) 705 { 706 ERR("missing column in table %s\n", debugstr_w(szTableName)); 707 msi_free_colinfo( colinfo, maxcount ); 708 return ERROR_FUNCTION_FAILED; 709 } 710 table_calc_column_offsets( db, colinfo, n ); 711 *sz = n; 712 return ERROR_SUCCESS; 713 } 714 715 UINT msi_create_table( MSIDATABASE *db, LPCWSTR name, column_info *col_info, 716 MSICONDITION persistent ) 717 { 718 UINT r, nField; 719 MSIVIEW *tv = NULL; 720 MSIRECORD *rec = NULL; 721 column_info *col; 722 MSITABLE *table; 723 UINT i; 724 725 /* only add tables that don't exist already */ 726 if( TABLE_Exists(db, name ) ) 727 { 728 WARN("table %s exists\n", debugstr_w(name)); 729 return ERROR_BAD_QUERY_SYNTAX; 730 } 731 732 table = msi_alloc( sizeof (MSITABLE) + lstrlenW(name)*sizeof (WCHAR) ); 733 if( !table ) 734 return ERROR_FUNCTION_FAILED; 735 736 table->ref_count = 1; 737 table->row_count = 0; 738 table->data = NULL; 739 table->data_persistent = NULL; 740 table->colinfo = NULL; 741 table->col_count = 0; 742 table->persistent = persistent; 743 lstrcpyW( table->name, name ); 744 745 for( col = col_info; col; col = col->next ) 746 table->col_count++; 747 748 table->colinfo = msi_alloc( table->col_count * sizeof(MSICOLUMNINFO) ); 749 if (!table->colinfo) 750 { 751 free_table( table ); 752 return ERROR_FUNCTION_FAILED; 753 } 754 755 for( i = 0, col = col_info; col; i++, col = col->next ) 756 { 757 UINT table_id = msi_add_string( db->strings, col->table, -1, persistent ); 758 UINT col_id = msi_add_string( db->strings, col->column, -1, persistent ); 759 760 table->colinfo[ i ].tablename = msi_string_lookup( db->strings, table_id, NULL ); 761 table->colinfo[ i ].number = i + 1; 762 table->colinfo[ i ].colname = msi_string_lookup( db->strings, col_id, NULL ); 763 table->colinfo[ i ].type = col->type; 764 table->colinfo[ i ].offset = 0; 765 table->colinfo[ i ].ref_count = 0; 766 table->colinfo[ i ].hash_table = NULL; 767 table->colinfo[ i ].temporary = col->temporary; 768 } 769 table_calc_column_offsets( db, table->colinfo, table->col_count); 770 771 r = TABLE_CreateView( db, szTables, &tv ); 772 TRACE("CreateView returned %x\n", r); 773 if( r ) 774 { 775 free_table( table ); 776 return r; 777 } 778 779 r = tv->ops->execute( tv, 0 ); 780 TRACE("tv execute returned %x\n", r); 781 if( r ) 782 goto err; 783 784 rec = MSI_CreateRecord( 1 ); 785 if( !rec ) 786 goto err; 787 788 r = MSI_RecordSetStringW( rec, 1, name ); 789 if( r ) 790 goto err; 791 792 r = tv->ops->insert_row( tv, rec, -1, persistent == MSICONDITION_FALSE ); 793 TRACE("insert_row returned %x\n", r); 794 if( r ) 795 goto err; 796 797 tv->ops->delete( tv ); 798 tv = NULL; 799 800 msiobj_release( &rec->hdr ); 801 rec = NULL; 802 803 if( persistent != MSICONDITION_FALSE ) 804 { 805 /* add each column to the _Columns table */ 806 r = TABLE_CreateView( db, szColumns, &tv ); 807 if( r ) 808 goto err; 809 810 r = tv->ops->execute( tv, 0 ); 811 TRACE("tv execute returned %x\n", r); 812 if( r ) 813 goto err; 814 815 rec = MSI_CreateRecord( 4 ); 816 if( !rec ) 817 goto err; 818 819 r = MSI_RecordSetStringW( rec, 1, name ); 820 if( r ) 821 goto err; 822 823 /* 824 * need to set the table, column number, col name and type 825 * for each column we enter in the table 826 */ 827 nField = 1; 828 for( col = col_info; col; col = col->next ) 829 { 830 r = MSI_RecordSetInteger( rec, 2, nField ); 831 if( r ) 832 goto err; 833 834 r = MSI_RecordSetStringW( rec, 3, col->column ); 835 if( r ) 836 goto err; 837 838 r = MSI_RecordSetInteger( rec, 4, col->type ); 839 if( r ) 840 goto err; 841 842 r = tv->ops->insert_row( tv, rec, -1, FALSE ); 843 if( r ) 844 goto err; 845 846 nField++; 847 } 848 if( !col ) 849 r = ERROR_SUCCESS; 850 } 851 852 err: 853 if (rec) 854 msiobj_release( &rec->hdr ); 855 /* FIXME: remove values from the string table on error */ 856 if( tv ) 857 tv->ops->delete( tv ); 858 859 if (r == ERROR_SUCCESS) 860 list_add_head( &db->tables, &table->entry ); 861 else 862 free_table( table ); 863 864 return r; 865 } 866 867 static UINT save_table( MSIDATABASE *db, const MSITABLE *t, UINT bytes_per_strref ) 868 { 869 BYTE *rawdata = NULL; 870 UINT rawsize, i, j, row_size, row_count; 871 UINT r = ERROR_FUNCTION_FAILED; 872 873 /* Nothing to do for non-persistent tables */ 874 if( t->persistent == MSICONDITION_FALSE ) 875 return ERROR_SUCCESS; 876 877 TRACE("Saving %s\n", debugstr_w( t->name ) ); 878 879 row_size = msi_table_get_row_size( db, t->colinfo, t->col_count, bytes_per_strref ); 880 row_count = t->row_count; 881 for (i = 0; i < t->row_count; i++) 882 { 883 if (!t->data_persistent[i]) 884 { 885 row_count = 1; /* yes, this is bizarre */ 886 break; 887 } 888 } 889 rawsize = row_count * row_size; 890 rawdata = msi_alloc_zero( rawsize ); 891 if( !rawdata ) 892 { 893 r = ERROR_NOT_ENOUGH_MEMORY; 894 goto err; 895 } 896 897 rawsize = 0; 898 for (i = 0; i < row_count; i++) 899 { 900 UINT ofs = 0, ofs_mem = 0; 901 902 if (!t->data_persistent[i]) break; 903 904 for (j = 0; j < t->col_count; j++) 905 { 906 UINT m = bytes_per_column( db, &t->colinfo[j], LONG_STR_BYTES ); 907 UINT n = bytes_per_column( db, &t->colinfo[j], bytes_per_strref ); 908 UINT k; 909 910 if (n != 2 && n != 3 && n != 4) 911 { 912 ERR("oops - unknown column width %d\n", n); 913 goto err; 914 } 915 if (t->colinfo[j].type & MSITYPE_STRING && n < m) 916 { 917 UINT id = read_table_int( t->data, i, ofs_mem, LONG_STR_BYTES ); 918 if (id > 1 << bytes_per_strref * 8) 919 { 920 ERR("string id %u out of range\n", id); 921 goto err; 922 } 923 } 924 for (k = 0; k < n; k++) 925 { 926 rawdata[ofs * row_count + i * n + k] = t->data[i][ofs_mem + k]; 927 } 928 ofs_mem += m; 929 ofs += n; 930 } 931 rawsize += row_size; 932 } 933 934 TRACE("writing %d bytes\n", rawsize); 935 r = write_stream_data( db->storage, t->name, rawdata, rawsize, TRUE ); 936 937 err: 938 msi_free( rawdata ); 939 return r; 940 } 941 942 static void msi_update_table_columns( MSIDATABASE *db, LPCWSTR name ) 943 { 944 MSITABLE *table; 945 UINT size, offset, old_count; 946 UINT n; 947 948 if (!(table = find_cached_table( db, name ))) return; 949 old_count = table->col_count; 950 msi_free_colinfo( table->colinfo, table->col_count ); 951 msi_free( table->colinfo ); 952 table->colinfo = NULL; 953 954 table_get_column_info( db, name, &table->colinfo, &table->col_count ); 955 if (!table->col_count) return; 956 957 size = msi_table_get_row_size( db, table->colinfo, table->col_count, LONG_STR_BYTES ); 958 offset = table->colinfo[table->col_count - 1].offset; 959 960 for ( n = 0; n < table->row_count; n++ ) 961 { 962 table->data[n] = msi_realloc( table->data[n], size ); 963 if (old_count < table->col_count) 964 memset( &table->data[n][offset], 0, size - offset ); 965 } 966 } 967 968 /* try to find the table name in the _Tables table */ 969 BOOL TABLE_Exists( MSIDATABASE *db, LPCWSTR name ) 970 { 971 UINT r, table_id, i; 972 MSITABLE *table; 973 974 if( !wcscmp( name, szTables ) || !wcscmp( name, szColumns ) || 975 !wcscmp( name, szStreams ) || !wcscmp( name, szStorages ) ) 976 return TRUE; 977 978 r = msi_string2id( db->strings, name, -1, &table_id ); 979 if( r != ERROR_SUCCESS ) 980 { 981 TRACE("Couldn't find id for %s\n", debugstr_w(name)); 982 return FALSE; 983 } 984 985 r = get_table( db, szTables, &table ); 986 if( r != ERROR_SUCCESS ) 987 { 988 ERR("table %s not available\n", debugstr_w(szTables)); 989 return FALSE; 990 } 991 992 for( i = 0; i < table->row_count; i++ ) 993 { 994 if( read_table_int( table->data, i, 0, LONG_STR_BYTES ) == table_id ) 995 return TRUE; 996 } 997 998 return FALSE; 999 } 1000 1001 /* below is the query interface to a table */ 1002 1003 typedef struct tagMSITABLEVIEW 1004 { 1005 MSIVIEW view; 1006 MSIDATABASE *db; 1007 MSITABLE *table; 1008 MSICOLUMNINFO *columns; 1009 UINT num_cols; 1010 UINT row_size; 1011 WCHAR name[1]; 1012 } MSITABLEVIEW; 1013 1014 static UINT TABLE_fetch_int( struct tagMSIVIEW *view, UINT row, UINT col, UINT *val ) 1015 { 1016 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1017 UINT offset, n; 1018 1019 if( !tv->table ) 1020 return ERROR_INVALID_PARAMETER; 1021 1022 if( (col==0) || (col>tv->num_cols) ) 1023 return ERROR_INVALID_PARAMETER; 1024 1025 /* how many rows are there ? */ 1026 if( row >= tv->table->row_count ) 1027 return ERROR_NO_MORE_ITEMS; 1028 1029 if( tv->columns[col-1].offset >= tv->row_size ) 1030 { 1031 ERR("Stuffed up %d >= %d\n", tv->columns[col-1].offset, tv->row_size ); 1032 ERR("%p %p\n", tv, tv->columns ); 1033 return ERROR_FUNCTION_FAILED; 1034 } 1035 1036 n = bytes_per_column( tv->db, &tv->columns[col - 1], LONG_STR_BYTES ); 1037 if (n != 2 && n != 3 && n != 4) 1038 { 1039 ERR("oops! what is %d bytes per column?\n", n ); 1040 return ERROR_FUNCTION_FAILED; 1041 } 1042 1043 offset = tv->columns[col-1].offset; 1044 *val = read_table_int(tv->table->data, row, offset, n); 1045 1046 /* TRACE("Data [%d][%d] = %d\n", row, col, *val ); */ 1047 1048 return ERROR_SUCCESS; 1049 } 1050 1051 static UINT get_stream_name( const MSITABLEVIEW *tv, UINT row, WCHAR **pstname ) 1052 { 1053 LPWSTR p, stname = NULL; 1054 UINT i, r, type, ival; 1055 DWORD len; 1056 LPCWSTR sval; 1057 MSIVIEW *view = (MSIVIEW *) tv; 1058 1059 TRACE("%p %d\n", tv, row); 1060 1061 len = lstrlenW( tv->name ) + 1; 1062 stname = msi_alloc( len*sizeof(WCHAR) ); 1063 if ( !stname ) 1064 { 1065 r = ERROR_OUTOFMEMORY; 1066 goto err; 1067 } 1068 1069 lstrcpyW( stname, tv->name ); 1070 1071 for ( i = 0; i < tv->num_cols; i++ ) 1072 { 1073 type = tv->columns[i].type; 1074 if ( type & MSITYPE_KEY ) 1075 { 1076 WCHAR number[0x20]; 1077 1078 r = TABLE_fetch_int( view, row, i+1, &ival ); 1079 if ( r != ERROR_SUCCESS ) 1080 goto err; 1081 1082 if ( tv->columns[i].type & MSITYPE_STRING ) 1083 { 1084 sval = msi_string_lookup( tv->db->strings, ival, NULL ); 1085 if ( !sval ) 1086 { 1087 r = ERROR_INVALID_PARAMETER; 1088 goto err; 1089 } 1090 } 1091 else 1092 { 1093 static const WCHAR fmt[] = { '%','d',0 }; 1094 UINT n = bytes_per_column( tv->db, &tv->columns[i], LONG_STR_BYTES ); 1095 1096 switch( n ) 1097 { 1098 case 2: 1099 swprintf( number, ARRAY_SIZE(number), fmt, ival-0x8000 ); 1100 break; 1101 case 4: 1102 swprintf( number, ARRAY_SIZE(number), fmt, ival^0x80000000 ); 1103 break; 1104 default: 1105 ERR( "oops - unknown column width %d\n", n ); 1106 r = ERROR_FUNCTION_FAILED; 1107 goto err; 1108 } 1109 sval = number; 1110 } 1111 1112 len += lstrlenW( szDot ) + lstrlenW( sval ); 1113 p = msi_realloc ( stname, len*sizeof(WCHAR) ); 1114 if ( !p ) 1115 { 1116 r = ERROR_OUTOFMEMORY; 1117 goto err; 1118 } 1119 stname = p; 1120 1121 lstrcatW( stname, szDot ); 1122 lstrcatW( stname, sval ); 1123 } 1124 else 1125 continue; 1126 } 1127 1128 *pstname = stname; 1129 return ERROR_SUCCESS; 1130 1131 err: 1132 msi_free( stname ); 1133 *pstname = NULL; 1134 return r; 1135 } 1136 1137 /* 1138 * We need a special case for streams, as we need to reference column with 1139 * the name of the stream in the same table, and the table name 1140 * which may not be available at higher levels of the query 1141 */ 1142 static UINT TABLE_fetch_stream( struct tagMSIVIEW *view, UINT row, UINT col, IStream **stm ) 1143 { 1144 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1145 UINT r; 1146 WCHAR *name; 1147 1148 if( !view->ops->fetch_int ) 1149 return ERROR_INVALID_PARAMETER; 1150 1151 r = get_stream_name( tv, row, &name ); 1152 if (r != ERROR_SUCCESS) 1153 { 1154 ERR("fetching stream, error = %u\n", r); 1155 return r; 1156 } 1157 1158 r = msi_get_stream( tv->db, name, stm ); 1159 if (r != ERROR_SUCCESS) 1160 ERR("fetching stream %s, error = %u\n", debugstr_w(name), r); 1161 1162 msi_free( name ); 1163 return r; 1164 } 1165 1166 /* Set a table value, i.e. preadjusted integer or string ID. */ 1167 static UINT table_set_bytes( MSITABLEVIEW *tv, UINT row, UINT col, UINT val ) 1168 { 1169 UINT offset, n, i; 1170 1171 if( !tv->table ) 1172 return ERROR_INVALID_PARAMETER; 1173 1174 if( (col==0) || (col>tv->num_cols) ) 1175 return ERROR_INVALID_PARAMETER; 1176 1177 if( row >= tv->table->row_count ) 1178 return ERROR_INVALID_PARAMETER; 1179 1180 if( tv->columns[col-1].offset >= tv->row_size ) 1181 { 1182 ERR("Stuffed up %d >= %d\n", tv->columns[col-1].offset, tv->row_size ); 1183 ERR("%p %p\n", tv, tv->columns ); 1184 return ERROR_FUNCTION_FAILED; 1185 } 1186 1187 msi_free( tv->columns[col-1].hash_table ); 1188 tv->columns[col-1].hash_table = NULL; 1189 1190 n = bytes_per_column( tv->db, &tv->columns[col - 1], LONG_STR_BYTES ); 1191 if ( n != 2 && n != 3 && n != 4 ) 1192 { 1193 ERR("oops! what is %d bytes per column?\n", n ); 1194 return ERROR_FUNCTION_FAILED; 1195 } 1196 1197 offset = tv->columns[col-1].offset; 1198 for ( i = 0; i < n; i++ ) 1199 tv->table->data[row][offset + i] = (val >> i * 8) & 0xff; 1200 1201 return ERROR_SUCCESS; 1202 } 1203 1204 static UINT int_to_table_storage( const MSITABLEVIEW *tv, UINT col, int val, UINT *ret ) 1205 { 1206 if ((tv->columns[col-1].type & MSI_DATASIZEMASK) == 2) 1207 { 1208 if (val == MSI_NULL_INTEGER) 1209 *ret = 0; 1210 else if ((val + 0x8000) & 0xffff0000) 1211 { 1212 ERR("value %d out of range\n", val); 1213 return ERROR_FUNCTION_FAILED; 1214 } 1215 else 1216 *ret = val + 0x8000; 1217 } 1218 else 1219 *ret = val ^ 0x80000000; 1220 1221 return ERROR_SUCCESS; 1222 } 1223 1224 static UINT TABLE_set_int( MSIVIEW *view, UINT row, UINT col, int val ) 1225 { 1226 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1227 UINT r, table_int; 1228 1229 TRACE("row %u, col %u, val %d.\n", row, col, val); 1230 1231 if ((r = int_to_table_storage( tv, col, val, &table_int ))) 1232 return r; 1233 1234 if (tv->columns[col-1].type & MSITYPE_KEY) 1235 { 1236 UINT key; 1237 1238 if ((r = TABLE_fetch_int( view, row, col, &key ))) 1239 return r; 1240 if (key != table_int) 1241 { 1242 ERR("Cannot modify primary key %s.%s.\n", 1243 debugstr_w(tv->table->name), debugstr_w(tv->columns[col-1].colname)); 1244 return ERROR_FUNCTION_FAILED; 1245 } 1246 } 1247 1248 return table_set_bytes( tv, row, col, table_int ); 1249 } 1250 1251 static UINT TABLE_set_string( MSIVIEW *view, UINT row, UINT col, const WCHAR *val, int len ) 1252 { 1253 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1254 BOOL persistent; 1255 UINT id, r; 1256 1257 TRACE("row %u, col %u, val %s.\n", row, col, debugstr_wn(val, len)); 1258 1259 persistent = (tv->table->persistent != MSICONDITION_FALSE) 1260 && tv->table->data_persistent[row]; 1261 1262 if (val) 1263 { 1264 r = msi_string2id( tv->db->strings, val, len, &id ); 1265 if (r != ERROR_SUCCESS) 1266 id = msi_add_string( tv->db->strings, val, len, persistent ); 1267 } 1268 else 1269 id = 0; 1270 1271 if (tv->columns[col-1].type & MSITYPE_KEY) 1272 { 1273 UINT key; 1274 1275 if ((r = TABLE_fetch_int( view, row, col, &key ))) 1276 return r; 1277 if (key != id) 1278 { 1279 ERR("Cannot modify primary key %s.%s.\n", 1280 debugstr_w(tv->table->name), debugstr_w(tv->columns[col-1].colname)); 1281 return ERROR_FUNCTION_FAILED; 1282 } 1283 } 1284 1285 return table_set_bytes( tv, row, col, id ); 1286 } 1287 1288 static UINT TABLE_get_row( struct tagMSIVIEW *view, UINT row, MSIRECORD **rec ) 1289 { 1290 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1291 1292 if (!tv->table) 1293 return ERROR_INVALID_PARAMETER; 1294 1295 return msi_view_get_row(tv->db, view, row, rec); 1296 } 1297 1298 static UINT add_stream( MSIDATABASE *db, const WCHAR *name, IStream *data ) 1299 { 1300 static const WCHAR insert[] = { 1301 'I','N','S','E','R','T',' ','I','N','T','O',' ', 1302 '`','_','S','t','r','e','a','m','s','`',' ', 1303 '(','`','N','a','m','e','`',',','`','D','a','t','a','`',')',' ', 1304 'V','A','L','U','E','S',' ','(','?',',','?',')',0}; 1305 static const WCHAR update[] = { 1306 'U','P','D','A','T','E',' ','`','_','S','t','r','e','a','m','s','`',' ', 1307 'S','E','T',' ','`','D','a','t','a','`',' ','=',' ','?',' ', 1308 'W','H','E','R','E',' ','`','N','a','m','e','`',' ','=',' ','?',0}; 1309 MSIQUERY *query; 1310 MSIRECORD *rec; 1311 UINT r; 1312 1313 TRACE("%p %s %p\n", db, debugstr_w(name), data); 1314 1315 if (!(rec = MSI_CreateRecord( 2 ))) 1316 return ERROR_OUTOFMEMORY; 1317 1318 r = MSI_RecordSetStringW( rec, 1, name ); 1319 if (r != ERROR_SUCCESS) 1320 goto done; 1321 1322 r = MSI_RecordSetIStream( rec, 2, data ); 1323 if (r != ERROR_SUCCESS) 1324 goto done; 1325 1326 r = MSI_DatabaseOpenViewW( db, insert, &query ); 1327 if (r != ERROR_SUCCESS) 1328 goto done; 1329 1330 r = MSI_ViewExecute( query, rec ); 1331 msiobj_release( &query->hdr ); 1332 if (r == ERROR_SUCCESS) 1333 goto done; 1334 1335 msiobj_release( &rec->hdr ); 1336 if (!(rec = MSI_CreateRecord( 2 ))) 1337 return ERROR_OUTOFMEMORY; 1338 1339 r = MSI_RecordSetIStream( rec, 1, data ); 1340 if (r != ERROR_SUCCESS) 1341 goto done; 1342 1343 r = MSI_RecordSetStringW( rec, 2, name ); 1344 if (r != ERROR_SUCCESS) 1345 goto done; 1346 1347 r = MSI_DatabaseOpenViewW( db, update, &query ); 1348 if (r != ERROR_SUCCESS) 1349 goto done; 1350 1351 r = MSI_ViewExecute( query, rec ); 1352 msiobj_release( &query->hdr ); 1353 1354 done: 1355 msiobj_release( &rec->hdr ); 1356 return r; 1357 } 1358 1359 static UINT TABLE_set_stream( MSIVIEW *view, UINT row, UINT col, IStream *stream ) 1360 { 1361 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1362 WCHAR *name; 1363 UINT r; 1364 1365 TRACE("row %u, col %u, stream %p.\n", row, col, stream); 1366 1367 if ((r = get_stream_name( tv, row - 1, &name ))) 1368 return r; 1369 1370 r = add_stream( tv->db, name, stream ); 1371 msi_free( name ); 1372 return r; 1373 } 1374 1375 static UINT get_table_value_from_record( MSITABLEVIEW *tv, MSIRECORD *rec, UINT iField, UINT *pvalue ) 1376 { 1377 MSICOLUMNINFO columninfo; 1378 UINT r; 1379 1380 if (!iField || iField > tv->num_cols || MSI_RecordIsNull( rec, iField )) 1381 return ERROR_FUNCTION_FAILED; 1382 1383 columninfo = tv->columns[ iField - 1 ]; 1384 1385 if ( MSITYPE_IS_BINARY(columninfo.type) ) 1386 { 1387 *pvalue = 1; /* refers to the first key column */ 1388 } 1389 else if ( columninfo.type & MSITYPE_STRING ) 1390 { 1391 int len; 1392 const WCHAR *sval = msi_record_get_string( rec, iField, &len ); 1393 if (sval) 1394 { 1395 r = msi_string2id( tv->db->strings, sval, len, pvalue ); 1396 if (r != ERROR_SUCCESS) 1397 return ERROR_NOT_FOUND; 1398 } 1399 else *pvalue = 0; 1400 } 1401 else 1402 return int_to_table_storage( tv, iField, MSI_RecordGetInteger( rec, iField ), pvalue ); 1403 1404 return ERROR_SUCCESS; 1405 } 1406 1407 static UINT TABLE_set_row( struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask ) 1408 { 1409 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1410 UINT i, val, r = ERROR_SUCCESS; 1411 1412 if ( !tv->table ) 1413 return ERROR_INVALID_PARAMETER; 1414 1415 /* test if any of the mask bits are invalid */ 1416 if ( mask >= (1<<tv->num_cols) ) 1417 return ERROR_INVALID_PARAMETER; 1418 1419 for ( i = 0; i < tv->num_cols; i++ ) 1420 { 1421 BOOL persistent; 1422 1423 /* only update the fields specified in the mask */ 1424 if ( !(mask&(1<<i)) ) 1425 continue; 1426 1427 persistent = (tv->table->persistent != MSICONDITION_FALSE) && 1428 (tv->table->data_persistent[row]); 1429 /* FIXME: should we allow updating keys? */ 1430 1431 val = 0; 1432 if ( !MSI_RecordIsNull( rec, i + 1 ) ) 1433 { 1434 r = get_table_value_from_record (tv, rec, i + 1, &val); 1435 if ( MSITYPE_IS_BINARY(tv->columns[ i ].type) ) 1436 { 1437 IStream *stm; 1438 LPWSTR stname; 1439 1440 if ( r != ERROR_SUCCESS ) 1441 return ERROR_FUNCTION_FAILED; 1442 1443 r = MSI_RecordGetIStream( rec, i + 1, &stm ); 1444 if ( r != ERROR_SUCCESS ) 1445 return r; 1446 1447 r = get_stream_name( tv, row, &stname ); 1448 if ( r != ERROR_SUCCESS ) 1449 { 1450 IStream_Release( stm ); 1451 return r; 1452 } 1453 1454 r = add_stream( tv->db, stname, stm ); 1455 IStream_Release( stm ); 1456 msi_free ( stname ); 1457 1458 if ( r != ERROR_SUCCESS ) 1459 return r; 1460 } 1461 else if ( tv->columns[i].type & MSITYPE_STRING ) 1462 { 1463 UINT x; 1464 1465 if ( r != ERROR_SUCCESS ) 1466 { 1467 int len; 1468 const WCHAR *sval = msi_record_get_string( rec, i + 1, &len ); 1469 val = msi_add_string( tv->db->strings, sval, len, persistent ); 1470 } 1471 else 1472 { 1473 TABLE_fetch_int(&tv->view, row, i + 1, &x); 1474 if (val == x) 1475 continue; 1476 } 1477 } 1478 else 1479 { 1480 if ( r != ERROR_SUCCESS ) 1481 return ERROR_FUNCTION_FAILED; 1482 } 1483 } 1484 1485 r = table_set_bytes( tv, row, i+1, val ); 1486 if ( r != ERROR_SUCCESS ) 1487 break; 1488 } 1489 return r; 1490 } 1491 1492 static UINT table_create_new_row( struct tagMSIVIEW *view, UINT *num, BOOL temporary ) 1493 { 1494 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1495 BYTE **p, *row; 1496 BOOL *b; 1497 UINT sz; 1498 BYTE ***data_ptr; 1499 BOOL **data_persist_ptr; 1500 UINT *row_count; 1501 1502 TRACE("%p %s\n", view, temporary ? "TRUE" : "FALSE"); 1503 1504 if( !tv->table ) 1505 return ERROR_INVALID_PARAMETER; 1506 1507 row = msi_alloc_zero( tv->row_size ); 1508 if( !row ) 1509 return ERROR_NOT_ENOUGH_MEMORY; 1510 1511 row_count = &tv->table->row_count; 1512 data_ptr = &tv->table->data; 1513 data_persist_ptr = &tv->table->data_persistent; 1514 if (*num == -1) 1515 *num = tv->table->row_count; 1516 1517 sz = (*row_count + 1) * sizeof (BYTE*); 1518 if( *data_ptr ) 1519 p = msi_realloc( *data_ptr, sz ); 1520 else 1521 p = msi_alloc( sz ); 1522 if( !p ) 1523 { 1524 msi_free( row ); 1525 return ERROR_NOT_ENOUGH_MEMORY; 1526 } 1527 1528 sz = (*row_count + 1) * sizeof (BOOL); 1529 if( *data_persist_ptr ) 1530 b = msi_realloc( *data_persist_ptr, sz ); 1531 else 1532 b = msi_alloc( sz ); 1533 if( !b ) 1534 { 1535 msi_free( row ); 1536 msi_free( p ); 1537 return ERROR_NOT_ENOUGH_MEMORY; 1538 } 1539 1540 *data_ptr = p; 1541 (*data_ptr)[*row_count] = row; 1542 1543 *data_persist_ptr = b; 1544 (*data_persist_ptr)[*row_count] = !temporary; 1545 1546 (*row_count)++; 1547 1548 return ERROR_SUCCESS; 1549 } 1550 1551 static UINT TABLE_execute( struct tagMSIVIEW *view, MSIRECORD *record ) 1552 { 1553 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1554 1555 TRACE("%p %p\n", tv, record); 1556 1557 TRACE("There are %d columns\n", tv->num_cols ); 1558 1559 return ERROR_SUCCESS; 1560 } 1561 1562 static UINT TABLE_close( struct tagMSIVIEW *view ) 1563 { 1564 TRACE("%p\n", view ); 1565 1566 return ERROR_SUCCESS; 1567 } 1568 1569 static UINT TABLE_get_dimensions( struct tagMSIVIEW *view, UINT *rows, UINT *cols) 1570 { 1571 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1572 1573 TRACE("%p %p %p\n", view, rows, cols ); 1574 1575 if( cols ) 1576 *cols = tv->num_cols; 1577 if( rows ) 1578 { 1579 if( !tv->table ) 1580 return ERROR_INVALID_PARAMETER; 1581 *rows = tv->table->row_count; 1582 } 1583 1584 return ERROR_SUCCESS; 1585 } 1586 1587 static UINT TABLE_get_column_info( struct tagMSIVIEW *view, 1588 UINT n, LPCWSTR *name, UINT *type, BOOL *temporary, 1589 LPCWSTR *table_name ) 1590 { 1591 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1592 1593 TRACE("%p %d %p %p\n", tv, n, name, type ); 1594 1595 if( ( n == 0 ) || ( n > tv->num_cols ) ) 1596 return ERROR_INVALID_PARAMETER; 1597 1598 if( name ) 1599 { 1600 *name = tv->columns[n-1].colname; 1601 if( !*name ) 1602 return ERROR_FUNCTION_FAILED; 1603 } 1604 1605 if( table_name ) 1606 { 1607 *table_name = tv->columns[n-1].tablename; 1608 if( !*table_name ) 1609 return ERROR_FUNCTION_FAILED; 1610 } 1611 1612 if( type ) 1613 *type = tv->columns[n-1].type; 1614 1615 if( temporary ) 1616 *temporary = tv->columns[n-1].temporary; 1617 1618 return ERROR_SUCCESS; 1619 } 1620 1621 static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row, UINT *column ); 1622 1623 static UINT table_validate_new( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *column ) 1624 { 1625 UINT r, row, i; 1626 1627 /* check there are no null values where they're not allowed */ 1628 for( i = 0; i < tv->num_cols; i++ ) 1629 { 1630 if ( tv->columns[i].type & MSITYPE_NULLABLE ) 1631 continue; 1632 1633 if ( MSITYPE_IS_BINARY(tv->columns[i].type) ) 1634 TRACE("skipping binary column\n"); 1635 else if ( tv->columns[i].type & MSITYPE_STRING ) 1636 { 1637 int len; 1638 const WCHAR *str = msi_record_get_string( rec, i+1, &len ); 1639 1640 if (!str || (!str[0] && !len)) 1641 { 1642 if (column) *column = i; 1643 return ERROR_INVALID_DATA; 1644 } 1645 } 1646 else 1647 { 1648 UINT n; 1649 1650 n = MSI_RecordGetInteger( rec, i+1 ); 1651 if (n == MSI_NULL_INTEGER) 1652 { 1653 if (column) *column = i; 1654 return ERROR_INVALID_DATA; 1655 } 1656 } 1657 } 1658 1659 /* check there are no duplicate keys */ 1660 r = msi_table_find_row( tv, rec, &row, column ); 1661 if (r == ERROR_SUCCESS) 1662 return ERROR_FUNCTION_FAILED; 1663 1664 return ERROR_SUCCESS; 1665 } 1666 1667 static int compare_record( MSITABLEVIEW *tv, UINT row, MSIRECORD *rec ) 1668 { 1669 UINT r, i, ivalue, x; 1670 1671 for (i = 0; i < tv->num_cols; i++ ) 1672 { 1673 if (!(tv->columns[i].type & MSITYPE_KEY)) continue; 1674 1675 r = get_table_value_from_record( tv, rec, i + 1, &ivalue ); 1676 if (r != ERROR_SUCCESS) 1677 return 1; 1678 1679 r = TABLE_fetch_int( &tv->view, row, i + 1, &x ); 1680 if (r != ERROR_SUCCESS) 1681 { 1682 WARN("TABLE_fetch_int should not fail here %u\n", r); 1683 return -1; 1684 } 1685 if (ivalue > x) 1686 { 1687 return 1; 1688 } 1689 else if (ivalue == x) 1690 { 1691 if (i < tv->num_cols - 1) continue; 1692 return 0; 1693 } 1694 else 1695 return -1; 1696 } 1697 return 1; 1698 } 1699 1700 static int find_insert_index( MSITABLEVIEW *tv, MSIRECORD *rec ) 1701 { 1702 int idx, c, low = 0, high = tv->table->row_count - 1; 1703 1704 TRACE("%p %p\n", tv, rec); 1705 1706 while (low <= high) 1707 { 1708 idx = (low + high) / 2; 1709 c = compare_record( tv, idx, rec ); 1710 1711 if (c < 0) 1712 high = idx - 1; 1713 else if (c > 0) 1714 low = idx + 1; 1715 else 1716 { 1717 TRACE("found %u\n", idx); 1718 return idx; 1719 } 1720 } 1721 TRACE("found %u\n", high + 1); 1722 return high + 1; 1723 } 1724 1725 static UINT TABLE_insert_row( struct tagMSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary ) 1726 { 1727 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1728 UINT i, r; 1729 1730 TRACE("%p %p %s\n", tv, rec, temporary ? "TRUE" : "FALSE" ); 1731 1732 /* check that the key is unique - can we find a matching row? */ 1733 r = table_validate_new( tv, rec, NULL ); 1734 if( r != ERROR_SUCCESS ) 1735 return ERROR_FUNCTION_FAILED; 1736 1737 if (row == -1) 1738 row = find_insert_index( tv, rec ); 1739 1740 r = table_create_new_row( view, &row, temporary ); 1741 TRACE("insert_row returned %08x\n", r); 1742 if( r != ERROR_SUCCESS ) 1743 return r; 1744 1745 /* shift the rows to make room for the new row */ 1746 for (i = tv->table->row_count - 1; i > row; i--) 1747 { 1748 memmove(&(tv->table->data[i][0]), 1749 &(tv->table->data[i - 1][0]), tv->row_size); 1750 tv->table->data_persistent[i] = tv->table->data_persistent[i - 1]; 1751 } 1752 1753 /* Re-set the persistence flag */ 1754 tv->table->data_persistent[row] = !temporary; 1755 return TABLE_set_row( view, row, rec, (1<<tv->num_cols) - 1 ); 1756 } 1757 1758 static UINT TABLE_delete_row( struct tagMSIVIEW *view, UINT row ) 1759 { 1760 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1761 UINT r, num_rows, num_cols, i; 1762 1763 TRACE("%p %d\n", tv, row); 1764 1765 if ( !tv->table ) 1766 return ERROR_INVALID_PARAMETER; 1767 1768 r = TABLE_get_dimensions( view, &num_rows, &num_cols ); 1769 if ( r != ERROR_SUCCESS ) 1770 return r; 1771 1772 if ( row >= num_rows ) 1773 return ERROR_FUNCTION_FAILED; 1774 1775 num_rows = tv->table->row_count; 1776 tv->table->row_count--; 1777 1778 /* reset the hash tables */ 1779 for (i = 0; i < tv->num_cols; i++) 1780 { 1781 msi_free( tv->columns[i].hash_table ); 1782 tv->columns[i].hash_table = NULL; 1783 } 1784 1785 for (i = row + 1; i < num_rows; i++) 1786 { 1787 memcpy(tv->table->data[i - 1], tv->table->data[i], tv->row_size); 1788 tv->table->data_persistent[i - 1] = tv->table->data_persistent[i]; 1789 } 1790 1791 msi_free(tv->table->data[num_rows - 1]); 1792 1793 return ERROR_SUCCESS; 1794 } 1795 1796 static UINT msi_table_update(struct tagMSIVIEW *view, MSIRECORD *rec, UINT row) 1797 { 1798 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1799 UINT r, new_row; 1800 1801 /* FIXME: MsiViewFetch should set rec index 0 to some ID that 1802 * sets the fetched record apart from other records 1803 */ 1804 1805 if (!tv->table) 1806 return ERROR_INVALID_PARAMETER; 1807 1808 r = msi_table_find_row(tv, rec, &new_row, NULL); 1809 if (r != ERROR_SUCCESS) 1810 { 1811 ERR("can't find row to modify\n"); 1812 return ERROR_FUNCTION_FAILED; 1813 } 1814 1815 /* the row cannot be changed */ 1816 if (row != new_row) 1817 return ERROR_FUNCTION_FAILED; 1818 1819 return TABLE_set_row(view, new_row, rec, (1 << tv->num_cols) - 1); 1820 } 1821 1822 static UINT msi_table_assign(struct tagMSIVIEW *view, MSIRECORD *rec) 1823 { 1824 MSITABLEVIEW *tv = (MSITABLEVIEW *)view; 1825 UINT r, row; 1826 1827 if (!tv->table) 1828 return ERROR_INVALID_PARAMETER; 1829 1830 r = msi_table_find_row(tv, rec, &row, NULL); 1831 if (r == ERROR_SUCCESS) 1832 return TABLE_set_row(view, row, rec, (1 << tv->num_cols) - 1); 1833 else 1834 return TABLE_insert_row( view, rec, -1, FALSE ); 1835 } 1836 1837 static UINT msi_refresh_record( struct tagMSIVIEW *view, MSIRECORD *rec, UINT row ) 1838 { 1839 MSIRECORD *curr; 1840 UINT r, i, count; 1841 1842 r = TABLE_get_row(view, row, &curr); 1843 if (r != ERROR_SUCCESS) 1844 return r; 1845 1846 /* Close the original record */ 1847 MSI_CloseRecord(&rec->hdr); 1848 1849 count = MSI_RecordGetFieldCount(rec); 1850 for (i = 0; i < count; i++) 1851 MSI_RecordCopyField(curr, i + 1, rec, i + 1); 1852 1853 msiobj_release(&curr->hdr); 1854 return ERROR_SUCCESS; 1855 } 1856 1857 static UINT TABLE_modify( struct tagMSIVIEW *view, MSIMODIFY eModifyMode, 1858 MSIRECORD *rec, UINT row) 1859 { 1860 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1861 UINT r, frow, column; 1862 1863 TRACE("%p %d %p\n", view, eModifyMode, rec ); 1864 1865 switch (eModifyMode) 1866 { 1867 case MSIMODIFY_DELETE: 1868 r = TABLE_delete_row( view, row ); 1869 break; 1870 case MSIMODIFY_VALIDATE_NEW: 1871 r = table_validate_new( tv, rec, &column ); 1872 if (r != ERROR_SUCCESS) 1873 { 1874 tv->view.error = MSIDBERROR_DUPLICATEKEY; 1875 tv->view.error_column = tv->columns[column].colname; 1876 r = ERROR_INVALID_DATA; 1877 } 1878 break; 1879 1880 case MSIMODIFY_INSERT: 1881 r = table_validate_new( tv, rec, NULL ); 1882 if (r != ERROR_SUCCESS) 1883 break; 1884 r = TABLE_insert_row( view, rec, -1, FALSE ); 1885 break; 1886 1887 case MSIMODIFY_INSERT_TEMPORARY: 1888 r = table_validate_new( tv, rec, NULL ); 1889 if (r != ERROR_SUCCESS) 1890 break; 1891 r = TABLE_insert_row( view, rec, -1, TRUE ); 1892 break; 1893 1894 case MSIMODIFY_REFRESH: 1895 r = msi_refresh_record( view, rec, row ); 1896 break; 1897 1898 case MSIMODIFY_UPDATE: 1899 r = msi_table_update( view, rec, row ); 1900 break; 1901 1902 case MSIMODIFY_ASSIGN: 1903 r = msi_table_assign( view, rec ); 1904 break; 1905 1906 case MSIMODIFY_MERGE: 1907 /* check row that matches this record */ 1908 r = msi_table_find_row( tv, rec, &frow, &column ); 1909 if (r != ERROR_SUCCESS) 1910 { 1911 r = table_validate_new( tv, rec, NULL ); 1912 if (r == ERROR_SUCCESS) 1913 r = TABLE_insert_row( view, rec, -1, FALSE ); 1914 } 1915 break; 1916 1917 case MSIMODIFY_REPLACE: 1918 case MSIMODIFY_VALIDATE: 1919 case MSIMODIFY_VALIDATE_FIELD: 1920 case MSIMODIFY_VALIDATE_DELETE: 1921 FIXME("%p %d %p - mode not implemented\n", view, eModifyMode, rec ); 1922 r = ERROR_CALL_NOT_IMPLEMENTED; 1923 break; 1924 1925 default: 1926 r = ERROR_INVALID_DATA; 1927 } 1928 1929 return r; 1930 } 1931 1932 static UINT TABLE_delete( struct tagMSIVIEW *view ) 1933 { 1934 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1935 1936 TRACE("%p\n", view ); 1937 1938 tv->table = NULL; 1939 tv->columns = NULL; 1940 1941 msi_free( tv ); 1942 1943 return ERROR_SUCCESS; 1944 } 1945 1946 static UINT TABLE_add_ref(struct tagMSIVIEW *view) 1947 { 1948 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1949 UINT i; 1950 1951 TRACE("%p %d\n", view, tv->table->ref_count); 1952 1953 for (i = 0; i < tv->table->col_count; i++) 1954 { 1955 if (tv->table->colinfo[i].type & MSITYPE_TEMPORARY) 1956 InterlockedIncrement(&tv->table->colinfo[i].ref_count); 1957 } 1958 1959 return InterlockedIncrement(&tv->table->ref_count); 1960 } 1961 1962 static UINT TABLE_remove_column(struct tagMSIVIEW *view, LPCWSTR table, UINT number) 1963 { 1964 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 1965 MSIRECORD *rec = NULL; 1966 MSIVIEW *columns = NULL; 1967 UINT row, r; 1968 1969 if (tv->table->col_count != number) 1970 return ERROR_BAD_QUERY_SYNTAX; 1971 1972 if (tv->table->colinfo[number-1].temporary) 1973 { 1974 UINT size = tv->table->colinfo[number-1].offset; 1975 tv->table->col_count--; 1976 tv->table->colinfo = msi_realloc( tv->table->colinfo, sizeof(*tv->table->colinfo) * tv->table->col_count ); 1977 1978 for (row = 0; row < tv->table->row_count; row++) 1979 tv->table->data[row] = msi_realloc( tv->table->data[row], size ); 1980 return ERROR_SUCCESS; 1981 } 1982 1983 rec = MSI_CreateRecord(2); 1984 if (!rec) 1985 return ERROR_OUTOFMEMORY; 1986 1987 MSI_RecordSetStringW(rec, 1, table); 1988 MSI_RecordSetInteger(rec, 2, number); 1989 1990 r = TABLE_CreateView(tv->db, szColumns, &columns); 1991 if (r != ERROR_SUCCESS) 1992 { 1993 msiobj_release(&rec->hdr); 1994 return r; 1995 } 1996 1997 r = msi_table_find_row((MSITABLEVIEW *)columns, rec, &row, NULL); 1998 if (r != ERROR_SUCCESS) 1999 goto done; 2000 2001 r = TABLE_delete_row(columns, row); 2002 if (r != ERROR_SUCCESS) 2003 goto done; 2004 2005 msi_update_table_columns(tv->db, table); 2006 2007 done: 2008 msiobj_release(&rec->hdr); 2009 columns->ops->delete(columns); 2010 return r; 2011 } 2012 2013 static UINT TABLE_release(struct tagMSIVIEW *view) 2014 { 2015 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2016 INT ref = tv->table->ref_count; 2017 UINT r; 2018 INT i; 2019 2020 TRACE("%p %d\n", view, ref); 2021 2022 for (i = tv->table->col_count - 1; i >= 0; i--) 2023 { 2024 if (tv->table->colinfo[i].type & MSITYPE_TEMPORARY) 2025 { 2026 ref = InterlockedDecrement(&tv->table->colinfo[i].ref_count); 2027 if (ref == 0) 2028 { 2029 r = TABLE_remove_column(view, tv->table->colinfo[i].tablename, 2030 tv->table->colinfo[i].number); 2031 if (r != ERROR_SUCCESS) 2032 break; 2033 } 2034 } 2035 } 2036 2037 ref = InterlockedDecrement(&tv->table->ref_count); 2038 if (ref == 0) 2039 { 2040 if (!tv->table->row_count) 2041 { 2042 list_remove(&tv->table->entry); 2043 free_table(tv->table); 2044 TABLE_delete(view); 2045 } 2046 } 2047 2048 return ref; 2049 } 2050 2051 static UINT TABLE_add_column(struct tagMSIVIEW *view, LPCWSTR table, UINT number, 2052 LPCWSTR column, UINT type, BOOL hold) 2053 { 2054 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2055 MSITABLE *msitable; 2056 MSIRECORD *rec; 2057 UINT r, i; 2058 2059 rec = MSI_CreateRecord(4); 2060 if (!rec) 2061 return ERROR_OUTOFMEMORY; 2062 2063 MSI_RecordSetStringW(rec, 1, table); 2064 MSI_RecordSetInteger(rec, 2, number); 2065 MSI_RecordSetStringW(rec, 3, column); 2066 MSI_RecordSetInteger(rec, 4, type); 2067 2068 r = TABLE_insert_row(&tv->view, rec, -1, FALSE); 2069 if (r != ERROR_SUCCESS) 2070 goto done; 2071 2072 msi_update_table_columns(tv->db, table); 2073 2074 if (!hold) 2075 goto done; 2076 2077 msitable = find_cached_table(tv->db, table); 2078 for (i = 0; i < msitable->col_count; i++) 2079 { 2080 if (!wcscmp( msitable->colinfo[i].colname, column )) 2081 { 2082 InterlockedIncrement(&msitable->colinfo[i].ref_count); 2083 break; 2084 } 2085 } 2086 2087 done: 2088 msiobj_release(&rec->hdr); 2089 return r; 2090 } 2091 2092 static UINT TABLE_drop(struct tagMSIVIEW *view) 2093 { 2094 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2095 MSIVIEW *tables = NULL; 2096 MSIRECORD *rec = NULL; 2097 UINT r, row; 2098 INT i; 2099 2100 TRACE("dropping table %s\n", debugstr_w(tv->name)); 2101 2102 for (i = tv->table->col_count - 1; i >= 0; i--) 2103 { 2104 r = TABLE_remove_column(view, tv->table->colinfo[i].tablename, 2105 tv->table->colinfo[i].number); 2106 if (r != ERROR_SUCCESS) 2107 return r; 2108 } 2109 2110 rec = MSI_CreateRecord(1); 2111 if (!rec) 2112 return ERROR_OUTOFMEMORY; 2113 2114 MSI_RecordSetStringW(rec, 1, tv->name); 2115 2116 r = TABLE_CreateView(tv->db, szTables, &tables); 2117 if (r != ERROR_SUCCESS) 2118 { 2119 msiobj_release(&rec->hdr); 2120 return r; 2121 } 2122 2123 r = msi_table_find_row((MSITABLEVIEW *)tables, rec, &row, NULL); 2124 if (r != ERROR_SUCCESS) 2125 goto done; 2126 2127 r = TABLE_delete_row(tables, row); 2128 if (r != ERROR_SUCCESS) 2129 goto done; 2130 2131 list_remove(&tv->table->entry); 2132 free_table(tv->table); 2133 2134 done: 2135 msiobj_release(&rec->hdr); 2136 tables->ops->delete(tables); 2137 2138 return r; 2139 } 2140 2141 static const MSIVIEWOPS table_ops = 2142 { 2143 TABLE_fetch_int, 2144 TABLE_fetch_stream, 2145 TABLE_set_int, 2146 TABLE_set_string, 2147 TABLE_set_stream, 2148 TABLE_set_row, 2149 TABLE_insert_row, 2150 TABLE_delete_row, 2151 TABLE_execute, 2152 TABLE_close, 2153 TABLE_get_dimensions, 2154 TABLE_get_column_info, 2155 TABLE_modify, 2156 TABLE_delete, 2157 TABLE_add_ref, 2158 TABLE_release, 2159 TABLE_add_column, 2160 NULL, 2161 TABLE_drop, 2162 }; 2163 2164 UINT TABLE_CreateView( MSIDATABASE *db, LPCWSTR name, MSIVIEW **view ) 2165 { 2166 MSITABLEVIEW *tv ; 2167 UINT r, sz; 2168 2169 TRACE("%p %s %p\n", db, debugstr_w(name), view ); 2170 2171 if ( !wcscmp( name, szStreams ) ) 2172 return STREAMS_CreateView( db, view ); 2173 else if ( !wcscmp( name, szStorages ) ) 2174 return STORAGES_CreateView( db, view ); 2175 2176 sz = FIELD_OFFSET( MSITABLEVIEW, name[lstrlenW( name ) + 1] ); 2177 tv = msi_alloc_zero( sz ); 2178 if( !tv ) 2179 return ERROR_FUNCTION_FAILED; 2180 2181 r = get_table( db, name, &tv->table ); 2182 if( r != ERROR_SUCCESS ) 2183 { 2184 msi_free( tv ); 2185 WARN("table not found\n"); 2186 return r; 2187 } 2188 2189 TRACE("table %p found with %d columns\n", tv->table, tv->table->col_count); 2190 2191 /* fill the structure */ 2192 tv->view.ops = &table_ops; 2193 tv->db = db; 2194 tv->columns = tv->table->colinfo; 2195 tv->num_cols = tv->table->col_count; 2196 tv->row_size = msi_table_get_row_size( db, tv->table->colinfo, tv->table->col_count, LONG_STR_BYTES ); 2197 2198 TRACE("%s one row is %d bytes\n", debugstr_w(name), tv->row_size ); 2199 2200 *view = (MSIVIEW*) tv; 2201 lstrcpyW( tv->name, name ); 2202 2203 return ERROR_SUCCESS; 2204 } 2205 2206 static WCHAR* create_key_string(MSITABLEVIEW *tv, MSIRECORD *rec) 2207 { 2208 DWORD i, p, len, key_len = 0; 2209 WCHAR *key; 2210 2211 for (i = 0; i < tv->num_cols; i++) 2212 { 2213 if (!(tv->columns[i].type & MSITYPE_KEY)) 2214 continue; 2215 if (MSI_RecordGetStringW( rec, i+1, NULL, &len ) == ERROR_SUCCESS) 2216 key_len += len; 2217 key_len++; 2218 } 2219 2220 key = msi_alloc( key_len * sizeof(WCHAR) ); 2221 if(!key) 2222 return NULL; 2223 2224 p = 0; 2225 for (i = 0; i < tv->num_cols; i++) 2226 { 2227 if (!(tv->columns[i].type & MSITYPE_KEY)) 2228 continue; 2229 if (p) 2230 key[p++] = '\t'; 2231 len = key_len - p; 2232 if (MSI_RecordGetStringW( rec, i+1, key + p, &len ) == ERROR_SUCCESS) 2233 p += len; 2234 } 2235 return key; 2236 } 2237 2238 static UINT msi_record_stream_name( const MSITABLEVIEW *tv, MSIRECORD *rec, LPWSTR name, UINT *len ) 2239 { 2240 UINT p = 0, l, i, r; 2241 2242 l = wcslen( tv->name ); 2243 if (name && *len > l) 2244 memcpy(name, tv->name, l * sizeof(WCHAR)); 2245 p += l; 2246 2247 for ( i = 0; i < tv->num_cols; i++ ) 2248 { 2249 if (!(tv->columns[i].type & MSITYPE_KEY)) 2250 continue; 2251 2252 if (name && *len > p + 1) 2253 name[p] = '.'; 2254 p++; 2255 2256 l = (*len > p ? *len - p : 0); 2257 r = MSI_RecordGetStringW( rec, i + 1, name ? name + p : NULL, &l ); 2258 if (r != ERROR_SUCCESS) 2259 return r; 2260 p += l; 2261 } 2262 2263 if (name && *len > p) 2264 name[p] = 0; 2265 2266 *len = p; 2267 return ERROR_SUCCESS; 2268 } 2269 2270 static UINT TransformView_fetch_int( MSIVIEW *view, UINT row, UINT col, UINT *val ) 2271 { 2272 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2273 2274 if (!tv->table || col > tv->table->col_count) 2275 { 2276 *val = 0; 2277 return ERROR_SUCCESS; 2278 } 2279 return TABLE_fetch_int( view, row, col, val ); 2280 } 2281 2282 static UINT TransformView_fetch_stream( MSIVIEW *view, UINT row, UINT col, IStream **stm ) 2283 { 2284 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2285 2286 if (!tv->table || col > tv->table->col_count) 2287 { 2288 *stm = NULL; 2289 return ERROR_SUCCESS; 2290 } 2291 return TABLE_fetch_stream( view, row, col, stm ); 2292 } 2293 2294 static UINT TransformView_set_row( MSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask ) 2295 { 2296 static const WCHAR query_pfx[] = 2297 L"INSERT INTO `_TransformView` (`Table`, `Column`, `Row`, `Data`, `Current`) VALUES ('"; 2298 2299 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2300 WCHAR buf[256], *query = buf; 2301 MSIRECORD *old_rec; 2302 MSIQUERY *q; 2303 WCHAR *key; 2304 UINT i, p, r, len, qlen; 2305 2306 if (!wcscmp( tv->name, szColumns )) 2307 { 2308 ERR( "trying to modify existing column\n" ); 2309 return ERROR_INSTALL_TRANSFORM_FAILURE; 2310 } 2311 2312 if (!wcscmp( tv->name, szTables )) 2313 { 2314 ERR( "trying to modify existing table\n" ); 2315 return ERROR_INSTALL_TRANSFORM_FAILURE; 2316 } 2317 2318 key = create_key_string( tv, rec ); 2319 if (!key) 2320 return ERROR_OUTOFMEMORY; 2321 2322 r = msi_view_get_row( tv->db, view, row, &old_rec ); 2323 if (r != ERROR_SUCCESS) 2324 old_rec = NULL; 2325 2326 for (i = 0; i < tv->num_cols; i++) 2327 { 2328 if (!(mask & (1 << i))) 2329 continue; 2330 if (tv->columns[i].type & MSITYPE_KEY) 2331 continue; 2332 2333 qlen = p = wcslen( query_pfx ); 2334 qlen += wcslen( tv->name ) + 3; /* strlen("','") */ 2335 qlen += wcslen( tv->columns[i].colname ) + 3; 2336 qlen += wcslen( key ) + 3; 2337 if (MSITYPE_IS_BINARY( tv->columns[i].type )) 2338 r = msi_record_stream_name( tv, rec, NULL, &len ); 2339 else 2340 r = MSI_RecordGetStringW( rec, i + 1, NULL, &len ); 2341 if (r != ERROR_SUCCESS) 2342 { 2343 if (old_rec) 2344 msiobj_release( &old_rec->hdr ); 2345 msi_free( key ); 2346 return r; 2347 } 2348 qlen += len + 3; 2349 if (old_rec && (r = MSI_RecordGetStringW( old_rec, i+1, NULL, &len ))) 2350 { 2351 msiobj_release( &old_rec->hdr ); 2352 msi_free( key ); 2353 return r; 2354 } 2355 qlen += len + 3; /* strlen("')") + 1 */ 2356 2357 if (qlen > ARRAY_SIZE(buf)) 2358 { 2359 query = msi_alloc( qlen * sizeof(WCHAR) ); 2360 if (!query) 2361 { 2362 if (old_rec) 2363 msiobj_release( &old_rec->hdr ); 2364 msi_free( key ); 2365 return ERROR_OUTOFMEMORY; 2366 } 2367 } 2368 2369 memcpy( query, query_pfx, p * sizeof(WCHAR) ); 2370 len = wcslen( tv->name ); 2371 memcpy( query + p, tv->name, len * sizeof(WCHAR) ); 2372 p += len; 2373 query[p++] = '\''; 2374 query[p++] = ','; 2375 query[p++] = '\''; 2376 len = wcslen( tv->columns[i].colname ); 2377 memcpy( query + p, tv->columns[i].colname, len * sizeof(WCHAR) ); 2378 p += len; 2379 query[p++] = '\''; 2380 query[p++] = ','; 2381 query[p++] = '\''; 2382 len = wcslen( key ); 2383 memcpy( query + p, key, len * sizeof(WCHAR) ); 2384 p += len; 2385 query[p++] = '\''; 2386 query[p++] = ','; 2387 query[p++] = '\''; 2388 len = qlen - p; 2389 if (MSITYPE_IS_BINARY( tv->columns[i].type )) 2390 msi_record_stream_name( tv, rec, query + p, &len ); 2391 else 2392 MSI_RecordGetStringW( rec, i + 1, query + p, &len ); 2393 p += len; 2394 query[p++] = '\''; 2395 query[p++] = ','; 2396 query[p++] = '\''; 2397 if (old_rec) 2398 { 2399 len = qlen - p; 2400 MSI_RecordGetStringW( old_rec, i + 1, query + p, &len ); 2401 p += len; 2402 } 2403 query[p++] = '\''; 2404 query[p++] = ')'; 2405 query[p++] = 0; 2406 2407 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2408 if (query != buf) 2409 msi_free( query ); 2410 if (r != ERROR_SUCCESS) 2411 { 2412 if (old_rec) 2413 msiobj_release( &old_rec->hdr ); 2414 msi_free( key ); 2415 return r; 2416 } 2417 2418 r = MSI_ViewExecute( q, NULL ); 2419 msiobj_release( &q->hdr ); 2420 if (r != ERROR_SUCCESS) 2421 { 2422 if (old_rec) 2423 msiobj_release( &old_rec->hdr ); 2424 msi_free( key ); 2425 return r; 2426 } 2427 } 2428 2429 if (old_rec) 2430 msiobj_release( &old_rec->hdr ); 2431 msi_free( key ); 2432 return ERROR_SUCCESS; 2433 } 2434 2435 static UINT TransformView_create_table( MSITABLEVIEW *tv, MSIRECORD *rec ) 2436 { 2437 static const WCHAR query_fmt[] = 2438 L"INSERT INTO `_TransformView` (`Table`, `Column`) VALUES ('%s', 'CREATE')"; 2439 2440 WCHAR buf[256], *query = buf; 2441 const WCHAR *name; 2442 MSIQUERY *q; 2443 int len; 2444 UINT r; 2445 2446 name = msi_record_get_string( rec, 1, &len ); 2447 if (!name) 2448 return ERROR_INSTALL_TRANSFORM_FAILURE; 2449 2450 len = _snwprintf( NULL, 0, query_fmt, name ) + 1; 2451 if (len > ARRAY_SIZE(buf)) 2452 { 2453 query = msi_alloc( len * sizeof(WCHAR) ); 2454 if (!query) 2455 return ERROR_OUTOFMEMORY; 2456 } 2457 swprintf( query, len, query_fmt, name ); 2458 2459 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2460 if (query != buf) 2461 msi_free( query ); 2462 if (r != ERROR_SUCCESS) 2463 return r; 2464 2465 r = MSI_ViewExecute( q, NULL ); 2466 msiobj_release( &q->hdr ); 2467 return r; 2468 } 2469 2470 static UINT TransformView_add_column( MSITABLEVIEW *tv, MSIRECORD *rec ) 2471 { 2472 static const WCHAR query_pfx[] = 2473 L"INSERT INTO `_TransformView` (`Table`, `Current`, `Column`, `Data`) VALUES ('"; 2474 2475 WCHAR buf[256], *query = buf; 2476 UINT i, p, len, r, qlen; 2477 MSIQUERY *q; 2478 2479 qlen = p = wcslen( query_pfx ); 2480 for (i = 1; i <= 4; i++) 2481 { 2482 r = MSI_RecordGetStringW( rec, i, NULL, &len ); 2483 if (r != ERROR_SUCCESS) 2484 return r; 2485 qlen += len + 3; /* strlen( "','" ) */ 2486 } 2487 2488 if (qlen > ARRAY_SIZE(buf)) 2489 { 2490 query = msi_alloc( len * sizeof(WCHAR) ); 2491 qlen = len; 2492 if (!query) 2493 return ERROR_OUTOFMEMORY; 2494 } 2495 2496 memcpy( query, query_pfx, p * sizeof(WCHAR) ); 2497 for (i = 1; i <= 4; i++) 2498 { 2499 len = qlen - p; 2500 MSI_RecordGetStringW( rec, i, query + p, &len ); 2501 p += len; 2502 query[p++] = '\''; 2503 if (i != 4) 2504 { 2505 query[p++] = ','; 2506 query[p++] = '\''; 2507 } 2508 } 2509 query[p++] = ')'; 2510 query[p++] = 0; 2511 2512 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2513 if (query != buf) 2514 msi_free( query ); 2515 if (r != ERROR_SUCCESS) 2516 return r; 2517 2518 r = MSI_ViewExecute( q, NULL ); 2519 msiobj_release( &q->hdr ); 2520 return r; 2521 } 2522 2523 static UINT TransformView_insert_row( MSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary ) 2524 { 2525 static const WCHAR query_fmt[] = 2526 L"INSERT INTO `_TransformView` (`Table`, `Column`, `Row`) VALUES ('%s', 'INSERT', '%s')"; 2527 2528 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2529 WCHAR buf[256], *query = buf; 2530 MSIQUERY *q; 2531 WCHAR *key; 2532 int len; 2533 UINT r; 2534 2535 if (!wcscmp(tv->name, szTables)) 2536 return TransformView_create_table( tv, rec ); 2537 2538 if (!wcscmp(tv->name, szColumns)) 2539 return TransformView_add_column( tv, rec ); 2540 2541 key = create_key_string( tv, rec ); 2542 if (!key) 2543 return ERROR_OUTOFMEMORY; 2544 2545 len = _snwprintf( NULL, 0, query_fmt, tv->name, key ) + 1; 2546 if (len > ARRAY_SIZE(buf)) 2547 { 2548 query = msi_alloc( len * sizeof(WCHAR) ); 2549 if (!query) 2550 { 2551 msi_free( key ); 2552 return ERROR_OUTOFMEMORY; 2553 } 2554 } 2555 swprintf( query, len, query_fmt, tv->name, key ); 2556 msi_free( key ); 2557 2558 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2559 if (query != buf) 2560 msi_free( query ); 2561 if (r != ERROR_SUCCESS) 2562 return r; 2563 2564 r = MSI_ViewExecute( q, NULL ); 2565 msiobj_release( &q->hdr ); 2566 if (r != ERROR_SUCCESS) 2567 return r; 2568 2569 return TransformView_set_row( view, row, rec, ~0 ); 2570 } 2571 2572 static UINT TransformView_drop_table( MSITABLEVIEW *tv, UINT row ) 2573 { 2574 static const WCHAR query_pfx[] = L"INSERT INTO `_TransformView` ( `Table`, `Column` ) VALUES ( '"; 2575 static const WCHAR query_sfx[] = L"', 'DROP' )"; 2576 2577 WCHAR buf[256], *query = buf; 2578 UINT r, table_id, len; 2579 const WCHAR *table; 2580 int table_len; 2581 MSIQUERY *q; 2582 2583 r = TABLE_fetch_int( &tv->view, row, 1, &table_id ); 2584 if (r != ERROR_SUCCESS) 2585 return r; 2586 2587 table = msi_string_lookup( tv->db->strings, table_id, &table_len ); 2588 if (!table) 2589 return ERROR_INSTALL_TRANSFORM_FAILURE; 2590 2591 len = ARRAY_SIZE(query_pfx) - 1 + table_len + ARRAY_SIZE(query_sfx); 2592 if (len > ARRAY_SIZE(buf)) 2593 { 2594 query = msi_alloc( len * sizeof(WCHAR) ); 2595 if (!query) 2596 return ERROR_OUTOFMEMORY; 2597 } 2598 2599 memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) ); 2600 len = ARRAY_SIZE(query_pfx) - 1; 2601 memcpy( query + len, table, table_len * sizeof(WCHAR) ); 2602 len += table_len; 2603 memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) ); 2604 2605 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2606 if (query != buf) 2607 msi_free( query ); 2608 if (r != ERROR_SUCCESS) 2609 return r; 2610 2611 r = MSI_ViewExecute( q, NULL ); 2612 msiobj_release( &q->hdr ); 2613 return r; 2614 } 2615 2616 static UINT TransformView_delete_row( MSIVIEW *view, UINT row ) 2617 { 2618 static const WCHAR query_pfx[] = L"INSERT INTO `_TransformView` ( `Table`, `Column`, `Row`) VALUES ( '"; 2619 static const WCHAR query_column[] = L"', 'DELETE', '"; 2620 static const WCHAR query_sfx[] = L"')"; 2621 2622 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2623 WCHAR *key, buf[256], *query = buf; 2624 UINT r, len, name_len, key_len; 2625 MSIRECORD *rec; 2626 MSIQUERY *q; 2627 2628 if (!wcscmp( tv->name, szColumns )) 2629 { 2630 ERR("trying to remove column\n"); 2631 return ERROR_INSTALL_TRANSFORM_FAILURE; 2632 } 2633 2634 if (!wcscmp( tv->name, szTables )) 2635 return TransformView_drop_table( tv, row ); 2636 2637 r = msi_view_get_row( tv->db, view, row, &rec ); 2638 if (r != ERROR_SUCCESS) 2639 return r; 2640 2641 key = create_key_string( tv, rec ); 2642 msiobj_release( &rec->hdr ); 2643 if (!key) 2644 return ERROR_OUTOFMEMORY; 2645 2646 name_len = wcslen( tv->name ); 2647 key_len = wcslen( key ); 2648 len = ARRAY_SIZE(query_pfx) + name_len + ARRAY_SIZE(query_column) + key_len + ARRAY_SIZE(query_sfx) - 2; 2649 if (len > ARRAY_SIZE(buf)) 2650 { 2651 query = msi_alloc( len * sizeof(WCHAR) ); 2652 if (!query) 2653 { 2654 msi_free( tv ); 2655 msi_free( key ); 2656 return ERROR_OUTOFMEMORY; 2657 } 2658 } 2659 2660 memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) ); 2661 len = ARRAY_SIZE(query_pfx) - 1; 2662 memcpy( query + len, tv->name, name_len * sizeof(WCHAR) ); 2663 len += name_len; 2664 memcpy( query + len, query_column, ARRAY_SIZE(query_column) * sizeof(WCHAR) ); 2665 len += ARRAY_SIZE(query_column) - 1; 2666 memcpy( query + len, key, key_len * sizeof(WCHAR) ); 2667 len += key_len; 2668 memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) ); 2669 msi_free( key ); 2670 2671 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2672 if (query != buf) 2673 msi_free( query ); 2674 if (r != ERROR_SUCCESS) 2675 return r; 2676 2677 r = MSI_ViewExecute( q, NULL ); 2678 msiobj_release( &q->hdr ); 2679 return r; 2680 } 2681 2682 static UINT TransformView_execute( MSIVIEW *view, MSIRECORD *record ) 2683 { 2684 return ERROR_SUCCESS; 2685 } 2686 2687 static UINT TransformView_close( MSIVIEW *view ) 2688 { 2689 return ERROR_SUCCESS; 2690 } 2691 2692 static UINT TransformView_get_dimensions( MSIVIEW *view, UINT *rows, UINT *cols ) 2693 { 2694 return TABLE_get_dimensions( view, rows, cols ); 2695 } 2696 2697 static UINT TransformView_get_column_info( MSIVIEW *view, UINT n, LPCWSTR *name, UINT *type, 2698 BOOL *temporary, LPCWSTR *table_name ) 2699 { 2700 return TABLE_get_column_info( view, n, name, type, temporary, table_name ); 2701 } 2702 2703 static UINT TransformView_delete( MSIVIEW *view ) 2704 { 2705 MSITABLEVIEW *tv = (MSITABLEVIEW*)view; 2706 if (!tv->table || tv->columns != tv->table->colinfo) 2707 msi_free( tv->columns ); 2708 return TABLE_delete( view ); 2709 } 2710 2711 static const MSIVIEWOPS transform_view_ops = 2712 { 2713 TransformView_fetch_int, 2714 TransformView_fetch_stream, 2715 NULL, 2716 NULL, 2717 NULL, 2718 TransformView_set_row, 2719 TransformView_insert_row, 2720 TransformView_delete_row, 2721 TransformView_execute, 2722 TransformView_close, 2723 TransformView_get_dimensions, 2724 TransformView_get_column_info, 2725 NULL, 2726 TransformView_delete, 2727 NULL, 2728 NULL, 2729 NULL, 2730 NULL, 2731 NULL 2732 }; 2733 2734 UINT TransformView_Create( MSIDATABASE *db, string_table *st, LPCWSTR name, MSIVIEW **view ) 2735 { 2736 static const WCHAR query_pfx[] = L"SELECT `Column`, `Data`, `Current` FROM `_TransformView` WHERE `Table`='"; 2737 static const WCHAR query_sfx[] = L"' AND `Row` IS NULL AND `Current` IS NOT NULL"; 2738 2739 WCHAR buf[256], *query = buf; 2740 UINT r, len, name_len, size, add_col; 2741 MSICOLUMNINFO *colinfo; 2742 MSITABLEVIEW *tv; 2743 MSIRECORD *rec; 2744 MSIQUERY *q; 2745 2746 name_len = wcslen( name ); 2747 2748 r = TABLE_CreateView( db, name, view ); 2749 if (r == ERROR_INVALID_PARAMETER) 2750 { 2751 /* table does not exist */ 2752 size = FIELD_OFFSET( MSITABLEVIEW, name[name_len + 1] ); 2753 tv = msi_alloc_zero( size ); 2754 if (!tv) 2755 return ERROR_OUTOFMEMORY; 2756 2757 tv->db = db; 2758 memcpy( tv->name, name, name_len * sizeof(WCHAR) ); 2759 *view = (MSIVIEW*)tv; 2760 } 2761 else if (r != ERROR_SUCCESS) 2762 { 2763 return r; 2764 } 2765 else 2766 { 2767 tv = (MSITABLEVIEW*)*view; 2768 } 2769 2770 tv->view.ops = &transform_view_ops; 2771 2772 len = ARRAY_SIZE(query_pfx) + name_len + ARRAY_SIZE(query_sfx) - 1; 2773 if (len > ARRAY_SIZE(buf)) 2774 { 2775 query = msi_alloc( len * sizeof(WCHAR) ); 2776 if (!query) 2777 { 2778 msi_free( tv ); 2779 return ERROR_OUTOFMEMORY; 2780 } 2781 } 2782 memcpy( query, query_pfx, ARRAY_SIZE(query_pfx) * sizeof(WCHAR) ); 2783 len = ARRAY_SIZE(query_pfx) - 1; 2784 memcpy( query + len, name, name_len * sizeof(WCHAR) ); 2785 len += name_len; 2786 memcpy( query + len, query_sfx, ARRAY_SIZE(query_sfx) * sizeof(WCHAR) ); 2787 2788 r = MSI_DatabaseOpenViewW( tv->db, query, &q ); 2789 if (query != buf) 2790 msi_free( query ); 2791 if (r != ERROR_SUCCESS) 2792 { 2793 msi_free( tv ); 2794 return r; 2795 } 2796 2797 r = MSI_ViewExecute( q, NULL ); 2798 if (r != ERROR_SUCCESS) 2799 { 2800 msi_free( tv ); 2801 return r; 2802 } 2803 2804 r = q->view->ops->get_dimensions( q->view, &add_col, NULL ); 2805 if (r != ERROR_SUCCESS) 2806 { 2807 MSI_ViewClose( q ); 2808 msiobj_release( &q->hdr ); 2809 msi_free( tv ); 2810 return r; 2811 } 2812 if (!add_col) 2813 { 2814 MSI_ViewClose( q ); 2815 msiobj_release( &q->hdr ); 2816 return ERROR_SUCCESS; 2817 } 2818 2819 colinfo = msi_alloc_zero( (add_col + tv->num_cols) * sizeof(*colinfo) ); 2820 if (!colinfo) 2821 { 2822 MSI_ViewClose( q ); 2823 msiobj_release( &q->hdr ); 2824 msi_free( tv ); 2825 return r; 2826 } 2827 2828 while (MSI_ViewFetch( q, &rec ) == ERROR_SUCCESS) 2829 { 2830 int name_len; 2831 const WCHAR *name = msi_record_get_string( rec, 1, &name_len ); 2832 const WCHAR *type = msi_record_get_string( rec, 2, NULL ); 2833 UINT name_id, idx; 2834 2835 idx = _wtoi( msi_record_get_string(rec, 3, NULL) ); 2836 colinfo[idx - 1].number = idx; 2837 colinfo[idx - 1].type = _wtoi( type ); 2838 2839 r = msi_string2id( st, name, name_len, &name_id ); 2840 if (r == ERROR_SUCCESS) 2841 colinfo[idx - 1].colname = msi_string_lookup( st, name_id, NULL ); 2842 else 2843 ERR( "column name %s is not defined in strings table\n", wine_dbgstr_w(name) ); 2844 } 2845 MSI_ViewClose( q ); 2846 msiobj_release( &q->hdr ); 2847 2848 memcpy( colinfo, tv->columns, tv->num_cols * sizeof(*colinfo) ); 2849 tv->columns = colinfo; 2850 tv->num_cols += add_col; 2851 return ERROR_SUCCESS; 2852 } 2853 2854 UINT MSI_CommitTables( MSIDATABASE *db ) 2855 { 2856 UINT r, bytes_per_strref; 2857 HRESULT hr; 2858 MSITABLE *table = NULL; 2859 2860 TRACE("%p\n",db); 2861 2862 r = msi_save_string_table( db->strings, db->storage, &bytes_per_strref ); 2863 if( r != ERROR_SUCCESS ) 2864 { 2865 WARN("failed to save string table r=%08x\n",r); 2866 return r; 2867 } 2868 2869 LIST_FOR_EACH_ENTRY( table, &db->tables, MSITABLE, entry ) 2870 { 2871 r = save_table( db, table, bytes_per_strref ); 2872 if( r != ERROR_SUCCESS ) 2873 { 2874 WARN("failed to save table %s (r=%08x)\n", 2875 debugstr_w(table->name), r); 2876 return r; 2877 } 2878 } 2879 2880 hr = IStorage_Commit( db->storage, 0 ); 2881 if (FAILED( hr )) 2882 { 2883 WARN("failed to commit changes 0x%08x\n", hr); 2884 r = ERROR_FUNCTION_FAILED; 2885 } 2886 return r; 2887 } 2888 2889 MSICONDITION MSI_DatabaseIsTablePersistent( MSIDATABASE *db, LPCWSTR table ) 2890 { 2891 MSITABLE *t; 2892 UINT r; 2893 2894 TRACE("%p %s\n", db, debugstr_w(table)); 2895 2896 if (!table) 2897 return MSICONDITION_ERROR; 2898 2899 r = get_table( db, table, &t ); 2900 if (r != ERROR_SUCCESS) 2901 return MSICONDITION_NONE; 2902 2903 return t->persistent; 2904 } 2905 2906 static UINT read_raw_int(const BYTE *data, UINT col, UINT bytes) 2907 { 2908 UINT ret = 0, i; 2909 2910 for (i = 0; i < bytes; i++) 2911 ret += (data[col + i] << i * 8); 2912 2913 return ret; 2914 } 2915 2916 static UINT msi_record_encoded_stream_name( const MSITABLEVIEW *tv, MSIRECORD *rec, LPWSTR *pstname ) 2917 { 2918 UINT r, len; 2919 WCHAR *name; 2920 2921 TRACE("%p %p\n", tv, rec); 2922 2923 r = msi_record_stream_name( tv, rec, NULL, &len ); 2924 if (r != ERROR_SUCCESS) 2925 return r; 2926 len++; 2927 2928 name = msi_alloc( len * sizeof(WCHAR) ); 2929 if (!name) 2930 return ERROR_OUTOFMEMORY; 2931 2932 r = msi_record_stream_name( tv, rec, name, &len ); 2933 if (r != ERROR_SUCCESS) 2934 { 2935 msi_free( name ); 2936 return r; 2937 } 2938 2939 *pstname = encode_streamname( FALSE, name ); 2940 msi_free( name ); 2941 return ERROR_SUCCESS; 2942 } 2943 2944 static MSIRECORD *msi_get_transform_record( const MSITABLEVIEW *tv, const string_table *st, 2945 IStorage *stg, const BYTE *rawdata, UINT bytes_per_strref ) 2946 { 2947 UINT i, val, ofs = 0; 2948 USHORT mask; 2949 MSICOLUMNINFO *columns = tv->columns; 2950 MSIRECORD *rec; 2951 2952 mask = rawdata[0] | (rawdata[1] << 8); 2953 rawdata += 2; 2954 2955 rec = MSI_CreateRecord( tv->num_cols ); 2956 if( !rec ) 2957 return rec; 2958 2959 TRACE("row ->\n"); 2960 for( i=0; i<tv->num_cols; i++ ) 2961 { 2962 if ( (mask&1) && (i>=(mask>>8)) ) 2963 break; 2964 /* all keys must be present */ 2965 if ( (~mask&1) && (~columns[i].type & MSITYPE_KEY) && ((1<<i) & ~mask) ) 2966 continue; 2967 2968 if( MSITYPE_IS_BINARY(tv->columns[i].type) ) 2969 { 2970 LPWSTR encname; 2971 IStream *stm = NULL; 2972 UINT r; 2973 2974 ofs += bytes_per_column( tv->db, &columns[i], bytes_per_strref ); 2975 2976 r = msi_record_encoded_stream_name( tv, rec, &encname ); 2977 if ( r != ERROR_SUCCESS ) 2978 { 2979 msiobj_release( &rec->hdr ); 2980 return NULL; 2981 } 2982 r = IStorage_OpenStream( stg, encname, NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &stm ); 2983 if ( r != ERROR_SUCCESS ) 2984 { 2985 msiobj_release( &rec->hdr ); 2986 msi_free( encname ); 2987 return NULL; 2988 } 2989 2990 MSI_RecordSetStream( rec, i+1, stm ); 2991 TRACE(" field %d [%s]\n", i+1, debugstr_w(encname)); 2992 msi_free( encname ); 2993 } 2994 else if( columns[i].type & MSITYPE_STRING ) 2995 { 2996 int len; 2997 const WCHAR *sval; 2998 2999 val = read_raw_int(rawdata, ofs, bytes_per_strref); 3000 sval = msi_string_lookup( st, val, &len ); 3001 msi_record_set_string( rec, i+1, sval, len ); 3002 TRACE(" field %d [%s]\n", i+1, debugstr_wn(sval, len)); 3003 ofs += bytes_per_strref; 3004 } 3005 else 3006 { 3007 UINT n = bytes_per_column( tv->db, &columns[i], bytes_per_strref ); 3008 switch( n ) 3009 { 3010 case 2: 3011 val = read_raw_int(rawdata, ofs, n); 3012 if (val) 3013 MSI_RecordSetInteger( rec, i+1, val-0x8000 ); 3014 TRACE(" field %d [0x%04x]\n", i+1, val ); 3015 break; 3016 case 4: 3017 val = read_raw_int(rawdata, ofs, n); 3018 if (val) 3019 MSI_RecordSetInteger( rec, i+1, val^0x80000000 ); 3020 TRACE(" field %d [0x%08x]\n", i+1, val ); 3021 break; 3022 default: 3023 ERR("oops - unknown column width %d\n", n); 3024 break; 3025 } 3026 ofs += n; 3027 } 3028 } 3029 return rec; 3030 } 3031 3032 static void dump_table( const string_table *st, const USHORT *rawdata, UINT rawsize ) 3033 { 3034 UINT i; 3035 for (i = 0; i < rawsize / 2; i++) 3036 { 3037 int len; 3038 const WCHAR *sval = msi_string_lookup( st, rawdata[i], &len ); 3039 MESSAGE(" %04x %s\n", rawdata[i], debugstr_wn(sval, len) ); 3040 } 3041 } 3042 3043 static UINT* msi_record_to_row( const MSITABLEVIEW *tv, MSIRECORD *rec ) 3044 { 3045 UINT i, r, *data; 3046 3047 data = msi_alloc( tv->num_cols *sizeof (UINT) ); 3048 for( i=0; i<tv->num_cols; i++ ) 3049 { 3050 data[i] = 0; 3051 3052 if ( ~tv->columns[i].type & MSITYPE_KEY ) 3053 continue; 3054 3055 /* turn the transform column value into a row value */ 3056 if ( ( tv->columns[i].type & MSITYPE_STRING ) && 3057 ! MSITYPE_IS_BINARY(tv->columns[i].type) ) 3058 { 3059 int len; 3060 const WCHAR *str = msi_record_get_string( rec, i+1, &len ); 3061 if (str) 3062 { 3063 r = msi_string2id( tv->db->strings, str, len, &data[i] ); 3064 3065 /* if there's no matching string in the string table, 3066 these keys can't match any record, so fail now. */ 3067 if (r != ERROR_SUCCESS) 3068 { 3069 msi_free( data ); 3070 return NULL; 3071 } 3072 } 3073 else data[i] = 0; 3074 } 3075 else 3076 { 3077 if (int_to_table_storage( tv, i + 1, MSI_RecordGetInteger( rec, i + 1 ), &data[i] )) 3078 { 3079 msi_free( data ); 3080 return NULL; 3081 } 3082 } 3083 } 3084 return data; 3085 } 3086 3087 static UINT msi_row_matches( MSITABLEVIEW *tv, UINT row, const UINT *data, UINT *column ) 3088 { 3089 UINT i, r, x, ret = ERROR_FUNCTION_FAILED; 3090 3091 for( i=0; i<tv->num_cols; i++ ) 3092 { 3093 if ( ~tv->columns[i].type & MSITYPE_KEY ) 3094 continue; 3095 3096 /* turn the transform column value into a row value */ 3097 r = TABLE_fetch_int( &tv->view, row, i+1, &x ); 3098 if ( r != ERROR_SUCCESS ) 3099 { 3100 ERR("TABLE_fetch_int shouldn't fail here\n"); 3101 break; 3102 } 3103 3104 /* if this key matches, move to the next column */ 3105 if ( x != data[i] ) 3106 { 3107 ret = ERROR_FUNCTION_FAILED; 3108 break; 3109 } 3110 if (column) *column = i; 3111 ret = ERROR_SUCCESS; 3112 } 3113 return ret; 3114 } 3115 3116 static UINT msi_table_find_row( MSITABLEVIEW *tv, MSIRECORD *rec, UINT *row, UINT *column ) 3117 { 3118 UINT i, r = ERROR_FUNCTION_FAILED, *data; 3119 3120 data = msi_record_to_row( tv, rec ); 3121 if( !data ) 3122 return r; 3123 for( i = 0; i < tv->table->row_count; i++ ) 3124 { 3125 r = msi_row_matches( tv, i, data, column ); 3126 if( r == ERROR_SUCCESS ) 3127 { 3128 *row = i; 3129 break; 3130 } 3131 } 3132 msi_free( data ); 3133 return r; 3134 } 3135 3136 typedef struct 3137 { 3138 struct list entry; 3139 LPWSTR name; 3140 } TRANSFORMDATA; 3141 3142 static UINT msi_table_load_transform( MSIDATABASE *db, IStorage *stg, 3143 string_table *st, TRANSFORMDATA *transform, 3144 UINT bytes_per_strref, int err_cond ) 3145 { 3146 BYTE *rawdata = NULL; 3147 MSITABLEVIEW *tv = NULL; 3148 UINT r, n, sz, i, mask, num_cols, colcol = 0, rawsize = 0; 3149 MSIRECORD *rec = NULL; 3150 WCHAR coltable[32]; 3151 const WCHAR *name; 3152 3153 if (!transform) 3154 return ERROR_SUCCESS; 3155 3156 name = transform->name; 3157 3158 coltable[0] = 0; 3159 TRACE("%p %p %p %s\n", db, stg, st, debugstr_w(name) ); 3160 3161 /* read the transform data */ 3162 read_stream_data( stg, name, TRUE, &rawdata, &rawsize ); 3163 if ( !rawdata ) 3164 { 3165 TRACE("table %s empty\n", debugstr_w(name) ); 3166 return ERROR_INVALID_TABLE; 3167 } 3168 3169 /* create a table view */ 3170 if ( err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM ) 3171 r = TransformView_Create( db, st, name, (MSIVIEW**) &tv ); 3172 else 3173 r = TABLE_CreateView( db, name, (MSIVIEW**) &tv ); 3174 if( r != ERROR_SUCCESS ) 3175 goto err; 3176 3177 r = tv->view.ops->execute( &tv->view, NULL ); 3178 if( r != ERROR_SUCCESS ) 3179 goto err; 3180 3181 TRACE("name = %s columns = %u row_size = %u raw size = %u\n", 3182 debugstr_w(name), tv->num_cols, tv->row_size, rawsize ); 3183 3184 /* interpret the data */ 3185 for (n = 0; n < rawsize;) 3186 { 3187 mask = rawdata[n] | (rawdata[n + 1] << 8); 3188 if (mask & 1) 3189 { 3190 /* 3191 * if the low bit is set, columns are continuous and 3192 * the number of columns is specified in the high byte 3193 */ 3194 sz = 2; 3195 num_cols = mask >> 8; 3196 if (num_cols > tv->num_cols) 3197 { 3198 ERR("excess columns in transform: %u > %u\n", num_cols, tv->num_cols); 3199 break; 3200 } 3201 3202 for (i = 0; i < num_cols; i++) 3203 { 3204 if( (tv->columns[i].type & MSITYPE_STRING) && 3205 ! MSITYPE_IS_BINARY(tv->columns[i].type) ) 3206 sz += bytes_per_strref; 3207 else 3208 sz += bytes_per_column( tv->db, &tv->columns[i], bytes_per_strref ); 3209 } 3210 } 3211 else 3212 { 3213 /* 3214 * If the low bit is not set, mask is a bitmask. 3215 * Excepting for key fields, which are always present, 3216 * each bit indicates that a field is present in the transform record. 3217 * 3218 * mask == 0 is a special case ... only the keys will be present 3219 * and it means that this row should be deleted. 3220 */ 3221 sz = 2; 3222 num_cols = tv->num_cols; 3223 for (i = 0; i < num_cols; i++) 3224 { 3225 if ((tv->columns[i].type & MSITYPE_KEY) || ((1 << i) & mask)) 3226 { 3227 if ((tv->columns[i].type & MSITYPE_STRING) && 3228 !MSITYPE_IS_BINARY(tv->columns[i].type)) 3229 sz += bytes_per_strref; 3230 else 3231 sz += bytes_per_column( tv->db, &tv->columns[i], bytes_per_strref ); 3232 } 3233 } 3234 } 3235 3236 /* check we didn't run of the end of the table */ 3237 if (n + sz > rawsize) 3238 { 3239 ERR("borked.\n"); 3240 dump_table( st, (USHORT *)rawdata, rawsize ); 3241 break; 3242 } 3243 3244 rec = msi_get_transform_record( tv, st, stg, &rawdata[n], bytes_per_strref ); 3245 if (rec) 3246 { 3247 WCHAR table[32]; 3248 DWORD sz = 32; 3249 UINT number = MSI_NULL_INTEGER; 3250 UINT row = 0; 3251 3252 if (!wcscmp( name, szColumns )) 3253 { 3254 MSI_RecordGetStringW( rec, 1, table, &sz ); 3255 number = MSI_RecordGetInteger( rec, 2 ); 3256 3257 /* 3258 * Native msi seems writes nul into the Number (2nd) column of 3259 * the _Columns table when there are new columns 3260 */ 3261 if ( number == MSI_NULL_INTEGER ) 3262 { 3263 /* reset the column number on a new table */ 3264 if (wcscmp( coltable, table )) 3265 { 3266 colcol = 0; 3267 lstrcpyW( coltable, table ); 3268 } 3269 3270 /* fix nul column numbers */ 3271 MSI_RecordSetInteger( rec, 2, ++colcol ); 3272 } 3273 } 3274 3275 if (TRACE_ON(msidb)) dump_record( rec ); 3276 3277 if (tv->table) 3278 r = msi_table_find_row( tv, rec, &row, NULL ); 3279 else 3280 r = ERROR_FUNCTION_FAILED; 3281 if (r == ERROR_SUCCESS) 3282 { 3283 if (!mask) 3284 { 3285 TRACE("deleting row [%d]:\n", row); 3286 r = tv->view.ops->delete_row( &tv->view, row ); 3287 if (r != ERROR_SUCCESS) 3288 WARN("failed to delete row %u\n", r); 3289 } 3290 else if (mask & 1) 3291 { 3292 TRACE("modifying full row [%d]:\n", row); 3293 r = tv->view.ops->set_row( &tv->view, row, rec, (1 << tv->num_cols) - 1 ); 3294 if (r != ERROR_SUCCESS) 3295 WARN("failed to modify row %u\n", r); 3296 } 3297 else 3298 { 3299 TRACE("modifying masked row [%d]:\n", row); 3300 r = tv->view.ops->set_row( &tv->view, row, rec, mask ); 3301 if (r != ERROR_SUCCESS) 3302 WARN("failed to modify row %u\n", r); 3303 } 3304 } 3305 else 3306 { 3307 TRACE("inserting row\n"); 3308 r = tv->view.ops->insert_row( &tv->view, rec, -1, FALSE ); 3309 if (r != ERROR_SUCCESS) 3310 WARN("failed to insert row %u\n", r); 3311 } 3312 3313 if (!(err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM) && 3314 !wcscmp( name, szColumns )) 3315 msi_update_table_columns( db, table ); 3316 3317 msiobj_release( &rec->hdr ); 3318 } 3319 3320 n += sz; 3321 } 3322 3323 err: 3324 /* no need to free the table, it's associated with the database */ 3325 msi_free( rawdata ); 3326 if( tv ) 3327 tv->view.ops->delete( &tv->view ); 3328 3329 return ERROR_SUCCESS; 3330 } 3331 3332 /* 3333 * msi_table_apply_transform 3334 * 3335 * Enumerate the table transforms in a transform storage and apply each one. 3336 */ 3337 UINT msi_table_apply_transform( MSIDATABASE *db, IStorage *stg, int err_cond ) 3338 { 3339 struct list transforms; 3340 IEnumSTATSTG *stgenum = NULL; 3341 TRANSFORMDATA *transform; 3342 TRANSFORMDATA *tables = NULL, *columns = NULL; 3343 HRESULT hr; 3344 STATSTG stat; 3345 string_table *strings; 3346 UINT ret = ERROR_FUNCTION_FAILED; 3347 UINT bytes_per_strref; 3348 BOOL property_update = FALSE; 3349 BOOL free_transform_view = FALSE; 3350 3351 TRACE("%p %p\n", db, stg ); 3352 3353 strings = msi_load_string_table( stg, &bytes_per_strref ); 3354 if( !strings ) 3355 goto end; 3356 3357 hr = IStorage_EnumElements( stg, 0, NULL, 0, &stgenum ); 3358 if (FAILED( hr )) 3359 goto end; 3360 3361 list_init(&transforms); 3362 3363 while ( TRUE ) 3364 { 3365 MSITABLEVIEW *tv = NULL; 3366 WCHAR name[0x40]; 3367 ULONG count = 0; 3368 3369 hr = IEnumSTATSTG_Next( stgenum, 1, &stat, &count ); 3370 if (FAILED( hr ) || !count) 3371 break; 3372 3373 decode_streamname( stat.pwcsName, name ); 3374 CoTaskMemFree( stat.pwcsName ); 3375 if ( name[0] != 0x4840 ) 3376 continue; 3377 3378 if ( !wcscmp( name+1, szStringPool ) || 3379 !wcscmp( name+1, szStringData ) ) 3380 continue; 3381 3382 transform = msi_alloc_zero( sizeof(TRANSFORMDATA) ); 3383 if ( !transform ) 3384 break; 3385 3386 list_add_tail( &transforms, &transform->entry ); 3387 3388 transform->name = strdupW( name + 1 ); 3389 3390 if ( !wcscmp( transform->name, szTables ) ) 3391 tables = transform; 3392 else if (!wcscmp( transform->name, szColumns ) ) 3393 columns = transform; 3394 else if (!wcscmp( transform->name, szProperty )) 3395 property_update = TRUE; 3396 3397 TRACE("transform contains stream %s\n", debugstr_w(name)); 3398 3399 /* load the table */ 3400 if (TABLE_CreateView( db, transform->name, (MSIVIEW**) &tv ) != ERROR_SUCCESS) 3401 continue; 3402 3403 if (tv->view.ops->execute( &tv->view, NULL ) != ERROR_SUCCESS) 3404 { 3405 tv->view.ops->delete( &tv->view ); 3406 continue; 3407 } 3408 3409 tv->view.ops->delete( &tv->view ); 3410 } 3411 3412 if (err_cond & MSITRANSFORM_ERROR_VIEWTRANSFORM) 3413 { 3414 static const WCHAR create_query[] = L"CREATE TABLE `_TransformView` ( " 3415 L"`Table` CHAR(0) NOT NULL TEMPORARY, `Column` CHAR(0) NOT NULL TEMPORARY, " 3416 L"`Row` CHAR(0) TEMPORARY, `Data` CHAR(0) TEMPORARY, `Current` CHAR(0) TEMPORARY " 3417 L"PRIMARY KEY `Table`, `Column`, `Row` ) HOLD"; 3418 MSIQUERY *query; 3419 UINT r; 3420 3421 r = MSI_DatabaseOpenViewW( db, create_query, &query ); 3422 if (r != ERROR_SUCCESS) 3423 goto end; 3424 3425 r = MSI_ViewExecute( query, NULL ); 3426 if (r == ERROR_SUCCESS) 3427 MSI_ViewClose( query ); 3428 msiobj_release( &query->hdr ); 3429 if (r == ERROR_BAD_QUERY_SYNTAX) 3430 FIXME( "support adding to _TransformView\n" ); 3431 if (r != ERROR_SUCCESS) 3432 goto end; 3433 free_transform_view = TRUE; 3434 } 3435 3436 /* 3437 * Apply _Tables and _Columns transforms first so that 3438 * the table metadata is correct, and empty tables exist. 3439 */ 3440 ret = msi_table_load_transform( db, stg, strings, tables, bytes_per_strref, err_cond ); 3441 if (ret != ERROR_SUCCESS && ret != ERROR_INVALID_TABLE) 3442 goto end; 3443 3444 ret = msi_table_load_transform( db, stg, strings, columns, bytes_per_strref, err_cond ); 3445 if (ret != ERROR_SUCCESS && ret != ERROR_INVALID_TABLE) 3446 goto end; 3447 3448 ret = ERROR_SUCCESS; 3449 3450 while ( !list_empty( &transforms ) ) 3451 { 3452 transform = LIST_ENTRY( list_head( &transforms ), TRANSFORMDATA, entry ); 3453 3454 if ( wcscmp( transform->name, szColumns ) && 3455 wcscmp( transform->name, szTables ) && 3456 ret == ERROR_SUCCESS ) 3457 { 3458 ret = msi_table_load_transform( db, stg, strings, transform, bytes_per_strref, err_cond ); 3459 } 3460 3461 list_remove( &transform->entry ); 3462 msi_free( transform->name ); 3463 msi_free( transform ); 3464 } 3465 3466 if ( ret == ERROR_SUCCESS ) 3467 { 3468 append_storage_to_db( db, stg ); 3469 if (property_update) msi_clone_properties( db ); 3470 } 3471 3472 end: 3473 if ( stgenum ) 3474 IEnumSTATSTG_Release( stgenum ); 3475 if ( strings ) 3476 msi_destroy_stringtable( strings ); 3477 if (ret != ERROR_SUCCESS && free_transform_view) 3478 { 3479 MSIQUERY *query; 3480 if (MSI_DatabaseOpenViewW( db, L"ALTER TABLE `_TransformView` FREE", 3481 &query ) == ERROR_SUCCESS) 3482 { 3483 if (MSI_ViewExecute( query, NULL ) == ERROR_SUCCESS) 3484 MSI_ViewClose( query ); 3485 msiobj_release( &query->hdr ); 3486 } 3487 } 3488 3489 return ret; 3490 } 3491