1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-26 01:22:12 +03:00

Use thread-safe strftime_l() instead of strftime().

This removes some setlocale() calls and a lot of commentary about how
dangerous that is.  strftime_l() is from POSIX 2008, and on Windows we
use _wcsftime_l().

While here, adjust error message for strftime_l() failure: it does not
in practice set errno (even though POSIX says it could), so no %m.

Author: Thomas Munro <thomas.munro@gmail.com>
Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Discussion: https://postgr.es/m/CA%2BhUKGJqVe0%2BPv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A%40mail.gmail.com
This commit is contained in:
Peter Eisentraut
2025-03-28 07:13:43 +01:00
parent 474d7a1fd8
commit 890fc826c9
4 changed files with 29 additions and 90 deletions

View File

@ -142,10 +142,7 @@ main(int argc, char *argv[])
init_locale("LC_MESSAGES", LC_MESSAGES, ""); init_locale("LC_MESSAGES", LC_MESSAGES, "");
#endif #endif
/* /* We keep these set to "C" always. See pg_locale.c for explanation. */
* We keep these set to "C" always, except transiently in pg_locale.c; see
* that file for explanations.
*/
init_locale("LC_MONETARY", LC_MONETARY, "C"); init_locale("LC_MONETARY", LC_MONETARY, "C");
init_locale("LC_NUMERIC", LC_NUMERIC, "C"); init_locale("LC_NUMERIC", LC_NUMERIC, "C");
init_locale("LC_TIME", LC_TIME, "C"); init_locale("LC_TIME", LC_TIME, "C");

View File

