From 4fa197f4beb0edabb426bb0a7e998e7dba0aacab Mon Sep 17 00:00:00 2001 From: Peter Eisentraut 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 . (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 . + */ +#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 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