]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Use thread-safe strftime_l() instead of strftime().
authorPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Mar 2025 06:13:43 +0000 (07:13 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Mar 2025 06:13:43 +0000 (07:13 +0100)
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

src/backend/main/main.c
src/backend/utils/adt/pg_locale.c
src/backend/utils/adt/pg_locale_libc.c
src/include/utils/pg_locale.h

index e8effe50242b6029b35f577865147b6ba5bc25ea..7d63cf94a6b44a3e030dfed61642a1d041d4fe52 100644 (file)
@@ -142,10 +142,7 @@ main(int argc, char *argv[])
        init_locale("LC_MESSAGES", LC_MESSAGES, "");
 #endif
 
-       /*
-        * We keep these set to "C" always, except transiently in pg_locale.c; see
-        * that file for explanations.
-        */
+       /* We keep these set to "C" always.  See pg_locale.c for explanation. */
        init_locale("LC_MONETARY", LC_MONETARY, "C");
        init_locale("LC_NUMERIC", LC_NUMERIC, "C");
        init_locale("LC_TIME", LC_TIME, "C");
index 4dd4313b779bdcfbe20c2937cdcc9e194755285a..6e43b708c0f7a0ef69f3ef1337ac911fa4999a07 100644 (file)
  * LC_MESSAGES is settable at run time and will take effect
  * immediately.
  *
- * The other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are also
- * settable at run-time.  However, we don't actually set those locale
- * categories permanently.  This would have bizarre effects like no
- * longer accepting standard floating-point literals in some locales.
- * 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 other categories, LC_MONETARY, LC_NUMERIC, and LC_TIME are
+ * permanentaly set to "C", and then we use temporary locale_t
+ * objects when we need to look up locale data based on the GUCs
+ * of the same name.  Information is cached when the GUCs change.
  * The cached information is only used by the formatting functions
  * (to_char, etc.) and the money type.  For the user, this should all be
  * 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.
  */
 static size_t
-strftime_win32(char *dst, size_t dstlen,
-                          const char *format, const struct tm *tm)
+strftime_l_win32(char *dst, size_t dstlen,
+                                const char *format, const struct tm *tm, locale_t locale)
 {
        size_t          len;
        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",
                         GetLastError());
 
-       len = wcsftime(wbuf, MAX_L10N_DATA, wformat, tm);
+       len = _wcsftime_l(wbuf, MAX_L10N_DATA, wformat, tm, locale);
        if (len == 0)
        {
                /*
@@ -705,8 +684,8 @@ strftime_win32(char *dst, size_t dstlen,
        return len;
 }
 
-/* redefine strftime() */
-#define strftime(a,b,c,d) strftime_win32(a,b,c,d)
+/* redefine strftime_l() */
+#define strftime_l(a,b,c,d,e) strftime_l_win32(a,b,c,d,e)
 #endif                                                 /* WIN32 */
 
 /*
@@ -748,10 +727,7 @@ cache_locale_time(void)
        bool            strftimefail = false;
        int                     encoding;
        int                     i;
-       char       *save_lc_time;
-#ifdef WIN32
-       char       *save_lc_ctype;
-#endif
+       locale_t        locale;
 
        /* did we do this already? */
        if (CurrentLCTimeValid)
@@ -759,39 +735,16 @@ cache_locale_time(void)
 
        elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time);
 
-       /*
-        * 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);
-
+       errno = ENOENT;
 #ifdef WIN32
-
-       /*
-        * On Windows, it appears that wcsftime() internally uses LC_CTYPE, so we
-        * must set it here.  This code looks the same as what PGLC_localeconv()
-        * does, but the underlying reason is different: this does NOT determine
-        * 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);
+       locale = _create_locale(LC_ALL, locale_time);
+       if (locale == (locale_t) 0)
+               _dosmaperr(GetLastError());
+#else
+       locale = newlocale(LC_ALL_MASK, locale_time, (locale_t) 0);
 #endif
-
-       setlocale(LC_TIME, locale_time);
+       if (!locale)
+               report_newlocale_failure(locale_time);
 
        /* We use times close to current time as data for strftime(). */
        timenow = time(NULL);
@@ -814,10 +767,10 @@ cache_locale_time(void)
        for (i = 0; i < 7; 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;
                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;
                bufptr += MAX_L10N_DATA;
        }
@@ -827,37 +780,26 @@ cache_locale_time(void)
        {
                timeinfo->tm_mon = i;
                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;
                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;
                bufptr += MAX_L10N_DATA;
        }
 
-       /*
-        * Restore the prevailing locale settings; as in PGLC_localeconv(),
-        * failure to do so is fatal.
-        */
 #ifdef WIN32
-       if (!setlocale(LC_CTYPE, save_lc_ctype))
-               elog(FATAL, "failed to restore LC_CTYPE to \"%s\"", save_lc_ctype);
+       _free_locale(locale);
+#else
+       freelocale(locale);
 #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
         * call functions that might throw errors, with a clean conscience.
         */
        if (strftimefail)
-               elog(ERROR, "strftime() failed: %m");
-
-       /* Release the pstrdup'd locale names */
-       pfree(save_lc_time);
-#ifdef WIN32
-       pfree(save_lc_ctype);
-#endif
+               elog(ERROR, "strftime_l() failed");
 
 #ifndef WIN32
 
index 8f9a86378971b4fa32a5d638b4a2aae172d0365c..199857e22dbecc145e4a226c01478870190a7833 100644 (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);
 static locale_t make_libc_collator(const char *collate,
                                                                   const char *ctype);
-static void report_newlocale_failure(const char *localename);
 
 #ifdef WIN32
 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 */
 
 /* simple subroutine for reporting errors from newlocale() */
-static void
+void
 report_newlocale_failure(const char *localename)
 {
        int                     save_errno;
index 0d5f0513cebab00a18aae6e7d0fb0ec0f88283e6..7df90f43f60e85e94cec3329913c5966da6e7774 100644 (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 void icu_validate_locale(const char *loc_str);
 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 */
 extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen,