Skip to content
This repository was archived by the owner on May 24, 2026. It is now read-only.

Commit d4a998c

Browse files
committed
Add global mutex / _libmsi_query_fetch fix
1 parent d48ad1c commit d4a998c

6 files changed

Lines changed: 255 additions & 29 deletions

File tree

msi-interop/database.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,21 @@ MsiOpenDatabaseA(LPCSTR szDatabasePath, LPCSTR szPersist, MSIHANDLE *phDatabase)
124124
persist_path = szPersist;
125125
}
126126

127+
libmsi_global_lock();
128+
127129
GError *error = NULL;
128130
LibmsiDatabase *db = libmsi_database_new(szDatabasePath, flags,
129131
persist_path, &error);
130-
if (!db)
132+
if (!db) {
133+
libmsi_global_unlock();
131134
return gerror_to_msi(error);
135+
}
132136

133137
MSIHANDLE handle = handle_table_alloc(G_OBJECT(db), HANDLE_DATABASE);
134138
g_object_unref(db); /* handle_table_alloc added its own ref */
135139

140+
libmsi_global_unlock();
141+
136142
if (handle == 0)
137143
return ERROR_GEN_FAILURE;
138144

@@ -180,10 +186,14 @@ MsiDatabaseCommit(MSIHANDLE hDatabase)
180186
if (!db)
181187
return ERROR_INVALID_HANDLE;
182188

189+
libmsi_global_lock();
190+
183191
GError *error = NULL;
184192
gboolean ok = libmsi_database_commit(db, &error);
185193
g_object_unref(db);
186194

195+
libmsi_global_unlock();
196+
187197
if (!ok)
188198
return gerror_to_msi(error);
189199

@@ -201,8 +211,11 @@ MsiGetDatabaseState(MSIHANDLE hDatabase)
201211
if (!db)
202212
return MSIDBSTATE_ERROR;
203213

214+
libmsi_global_lock();
204215
MSIDBSTATE ret = libmsi_database_is_readonly(db) ? MSIDBSTATE_READ : MSIDBSTATE_WRITE;
205216
g_object_unref(db);
217+
libmsi_global_unlock();
218+
206219
return ret;
207220
}
208221

