Here is an up to date patch given some churn on the master branch.
--
Tristan Partin
Neon (https://neon.tech)
From b68cec481768c7c635ec48329b4764eced264572 Mon Sep 17 00:00:00 2001
From: Tristan Partin <[email protected]>
Date: Fri, 30 Jun 2023 09:31:04 -0500
Subject: [PATCH v3 1/3] Skip checking for uselocale on Windows
Windows doesn't have uselocale, so skip it.
---
meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index 0c44f19cb9..3356f72bf0 100644
--- a/meson.build
+++ b/meson.build
@@ -2438,7 +2438,7 @@ func_checks = [
['strsignal'],
['sync_file_range'],
['syncfs'],
- ['uselocale'],
+ ['uselocale', {'skip': host_system == 'windows'}],
['wcstombs_l'],
]
--
Tristan Partin
Neon (https://neon.tech)
From 3207694a1d214a7d5b844f3f6dfd8378408172af Mon Sep 17 00:00:00 2001
From: Tristan Partin <[email protected]>
Date: Fri, 30 Jun 2023 11:13:29 -0500
Subject: [PATCH v3 2/3] Add locale_is_c function
In some places throughout the codebase, there are string comparisons for
locales matching C or POSIX. Encapsulate this logic into a single
function and use it.
---
src/backend/utils/adt/pg_locale.c | 39 ++++++++++++++++++-------------
src/backend/utils/init/postinit.c | 4 +---
src/backend/utils/mb/mbutils.c | 5 ++--
src/include/utils/pg_locale.h | 1 +
4 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index aa9da99308..047c02dbab 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -191,6 +191,23 @@ wcstombs_l(char *dest, const wchar_t *src, size_t n, locale_t loc)
}
#endif
+
+/*
+ * Check if a locale is the C locale
+ *
+ * POSIX is an alias for C. Passing ingore_case as true will use
+ * pg_strcasecmp() instead of strcmp().
+ */
+bool
+locale_is_c(const char *locale, bool ignore_case)
+{
+ if (!ignore_case)
+ return strcmp(locale, "C") == 0 || strcmp(locale, "POSIX") == 0;
+
+ return pg_strcasecmp(locale, "C") == 0 || pg_strcasecmp(locale, "POSIX") == 0;
+}
+
+
/*
* pg_perm_setlocale
*
@@ -1276,10 +1293,8 @@ lookup_collation_cache(Oid collation, bool set_flags)
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
collctype = TextDatumGetCString(datum);
- cache_entry->collate_is_c = ((strcmp(collcollate, "C") == 0) ||
- (strcmp(collcollate, "POSIX") == 0));
- cache_entry->ctype_is_c = ((strcmp(collctype, "C") == 0) ||
- (strcmp(collctype, "POSIX") == 0));
+ cache_entry->collate_is_c = locale_is_c(collcollate, false);
+ cache_entry->ctype_is_c = locale_is_c(collctype, false);
}
else
{
@@ -1327,12 +1342,8 @@ lc_collate_is_c(Oid collation)
if (!localeptr)
elog(ERROR, "invalid LC_COLLATE setting");
- if (strcmp(localeptr, "C") == 0)
- result = true;
- else if (strcmp(localeptr, "POSIX") == 0)
- result = true;
- else
- result = false;
+ result = locale_is_c(localeptr, false);
+
return (bool) result;
}
@@ -1380,12 +1391,8 @@ lc_ctype_is_c(Oid collation)
if (!localeptr)
elog(ERROR, "invalid LC_CTYPE setting");
- if (strcmp(localeptr, "C") == 0)
- result = true;
- else if (strcmp(localeptr, "POSIX") == 0)
- result = true;
- else
- result = false;
+ result = locale_is_c(localeptr, false);
+
return (bool) result;
}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0f9b92b32e..a92a0c438f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -419,9 +419,7 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
" which is not recognized by setlocale().", ctype),
errhint("Recreate the database with another locale or install the missing locale.")));
- if (strcmp(ctype, "C") == 0 ||
- strcmp(ctype, "POSIX") == 0)
- database_ctype_is_c = true;
+ database_ctype_is_c = locale_is_c(ctype, false);
if (dbform->datlocprovider == COLLPROVIDER_ICU)
{
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index 67a1ab2ab2..997156515c 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -39,6 +39,7 @@
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
+#include "utils/pg_locale.h"
#include "utils/syscache.h"
#include "varatt.h"
@@ -1237,9 +1238,7 @@ pg_bind_textdomain_codeset(const char *domainname)
int new_msgenc;
#ifndef WIN32
- const char *ctype = setlocale(LC_CTYPE, NULL);
-
- if (pg_strcasecmp(ctype, "C") == 0 || pg_strcasecmp(ctype, "POSIX") == 0)
+ if (locale_is_c(locale_ctype, true))
#endif
if (encoding != PG_SQL_ASCII &&
raw_pg_bind_textdomain_codeset(domainname, encoding))
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 6447bea8e0..e08d96e099 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -54,6 +54,7 @@ extern PGDLLIMPORT bool database_ctype_is_c;
extern bool check_locale(int category, const char *locale, char **canonname);
extern char *pg_perm_setlocale(int category, const char *locale);
+extern bool locale_is_c(const char *locale, bool ignore_case);
extern bool lc_collate_is_c(Oid collation);
extern bool lc_ctype_is_c(Oid collation);
--
Tristan Partin
Neon (https://neon.tech)
From bc0a79fd0b1cf5f45c93dfd40fc00c054646fb00 Mon Sep 17 00:00:00 2001
From: Tristan Partin <[email protected]>
Date: Mon, 3 Jul 2023 08:51:59 -0500
Subject: [PATCH v3 3/3] Use thread-safe locale APIs
setlocale() has been a thorn in Postgres's side for a while. Most
recently this has manifested in bug #17946 where loading libperl would
cause the localization to change out from under Postgres. On Windows,
use _ENABLE_PER_THREAD_LOCALE.
---
src/backend/main/main.c | 55 +++-
src/backend/utils/adt/cash.c | 29 +++
src/backend/utils/adt/formatting.c | 11 +-
src/backend/utils/adt/pg_locale.c | 388 ++++++++++++++++++-----------
src/backend/utils/init/postinit.c | 4 +-
src/backend/utils/mb/mbutils.c | 2 +-
src/common/exec.c | 16 --
src/include/utils/pg_locale.h | 2 +-
8 files changed, 325 insertions(+), 182 deletions(-)
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index ed11e8be7f..093df88f14 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -59,6 +59,11 @@ int
main(int argc, char *argv[])
{
bool do_check_root = true;
+ char *collate_locale;
+ char *ctype_locale;
+#ifdef LC_MESSAGES
+ char *messages_locale;
+#endif
reached_main = true;
@@ -111,15 +116,39 @@ main(int argc, char *argv[])
* these set to "C" then message localization might not work well in the
* postmaster.
*/
- init_locale("LC_COLLATE", LC_COLLATE, "");
- init_locale("LC_CTYPE", LC_CTYPE, "");
+
+ /*
+ * Save off the original locale values prior to the first call of
+ * uselocale(). This ensures that we don't call pg_setlocale() with an empty
+ * string. See the function comment for pg_setlocale() for further
+ * explanation. Note that this restriction doesn't exist on Windows, but we
+ * can use the same code path anyway.
+ */
+ collate_locale = setlocale(LC_COLLATE, "");
+ ctype_locale = setlocale(LC_CTYPE, "");
+
+#ifdef LC_MESSAGES
+ messages_locale = setlocale(LC_MESSAGES, "");
+#endif
+
+
+#ifdef HAVE__CONFIGTHREADLOCALE
+ /*
+ * This call could most likely happen sooner, but just to err on the side of
+ * caution, call it after absorbing locales from the environment.
+ */
+ _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
+#endif
+
+ init_locale("LC_COLLATE", LC_COLLATE, collate_locale);
+ init_locale("LC_CTYPE", LC_CTYPE, ctype_locale);
/*
* LC_MESSAGES will get set later during GUC option processing, but we set
* it here to allow startup error messages to be localized.
*/
#ifdef LC_MESSAGES
- init_locale("LC_MESSAGES", LC_MESSAGES, "");
+ init_locale("LC_MESSAGES", LC_MESSAGES, messages_locale);
#endif
/*
@@ -130,13 +159,6 @@ main(int argc, char *argv[])
init_locale("LC_NUMERIC", LC_NUMERIC, "C");
init_locale("LC_TIME", LC_TIME, "C");
- /*
- * Now that we have absorbed as much as we wish to from the locale
- * environment, remove any LC_ALL setting, so that the environment
- * variables installed by pg_perm_setlocale have force.
- */
- unsetenv("LC_ALL");
-
/*
* Catch standard options before doing much else, in particular before we
* insist on not being root.
@@ -307,8 +329,17 @@ startup_hacks(const char *progname)
static void
init_locale(const char *categoryname, int category, const char *locale)
{
- if (pg_perm_setlocale(category, locale) == NULL &&
- pg_perm_setlocale(category, "C") == NULL)
+ /*
+ * Since we are initializing global locales, NULL and empty string are
+ * invalid arguments. See the pg_setlocale() function description for more
+ * explanation on the empty string. NULL just doesn't make sense in this
+ * context of initializing locales.
+ */
+ Assert(locale);
+ Assert(locale[0] != '\0');
+
+ if (pg_setlocale(category, locale) == NULL &&
+ pg_setlocale(category, "C") == NULL)
elog(FATAL, "could not adopt \"%s\" locale nor C locale for %s",
locale, categoryname);
}
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 32fbad2f57..5c254d912f 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -111,6 +111,11 @@ cash_in(PG_FUNCTION_ARGS)
*csymbol;
struct lconv *lconvert = PGLC_localeconv();
+ if (lconvert == NULL)
+ ereturn(escontext, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/*
* frac_digits will be CHAR_MAX in some locales, notably C. However, just
* testing for == CHAR_MAX is risky, because of compilers like gcc that
@@ -325,6 +330,11 @@ cash_out(PG_FUNCTION_ARGS)
sep_by_space;
struct lconv *lconvert = PGLC_localeconv();
+ if (lconvert == NULL)
+ ereturn(fcinfo->context, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/* see comments about frac_digits in cash_in() */
points = lconvert->frac_digits;
if (points < 0 || points > 10)
@@ -1036,6 +1046,11 @@ cash_numeric(PG_FUNCTION_ARGS)
int fpoint;
struct lconv *lconvert = PGLC_localeconv();
+ if (lconvert == NULL)
+ ereturn(fcinfo->context, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/* see comments about frac_digits in cash_in() */
fpoint = lconvert->frac_digits;
if (fpoint < 0 || fpoint > 10)
@@ -1095,6 +1110,11 @@ numeric_cash(PG_FUNCTION_ARGS)
Datum numeric_scale;
struct lconv *lconvert = PGLC_localeconv();
+ if (lconvert == NULL)
+ ereturn(fcinfo->context, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/* see comments about frac_digits in cash_in() */
fpoint = lconvert->frac_digits;
if (fpoint < 0 || fpoint > 10)
@@ -1128,6 +1148,11 @@ int4_cash(PG_FUNCTION_ARGS)
int i;
struct lconv *lconvert = PGLC_localeconv();
+ if (lconvert == NULL)
+ ereturn(fcinfo->context, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/* see comments about frac_digits in cash_in() */
fpoint = lconvert->frac_digits;
if (fpoint < 0 || fpoint > 10)
@@ -1158,6 +1183,10 @@ int8_cash(PG_FUNCTION_ARGS)
int i;
struct lconv *lconvert = PGLC_localeconv();
+ ereturn(fcinfo->context, (Datum) 0,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not allocate memory for locale information")));
+
/* see comments about frac_digits in cash_in() */
fpoint = lconvert->frac_digits;
if (fpoint < 0 || fpoint > 10)
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..d8dbcbd56b 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -5049,12 +5049,12 @@ NUM_prepare_locale(NUMProc *Np)
/*
* Positive / Negative number sign
*/
- if (lconv->negative_sign && *lconv->negative_sign)
+ if (lconv && lconv->negative_sign && *lconv->negative_sign)
Np->L_negative_sign = lconv->negative_sign;
else
Np->L_negative_sign = "-";
- if (lconv->positive_sign && *lconv->positive_sign)
+ if (lconv && lconv->positive_sign && *lconv->positive_sign)
Np->L_positive_sign = lconv->positive_sign;
else
Np->L_positive_sign = "+";
@@ -5062,9 +5062,8 @@ NUM_prepare_locale(NUMProc *Np)
/*
* Number decimal point
*/
- if (lconv->decimal_point && *lconv->decimal_point)
+ if (lconv && lconv->decimal_point && *lconv->decimal_point)
Np->decimal = lconv->decimal_point;
-
else
Np->decimal = ".";
@@ -5078,7 +5077,7 @@ NUM_prepare_locale(NUMProc *Np)
* but "" for thousands_sep, so we set the thousands_sep too.
* http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php
*/
- if (lconv->thousands_sep && *lconv->thousands_sep)
+ if (lconv && lconv->thousands_sep && *lconv->thousands_sep)
Np->L_thousands_sep = lconv->thousands_sep;
/* Make sure thousands separator doesn't match decimal point symbol. */
else if (strcmp(Np->decimal, ",") != 0)
@@ -5089,7 +5088,7 @@ NUM_prepare_locale(NUMProc *Np)
/*
* Currency symbol
*/
- if (lconv->currency_symbol && *lconv->currency_symbol)
+ if (lconv && lconv->currency_symbol && *lconv->currency_symbol)
Np->L_currency_symbol = lconv->currency_symbol;
else
Np->L_currency_symbol = " ";
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 047c02dbab..178f387630 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -98,6 +98,23 @@ char *locale_time;
int icu_validation_level = WARNING;
+#ifdef HAVE_USELOCALE
+/*
+ * Current locale settings
+ *
+ * uselocale() and friends don't have an equivalent to setlocale(cat, NULL), so
+ * as we set locales for various categories, stash them for retrieval as needed.
+ */
+static struct {
+ char *collate;
+ char *ctype;
+ char *messages;
+ char *monetary;
+ char *numeric;
+ char *time;
+} curr_locales = { 0 };
+#endif /* HAVE_USELOCALE */
+
/*
* lc_time localization cache.
*
@@ -208,28 +225,145 @@ locale_is_c(const char *locale, bool ignore_case)
}
+#ifdef HAVE_USELOCALE
+/*
+ * Turns locale categories into their mask equivalents for use in newlocale(3)
+ *
+ * GCC has the masks defined as (1 << category), but this isn't guaranteed to be
+ * the same in other libcs.
+ */
+static int
+category_to_mask(int category)
+{
+ switch (category) {
+ case LC_ALL:
+ return LC_ALL_MASK;
+ 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;
+ }
+
+ pg_unreachable();
+}
+
+
+static char *
+pg_getlocale(int category)
+{
+ switch (category) {
+ case LC_COLLATE:
+ return curr_locales.collate;
+ case LC_CTYPE:
+ return curr_locales.ctype;
+ case LC_MESSAGES:
+ return curr_locales.messages;
+ case LC_MONETARY:
+ return curr_locales.monetary;
+ case LC_NUMERIC:
+ return curr_locales.numeric;
+ case LC_TIME:
+ return curr_locales.time;
+ default:
+ pg_unreachable();
+ }
+}
+#endif /* HAVE_USELOCALE */
+
+
/*
- * pg_perm_setlocale
- *
- * This wraps the libc function setlocale(), with two additions. First, when
- * changing LC_CTYPE, update gettext's encoding for the current message
- * domain. GNU gettext automatically tracks LC_CTYPE on most platforms, but
- * not on Windows. Second, if the operation is successful, the corresponding
- * LC_XXX environment variable is set to match. By setting the environment
- * variable, we ensure that any subsequent use of setlocale(..., "") will
- * preserve the settings made through this routine. Of course, LC_ALL must
- * also be unset to fully ensure that, but that has to be done elsewhere after
- * all the individual LC_XXX variables have been set correctly. (Thank you
- * Perl for making this kluge necessary.)
+ * pg_setlocale
+ *
+ * This wraps the libc functions uselocale() or setlocale() (depending on the
+ * platform), with a single addition. When changing LC_CTYPE, update gettext's
+ * encoding for the current message domain. GNU gettext automatically tracks
+ * LC_CTYPE on most platforms, but not on Windows. This function will abort if
+ * locale is the empty string. A workaround is to pass the output of
+ * setlocale(category, "") to the locale argument.
*/
char *
-pg_perm_setlocale(int category, const char *locale)
+pg_setlocale(int category, const char *locale)
{
char *result;
- const char *envvar;
+#ifdef HAVE_USELOCALE
+ char **save;
+ int category_mask;
+ locale_t prev_locale;
+ locale_t temp_locale;
+ locale_t work_locale;
+
+ if (locale == NULL)
+ return pg_getlocale(category);
+
+ Assert(locale[0] != '\0');
+
+ category_mask = category_to_mask(category);
+
+ prev_locale = uselocale((locale_t) 0);
+ Assert(saved_locale != (locale_t) 0);
+
+ result = strdup(locale);
+ work_locale = duplocale(prev_locale);
+ if (!result || work_locale == (locale_t) 0) {
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+
+ return NULL;
+ }
+
+ temp_locale = newlocale(category_mask, locale, work_locale);
+ if (temp_locale == (locale_t) 0) {
+ freelocale(work_locale);
+
+ return NULL; /* fall out immediately on failure */
+ }
+ work_locale = temp_locale;
+
+ uselocale(work_locale);
+ if (prev_locale != LC_GLOBAL_LOCALE)
+ freelocale(prev_locale);
+
+ /*
+ * Copying the locale must occur before we potentially bind the text domain.
+ */
+ switch (category) {
+ case LC_COLLATE:
+ save = &curr_locales.collate;
+ break;
+ case LC_CTYPE:
+ save = &curr_locales.ctype;
+ break;
+ case LC_MESSAGES:
+ save = &curr_locales.messages;
+ break;
+ case LC_MONETARY:
+ save = &curr_locales.monetary;
+ break;
+ case LC_NUMERIC:
+ save = &curr_locales.numeric;
+ break;
+ case LC_TIME:
+ save = &curr_locales.time;
+ break;
+ default:
+ pg_unreachable();
+ }
+
+ /* Free old locale string, and stash the new one */
+ free(*save);
+ *save = result;
-#ifndef WIN32
- result = setlocale(category, locale);
#else
/*
@@ -248,11 +382,12 @@ pg_perm_setlocale(int category, const char *locale)
else
#endif
result = setlocale(category, locale);
-#endif /* WIN32 */
if (result == NULL)
return result; /* fall out immediately on failure */
+#endif /* HAVE_USELOCALE */
+
/*
* Use the right encoding in translated messages. Under ENABLE_NLS, let
* pg_bind_textdomain_codeset() figure it out. Under !ENABLE_NLS, message
@@ -262,11 +397,13 @@ pg_perm_setlocale(int category, const char *locale)
*/
if (category == LC_CTYPE)
{
+#ifndef HAVE_USELOCALE
static char save_lc_ctype[LOCALE_NAME_BUFLEN];
/* copy setlocale() return value before callee invokes it again */
strlcpy(save_lc_ctype, result, sizeof(save_lc_ctype));
result = save_lc_ctype;
+#endif
#ifdef ENABLE_NLS
SetMessageEncoding(pg_bind_textdomain_codeset(textdomain(NULL)));
@@ -275,42 +412,6 @@ pg_perm_setlocale(int category, const char *locale)
#endif
}
- switch (category)
- {
- case LC_COLLATE:
- envvar = "LC_COLLATE";
- break;
- case LC_CTYPE:
- envvar = "LC_CTYPE";
- break;
-#ifdef LC_MESSAGES
- case LC_MESSAGES:
- envvar = "LC_MESSAGES";
-#ifdef WIN32
- result = IsoLocaleName(locale);
- if (result == NULL)
- result = (char *) locale;
- elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", result);
-#endif /* WIN32 */
- break;
-#endif /* LC_MESSAGES */
- case LC_MONETARY:
- envvar = "LC_MONETARY";
- break;
- case LC_NUMERIC:
- envvar = "LC_NUMERIC";
- break;
- case LC_TIME:
- envvar = "LC_TIME";
- break;
- default:
- elog(FATAL, "unrecognized LC category: %d", category);
- return NULL; /* keep compiler quiet */
- }
-
- if (setenv(envvar, result, 1) != 0)
- return NULL;
-
return result;
}
@@ -328,6 +429,47 @@ pg_perm_setlocale(int category, const char *locale)
bool
check_locale(int category, const char *locale, char **canonname)
{
+#ifdef HAVE_USELOCALE
+ locale_t saved_locale;
+ locale_t temp_locale;
+ locale_t work_locale;
+ int category_mask;
+
+ category_mask = category_to_mask(category);
+
+ if (canonname)
+ *canonname = NULL; /* in case of failure */
+
+ saved_locale = uselocale((locale_t) 0);
+ work_locale = duplocale(saved_locale);
+ if (work_locale == (locale_t) 0) {
+ if (errno == ENOMEM)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+
+ return false;
+ }
+
+ temp_locale = newlocale(category_mask, locale, work_locale);
+ if (temp_locale == (locale_t) 0) {
+ freelocale(work_locale);
+
+ return false;
+ }
+ work_locale = temp_locale;
+
+ uselocale(work_locale);
+ if (canonname)
+ *canonname = pstrdup(locale); /* TODO: This does not handle "" as the locale */
+
+ uselocale(saved_locale);
+ freelocale(work_locale);
+
+ return true;
+
+#else
+
char *save;
char *res;
@@ -354,6 +496,7 @@ check_locale(int category, const char *locale, char **canonname)
pfree(save);
return (res != NULL);
+#endif /* HAVE_USELOCALE */
}
@@ -366,7 +509,7 @@ check_locale(int category, const char *locale, char **canonname)
*
* Note: we accept value = "" as selecting the postmaster's environment
* value, whatever it was (so long as the environment setting is legal).
- * This will have been locked down by an earlier call to pg_perm_setlocale.
+ * This will have been locked down by an earlier call to pg_setlocale.
*/
bool
check_locale_monetary(char **newval, void **extra, GucSource source)
@@ -445,7 +588,7 @@ assign_locale_messages(const char *newval, void *extra)
* We ignore failure, as per comment above.
*/
#ifdef LC_MESSAGES
- (void) pg_perm_setlocale(LC_MESSAGES, newval);
+ (void) pg_setlocale(LC_MESSAGES, newval);
#endif
}
@@ -541,11 +684,8 @@ PGLC_localeconv(void)
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
+ locale_t saved_locale;
+ locale_t work_locale;
/* Did we do it already? */
if (CurrentLocaleConvValid)
@@ -572,16 +712,15 @@ PGLC_localeconv(void)
*/
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 prevailing locale */
+ saved_locale = uselocale((locale_t) 0);
+ Assert(saved_locale != (locale_t) 0);
- save_lc_numeric = setlocale(LC_NUMERIC, NULL);
- if (!save_lc_numeric)
- elog(ERROR, "setlocale(NULL) failed");
- save_lc_numeric = pstrdup(save_lc_numeric);
+ work_locale = duplocale(saved_locale);
+ if (work_locale == (locale_t) 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
#ifdef WIN32
@@ -599,37 +738,37 @@ PGLC_localeconv(void)
* 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);
+ work_locale = newlocale(LC_CTYPE_MASK, locale_numeric, work_locale);
+ Assert(work_locale != (locale_t) 0);
#endif
/* Get formatting information for numeric */
- setlocale(LC_NUMERIC, locale_numeric);
+ work_locale = newlocale(LC_NUMERIC_MASK, locale_numeric, work_locale);
+ Assert(work_locale != (locale_t) 0);
+ uselocale(work_locale);
extlconv = localeconv();
- /* Must copy data now in case setlocale() overwrites it */
+ /* Must copy data now in case updating the locale overwrites it */
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);
+ work_locale = newlocale(LC_CTYPE_MASK, locale_monetary, work_locale);
+ Assert(work_locale != (locale_t) 0);
#endif
/* Get formatting information for monetary */
- setlocale(LC_MONETARY, locale_monetary);
+ work_locale = newlocale(LC_MONETARY_MASK, locale_monetary, work_locale);
+ Assert(work_locale != (locale_t) 0);
+ uselocale(work_locale);
extlconv = localeconv();
- /* Must copy data now in case setlocale() overwrites it */
+ /* Must copy data now in case updating the locale 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);
@@ -647,22 +786,9 @@ 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);
+ /* Restore the prevailing locale settings; failure to do so is fatal. */
+ uselocale(saved_locale);
+ freelocale(work_locale);
/*
* At this point we've done our best to clean up, and can call functions
@@ -673,13 +799,6 @@ PGLC_localeconv(void)
{
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,
@@ -826,10 +945,8 @@ cache_locale_time(void)
bool strftimefail = false;
int encoding;
int i;
- char *save_lc_time;
-#ifdef WIN32
- char *save_lc_ctype;
-#endif
+ locale_t saved_locale;
+ locale_t work_locale;
/* did we do this already? */
if (CurrentLCTimeValid)
@@ -844,11 +961,15 @@ cache_locale_time(void)
* 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);
+ /* Save prevailing locale */
+ saved_locale = uselocale((locale_t) 0);
+ Assert(saved_locale != (locale_t) 0);
+
+ work_locale = duplocale(saved_locale);
+ if (work_locale == (locale_t) 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
#ifdef WIN32
@@ -859,17 +980,14 @@ cache_locale_time(void)
* 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);
+ work_locale = newlocale(LC_CTYPE_MASK, locale_time, work_locale);
+ Assert(work_locale != (locale_t) 0);
#endif
- setlocale(LC_TIME, locale_time);
+ work_locale = newlocale(LC_TIME_MASK, locale_time, work_locale);
+ Assert(work_locale != (locale_t) 0);
+ uselocale(work_locale);
/* We use times close to current time as data for strftime(). */
timenow = time(NULL);
@@ -917,12 +1035,8 @@ cache_locale_time(void)
* 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);
-#endif
- if (!setlocale(LC_TIME, save_lc_time))
- elog(FATAL, "failed to restore LC_TIME to \"%s\"", save_lc_time);
+ uselocale(saved_locale);
+ freelocale(work_locale);
/*
* At this point we've done our best to clean up, and can throw errors, or
@@ -931,12 +1045,6 @@ cache_locale_time(void)
if (strftimefail)
elog(ERROR, "strftime() failed: %m");
- /* Release the pstrdup'd locale names */
- pfree(save_lc_time);
-#ifdef WIN32
- pfree(save_lc_ctype);
-#endif
-
#ifndef WIN32
/*
@@ -1331,18 +1439,14 @@ lc_collate_is_c(Oid collation)
if (collation == DEFAULT_COLLATION_OID)
{
static int result = -1;
- char *localeptr;
if (default_locale.provider == COLLPROVIDER_ICU)
return false;
if (result >= 0)
return (bool) result;
- localeptr = setlocale(LC_COLLATE, NULL);
- if (!localeptr)
- elog(ERROR, "invalid LC_COLLATE setting");
- result = locale_is_c(localeptr, false);
+ result = locale_is_c(curr_locales.collate, false);
return (bool) result;
}
@@ -1375,23 +1479,19 @@ lc_ctype_is_c(Oid collation)
/*
* If we're asked about the default collation, we have to inquire of the C
- * library. Cache the result so we only have to compute it once.
+ * library.
*/
if (collation == DEFAULT_COLLATION_OID)
{
static int result = -1;
- char *localeptr;
if (default_locale.provider == COLLPROVIDER_ICU)
return false;
if (result >= 0)
return (bool) result;
- localeptr = setlocale(LC_CTYPE, NULL);
- if (!localeptr)
- elog(ERROR, "invalid LC_CTYPE setting");
- result = locale_is_c(localeptr, false);
+ result = locale_is_c(curr_locales.ctype, false);
return (bool) result;
}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index a92a0c438f..0741f5666b 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -405,14 +405,14 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
ctype = TextDatumGetCString(datum);
- if (pg_perm_setlocale(LC_COLLATE, collate) == NULL)
+ if (pg_setlocale(LC_COLLATE, collate) == NULL)
ereport(FATAL,
(errmsg("database locale is incompatible with operating system"),
errdetail("The database was initialized with LC_COLLATE \"%s\", "
" which is not recognized by setlocale().", collate),
errhint("Recreate the database with another locale or install the missing locale.")));
- if (pg_perm_setlocale(LC_CTYPE, ctype) == NULL)
+ if (pg_setlocale(LC_CTYPE, ctype) == NULL)
ereport(FATAL,
(errmsg("database locale is incompatible with operating system"),
errdetail("The database was initialized with LC_CTYPE \"%s\", "
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index 997156515c..8dc7da47bd 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -1238,7 +1238,7 @@ pg_bind_textdomain_codeset(const char *domainname)
int new_msgenc;
#ifndef WIN32
- if (locale_is_c(locale_ctype, true))
+ if (locale_is_c(pg_setlocale(LC_CTYPE, NULL), true))
#endif
if (encoding != PG_SQL_ASCII &&
raw_pg_bind_textdomain_codeset(domainname, encoding))
diff --git a/src/common/exec.c b/src/common/exec.c
index f209b934df..f7a9643be4 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -438,22 +438,6 @@ set_pglocale_pgservice(const char *argv0, const char *app)
char path[MAXPGPATH];
char my_exec_path[MAXPGPATH];
- /* don't set LC_ALL in the backend */
- if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0)
- {
- setlocale(LC_ALL, "");
-
- /*
- * One could make a case for reproducing here PostmasterMain()'s test
- * for whether the process is multithreaded. Unlike the postmaster,
- * no frontend program calls sigprocmask() or otherwise provides for
- * mutual exclusion between signal handlers. While frontends using
- * fork(), if multithreaded, are formally exposed to undefined
- * behavior, we have not witnessed a concrete bug. Therefore,
- * complaining about multithreading here may be mere pedantry.
- */
- }
-
if (find_my_exec(argv0, my_exec_path) < 0)
return;
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index e08d96e099..585cdd3931 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -52,7 +52,7 @@ extern PGDLLIMPORT char *localized_full_months[];
extern PGDLLIMPORT bool database_ctype_is_c;
extern bool check_locale(int category, const char *locale, char **canonname);
-extern char *pg_perm_setlocale(int category, const char *locale);
+extern char *pg_setlocale(int category, const char *locale);
extern bool locale_is_c(const char *locale, bool ignore_case);
extern bool lc_collate_is_c(Oid collation);
--
Tristan Partin
Neon (https://neon.tech)