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:
@ -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");
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user