@@ -220,16 +233,22 @@ MsiDatabaseGetPrimaryKeysA(MSIHANDLE hDatabase, LPCSTR szTableName, MSIHANDLE *p
220233
if (!db)
221234
return ERROR_INVALID_HANDLE;
222235

236+
libmsi_global_lock();
237+
223238
GError *error = NULL;
224239
LibmsiRecord *rec = libmsi_database_get_primary_keys(db, szTableName, &error);
225240
g_object_unref(db);
226241

227-
if (!rec)
242+
if (!rec) {
243+
libmsi_global_unlock();
228244
return gerror_to_msi(error);
245+
}
229246

230247
MSIHANDLE handle = handle_table_alloc(G_OBJECT(rec), HANDLE_RECORD);
231248
g_object_unref(rec);
232249

250+
libmsi_global_unlock();
251+
233252
if (handle == 0)
234253
return ERROR_GEN_FAILURE;
235254

@@ -266,10 +285,14 @@ MsiDatabaseIsTablePersistentA(MSIHANDLE hDatabase, LPCSTR szTableName)
266285
if (!db)
267286
return MSICONDITION_ERROR;
268287

288+
libmsi_global_lock();
289+
269290
GError *error = NULL;
270291
gboolean persistent = libmsi_database_is_table_persistent(db, szTableName, &error);
271292
g_object_unref(db);
272293

294+
libmsi_global_unlock();
295+
273296
if (error) {
274297
g_error_free(error);
275298
return MSICONDITION_ERROR;
@@ -310,11 +333,15 @@ MsiDatabaseImportA(MSIHANDLE hDatabase, LPCSTR szFolderPath, LPCSTR szFileName)
310333
/* Build full path: folder + separator + file */
311334
char *full_path = g_build_filename(szFolderPath, szFileName, NULL);
312335

336+
libmsi_global_lock();
337+
313338
GError *error = NULL;
314339
gboolean ok = libmsi_database_import(db, full_path, &error);
315340
g_free(full_path);
316341
g_object_unref(db);
317342

343+
libmsi_global_unlock();
344+
318345
if (!ok)
319346
return gerror_to_msi(error);
320347

@@ -372,6 +399,8 @@ MsiDatabaseExportA(MSIHANDLE hDatabase, LPCSTR szTableName, LPCSTR szFolderPath,
372399
return ERROR_FUNCTION_FAILED;
373400
}
374401

402+
libmsi_global_lock();
403+
375404
GError *error = NULL;
376405
gboolean ok = libmsi_database_export(db, szTableName, fd, &error);
377406

@@ -383,6 +412,8 @@ MsiDatabaseExportA(MSIHANDLE hDatabase, LPCSTR szTableName, LPCSTR szFolderPath,
383412

384413
g_object_unref(db);
385414

415+
libmsi_global_unlock();
416+
386417
if (!ok)
387418
return gerror_to_msi(error);
388419

@@ -431,11 +462,15 @@ MsiDatabaseMergeA(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge, LPCSTR szTableN
431462
return ERROR_INVALID_HANDLE;
432463
}
433464

465+
libmsi_global_lock();
466+
434467
GError *error = NULL;
435468
gboolean ok = libmsi_database_merge(db, db_merge, szTableName, &error);
436469
g_object_unref(db);
437470
g_object_unref(db_merge);
438471

472+
libmsi_global_unlock();
473+
439474
if (!ok)
440475
return gerror_to_msi(error);
441476

@@ -473,10 +508,14 @@ MsiDatabaseApplyTransformA(MSIHANDLE hDatabase, LPCSTR szTransformFile,
473508
if (!db)
474509
return ERROR_INVALID_HANDLE;
475510

511+
libmsi_global_lock();
512+
476513
GError *error = NULL;
477514
gboolean ok = libmsi_database_apply_transform(db, szTransformFile, &error);
478515
g_object_unref(db);
479516

517+
libmsi_global_unlock();
518+
480519
if (!ok)
481520
return gerror_to_msi(error);
482521

msi-interop/handle_table.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,36 @@ static guint free_list_count = 0;
2121
static GMutex table_mutex;
2222
static gboolean initialized = FALSE;
2323

24+
/*
25+
* Global recursive mutex that serializes ALL calls into libmsi and libgsf.
26+
*
27+
* Neither libmsi nor libgsf is thread-safe:
28+
* - Every public libmsi_* function does g_return_val_if_fail(LIBMSI_IS_*)
29+
* which exercises the GObject type system concurrently.
30+
* - libmsi_database_new calls g_object_new(LIBMSI_TYPE_DATABASE, ...) and
31+
* then opens OLE compound files via libgsf, which has no internal locking.
32+
* - Summary info, query, and record operations all touch shared database
33+
* state (string pools, table lists, streams) without synchronization.
34+
*
35+
* A single GRecMutex protects all libmsi/libgsf calls. It must be recursive
36+
* because our W-suffix functions call their A-suffix counterparts (e.g.
37+
* MsiOpenDatabaseW -> MsiOpenDatabaseA), and MsiGetSummaryInformationA
38+
* may internally call MsiOpenDatabaseA when hDatabase == 0.
39+
*/
40+
static GRecMutex libmsi_mutex;
41+
42+
void
43+
libmsi_global_lock(void)
44+
{
45+
g_rec_mutex_lock(&libmsi_mutex);
46+
}
47+
48+
void
49+
libmsi_global_unlock(void)
50+
{
51+
g_rec_mutex_unlock(&libmsi_mutex);
52+
}
53+
2454
#define INITIAL_CAPACITY 64
2555

2656
void

msi-interop/handle_table.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,10 @@ unsigned int handle_table_close(MSIHANDLE handle);
3131
// Close all handles, unrefs all GObjects. Returns count of handles that were open.
3232
unsigned int handle_table_close_all(void);
3333

34+
// Global recursive mutex protecting ALL calls into libmsi/libgsf.
35+
// Must be held around every libmsi API call to prevent concurrent access
36+
// to the non-thread-safe GObject type system and libgsf storage layer.
37+
void libmsi_global_lock(void);
38+
void libmsi_global_unlock(void);
39+
3440
#endif

0 commit comments

Comments
 (0)