From 2d0a40637d6c463346e7cce318419909366b1847 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Mon, 13 Jul 2026 14:25:25 -0500 Subject: [PATCH v1 2/2] pgstat: change to_serialized_data callback to return bool Change the to_serialized_data callback signature from void to bool, making it symmetric with from_serialized_data. This allows extensions to signal write failure back to pgstat_write_statsfile(). When the callback returns false, pgstat_write_statsfile() discards the temporary stats file, preventing a corrupt file from being committed to disk. Update test_custom_stats to check write_chunk() return values and propagate failure through the new bool return. --- src/backend/utils/activity/pgstat.c | 16 ++++++++-- src/include/utils/pgstat_internal.h | 6 ++-- .../test_custom_stats/test_custom_var_stats.c | 29 ++++++++++++------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index debcc87af50..77bd3880b59 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -1775,8 +1775,12 @@ pgstat_write_statsfile(void) pgstat_get_entry_len(ps->key.kind)); /* Write more data for the entry, if required */ - if (kind_info->to_serialized_data) - kind_info->to_serialized_data(&ps->key, shstats, fpout); + if (kind_info->to_serialized_data && + !kind_info->to_serialized_data(&ps->key, shstats, fpout)) + { + dshash_seq_term(&hstat); + goto error; + } } dshash_seq_term(&hstat); @@ -1810,6 +1814,14 @@ pgstat_write_statsfile(void) unlink(tmpfile); } + goto finish; + +error: + elog(DEBUG2, "discarding temporary statistics file \"%s\"", tmpfile); + FreeFile(fpout); + unlink(tmpfile); + +finish: /* Finish callbacks, if required */ for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) { diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index 02022f19b81..1c03e797cbb 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -322,7 +322,9 @@ typedef struct PgStat_KindInfo * an entry, in the stats file or optionally in a different file. * Optional. * - * to_serialized_data: write auxiliary data for an entry. + * to_serialized_data: write auxiliary data for an entry. Returns true on + * success, false on write error. When false is returned, + * pgstat_write_statsfile() will discard the stats file. * * from_serialized_data: read auxiliary data for an entry. Returns true * on success, false on read error. @@ -332,7 +334,7 @@ typedef struct PgStat_KindInfo * just written or read. "header" is a pointer to the stats data; it may * be modified only in from_serialized_data to reconstruct an entry. */ - void (*to_serialized_data) (const PgStat_HashKey *key, + bool (*to_serialized_data) (const PgStat_HashKey *key, const PgStatShared_Common *header, FILE *statfile); bool (*from_serialized_data) (const PgStat_HashKey *key, diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index 34d474be604..a39ada0b67c 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -26,7 +26,7 @@ PG_MODULE_MAGIC_EXT( ); /* Local helpers for stats file I/O */ -#define write_chunk(fpout, ptr, len) ((void) fwrite(ptr, len, 1, fpout)) +#define write_chunk(fpout, ptr, len) (fwrite(ptr, len, 1, fpout) == 1) #define write_chunk_s(fpout, ptr) write_chunk(fpout, ptr, sizeof(*ptr)) #define read_chunk(fpin, ptr, len) (fread(ptr, 1, len, fpin) == (len)) #define read_chunk_s(fpin, ptr) read_chunk(fpin, ptr, sizeof(*ptr)) @@ -94,7 +94,7 @@ static bool test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait); /* Serialization callback: write auxiliary entry data */ -static void test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, +static bool test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, const PgStatShared_Common *header, FILE *statfile); @@ -193,7 +193,7 @@ test_custom_stats_var_flush_pending_cb(PgStat_EntryRef *entry_ref, bool nowait) * - The length of the description. * - The description data itself. */ -static void +static bool test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, const PgStatShared_Common *header, FILE *statfile) @@ -208,7 +208,8 @@ test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, * First mark the main file with a magic number, keeping a trace that some * auxiliary data will exist in the secondary statistics file. */ - write_chunk_s(statfile, &magic_number); + if (!write_chunk_s(statfile, &magic_number)) + return false; /* Open statistics file for writing. */ if (!fd_description) @@ -220,7 +221,7 @@ test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, (errcode_for_file_access(), errmsg("could not open statistics file \"%s\" for writing: %m", TEST_CUSTOM_AUX_DATA_DESC))); - return; + return false; } /* Initialize offset for secondary statistics file. */ @@ -228,14 +229,16 @@ test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, } /* Write offset to the main data file */ - write_chunk_s(statfile, &fd_description_offset); + if (!write_chunk_s(statfile, &fd_description_offset)) + return false; /* * First write the entry key to the secondary statistics file. This will * be cross-checked with the key read from main stats file at loading * time. */ - write_chunk_s(fd_description, (PgStat_HashKey *) key); + if (!write_chunk_s(fd_description, (PgStat_HashKey *) key)) + return false; fd_description_offset += sizeof(PgStat_HashKey); if (!custom_stats_description_dsa) @@ -246,9 +249,10 @@ test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, { /* length to description file */ len = 0; - write_chunk_s(fd_description, &len); + if (!write_chunk_s(fd_description, &len)) + return false; fd_description_offset += sizeof(size_t); - return; + return true; } /* @@ -258,14 +262,17 @@ test_custom_stats_var_to_serialized_data(const PgStat_HashKey *key, description = dsa_get_address(custom_stats_description_dsa, entry->description); len = strlen(description) + 1; - write_chunk_s(fd_description, &len); - write_chunk(fd_description, description, len); + if (!write_chunk_s(fd_description, &len)) + return false; + if (!write_chunk(fd_description, description, len)) + return false; /* * Update offset for next entry, counting for the length (size_t) of the * description and the description contents. */ fd_description_offset += len + sizeof(size_t); + return true; } /* -- 2.50.1 (Apple Git-155)