Module: wine Branch: master Commit: 51d5242a5fa07a76077f2318fbbff968d760389d URL: https://source.winehq.org/git/wine.git/?a=commit;h=51d5242a5fa07a76077f2318f...
Author: Erich E. Hoover erich.e.hoover@gmail.com Date: Fri Mar 15 10:03:56 2019 +0100
msi: Add support for deleting streams from an MSI database.
msidb allows developers to remove "streams" (cabinet files) from a database with the "-k" mode flag. To support that feature we need MSIMODIFY_DELETE support in the underlying MSI implementation.
Signed-off-by: Erich E. Hoover erich.e.hoover@gmail.com Signed-off-by: Hans Leidekker hans@codeweavers.com Signed-off-by: Alexandre Julliard julliard@winehq.org
---
dlls/msi/streams.c | 29 +++++++++++++++++--- dlls/msi/tests/db.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-)
diff --git a/dlls/msi/streams.c b/dlls/msi/streams.c index cfce816..777eb98 100644 --- a/dlls/msi/streams.c +++ b/dlls/msi/streams.c @@ -218,8 +218,28 @@ static UINT STREAMS_insert_row(struct tagMSIVIEW *view, MSIRECORD *rec, UINT row
static UINT STREAMS_delete_row(struct tagMSIVIEW *view, UINT row) { - FIXME("(%p %d): stub!\n", view, row); - return ERROR_SUCCESS; + MSIDATABASE *db = ((MSISTREAMSVIEW *)view)->db; + UINT i, num_rows = db->num_streams - 1; + const WCHAR *name; + WCHAR *encname; + HRESULT hr; + + TRACE("(%p %d)\n", view, row); + + if (!db->num_streams || row > num_rows) + return ERROR_FUNCTION_FAILED; + + name = msi_string_lookup( db->strings, db->streams[row].str_index, NULL ); + if (!(encname = encode_streamname( FALSE, name ))) return ERROR_OUTOFMEMORY; + IStream_Release( db->streams[row].stream ); + + for (i = row; i < num_rows; i++) + db->streams[i] = db->streams[i + 1]; + db->num_streams = num_rows; + + hr = IStorage_DestroyElement( db->storage, encname ); + msi_free( encname ); + return FAILED( hr ) ? ERROR_FUNCTION_FAILED : ERROR_SUCCESS; }
static UINT STREAMS_execute(struct tagMSIVIEW *view, MSIRECORD *record) @@ -317,12 +337,15 @@ static UINT STREAMS_modify(struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRE r = streams_modify_update(view, rec); break;
+ case MSIMODIFY_DELETE: + r = STREAMS_delete_row(view, row - 1); + break; + case MSIMODIFY_VALIDATE_NEW: case MSIMODIFY_INSERT_TEMPORARY: case MSIMODIFY_REFRESH: case MSIMODIFY_REPLACE: case MSIMODIFY_MERGE: - case MSIMODIFY_DELETE: case MSIMODIFY_VALIDATE: case MSIMODIFY_VALIDATE_FIELD: case MSIMODIFY_VALIDATE_DELETE: diff --git a/dlls/msi/tests/db.c b/dlls/msi/tests/db.c index 0382e54..9fbf43e 100644 --- a/dlls/msi/tests/db.c +++ b/dlls/msi/tests/db.c @@ -1779,6 +1779,84 @@ static void test_streamtable(void) MsiCloseHandle( view ); MsiCloseHandle( hdb ); DeleteFileA(msifile); + + /* insert a file into the _Streams table */ + r = MsiOpenDatabaseW(msifileW, MSIDBOPEN_CREATEDIRECT, &hdb); + ok(r == ERROR_SUCCESS, "Failed to create database\n"); + ok( hdb, "failed to create db\n"); + create_file( "test.txt" ); + rec = MsiCreateRecord( 2 ); + MsiRecordSetStringA( rec, 1, "data" ); + r = MsiRecordSetStreamA( rec, 2, "test.txt" ); + ok( r == ERROR_SUCCESS, "Failed to add stream data to the record: %d\n", r); + DeleteFileA("test.txt"); + r = MsiDatabaseOpenViewA( hdb, + "INSERT INTO `_Streams` ( `Name`, `Data` ) VALUES ( ?, ? )", &view ); + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); + r = MsiViewExecute( view, rec ); + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); + MsiCloseHandle( rec ); + MsiViewClose( view ); + MsiCloseHandle( view ); + r = MsiDatabaseCommit( hdb ); + ok( r == ERROR_SUCCESS , "Failed to commit database\n" ); + + /* open a handle to the "data" stream */ + r = MsiDatabaseOpenViewA( hdb, + "SELECT `Name`, `Data` FROM `_Streams` WHERE `Name` = 'data'", &view ); + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); + r = MsiViewExecute( view, 0 ); + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); + r = MsiViewFetch( view, &rec ); + ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + MsiViewClose( view ); + MsiCloseHandle( view ); + /* read the stream while it still exists (normal case) */ + size = MAX_PATH; + r = MsiRecordGetStringA( rec, 1, file, &size ); + ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r); + ok( !lstrcmpA(file, "data"), "Expected 'data', got %s\n", file); + size = MAX_PATH; + memset(buf, 0, MAX_PATH); + r = MsiRecordReadStream( rec, 2, buf, &size ); + ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r); + ok( !lstrcmpA(buf, "test.txt\n"), "Expected 'test.txt\n', got '%s' (%d)\n", buf, size); + MsiCloseHandle( rec ); + + /* open a handle to the "data" stream (and keep it open during removal) */ + r = MsiDatabaseOpenViewA( hdb, + "SELECT `Name`, `Data` FROM `_Streams` WHERE `Name` = 'data'", &view ); + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); + r = MsiViewExecute( view, 0 ); + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); + r = MsiViewFetch( view, &rec ); + ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r); + MsiViewClose( view ); + MsiCloseHandle( view ); + + /* remove the stream */ + r = MsiDatabaseOpenViewA( hdb, + "DELETE FROM `_Streams` WHERE `Name` = 'data'", &view ); + ok( r == ERROR_SUCCESS, "Failed to open database view: %d\n", r); + r = MsiViewExecute( view, 0 ); + ok( r == ERROR_SUCCESS, "Failed to execute view: %d\n", r); + MsiViewClose( view ); + MsiCloseHandle( view ); + + /* attempt to read the stream that no longer exists (abnormal case) */ + size = MAX_PATH; + r = MsiRecordGetStringA( rec, 1, file, &size ); + ok( r == ERROR_SUCCESS, "Failed to get string: %d\n", r); + ok( !lstrcmpA(file, "data"), "Expected 'data', got %s\n", file); + size = MAX_PATH; + memset(buf, 0, MAX_PATH); + r = MsiRecordReadStream( rec, 2, buf, &size ); + ok( r == ERROR_SUCCESS, "Failed to get stream: %d\n", r); + todo_wine ok( size == 0, "Expected empty buffer, got %d bytes\n", size); + MsiCloseHandle( rec ); + + MsiCloseHandle( hdb ); + DeleteFileA(msifile); }
static void test_binary(void)