On 09.02.25 15:52, Peter Eisentraut wrote:
This patch set is still desirable. Here is a rebased version of the v5
patches. I haven't applied any corrections or review comments.
Here is the same patch set with some review comments.
Patch 0001 looks okay to me. I'm just offering some cosmetic
improvements in patch 0004.
Patch 0002 also looks okay, except that the error handling could be
unified with existing code, as I had previously pointed out. Patch 0005
fixes that.
About patch 0003:
I had previously pointed out that the canonicalization might have been
intentional, and that we have canonicalization of ICU locale names. But
we don't have to keep the setlocale()-based locale checking
implementation just for that, I think. (If this was meant to be a real
feature offered by libc, there should be a get_locale_name(locale_t)
function.)
I'm unsure about the correct error handling of _create_locale() on
Windows. Does _dosmaperr(GetLastError()) do anything useful in this
context, or is this just copied from elsewhere? If it's useful, maybe
it should be added to report_newlocale_failure().
Also, maybe we don't need per-category locale checking? Would it not be
enough to check the locale using LC_ALL_MASK? Is there a scenario where
a locale name would work for one category but not another? I think the
old code was just conveniently coded that way so that you only have to
save and restore one locale category. But we wouldn't have to do it
that way anymore if we use newlocale().
From 94879f695ff8961255c2daa46b81ce378a55732d Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.mu...@gmail.com>
Date: Tue, 13 Aug 2024 14:15:54 +1200
Subject: [PATCH v6.1 1/5] Provide thread-safe pg_localeconv_r().
This involves four different implementation strategies:
1. For Windows, we now require _configthreadlocale() to be available
and work, and the documentation says that the object returned by
localeconv() is in thread-local memory.
2. For glibc, we translate to nl_langinfo_l() calls, because it offers
the same information that way as an extension, and that API is
thread-safe.
3. For macOS/*BSD, use localeconv_l(), which is thread-safe.
4. For everything else, use uselocale() to set the locale for the
thread, and use a big ugly lock to defend against the returned object
being concurrently clobbered. In practice this currently means only
Solaris.
The new call is used in pg_locale.c, replacing calls to setlocale() and
localeconv().
This patch adds a hard requirement on Windows' _configthreadlocale().
In the past there were said to be MinGW systems that didn't have it, or
had it but it didn't work. As far as I can tell, CI (optional MinGW
task + mingw cross build warning task) and build farm (fairywren msys)
should be happy with this. Fingers crossed. (There are places that use
configure checks for that in ECPG; other proposed patches would remove
those later.)
Reviewed-by: Heikki Linnakangas <hlinn...@iki.fi>
Discussion:
https://postgr.es/m/CA%2BhUKGJqVe0%2BPv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A%40mail.gmail.com
---
configure | 2 +-
configure.ac | 1 +
meson.build | 1 +
src/backend/utils/adt/pg_locale.c | 128 ++---------
src/include/pg_config.h.in | 3 +
src/include/port.h | 6 +
src/port/Makefile | 1 +
src/port/meson.build | 1 +
src/port/pg_localeconv_r.c | 367 ++++++++++++++++++++++++++++++
9 files changed, 402 insertions(+), 108 deletions(-)
create mode 100644 src/port/pg_localeconv_r.c
diff --git a/configure b/configure
index 0ffcaeb4367..3e7c5fc91d6 100755
--- a/configure
+++ b/configure
@@ -14934,7 +14934,7 @@ fi
LIBS_including_readline="$LIBS"
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
-for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info
getauxval getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s
posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast
strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l
+for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info
getauxval getifaddrs getpeerucred inet_pton localeconv_l kqueue mbstowcs_l
memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle
setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale
wcstombs_l
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.ac b/configure.ac
index f56681e0d91..e56136049e9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1710,6 +1710,7 @@ AC_CHECK_FUNCS(m4_normalize([
getifaddrs
getpeerucred
inet_pton
+ localeconv_l
kqueue
mbstowcs_l
memset_s
diff --git a/meson.build b/meson.build
index 1ceadb9a830..a8a6f34f6c6 100644
--- a/meson.build
+++ b/meson.build
@@ -2634,6 +2634,7 @@ func_checks = [
['inet_aton'],
['inet_pton'],
['kqueue'],
+ ['localeconv_l'],
['mbstowcs_l'],
['memset_s'],
['mkdtemp'],
diff --git a/src/backend/utils/adt/pg_locale.c
b/src/backend/utils/adt/pg_locale.c
index 7d92f580a57..4dd4313b779 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -547,12 +547,8 @@ PGLC_localeconv(void)
static struct lconv CurrentLocaleConv;
static bool CurrentLocaleConvAllocated = false;
struct lconv *extlconv;
- struct lconv worklconv;
- char *save_lc_monetary;
- char *save_lc_numeric;
-#ifdef WIN32
- char *save_lc_ctype;
-#endif
+ struct lconv tmp;
+ struct lconv worklconv = {0};
/* Did we do it already? */
if (CurrentLocaleConvValid)
@@ -566,77 +562,21 @@ PGLC_localeconv(void)
}
/*
- * This is tricky because we really don't want to risk throwing error
- * while the locale is set to other than our usual settings. Therefore,
- * the process is: collect the usual settings, set locale to special
- * setting, copy relevant data into worklconv using strdup(), restore
- * normal settings, convert data to desired encoding, and finally stash
- * the collected data in CurrentLocaleConv. This makes it safe if we
- * throw an error during encoding conversion or run out of memory
anywhere
- * in the process. All data pointed to by struct lconv members is
- * allocated with strdup, to avoid premature elog(ERROR) and to allow
- * using a single cleanup routine.
+ * Use thread-safe method of obtaining a copy of lconv from the
operating
+ * system.
*/
- memset(&worklconv, 0, sizeof(worklconv));
-
- /* Save prevailing values of monetary and numeric locales */
- save_lc_monetary = setlocale(LC_MONETARY, NULL);
- if (!save_lc_monetary)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_monetary = pstrdup(save_lc_monetary);
-
- save_lc_numeric = setlocale(LC_NUMERIC, NULL);
- if (!save_lc_numeric)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_numeric = pstrdup(save_lc_numeric);
-
-#ifdef WIN32
-
- /*
- * The POSIX standard explicitly says that it is undefined what happens
if
- * LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from
- * that implied by LC_CTYPE. In practice, all Unix-ish platforms seem
to
- * believe that localeconv() should return strings that are encoded in
the
- * codeset implied by the LC_MONETARY or LC_NUMERIC locale name. Hence,
- * once we have successfully collected the localeconv() results, we will
- * convert them from that codeset to the desired server encoding.
- *
- * Windows, of course, resolutely does things its own way; on that
- * platform LC_CTYPE has to match LC_MONETARY/LC_NUMERIC to get sane
- * results. Hence, we must temporarily set that category as well.
- */
-
- /* 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);
-
- /* Here begins the critical section where we must not throw error */
-
- /* use numeric to set the ctype */
- setlocale(LC_CTYPE, locale_numeric);
-#endif
-
- /* Get formatting information for numeric */
- setlocale(LC_NUMERIC, locale_numeric);
- extlconv = localeconv();
-
- /* Must copy data now in case setlocale() overwrites it */
+ if (pg_localeconv_r(locale_monetary,
+ locale_numeric,
+ &tmp) != 0)
+ elog(ERROR,
+ "could not get lconv for LC_MONETARY = \"%s\",
LC_NUMERIC = \"%s\": %m",
+ locale_monetary, locale_numeric);
+
+ /* Must copy data now now so we can re-encode it. */
+ extlconv = &tmp;
worklconv.decimal_point = strdup(extlconv->decimal_point);
worklconv.thousands_sep = strdup(extlconv->thousands_sep);
worklconv.grouping = strdup(extlconv->grouping);
-
-#ifdef WIN32
- /* use monetary to set the ctype */
- setlocale(LC_CTYPE, locale_monetary);
-#endif
-
- /* Get formatting information for monetary */
- setlocale(LC_MONETARY, locale_monetary);
- extlconv = localeconv();
-
- /* Must copy data now in case setlocale() overwrites it */
worklconv.int_curr_symbol = strdup(extlconv->int_curr_symbol);
worklconv.currency_symbol = strdup(extlconv->currency_symbol);
worklconv.mon_decimal_point = strdup(extlconv->mon_decimal_point);
@@ -654,45 +594,19 @@ PGLC_localeconv(void)
worklconv.p_sign_posn = extlconv->p_sign_posn;
worklconv.n_sign_posn = extlconv->n_sign_posn;
- /*
- * Restore the prevailing locale settings; failure to do so is fatal.
- * Possibly we could limp along with nondefault LC_MONETARY or
LC_NUMERIC,
- * but proceeding with the wrong value of LC_CTYPE would certainly be
bad
- * news; and considering that the prevailing LC_MONETARY and LC_NUMERIC
- * are almost certainly "C", there's really no reason that restoring
those
- * should fail.
- */
-#ifdef WIN32
- if (!setlocale(LC_CTYPE, save_lc_ctype))
- elog(FATAL, "failed to restore LC_CTYPE to \"%s\"",
save_lc_ctype);
-#endif
- if (!setlocale(LC_MONETARY, save_lc_monetary))
- elog(FATAL, "failed to restore LC_MONETARY to \"%s\"",
save_lc_monetary);
- if (!setlocale(LC_NUMERIC, save_lc_numeric))
- elog(FATAL, "failed to restore LC_NUMERIC to \"%s\"",
save_lc_numeric);
+ /* Free the contents of the object populated by pg_localeconv_r(). */
+ pg_localeconv_free(&tmp);
+
+ /* If any of the preceding strdup calls failed, complain now. */
+ if (!struct_lconv_is_valid(&worklconv))
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
- /*
- * At this point we've done our best to clean up, and can call functions
- * that might possibly throw errors with a clean conscience. But let's
- * make sure we don't leak any already-strdup'd fields in worklconv.
- */
PG_TRY();
{
int encoding;
- /* Release the pstrdup'd locale names */
- pfree(save_lc_monetary);
- pfree(save_lc_numeric);
-#ifdef WIN32
- pfree(save_lc_ctype);
-#endif
-
- /* If any of the preceding strdup calls failed, complain now. */
- if (!struct_lconv_is_valid(&worklconv))
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
-
/*
* Now we must perform encoding conversion from whatever's
associated
* with the locales into the database encoding. If we can't
identify
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 07b2f798abd..efca32c33c0 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -265,6 +265,9 @@
/* Define to 1 if you have the `zstd' library (-lzstd). */
#undef HAVE_LIBZSTD
+/* Define to 1 if you have the `localeconv_l' function. */
+#undef HAVE_LOCALECONV_L
+
/* Define to 1 if you have the <mbarrier.h> header file. */
#undef HAVE_MBARRIER_H
diff --git a/src/include/port.h b/src/include/port.h
index 703cad868ba..3faae03d246 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -487,6 +487,12 @@ extern void *bsearch_arg(const void *key, const void
*base0,
int (*compar) (const void *,
const void *, void *),
void *arg);
+/* port/pg_localeconv_r.c */
+extern int pg_localeconv_r(const char *lc_monetary,
+ const char *lc_numeric,
+ struct lconv *output);
+extern void pg_localeconv_free(struct lconv *lconv);
+
/* port/chklocale.c */
extern int pg_get_encoding_from_locale(const char *ctype, bool
write_message);
diff --git a/src/port/Makefile b/src/port/Makefile
index 4c224319512..7843d7b67cb 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -44,6 +44,7 @@ OBJS = \
noblock.o \
path.o \
pg_bitutils.o \
+ pg_localeconv_r.o \
pg_popcount_avx512.o \
pg_strong_random.o \
pgcheckdir.o \
diff --git a/src/port/meson.build b/src/port/meson.build
index 7fcfa728d43..653539ba5b3 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -7,6 +7,7 @@ pgport_sources = [
'noblock.c',
'path.c',
'pg_bitutils.c',
+ 'pg_localeconv_r.c',
'pg_popcount_avx512.c',
'pg_strong_random.c',
'pgcheckdir.c',
diff --git a/src/port/pg_localeconv_r.c b/src/port/pg_localeconv_r.c
new file mode 100644
index 00000000000..efb98cd127d
--- /dev/null
+++ b/src/port/pg_localeconv_r.c
@@ -0,0 +1,367 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_localeconv_r.c
+ * Thread-safe implementations of localeconv()
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/port/pg_localeconv_r.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#if !defined(WIN32)
+#include <langinfo.h>
+#include <pthread.h>
+#endif
+
+#include <limits.h>
+
+#ifdef MON_THOUSANDS_SEP
+/*
+ * One of glibc's extended langinfo items detected. Assume that the full set
+ * is present, which means we can use nl_langinfo_l() instead of localeconv().
+ */
+#define TRANSLATE_FROM_LANGINFO
+#endif
+
+struct lconv_member_info
+{
+ bool is_string;
+ int category;
+ size_t offset;
+#ifdef TRANSLATE_FROM_LANGINFO
+ nl_item item;
+#endif
+};
+
+/* Some macros to declare the lconv members compactly. */
+#ifdef TRANSLATE_FROM_LANGINFO
+#define LCONV_M(is_string, category, name, item)
\
+ { is_string, category, offsetof(struct lconv, name), item }
+#else
+#define LCONV_M(is_string, category, name, item) \
+ { is_string, category, offsetof(struct lconv, name) }
+#endif
+#define LCONV_S(c, n, i) LCONV_M(true, c, n, i)
+#define LCONV_C(c, n, i) LCONV_M(false, c, n, i)
+
+/*
+ * The work of populating lconv objects is driven by this table. Since we
+ * tolerate non-matching encodings in LC_NUMERIC and LC_MONETARY, we have to
+ * call the underlying OS routine multiple times, with the correct locales.
+ * The first column of this table says which locale applies to each struct
+ * member. The second column is the name of the struct member. The third
+ * column is the name of the nl_item, if translating from nl_langinfo_l() (it's
+ * always the member name, in upper case).
+ */
+const static struct lconv_member_info table[] = {
+ /* String fields. */
+ LCONV_S(LC_NUMERIC, decimal_point, DECIMAL_POINT),
+ LCONV_S(LC_NUMERIC, thousands_sep, THOUSANDS_SEP),
+ LCONV_S(LC_NUMERIC, grouping, GROUPING),
+ LCONV_S(LC_MONETARY, int_curr_symbol, INT_CURR_SYMBOL),
+ LCONV_S(LC_MONETARY, currency_symbol, CURRENCY_SYMBOL),
+ LCONV_S(LC_MONETARY, mon_decimal_point, MON_DECIMAL_POINT),
+ LCONV_S(LC_MONETARY, mon_thousands_sep, MON_THOUSANDS_SEP),
+ LCONV_S(LC_MONETARY, mon_grouping, MON_GROUPING),
+ LCONV_S(LC_MONETARY, positive_sign, POSITIVE_SIGN),
+ LCONV_S(LC_MONETARY, negative_sign, NEGATIVE_SIGN),
+
+ /* Character fields. */
+ LCONV_C(LC_MONETARY, int_frac_digits, INT_FRAC_DIGITS),
+ LCONV_C(LC_MONETARY, frac_digits, FRAC_DIGITS),
+ LCONV_C(LC_MONETARY, p_cs_precedes, P_CS_PRECEDES),
+ LCONV_C(LC_MONETARY, p_sep_by_space, P_SEP_BY_SPACE),
+ LCONV_C(LC_MONETARY, n_cs_precedes, N_CS_PRECEDES),
+ LCONV_C(LC_MONETARY, n_sep_by_space, N_SEP_BY_SPACE),
+ LCONV_C(LC_MONETARY, p_sign_posn, P_SIGN_POSN),
+ LCONV_C(LC_MONETARY, n_sign_posn, N_SIGN_POSN),
+};
+
+static inline char **
+lconv_string_member(struct lconv *lconv, int i)
+{
+ return (char **) ((char *) lconv + table[i].offset);
+}
+
+static inline char *
+lconv_char_member(struct lconv *lconv, int i)
+{
+ return (char *) lconv + table[i].offset;
+}
+
+/*
+ * Free the members of a struct lconv populated by pg_localeconv_r(). The
+ * struct itself is in storage provided by the caller of pg_localeconv_r().
+ */
+void
+pg_localeconv_free(struct lconv *lconv)
+{
+ for (int i = 0; i < lengthof(table); ++i)
+ if (table[i].is_string)
+ free(*lconv_string_member(lconv, i));
+}
+
+#ifdef TRANSLATE_FROM_LANGINFO
+/*
+ * Fill in struct lconv members using the equivalent nl_langinfo_l() items.
+ */
+static int
+pg_localeconv_from_langinfo(struct lconv *dst,
+ locale_t
monetary_locale,
+ locale_t numeric_locale)
+{
+ for (int i = 0; i < lengthof(table); ++i)
+ {
+ locale_t locale;
+
+ locale = table[i].category == LC_NUMERIC ?
+ numeric_locale : monetary_locale;
+
+ if (table[i].is_string)
+ {
+ char *string;
+
+ string = nl_langinfo_l(table[i].item, locale);
+ if (!(string = strdup(string)))
+ {
+ pg_localeconv_free(dst);
+ errno = ENOMEM;
+ return -1;
+ }
+ *lconv_string_member(dst, i) = string;
+ }
+ else
+ {
+ *lconv_char_member(dst, i) =
+ *nl_langinfo_l(table[i].item, locale);
+ }
+ }
+
+ return 0;
+}
+#else
+/*
+ * Copy members from a given category. Note that you have to call this twice
+ * to copy the LC_MONETARY and then LC_NUMERIC members.
+ */
+static int
+pg_localeconv_copy_members(struct lconv *dst,
+ struct lconv *src,
+ int category)
+{
+ for (int i = 0; i < lengthof(table); ++i)
+ {
+ if (table[i].category != category)
+ continue;
+
+ if (table[i].is_string)
+ {
+ char *string;
+
+ string = *lconv_string_member(src, i);
+ if (!(string = strdup(string)))
+ {
+ pg_localeconv_free(dst);
+ errno = ENOMEM;
+ return -1;
+ }
+ *lconv_string_member(dst, i) = string;
+ }
+ else
+ {
+ *lconv_char_member(dst, i) = *lconv_char_member(src, i);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/*
+ * A thread-safe routine to get a copy of the lconv struct for a given
+ * LC_NUMERIC and LC_MONETARY. Different approaches are used on different
+ * OSes, because the standard interface is so multi-threading unfriendly.
+ *
+ * 1. On Windows, there is no uselocale(), but there is a way to put
+ * setlocale() into a thread-local mode temporarily. Its localeconv() is
+ * documented as returning a pointer to thread-local storage, so we don't have
+ * to worry about concurrent callers.
+ *
+ * 2. On Glibc, as an extension, all the information required to populate
+ * struct lconv is also available via nl_langpath_l(), which is thread-safe.
+ *
+ * 3. On macOS and *BSD, there is localeconv_l(), so we can create a temporary
+ * locale_t to pass in, and the result is a pointer to storage associated with
+ * the locale_t so we control its lifetime and we don't have to worry about
+ * concurrent calls clobbering it.
+ *
+ * 4. Otherwise, we wrap plain old localeconv() in uselocale() to avoid
+ * touching the global locale, but the output buffer is allowed by the standard
+ * to be overwritten by concurrent calls to localeconv(). We protect against
+ * _this_ function doing that with a Big Lock, but there isn't much we can do
+ * about code outside our tree that might call localeconv(), given such a poor
+ * interface.
+ *
+ * The POSIX standard explicitly says that it is undefined what happens if
+ * LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from that
+ * implied by LC_CTYPE. In practice, all Unix-ish platforms seem to believe
+ * that localeconv() should return strings that are encoded in the codeset
+ * implied by the LC_MONETARY or LC_NUMERIC locale name. On Windows, LC_CTYPE
+ * has to match to get sane results.
+ *
+ * To get predicable results on all platforms, we'll call the underlying
+ * routines with LC_ALL set to the appropriate locale for each set of members,
+ * and merge the results. Three members of the resulting object are therefore
+ * guaranteed to be encoded with LC_NUMERIC's codeset: "decimal_point",
+ * "thousands_sep" and "grouping". All other members are encoded with
+ * LC_MONETARY's codeset.
+ *
+ * Returns 0 on success. Returns non-zero on failure, and sets errno. On
+ * success, the caller is responsible for calling pg_localeconf_free() on the
+ * output struct to free the string members it contains.
+ */
+int
+pg_localeconv_r(const char *lc_monetary,
+ const char *lc_numeric,
+ struct lconv *output)
+{
+#ifdef WIN32
+ wchar_t *save_lc_ctype = NULL;
+ wchar_t *save_lc_monetary = NULL;
+ wchar_t *save_lc_numeric = NULL;
+ int save_config_thread_locale;
+ int result = -1;
+
+ /* Put setlocale() into thread-local mode. */
+ save_config_thread_locale =
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+
+ /*
+ * Capture the current values as wide strings. Otherwise, we might not
be
+ * able to restore them if their names contain non-ASCII characters and
+ * the intermediate locale changes the expected encoding. We don't want
+ * to leave the caller in an unexpected state by failing to restore, or
+ * crash the runtime library.
+ */
+ save_lc_ctype = _wsetlocale(LC_CTYPE, NULL);
+ if (!save_lc_ctype || !(save_lc_ctype = wcsdup(save_lc_ctype)))
+ goto exit;
+ save_lc_monetary = _wsetlocale(LC_MONETARY, NULL);
+ if (!save_lc_monetary || !(save_lc_monetary = wcsdup(save_lc_monetary)))
+ goto exit;
+ save_lc_numeric = _wsetlocale(LC_NUMERIC, NULL);
+ if (!save_lc_numeric || !(save_lc_numeric = wcsdup(save_lc_numeric)))
+ goto exit;
+
+ memset(output, 0, sizeof(*output));
+
+ /* Copy the LC_MONETARY members. */
+ if (!setlocale(LC_ALL, lc_monetary))
+ goto exit;
+ result = pg_localeconv_copy_members(output, localeconv(), LC_MONETARY);
+ if (result != 0)
+ goto exit;
+
+ /* Copy the LC_NUMERIC members. */
+ if (!setlocale(LC_ALL, lc_numeric))
+ goto exit;
+ result = pg_localeconv_copy_members(output, localeconv(), LC_NUMERIC);
+
+exit:
+ /* Restore everything we changed. */
+ if (save_lc_ctype)
+ {
+ _wsetlocale(LC_CTYPE, save_lc_ctype);
+ free(save_lc_ctype);
+ }
+ if (save_lc_monetary)
+ {
+ _wsetlocale(LC_MONETARY, save_lc_monetary);
+ free(save_lc_monetary);
+ }
+ if (save_lc_numeric)
+ {
+ _wsetlocale(LC_NUMERIC, save_lc_numeric);
+ free(save_lc_numeric);
+ }
+ _configthreadlocale(save_config_thread_locale);
+
+ return result;
+
+#else
+ locale_t monetary_locale;
+ locale_t numeric_locale;
+ int result;
+
+ /*
+ * All variations on Unix require locale_t objects for LC_MONETARY and
+ * LC_NUMERIC. We'll set all locale categories, so that we can don't
have
+ * to worry about POSIX's undefined behavior if LC_CTYPE's encoding
+ * doesn't match.
+ */
+ errno = ENOENT;
+ monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
+ if (monetary_locale == 0)
+ return -1;
+ numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
+ if (numeric_locale == 0)
+ {
+ freelocale(monetary_locale);
+ return -1;
+ }
+
+ memset(output, 0, sizeof(*output));
+#if defined(TRANSLATE_FROM_LANGINFO)
+ /* Copy from non-standard nl_langinfo_l() extended items. */
+ result = pg_localeconv_from_langinfo(output,
+
monetary_locale,
+
numeric_locale);
+#elif defined(HAVE_LOCALE_CONV_L)
+ /* Copy the LC_MONETARY members from a thread-safe lconv object. */
+ result = pg_localeconv_copy_members(output,
+
localeconv_l(monetary_locale),
+
LC_MONETARY);
+ if (result != 0)
+ goto exit;
+ /* Copy the LC_NUMERIC members from a thread-safe lconv object. */
+ result = pg_localeconv_copy_members(output,
+
localeconv_l(numeric_locale),
+
LC_NUMERIC);
+#else
+ /* We have nothing better than standard POSIX facilities. */
+ {
+ static pthread_mutex_t big_lock = PTHREAD_MUTEX_INITIALIZER;
+ locale_t save_locale;
+
+ pthread_mutex_lock(&big_lock);
+ /* Copy the LC_MONETARY members. */
+ save_locale = uselocale(monetary_locale);
+ result = pg_localeconv_copy_members(output,
+
localeconv(),
+
LC_MONETARY);
+ if (result == 0)
+ {
+ /* Copy the LC_NUMERIC members. */
+ uselocale(numeric_locale);
+ result = pg_localeconv_copy_members(output,
+
localeconv(),
+
LC_NUMERIC);
+ }
+ pthread_mutex_unlock(&big_lock);
+
+ uselocale(save_locale);
+ }
+#endif
+
+ freelocale(monetary_locale);
+ freelocale(numeric_locale);
+
+ return result;
+#endif
+}
--
2.48.1
From b0c9f2c5c79f1f156af2e92aa84a2e2bdf11b0f5 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.mu...@gmail.com>
Date: Wed, 14 Aug 2024 23:06:59 +1200
Subject: [PATCH v6.1 2/5] 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
set errno, so no %m.
Reviewed-by:
Discussion:
https://postgr.es/m/CA%2BhUKGJqVe0%2BPv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A%40mail.gmail.com
---
src/backend/main/main.c | 5 +-
src/backend/utils/adt/pg_locale.c | 113 ++++++++----------------------
2 files changed, 31 insertions(+), 87 deletions(-)
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index e8effe50242..7d63cf94a6b 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -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");
diff --git a/src/backend/utils/adt/pg_locale.c
b/src/backend/utils/adt/pg_locale.c
index 4dd4313b779..84257c2ce74 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -18,34 +18,13 @@
* 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,21 @@ 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
+ if (locale == (locale_t) 0)
+ {
+ if (errno == ENOMEM)
+ elog(ERROR, "out of memory");
- setlocale(LC_TIME, locale_time);
+ elog(ERROR, "coud not create locale \"%s\": %m", locale_time);
+ }
/* We use times close to current time as data for strftime(). */
timenow = time(NULL);
@@ -814,10 +772,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 +785,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
--
2.48.1
From cc883e9de458ab200153173f37edf6f8b0016abd Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.mu...@gmail.com>
Date: Thu, 15 Aug 2024 16:45:27 +1200
Subject: [PATCH v6.1 3/5] Remove setlocale() from check_locale().
Validate locale names with newlocale() or _create_locale() instead, to
avoid clobbering global state.
This also removes the "canonicalization" of the locale name, which
previously had two user-visible effects:
1. CREATE DATABASE ... LOCALE = '' would look up the locale from
environment variables, but this was not documented behavior and doesn't
seem too useful. A default will normally be inherited from the template
if you just leave the option off. (Note that initdb still chooses
default values from the server environment, and that would often be the
original source of the template database's values.)
2. On Windows only, the setlocale() step reached by CREATE DATABASE ...
LOCALE = '...' did accept a wide range of formats and do some
canonicalization, when using (deprecated) pre-BCP 47 locale names, but
again it seems enough to let that happen in the initdb phase only.
Now, CREATE DATABASE and SET lc_XXX reject ''. CREATE COLLATION
continues to accept it, as it never recorded canonicalized values so
there may be system catalogs containing verbatim '' in the wild. We'll
need to research the upgrade implications before banning it there.
Thanks to Peter Eisentraut and Tom Lane for the suggestion that we might
not really need to preserve those behaviors in the backend, in our hunt
for non-thread-safe code.
Reviewed-by: Heikki Linnakangas <hlinn...@iki.fi>
Discussion:
https://postgr.es/m/CA%2BhUKGJqVe0%2BPv9dvC9dSums_PXxGo9SWcxYAMBguWJUGbWz-A%40mail.gmail.com
Discussion:
https://postgr.es/m/CA%2BhUKGK57sgUYKO03jB4VarTsswfMyScFAyJpVnYD8c%2Bg12_mg%40mail.gmail.com
---
src/backend/commands/dbcommands.c | 7 +-
src/backend/utils/adt/pg_locale.c | 111 ++++++++++++++++++------------
src/bin/initdb/initdb.c | 2 -
src/include/port/win32_port.h | 1 -
src/include/utils/pg_locale.h | 2 +-
5 files changed, 70 insertions(+), 53 deletions(-)
diff --git a/src/backend/commands/dbcommands.c
b/src/backend/commands/dbcommands.c
index 46310add459..a71785b952d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -729,7 +729,6 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
const char *dblocale = NULL;
char *dbicurules = NULL;
char dblocprovider = '\0';
- char *canonname;
int encoding = -1;
bool dbistemplate = false;
bool dballowconnections = true;
@@ -1063,18 +1062,16 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
errmsg("invalid server encoding %d",
encoding)));
/* Check that the chosen locales are valid, and get canonical spellings
*/
- if (!check_locale(LC_COLLATE, dbcollate, &canonname))
+ if (!check_locale(LC_COLLATE, dbcollate))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("invalid LC_COLLATE locale name:
\"%s\"", dbcollate),
errhint("If the locale name is specific to
ICU, use ICU_LOCALE.")));
- dbcollate = canonname;
- if (!check_locale(LC_CTYPE, dbctype, &canonname))
+ if (!check_locale(LC_CTYPE, dbctype))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("invalid LC_CTYPE locale name: \"%s\"",
dbctype),
errhint("If the locale name is specific to
ICU, use ICU_LOCALE.")));
- dbctype = canonname;
check_encoding_locale_matches(encoding, dbcollate, dbctype);
diff --git a/src/backend/utils/adt/pg_locale.c
b/src/backend/utils/adt/pg_locale.c
index 84257c2ce74..cb39a0fcdf3 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -172,6 +172,36 @@ static pg_locale_t last_collation_cache_locale = NULL;
static char *IsoLocaleName(const char *);
#endif
+#ifndef WIN32
+/*
+ * The newlocale() function needs LC_xxx_MASK, but sometimes we have LC_xxx,
+ * and POSIX doesn't offer a way to translate.
+ */
+static int
+get_lc_category_mask(int category)
+{
+ switch (category)
+ {
+ case LC_COLLATE:
+ return LC_COLLATE_MASK;
+ case LC_CTYPE:
+ return LC_CTYPE_MASK;
+#ifdef LC_MESSAGES
+ case LC_MESSAGES:
+ return LC_MESSAGES_MASK;
+#endif
+ case LC_MONETARY:
+ return LC_MONETARY_MASK;
+ case LC_NUMERIC:
+ return LC_NUMERIC_MASK;
+ case LC_TIME:
+ return LC_TIME_MASK;
+ default:
+ return 0;
+ };
+}
+#endif
+
/*
* pg_perm_setlocale
*
@@ -281,19 +311,11 @@ pg_perm_setlocale(int category, const char *locale)
/*
* Is the locale name valid for the locale category?
- *
- * If successful, and canonname isn't NULL, a palloc'd copy of the locale's
- * canonical name is stored there. This is especially useful for figuring out
- * what locale name "" means (ie, the server environment value). (Actually,
- * it seems that on most implementations that's the only thing it's good for;
- * we could wish that setlocale gave back a canonically spelled version of
- * the locale name, but typically it doesn't.)
*/
bool
-check_locale(int category, const char *locale, char **canonname)
+check_locale(int category, const char *locale)
{
- char *save;
- char *res;
+ locale_t loc;
/* Don't let Windows' non-ASCII locale names in. */
if (!pg_is_ascii(locale))
@@ -305,41 +327,42 @@ check_locale(int category, const char *locale, char
**canonname)
return false;
}
- if (canonname)
- *canonname = NULL; /* in case of failure */
-
- save = setlocale(category, NULL);
- if (!save)
- return false; /* won't happen, we hope */
-
- /* save may be pointing at a modifiable scratch variable, see above. */
- save = pstrdup(save);
-
- /* set the locale with setlocale, to see if it accepts it. */
- res = setlocale(category, locale);
-
- /* save canonical name if requested. */
- if (res && canonname)
- *canonname = pstrdup(res);
-
- /* restore old value. */
- if (!setlocale(category, save))
- elog(WARNING, "failed to restore old locale \"%s\"", save);
- pfree(save);
+ if (locale[0] == 0)
+ {
+ /*
+ * We only accept explicit locale names, not "". We don't want
to
+ * rely on environment variables in the backend.
+ */
+ return false;
+ }
- /* Don't let Windows' non-ASCII locale names out. */
- if (canonname && *canonname && !pg_is_ascii(*canonname))
+ /*
+ * See if we can open it. Unfortunately we can't always distinguish
+ * out-of-memory from invalid locale name.
+ */
+ errno = ENOENT;
+#ifdef WIN32
+ loc = _create_locale(category, locale);
+ if (loc == (locale_t) 0)
+ _dosmaperr(GetLastError());
+#else
+ loc = newlocale(get_lc_category_mask(category), locale, (locale_t) 0);
+#endif
+ if (loc == (locale_t) 0)
{
- ereport(WARNING,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("locale name \"%s\" contains non-ASCII
characters",
- *canonname)));
- pfree(*canonname);
- *canonname = NULL;
+ if (errno == ENOMEM)
+ elog(ERROR, "out of memory");
+
+ /* Otherwise assume the locale doesn't exist. */
return false;
}
+#ifdef WIN32
+ _free_locale(loc);
+#else
+ freelocale(loc);
+#endif
- return (res != NULL);
+ return true;
}
@@ -357,7 +380,7 @@ check_locale(int category, const char *locale, char
**canonname)
bool
check_locale_monetary(char **newval, void **extra, GucSource source)
{
- return check_locale(LC_MONETARY, *newval, NULL);
+ return check_locale(LC_MONETARY, *newval);
}
void
@@ -369,7 +392,7 @@ assign_locale_monetary(const char *newval, void *extra)
bool
check_locale_numeric(char **newval, void **extra, GucSource source)
{
- return check_locale(LC_NUMERIC, *newval, NULL);
+ return check_locale(LC_NUMERIC, *newval);
}
void
@@ -381,7 +404,7 @@ assign_locale_numeric(const char *newval, void *extra)
bool
check_locale_time(char **newval, void **extra, GucSource source)
{
- return check_locale(LC_TIME, *newval, NULL);
+ return check_locale(LC_TIME, *newval);
}
void
@@ -417,7 +440,7 @@ check_locale_messages(char **newval, void **extra,
GucSource source)
* On Windows, we can't even check the value, so accept blindly
*/
#if defined(LC_MESSAGES) && !defined(WIN32)
- return check_locale(LC_MESSAGES, *newval, NULL);
+ return check_locale(LC_MESSAGES, *newval);
#else
return true;
#endif
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 759672a9b97..69af8340116 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2205,8 +2205,6 @@ locale_date_order(const char *locale)
* it seems that on most implementations that's the only thing it's good for;
* we could wish that setlocale gave back a canonically spelled version of
* the locale name, but typically it doesn't.)
- *
- * this should match the backend's check_locale() function
*/
static void
check_locale_name(int category, const char *locale, char **canonname)
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index ff7028bdc81..d406ce9206c 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -474,7 +474,6 @@ extern char *pgwin32_setlocale(int category, const char
*locale);
#define setlocale(a,b) pgwin32_setlocale(a,b)
-
/* In backend/port/win32/signal.c */
extern PGDLLIMPORT volatile int pg_signal_queue;
extern PGDLLIMPORT int pg_signal_mask;
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 0d5f0513ceb..c22f8c762e6 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -35,7 +35,7 @@ extern PGDLLIMPORT char *localized_full_months[];
/* is the databases's LC_CTYPE the C locale? */
extern PGDLLIMPORT bool database_ctype_is_c;
-extern bool check_locale(int category, const char *locale, char **canonname);
+extern bool check_locale(int category, const char *locale);
extern char *pg_perm_setlocale(int category, const char *locale);
/*
--
2.48.1
From 14b0d95ab7ec01aa5e65f56aea516e13b1f44789 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 14 Feb 2025 14:30:17 +0100
Subject: [PATCH v6.1 4/5] fixup! Provide thread-safe pg_localeconv_r().
NOTE: Also remove the disucssion about _configthreadlocale() from the
commit message, since that was already done earlier.
---
configure | 2 +-
configure.ac | 2 +-
src/port/pg_localeconv_r.c | 10 +++++-----
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/configure b/configure
index 3e7c5fc91d6..f5f82b29b43 100755
--- a/configure
+++ b/configure
@@ -14934,7 +14934,7 @@ fi
LIBS_including_readline="$LIBS"
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
-for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info
getauxval getifaddrs getpeerucred inet_pton localeconv_l kqueue mbstowcs_l
memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle
setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale
wcstombs_l
+for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info
getauxval getifaddrs getpeerucred inet_pton kqueue localeconv_l mbstowcs_l
memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle
setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale
wcstombs_l
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.ac b/configure.ac
index e56136049e9..0017d590868 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1710,8 +1710,8 @@ AC_CHECK_FUNCS(m4_normalize([
getifaddrs
getpeerucred
inet_pton
- localeconv_l
kqueue
+ localeconv_l
mbstowcs_l
memset_s
posix_fallocate
diff --git a/src/port/pg_localeconv_r.c b/src/port/pg_localeconv_r.c
index efb98cd127d..ec5cd4ef1cc 100644
--- a/src/port/pg_localeconv_r.c
+++ b/src/port/pg_localeconv_r.c
@@ -55,7 +55,7 @@ struct lconv_member_info
* The work of populating lconv objects is driven by this table. Since we
* tolerate non-matching encodings in LC_NUMERIC and LC_MONETARY, we have to
* call the underlying OS routine multiple times, with the correct locales.
- * The first column of this table says which locale applies to each struct
+ * The first column of this table says which locale category applies to each
struct
* member. The second column is the name of the struct member. The third
* column is the name of the nl_item, if translating from nl_langinfo_l() (it's
* always the member name, in upper case).
@@ -146,7 +146,7 @@ pg_localeconv_from_langinfo(struct lconv *dst,
return 0;
}
-#else
+#else /* not
TRANSLATE_FROM_LANGINFO */
/*
* Copy members from a given category. Note that you have to call this twice
* to copy the LC_MONETARY and then LC_NUMERIC members.
@@ -182,7 +182,7 @@ pg_localeconv_copy_members(struct lconv *dst,
return 0;
}
-#endif
+#endif /* not
TRANSLATE_FROM_LANGINFO */
/*
* A thread-safe routine to get a copy of the lconv struct for a given
@@ -294,7 +294,7 @@ pg_localeconv_r(const char *lc_monetary,
return result;
-#else
+#else /* !WIN32 */
locale_t monetary_locale;
locale_t numeric_locale;
int result;
@@ -363,5 +363,5 @@ pg_localeconv_r(const char *lc_monetary,
freelocale(numeric_locale);
return result;
-#endif
+#endif /* !WIN32 */
}
--
2.48.1
From 3aa25d7c39b51c6f335eedbde80698bd6960badb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 14 Feb 2025 14:47:52 +0100
Subject: [PATCH v6.1 5/5] fixup! Use thread-safe strftime_l() instead of
strftime().
---
src/backend/utils/adt/pg_locale.c | 9 ++-------
src/backend/utils/adt/pg_locale_libc.c | 3 +--
src/include/utils/pg_locale.h | 1 +
3 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c
b/src/backend/utils/adt/pg_locale.c
index cb39a0fcdf3..44621acf9c9 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -766,13 +766,8 @@ cache_locale_time(void)
#else
locale = newlocale(LC_ALL_MASK, locale_time, (locale_t) 0);
#endif
- if (locale == (locale_t) 0)
- {
- if (errno == ENOMEM)
- elog(ERROR, "out of memory");
-
- elog(ERROR, "coud not create locale \"%s\": %m", locale_time);
- }
+ if (!locale)
+ report_newlocale_failure(locale_time);
/* We use times close to current time as data for strftime(). */
timenow = time(NULL);
diff --git a/src/backend/utils/adt/pg_locale_libc.c
b/src/backend/utils/adt/pg_locale_libc.c
index 8f9a8637897..199857e22db 100644
--- a/src/backend/utils/adt/pg_locale_libc.c
+++ b/src/backend/utils/adt/pg_locale_libc.c
@@ -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;
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index c22f8c762e6..32a41913c3f 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -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,
--
2.48.1