Here is a patch for using gmtime_r() and localtime_r() instead of
gmtime() and localtime(), for thread-safety.
There are a few affected calls in libpq and ecpg's libpgtypes, which are
probably effectively bugs, because those libraries already claim to be
thread-safe.
There is one affected call in the backend. Most of the backend
otherwise uses the custom functions pg_gmtime() and pg_localtime(),
which are implemented differently.
Some portability fun: gmtime_r() and localtime_r() are in POSIX but are
not available on Windows. Windows has functions gmtime_s() and
localtime_s() that can fulfill the same purpose, so we can add some
small wrappers around them. (Note that these *_s() functions are also
different from the *_s() functions in the bounds-checking extension of
C11. We are not using those here.)
MinGW exposes neither *_r() nor *_s() by default. You can get at the
POSIX-style *_r() functions by defining _POSIX_C_SOURCE appropriately
before including <time.h>. (There is apparently probably also a way to
get at the Windows-style *_s() functions by supplying some additional
options or defines. But we might as well just use the POSIX ones.)From 4fa197f4beb0edabb426bb0a7e998e7dba0aacab Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Wed, 26 Jun 2024 20:37:02 +0200
Subject: [PATCH] thread-safety: gmtime_r(), localtime_r()
Use gmtime_r() and localtime_r() instead of gmtime() and localtime(),
for thread-safety.
There are a few affected calls in libpq and ecpg's libpgtypes, which
are probably effectively bugs, because those libraries already claim
to be thread-safe.
There is one affected call in the backend. Most of the backend
otherwise uses the custom functions pg_gmtime() and pg_localtime(),
which are implemented differently.
Portability: gmtime_r() and localtime_r() are in POSIX but are not
available on Windows. Windows has functions gmtime_s() and
localtime_s() that can fulfill the same purpose, so we add some small
wrappers around them. (Note that these *_s() functions are also
different from the *_s() functions in the bounds-checking extension of
C11. We are not using those here.)
MinGW exposes neither *_r() nor *_s() by default. You can get at the
POSIX-style *_r() functions by defining _POSIX_C_SOURCE appropriately
before including <time.h>. (There is probably also a way to get at
the Windows-style *_s() functions by supplying some additional options
or defines. But we might as well just use the POSIX ones.)
---
src/backend/utils/adt/pg_locale.c | 3 ++-
src/include/port/win32_port.h | 11 +++++++++++
src/interfaces/ecpg/pgtypeslib/dt_common.c | 11 +++++++----
src/interfaces/ecpg/pgtypeslib/timestamp.c | 3 ++-
src/interfaces/libpq/fe-trace.c | 3 ++-
5 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c
b/src/backend/utils/adt/pg_locale.c
index 7e5bb2b703a..55621e555b9 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -809,6 +809,7 @@ cache_locale_time(void)
char *bufptr;
time_t timenow;
struct tm *timeinfo;
+ struct tm timeinfobuf;
bool strftimefail = false;
int encoding;
int i;
@@ -859,7 +860,7 @@ cache_locale_time(void)
/* We use times close to current time as data for strftime(). */
timenow = time(NULL);
- timeinfo = localtime(&timenow);
+ timeinfo = localtime_r(&timenow, &timeinfobuf);
/* Store the strftime results in MAX_L10N_DATA-sized portions of buf[]
*/
bufptr = buf;
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 3d1de166cb0..6d5b8b50f23 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -415,6 +415,17 @@ extern int _pglstat64(const char *name, struct stat *buf);
#undef ETIMEDOUT
#define ETIMEDOUT WSAETIMEDOUT
+/*
+ * Supplement to <time.h>.
+ */
+#ifdef _MSC_VER
+#define gmtime_r(clock, result) (gmtime_s(result, clock) ? NULL : (result))
+#define localtime_r(clock, result) (localtime_s(result, clock) ? NULL :
(result))
+#else
+/* define before including <time.h> for getting localtime_r() etc. on MinGW */
+#define _POSIX_C_SOURCE 1
+#endif
+
/*
* Locale stuff.
*
diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c
b/src/interfaces/ecpg/pgtypeslib/dt_common.c
index ed08088bfe1..48074fd3ad1 100644
--- a/src/interfaces/ecpg/pgtypeslib/dt_common.c
+++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c
@@ -949,9 +949,10 @@ int
GetEpochTime(struct tm *tm)
{
struct tm *t0;
+ struct tm tmbuf;
time_t epoch = 0;
- t0 = gmtime(&epoch);
+ t0 = gmtime_r(&epoch, &tmbuf);
if (t0)
{
@@ -973,12 +974,13 @@ abstime2tm(AbsoluteTime _time, int *tzp, struct tm *tm,
char **tzn)
{
time_t time = (time_t) _time;
struct tm *tx;
+ struct tm tmbuf;
errno = 0;
if (tzp != NULL)
- tx = localtime((time_t *) &time);
+ tx = localtime_r((time_t *) &time, &tmbuf);
else
- tx = gmtime((time_t *) &time);
+ tx = gmtime_r((time_t *) &time, &tmbuf);
if (!tx)
{
@@ -2810,9 +2812,10 @@ PGTYPEStimestamp_defmt_scan(char **str, char *fmt,
timestamp * d,
/* number of seconds in scan_val.luint_val */
{
struct tm *tms;
+ struct tm tmbuf;
time_t et = (time_t)
scan_val.luint_val;
- tms = gmtime(&et);
+ tms = gmtime_r(&et, &tmbuf);
if (tms)
{
diff --git a/src/interfaces/ecpg/pgtypeslib/timestamp.c
b/src/interfaces/ecpg/pgtypeslib/timestamp.c
index f1b143fbd2e..2934718c281 100644
--- a/src/interfaces/ecpg/pgtypeslib/timestamp.c
+++ b/src/interfaces/ecpg/pgtypeslib/timestamp.c
@@ -134,11 +134,12 @@ timestamp2tm(timestamp dt, int *tzp, struct tm *tm,
fsec_t *fsec, const char **t
if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
{
#if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
+ struct tm tmbuf;
utime = dt / USECS_PER_SEC +
((date0 - date2j(1970, 1, 1)) *
INT64CONST(86400));
- tx = localtime(&utime);
+ tx = localtime_r(&utime, &tmbuf);
tm->tm_year = tx->tm_year + 1900;
tm->tm_mon = tx->tm_mon + 1;
tm->tm_mday = tx->tm_mday;
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index d7a61ec9cc1..9832329de9b 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -81,6 +81,7 @@ pqTraceFormatTimestamp(char *timestr, size_t ts_len)
{
struct timeval tval;
time_t now;
+ struct tm tmbuf;
gettimeofday(&tval, NULL);
@@ -93,7 +94,7 @@ pqTraceFormatTimestamp(char *timestr, size_t ts_len)
now = tval.tv_sec;
strftime(timestr, ts_len,
"%Y-%m-%d %H:%M:%S",
- localtime(&now));
+ localtime_r(&now, &tmbuf));
/* append microseconds */
snprintf(timestr + strlen(timestr), ts_len - strlen(timestr),
".%06u", (unsigned int) (tval.tv_usec));
--
2.45.2