@ -18,34 +18,13 @@
* LC_MESSAGES is settable at run time and will take effect * LC_MESSAGES is settable at run time and will take effect
* immediately. * immediately.
* *
* The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are also * The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are
* settable at run-time. However, we don't actually set those locale * permanentaly set to "C", and then we use temporary locale_t
* categories permanently. This would have bizarre effects like no * objects when we need to look up locale data based on the GUCs
* longer accepting standard floating-point literals in some locales. * of the same name. Information is cached when the GUCs change.
* Instead, we only set these locale categories briefly when needed,
* cache the required information obtained from localeconv() or
* strftime(), and then set the locale categories back to "C".
* The cached information is only used by the formatting functions * The cached information is only used by the formatting functions
* (to_char, etc.) and the money type. For the user, this should all be * (to_char, etc.) and the money type. For the user, this should all be
* transparent. * transparent.
*
* !!! NOW HEAR THIS !!!
*
* We've been bitten repeatedly by this bug, so let's try to keep it in
* mind in future: on some platforms, the locale functions return pointers
* to static data that will be overwritten by any later locale function.
* Thus, for example, the obvious-looking sequence
* save = setlocale(category, NULL);
* if (!setlocale(category, value))
* fail = true;
* setlocale(category, save);
* DOES NOT WORK RELIABLY: on some platforms the second setlocale() call
* will change the memory save is pointing at. To do this sort of thing
* safely, you *must* pstrdup what setlocale returns the first time.
*
* The POSIX locale standard is available here:
*
* http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html
*---------- *----------
*/ */
@ -667,8 +646,8 @@ PGLC_localeconv(void)
* pg_strftime(), which isn't locale-aware and does not need to be replaced. * pg_strftime(), which isn't locale-aware and does not need to be replaced.
*/ */
static size_t static size_t
strftime_win32(char *dst, size_t dstlen, strftime_l_win32(char *dst, size_t dstlen,
const char *format, const struct tm *tm) const char *format, const struct tm *tm, locale_t locale)
{ {
size_t len; size_t len;
wchar_t wformat[8]; /* formats used below need 3 chars */ wchar_t wformat[8]; /* formats used below need 3 chars */
@ -684,7 +663,7 @@ strftime_win32(char *dst, size_t dstlen,
elog(ERROR, "could not convert format string from UTF-8: error code %lu", elog(ERROR, "could not convert format string from UTF-8: error code %lu",
GetLastError()); GetLastError());
len = wcsftime(wbuf, MAX_L10N_DATA, wformat, tm); len = _wcsftime_l(wbuf, MAX_L10N_DATA, wformat, tm, locale);
if (len == 0) if (len == 0)
{ {
/* /*
@ -705,8 +684,8 @@ strftime_win32(char *dst, size_t dstlen,
return len; return len;
} }
/* redefine strftime() */ /* redefine strftime_l() */
#define strftime(a,b,c,d) strftime_win32(a,b,c,d) #define strftime_l(a,b,c,d,e) strftime_l_win32(a,b,c,d,e)
#endif /* WIN32 */ #endif /* WIN32 */
/* /*
@ -748,10 +727,7 @@ cache_locale_time(void)
bool strftimefail = false; bool strftimefail = false;
int encoding; int encoding;
int i; int i;
char *save_lc_time; locale_t locale;
#ifdef WIN32
char *save_lc_ctype;
#endif
/* did we do this already? */ /* did we do this already? */
if (CurrentLCTimeValid) if (CurrentLCTimeValid)
@ -759,39 +735,16 @@ cache_locale_time(void)
elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time); elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time);
/* errno = ENOENT;
* As in PGLC_localeconv(), it's critical that we not throw error while
* libc's locale settings have nondefault values. Hence, we just call
* strftime() within the critical section, and then convert and save its
* results afterwards.
*/
/* Save prevailing value of time locale */
save_lc_time = setlocale(LC_TIME, NULL);
if (!save_lc_time)
elog(ERROR, "setlocale(NULL) failed");
save_lc_time = pstrdup(save_lc_time);
#ifdef WIN32 #ifdef WIN32
locale = _create_locale(LC_ALL, locale_time);
/* if (locale == (locale_t) 0)
* On Windows, it appears that wcsftime() internally uses LC_CTYPE, so we _dosmaperr(GetLastError());
* must set it here. This code looks the same as what PGLC_localeconv() #else
* does, but the underlying reason is different: this does NOT determine locale = newlocale(LC_ALL_MASK, locale_time, (locale_t) 0);
* the encoding we'll get back from strftime_win32().
*/
/* Save prevailing value of ctype locale */
save_lc_ctype = setlocale(LC_CTYPE, NULL);
if (!save_lc_ctype)
elog(ERROR, "setlocale(NULL) failed");
save_lc_ctype = pstrdup(save_lc_ctype);
/* use lc_time to set the ctype */
setlocale(LC_CTYPE, locale_time);
#endif #endif
if (!locale)
setlocale(LC_TIME, locale_time); report_newlocale_failure(locale_time);
/* We use times close to current time as data for strftime(). */ /* We use times close to current time as data for strftime(). */
timenow = time(NULL); timenow = time(NULL);
@ -814,10 +767,10 @@ cache_locale_time(void)
for (i = 0; i < 7; i++) for (i = 0; i < 7; i++)
{ {
timeinfo->tm_wday = i; timeinfo->tm_wday = i;
if (strftime(bufptr, MAX_L10N_DATA, "%a", timeinfo) <= 0) if (strftime_l(bufptr, MAX_L10N_DATA, "%a", timeinfo, locale) <= 0)
strftimefail = true; strftimefail = true;
bufptr += MAX_L10N_DATA; bufptr += MAX_L10N_DATA;
if (strftime(bufptr, MAX_L10N_DATA, "%A", timeinfo) <= 0) if (strftime_l(bufptr, MAX_L10N_DATA, "%A", timeinfo, locale) <= 0)
strftimefail = true; strftimefail = true;
bufptr += MAX_L10N_DATA; bufptr += MAX_L10N_DATA;
} }
@ -827,37 +780,26 @@ cache_locale_time(void)
{ {
timeinfo->tm_mon = i; timeinfo->tm_mon = i;
timeinfo->tm_mday = 1; /* make sure we don't have invalid date */ timeinfo->tm_mday = 1; /* make sure we don't have invalid date */
if (strftime(bufptr, MAX_L10N_DATA, "%b", timeinfo) <= 0) if (strftime_l(bufptr, MAX_L10N_DATA, "%b", timeinfo, locale) <= 0)
strftimefail = true; strftimefail = true;
bufptr += MAX_L10N_DATA; bufptr += MAX_L10N_DATA;
if (strftime(bufptr, MAX_L10N_DATA, "%B", timeinfo) <= 0) if (strftime_l(bufptr, MAX_L10N_DATA, "%B", timeinfo, locale) <= 0)
strftimefail = true; strftimefail = true;
bufptr += MAX_L10N_DATA; bufptr += MAX_L10N_DATA;
} }
/*
* Restore the prevailing locale settings; as in PGLC_localeconv(),
* failure to do so is fatal.
*/
#ifdef WIN32 #ifdef WIN32
if (!setlocale(LC_CTYPE, save_lc_ctype)) _free_locale(locale);
elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype); #else
freelocale(locale);
#endif #endif
if (!setlocale(LC_TIME, save_lc_time))
elog(FATAL, "failed to restore LC_TIME to \"%s\"", save_lc_time);
/* /*
* At this point we've done our best to clean up, and can throw errors, or * At this point we've done our best to clean up, and can throw errors, or
* call functions that might throw errors, with a clean conscience. * call functions that might throw errors, with a clean conscience.
*/ */
if (strftimefail) if (strftimefail)
elog(ERROR, "strftime() failed: %m"); elog(ERROR, "strftime_l() failed");
/* Release the pstrdup'd locale names */
pfree(save_lc_time);
#ifdef WIN32
pfree(save_lc_ctype);
#endif
#ifndef WIN32 #ifndef WIN32

View File

@ -59,7 +59,6 @@ static size_t strnxfrm_libc(char *dest, size_t destsize,
extern char *get_collation_actual_version_libc(const char *collcollate); extern char *get_collation_actual_version_libc(const char *collcollate);
static locale_t make_libc_collator(const char *collate, static locale_t make_libc_collator(const char *collate,
const char *ctype); const char *ctype);
static void report_newlocale_failure(const char *localename);
#ifdef WIN32 #ifdef WIN32
static int strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, static int strncoll_libc_win32_utf8(const char *arg1, ssize_t len1,
@ -801,7 +800,7 @@ strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, const char *arg2,
#endif /* WIN32 */ #endif /* WIN32 */
/* simple subroutine for reporting errors from newlocale() */ /* simple subroutine for reporting errors from newlocale() */
static void void
report_newlocale_failure(const char *localename) report_newlocale_failure(const char *localename)
{ {
int save_errno; int save_errno;

View File

@ -155,6 +155,7 @@ extern int builtin_locale_encoding(const char *locale);
extern const char *builtin_validate_locale(int encoding, const char *locale); extern const char *builtin_validate_locale(int encoding, const char *locale);
extern void icu_validate_locale(const char *loc_str); extern void icu_validate_locale(const char *loc_str);
extern char *icu_language_tag(const char *loc_str, int elevel); extern char *icu_language_tag(const char *loc_str, int elevel);
extern void report_newlocale_failure(const char *localename);
/* These functions convert from/to libc's wchar_t, *not* pg_wchar_t */ /* These functions convert from/to libc's wchar_t, *not* pg_wchar_t */
extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen, extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen,