From: Tristan Partin <tristan@neon.tech>
To: Tristan Partin <tristan@neon.tech>
To: joe@cd
To: Heikki Linnakangas <hlinnaka@iki.fi>
To: Tom Lane <tgl@sss.pgh.pa.us>
To: gdo@leader.it
Cc: PostgreSQL-development <pgsql-hackers@postgresql.org>
Subject: Re: BUG #17946: LC_MONETARY & DO LANGUAGE plperl - BUG
Date: Mon, 10 Jul 2023 19:52:32 -0500
Message-ID: <CTYXENTV385K.18K3T1Q7IVAB7@gonk> (raw)
In-Reply-To: <CTUJD214VCXS.1SD6HDEQMICD0@gonk>
References: <17946-3e84cb577e9551c3@postgresql.org>
<1564676.1685043220@sss.pgh.pa.us>
<555bb26e-c27c-ab65-028f-3fd5b82df9de@iki.fi>
<CTPMCSM9UB07.3CUPU2JW4BBZ6@gonk>
<1645ebad-798f-87ad-43ac-72d2bf14d2d6@cd>
<CTSM7EZZMY12.1F7GV7Y2KPBBD@gonk>
<CTUJC778UCIW.28QKG8IC14KCO@gonk>
<CTUJD214VCXS.1SD6HDEQMICD0@gonk>
Here is an up to date patch given some churn on the master branch.
--
Tristan Partin
Neon (https://neon.tech)
Attachments:download all (3 files, .tar.gz)v3-0001-Skip-checking-for-uselocale-on-Windows.patch (text/x-patch, 677B) download
From b68cec481768c7c635ec48329b4764eced264572 Mon Sep 17 00:00:00 2001
From: Tristan Partin <tristan@neon.tech>
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.buildindex 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)
v3-0002-Add-locale_is_c-function.patch (text/x-patch, 4.5K) download
From 3207694a1d214a7d5b844f3f6dfd8378408172af Mon Sep 17 00:00:00 2001
From: Tristan Partin <tristan@neon.tech>
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.cindex 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.cindex 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.cindex 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.hindex 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)
v3-0003-Use-thread-safe-locale-APIs.patch (text/x-patch, 27.6K) download
From bc0a79fd0b1cf5f45c93dfd40fc00c054646fb00 Mon Sep 17 00:00:00 2001
From: Tristan Partin <tristan@neon.tech>
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.cindex 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.cindex 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.cindex 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.cindex 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.cindex 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.cindex 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.cindex 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.hindex 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)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-bugs@postgresql.org
Cc: tristan@neon.tech, joe@cd, hlinnaka@iki.fi, tgl@sss.pgh.pa.us, gdo@leader.it, pgsql-hackers@postgresql.org
Subject: Re: BUG #17946: LC_MONETARY & DO LANGUAGE plperl - BUG
In-Reply-To: <CTYXENTV385K.18K3T1Q7IVAB7@gonk>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox