Skip to content

Commit b891e33

Browse files
committed
fix: avoid a segfault crash in cloudsync_init
Allocate in TopMemoryContext to survive SPI cleanup
1 parent ec83414 commit b891e33

File tree

3 files changed

+45
-21
lines changed

3 files changed

+45
-21
lines changed

docker/postgresql/smoke_test.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ SET client_min_messages = debug1; SET log_min_messages = debug1;
2626
-- SET client_min_messages = debug1; SET log_min_messages = debug1;
2727

2828
-- Init on a simple table should succeed
29+
SELECT cloudsync_cleanup('smoke_tbl');
2930
DROP TABLE IF EXISTS smoke_tbl;
3031
CREATE TABLE smoke_tbl (id TEXT PRIMARY KEY, val TEXT);
3132
SELECT cloudsync_init('smoke_tbl', 'CLS', true);

src/postgresql/cloudsync_postgresql.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ void _PG_init(void) {
7676

7777
// Initialize memory debugger (NOOP in production)
7878
cloudsync_memory_init(1);
79+
80+
// load config, if exists
81+
cloudsync_context *ctx = get_cloudsync_context();
82+
if (cloudsync_config_exists(NULL)) {
83+
if (cloudsync_context_init(ctx, NULL) == NULL) {
84+
ereport(ERROR,
85+
(errcode(ERRCODE_INTERNAL_ERROR),
86+
errmsg("An error occurred while trying to initialize context")));
87+
88+
}
89+
90+
// make sure to update internal version to current version
91+
dbutils_settings_set_key_value(NULL, ctx, CLOUDSYNC_KEY_LIBVERSION, CLOUDSYNC_VERSION);
92+
}
7993
}
8094

