[PATCH 1/9] msi: Add support for deleting streams from an MSI database.

Hans Leidekker hans at codeweavers.com
Fri Mar 15 04:03:56 CDT 2019


From: "Erich E. Hoover" <erich.e.hoover at gmail.com>

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.

Per Hans' request, this version has been updated with additional error
checking, unconditionally releasing the stream (see tests), and added
tests for the strange behavior that occurs when deleting in-use
streams.

v3: Avoid potential double free.

Signed-off-by: Erich E. Hoover <erich.e.hoover at gmail.com>
Signed-off-by: Hans Leidekker <hans at codeweavers.com>
---
 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 cfce816ea0..777eb98cff 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 0382e5454f..9fbf43eaee 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)
-- 
2.20.1




More information about the wine-devel mailing list