8195
void _PG_fini(void) {

src/postgresql/database_postgresql.c

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,6 @@ static int map_spi_result(int rc) {
228228
static int set_last_error(int errcode, const char *errmsg);
229229

230230
int database_select1_value (db_t *db, const char *sql, char **ptr_value, int64_t *int_value, DBTYPE expected_type) {
231-
elog(DEBUG1, "database_select1_value: %s", sql);
232-
233231
// init values and sanity check expected_type
234232
if (ptr_value) *ptr_value = NULL;
235233
*int_value = 0;
@@ -285,7 +283,11 @@ int database_select1_value (db_t *db, const char *sql, char **ptr_value, int64_t
285283
text *txt = DatumGetTextP(datum);
286284
int len = VARSIZE(txt) - VARHDRSZ;
287285
if (len > 0) {
286+
// CRITICAL: Allocate in TopMemoryContext to survive SPI cleanup
287+
MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext);
288288
char *ptr = cloudsync_memory_alloc(len + 1);
289+
MemoryContextSwitchTo(oldctx);
290+
289291
if (!ptr) {
290292
return set_last_error(DBRES_NOMEM, "Memory allocation failed");
291293
}
@@ -298,7 +300,11 @@ int database_select1_value (db_t *db, const char *sql, char **ptr_value, int64_t
298300
bytea *ba = DatumGetByteaP(datum);
299301
int len = VARSIZE(ba) - VARHDRSZ;
300302
if (len > 0) {
303+
// CRITICAL: Allocate in TopMemoryContext to survive SPI cleanup
304+
MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext);
301305
char *ptr = cloudsync_memory_alloc(len);
306+
MemoryContextSwitchTo(oldctx);
307+
302308
if (!ptr) {
303309
return set_last_error(DBRES_NOMEM, "Memory allocation failed");
304310
}
@@ -336,7 +342,11 @@ int database_select3_values (db_t *db, const char *sql, char **value, int64_t *l
336342
bytea *ba = DatumGetByteaP(datum1);
337343
int blob_len = VARSIZE(ba) - VARHDRSZ;
338344
if (blob_len > 0) {
345+
// Allocate in TopMemoryContext to survive SPI cleanup
346+
MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext);
339347
char *ptr = cloudsync_memory_alloc(blob_len);
348+
MemoryContextSwitchTo(oldctx);
349+
340350
if (!ptr) return DBRES_NOMEM;
341351
memcpy(ptr, VARDATA(ba), blob_len);
342352
*value = ptr;
@@ -346,7 +356,11 @@ int database_select3_values (db_t *db, const char *sql, char **value, int64_t *l
346356
text *txt = DatumGetTextP(datum1);
347357
int text_len = VARSIZE(txt) - VARHDRSZ;
348358
if (text_len > 0) {
359+
// Allocate in TopMemoryContext to survive SPI cleanup
360+
MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext);
349361
char *ptr = cloudsync_memory_alloc(text_len + 1);
362+
MemoryContextSwitchTo(oldctx);
363+
350364
if (!ptr) return DBRES_NOMEM;
351365
memcpy(ptr, VARDATA(txt), text_len);
352366
ptr[text_len] = '\0';
@@ -418,7 +432,6 @@ bool database_system_exists (db_t *db, const char *name, const char *type) {
418432
// MARK: - GENERAL -
419433

420434
int database_exec (db_t *db, const char *sql) {
421-
elog(DEBUG1, "database_exec %s", sql);
422435
if (!sql) return set_last_error(DBRES_ERROR, "SQL statement is NULL");
423436

424437
int rc;
@@ -457,7 +470,6 @@ int database_exec (db_t *db, const char *sql) {
457470
}
458471

459472
int database_exec_callback (db_t *db, const char *sql, int (*callback)(void *xdata, int argc, char **values, char **names), void *xdata) {
460-
elog(DEBUG1, "database_exec_callback %s", sql);
461473
if (!sql) return set_last_error(DBRES_ERROR, "SQL statement is NULL");;
462474

463475
int rc;
@@ -608,8 +620,6 @@ static char *last_error_msg = NULL;
608620
// Helper function to record errors and return the error code
609621
// This allows callers to write: return set_last_error(code, msg);
610622
static int set_last_error(int errcode, const char *errmsg) {
611-
// elog(DEBUG1, "set_last_error: %d %s", errcode, errmsg ? errmsg : "(null)");
612-
613623
last_error_code = errcode;
614624

615625
if (last_error_msg) {
@@ -830,9 +840,13 @@ uint64_t database_schema_hash (db_t *db) {
830840
"FROM information_schema.columns WHERE table_schema = 'public'",
831841
&schema);
832842

833-
if (!schema) return 0;
843+
if (!schema) {
844+
elog(INFO, "database_schema_hash: schema is NULL");
845+
return 0;
846+
}
834847

835-
uint64_t hash = fnv1a_hash(schema, strlen(schema));
848+
size_t schema_len = strlen(schema);
849+
uint64_t hash = fnv1a_hash(schema, schema_len);
836850
cloudsync_memory_free(schema);
837851
return hash;
838852
}
@@ -855,7 +869,9 @@ int database_update_schema_hash (db_t *db, uint64_t *hash) {
855869

856870
if (rc != DBRES_OK || !schema) return set_last_error(DBRES_ERROR, "database_update_schema_hash error 1");
857871

858-
uint64_t h = fnv1a_hash(schema, strlen(schema));
872+
size_t schema_len = strlen(schema);
873+
DEBUG_ALWAYS("database_update_schema_hash len %zu", schema_len);
874+
uint64_t h = fnv1a_hash(schema, schema_len);
859875
cloudsync_memory_free(schema);
860876
if (hash && *hash == h) return set_last_error(DBRES_CONSTRAINT, "database_update_schema_hash constraint");
861877

@@ -878,15 +894,17 @@ int database_update_schema_hash (db_t *db, uint64_t *hash) {
878894
// MARK: - VM -
879895

880896
int database_prepare (db_t *db, const char *sql, dbvm_t **vm, int flags) {
881-
elog(DEBUG1, "database_prepare: %s", sql);
882-
883897
if (!sql || !vm) {
884898
return set_last_error(DBRES_ERROR, "Invalid parameters to database_prepare");
885899
}
886900

901+
// Allocate wrapper/sql in a long-lived context (SPI contexts can be reset on SPI_finish).
902+
MemoryContext oldctx = MemoryContextSwitchTo(TopMemoryContext);
903+
887904
// Convert ? placeholders to $1, $2, etc.
888905
char *pg_sql = convert_placeholders(sql);
889906
if (!pg_sql) {
907+
MemoryContextSwitchTo(oldctx);
890908
return set_last_error(DBRES_ERROR, "Failed to convert SQL placeholders");
891909
}
892910

@@ -906,11 +924,11 @@ int database_prepare (db_t *db, const char *sql, dbvm_t **vm, int flags) {
906924
}
907925

908926
*vm = (dbvm_t*)wrapper;
927+
MemoryContextSwitchTo(oldctx);
909928
return set_last_error(DBRES_OK, NULL);
910929
}
911930

912931
int databasevm_step (dbvm_t *vm) {
913-
elog(DEBUG1, "databasevm_step: %s", databasevm_sql(vm));
914932
if (!vm) {
915933
return set_last_error(DBRES_ERROR, "NULL vm in databasevm_step");
916934
}
@@ -1002,7 +1020,6 @@ int databasevm_step (dbvm_t *vm) {
10021020
}
10031021

10041022
void databasevm_finalize (dbvm_t *vm) {
1005-
elog(DEBUG1, "databasevm_finalize: %s", databasevm_sql(vm));
10061023
if (!vm) return;
10071024

10081025
pg_stmt_wrapper_t *wrapper = (pg_stmt_wrapper_t*)vm;
@@ -1023,7 +1040,6 @@ void databasevm_finalize (dbvm_t *vm) {
10231040
}
10241041

10251042
void databasevm_reset (dbvm_t *vm) {
1026-
elog(DEBUG1, "databasevm_reset: %s", databasevm_sql(vm));
10271043
if (!vm) return;
10281044

10291045
pg_stmt_wrapper_t *wrapper = (pg_stmt_wrapper_t*)vm;
@@ -1038,7 +1054,6 @@ void databasevm_reset (dbvm_t *vm) {
10381054
}
10391055

10401056
void databasevm_clear_bindings (dbvm_t *vm) {
1041-
elog(DEBUG1, "databasevm_clear_bindings: %s", databasevm_sql(vm));
10421057
if (!vm) return;
10431058

10441059
pg_stmt_wrapper_t *wrapper = (pg_stmt_wrapper_t*)vm;
@@ -1577,8 +1592,6 @@ void database_result_value (dbcontext_t *context, dbvalue_t *value) {
15771592
// MARK: - SAVEPOINTS -
15781593

15791594
int database_begin_savepoint (db_t *db, const char *savepoint_name) {
1580-
elog(DEBUG1, "database_begin_savepoint: %s", savepoint_name);
1581-
15821595
PG_TRY();
15831596
{
15841597
BeginInternalSubTransaction(NULL);
@@ -1598,8 +1611,6 @@ int database_begin_savepoint (db_t *db, const char *savepoint_name) {
15981611
}
15991612

16001613
int database_commit_savepoint (db_t *db, const char *savepoint_name) {
1601-
elog(DEBUG1, "database_commit_savepoint: %s", savepoint_name);
1602-
16031614
PG_TRY();
16041615
{
16051616
ReleaseCurrentSubTransaction();
@@ -1623,8 +1634,6 @@ int database_commit_savepoint (db_t *db, const char *savepoint_name) {
16231634
}
16241635

16251636
int database_rollback_savepoint (db_t *db, const char *savepoint_name) {
1626-
elog(DEBUG1, "database_rollback_savepoint: %s", savepoint_name);
1627-
16281637
PG_TRY();
16291638
{
16301639
RollbackAndReleaseCurrentSubTransaction();

0 commit comments

Comments
 (0)