v2-0001-Improve-locale-thread-safety-of-ECPG.patch
text/x-patch
Filename: v2-0001-Improve-locale-thread-safety-of-ECPG.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Improve locale thread safety of ECPG.
| File | + | − |
|---|---|---|
| config/c-library.m4 | 0 | 55 |
| configure | 1 | 114 |
| configure.ac | 2 | 4 |
| meson.build | 3 | 34 |
| src/include/pg_config.h.in | 6 | 9 |
| src/include/port.h | 29 | 0 |
| src/include/utils/pg_locale.h | 1 | 1 |
| src/interfaces/ecpg/ecpglib/connect.c | 0 | 37 |
| src/interfaces/ecpg/ecpglib/data.c | 1 | 1 |
| src/interfaces/ecpg/ecpglib/descriptor.c | 0 | 37 |
| src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 0 | 15 |
| src/interfaces/ecpg/ecpglib/execute.c | 0 | 55 |
| src/interfaces/ecpg/pgtypeslib/dt_common.c | 3 | 3 |
| src/interfaces/ecpg/pgtypeslib/interval.c | 2 | 2 |
| src/interfaces/ecpg/pgtypeslib/numeric.c | 1 | 1 |
| src/port/locale.c | 73 | 0 |
| src/port/Makefile | 1 | 0 |
| src/port/meson.build | 1 | 0 |
| src/port/snprintf.c | 33 | 0 |
From bc64f2db61b3e2cf7a0cd00b1deaa260d91e2b3c Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Sat, 10 Aug 2024 11:01:21 +1200
Subject: [PATCH v2] Improve locale thread safety of ECPG.
Remove the use of setlocale() as a fallback strategy on systems that
don't have uselocale(), where ECPG tries to control LC_NUMERIC
formatting on input and output of floating point numbers.
Instead, create a new function pg_get_c_locale() that can be used to get
a locale_t object for use with thread-safe parsing and formatting
functions. Then:
1. Use strtod_l() for parsing, and supply an implementation using
uselocale() if it is missing. (All systems have one of strtod_l(),
_strtod_l() or uselocale() for a replacement.)
2. Inside our own snprintf.c, use snprintf_l() or _snprintf_l() where
we punt floating point numbers to the system snprintf(), or wrap it in
uselocale() if those are not available.
Since the non-standard _l() functions require <xlocale.h> on *BSD/macOS
systems, simplify the configure probes: instead of XXX_IN_XLOCALE_H for
several features XXX, let's just include <xlocale.h> if HAVE_XLOCALE_H.
The reason for the extra complication was apparently that some old glibc
systems also had an <xlocale.h>, and you weren't supposed to include it
directly, but it's gone now (as far as I can tell it was harmless to do
so anyway).
Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech
---
config/c-library.m4 | 55 ---------
configure | 115 +------------------
configure.ac | 6 +-
meson.build | 37 +-----
src/include/pg_config.h.in | 15 +--
src/include/port.h | 29 +++++
src/include/utils/pg_locale.h | 2 +-
src/interfaces/ecpg/ecpglib/connect.c | 37 ------
src/interfaces/ecpg/ecpglib/data.c | 2 +-
src/interfaces/ecpg/ecpglib/descriptor.c | 37 ------
src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 15 ---
src/interfaces/ecpg/ecpglib/execute.c | 55 ---------
src/interfaces/ecpg/pgtypeslib/dt_common.c | 6 +-
src/interfaces/ecpg/pgtypeslib/interval.c | 4 +-
src/interfaces/ecpg/pgtypeslib/numeric.c | 2 +-
src/port/Makefile | 1 +
src/port/locale.c | 73 ++++++++++++
src/port/meson.build | 1 +
src/port/snprintf.c | 33 ++++++
19 files changed, 157 insertions(+), 368 deletions(-)
create mode 100644 src/port/locale.c
diff --git a/config/c-library.m4 b/config/c-library.m4
index aa8223d2ef..421bc612b2 100644
--- a/config/c-library.m4
+++ b/config/c-library.m4
@@ -81,58 +81,3 @@ AC_DEFUN([PGAC_STRUCT_SOCKADDR_SA_LEN],
[#include <sys/types.h>
#include <sys/socket.h>
])])# PGAC_STRUCT_SOCKADDR_MEMBERS
-
-
-# PGAC_TYPE_LOCALE_T
-# ------------------
-# Check for the locale_t type and find the right header file. macOS
-# needs xlocale.h; standard is locale.h, but glibc <= 2.25 also had an
-# xlocale.h file that we should not use, so we check the standard
-# header first.
-AC_DEFUN([PGAC_TYPE_LOCALE_T],
-[AC_CACHE_CHECK([for locale_t], pgac_cv_type_locale_t,
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[#include <locale.h>
-locale_t x;],
-[])],
-[pgac_cv_type_locale_t=yes],
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[#include <xlocale.h>
-locale_t x;],
-[])],
-[pgac_cv_type_locale_t='yes (in xlocale.h)'],
-[pgac_cv_type_locale_t=no])])])
-if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then
- AC_DEFINE(LOCALE_T_IN_XLOCALE, 1,
- [Define to 1 if `locale_t' requires <xlocale.h>.])
-fi])# PGAC_TYPE_LOCALE_T
-
-
-# PGAC_FUNC_WCSTOMBS_L
-# --------------------
-# Try to find a declaration for wcstombs_l(). It might be in stdlib.h
-# (following the POSIX requirement for wcstombs()), or in locale.h, or in
-# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE.
-#
-AC_DEFUN([PGAC_FUNC_WCSTOMBS_L],
-[AC_CACHE_CHECK([for wcstombs_l declaration], pgac_cv_func_wcstombs_l,
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[#include <stdlib.h>
-#include <locale.h>],
-[#ifndef wcstombs_l
-(void) wcstombs_l;
-#endif])],
-[pgac_cv_func_wcstombs_l='yes'],
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
-[#include <stdlib.h>
-#include <locale.h>
-#include <xlocale.h>],
-[#ifndef wcstombs_l
-(void) wcstombs_l;
-#endif])],
-[pgac_cv_func_wcstombs_l='yes (in xlocale.h)'],
-[pgac_cv_func_wcstombs_l='no'])])])
-if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then
- AC_DEFINE(WCSTOMBS_L_IN_XLOCALE, 1,
- [Define to 1 if `wcstombs_l' requires <xlocale.h>.])
-fi])# PGAC_FUNC_WCSTOMBS_L
diff --git a/configure b/configure
index 4f3aa44756..298492ec25 100755
--- a/configure
+++ b/configure
@@ -14635,55 +14635,6 @@ _ACEOF
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for locale_t" >&5
-$as_echo_n "checking for locale_t... " >&6; }
-if ${pgac_cv_type_locale_t+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <locale.h>
-locale_t x;
-int
-main ()
-{
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- pgac_cv_type_locale_t=yes
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <xlocale.h>
-locale_t x;
-int
-main ()
-{
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- pgac_cv_type_locale_t='yes (in xlocale.h)'
-else
- pgac_cv_type_locale_t=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_type_locale_t" >&5
-$as_echo "$pgac_cv_type_locale_t" >&6; }
-if test "$pgac_cv_type_locale_t" = 'yes (in xlocale.h)'; then
-
-$as_echo "#define LOCALE_T_IN_XLOCALE 1" >>confdefs.h
-
-fi
-
# MSVC doesn't cope well with defining restrict to __restrict, the
# spelling it understands, because it conflicts with
# __declspec(restrict). Therefore we define pg_restrict to the
@@ -15170,59 +15121,6 @@ if test x"$pgac_cv_var_int_timezone" = xyes ; then
$as_echo "#define HAVE_INT_TIMEZONE 1" >>confdefs.h
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for wcstombs_l declaration" >&5
-$as_echo_n "checking for wcstombs_l declaration... " >&6; }
-if ${pgac_cv_func_wcstombs_l+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <stdlib.h>
-#include <locale.h>
-int
-main ()
-{
-#ifndef wcstombs_l
-(void) wcstombs_l;
-#endif
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- pgac_cv_func_wcstombs_l='yes'
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <stdlib.h>
-#include <locale.h>
-#include <xlocale.h>
-int
-main ()
-{
-#ifndef wcstombs_l
-(void) wcstombs_l;
-#endif
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- pgac_cv_func_wcstombs_l='yes (in xlocale.h)'
-else
- pgac_cv_func_wcstombs_l='no'
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_func_wcstombs_l" >&5
-$as_echo "$pgac_cv_func_wcstombs_l" >&6; }
-if test "$pgac_cv_func_wcstombs_l" = 'yes (in xlocale.h)'; then
-
-$as_echo "#define WCSTOMBS_L_IN_XLOCALE 1" >>confdefs.h
-
fi
# Some versions of libedit contain strlcpy(), setproctitle(), and other
@@ -15232,7 +15130,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 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 getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast snprintf_l strtod_l 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"
@@ -16005,17 +15903,6 @@ fi
# Win32 (really MinGW) support
if test "$PORTNAME" = "win32"; then
- for ac_func in _configthreadlocale
-do :
- ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale"
-if test "x$ac_cv_func__configthreadlocale" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE__CONFIGTHREADLOCALE 1
-_ACEOF
-
-fi
-done
-
case " $LIBOBJS " in
*" dirmod.$ac_objext "* ) ;;
*) LIBOBJS="$LIBOBJS dirmod.$ac_objext"
diff --git a/configure.ac b/configure.ac
index 049bc01491..302aadd427 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1620,8 +1620,6 @@ PGAC_UNION_SEMUN
AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>])
PGAC_STRUCT_SOCKADDR_SA_LEN
-PGAC_TYPE_LOCALE_T
-
# MSVC doesn't cope well with defining restrict to __restrict, the
# spelling it understands, because it conflicts with
# __declspec(restrict). Therefore we define pg_restrict to the
@@ -1720,7 +1718,6 @@ fi
##
PGAC_VAR_INT_TIMEZONE
-PGAC_FUNC_WCSTOMBS_L
# Some versions of libedit contain strlcpy(), setproctitle(), and other
# symbols that that library has no business exposing to the world. Pending
@@ -1744,6 +1741,8 @@ AC_CHECK_FUNCS(m4_normalize([
pthread_is_threaded_np
setproctitle
setproctitle_fast
+ snprintf_l
+ strtod_l
strchrnul
strsignal
syncfs
@@ -1833,7 +1832,6 @@ fi
# Win32 (really MinGW) support
if test "$PORTNAME" = "win32"; then
- AC_CHECK_FUNCS(_configthreadlocale)
AC_LIBOBJ(dirmod)
AC_LIBOBJ(kill)
AC_LIBOBJ(open)
diff --git a/meson.build b/meson.build
index cc176f11b5..932334ed63 100644
--- a/meson.build
+++ b/meson.build
@@ -2407,6 +2407,7 @@ header_checks = [
'sys/ucred.h',
'termios.h',
'ucred.h',
+ 'xlocale.h',
]
foreach header : header_checks
@@ -2550,15 +2551,6 @@ else
cdata.set('STRERROR_R_INT', false)
endif
-# Find the right header file for the locale_t type. macOS needs xlocale.h;
-# standard is locale.h, but glibc <= 2.25 also had an xlocale.h file that
-# we should not use so we check the standard header first. MSVC has a
-# replacement defined in src/include/port/win32_port.h.
-if not cc.has_type('locale_t', prefix: '#include <locale.h>') and \
- cc.has_type('locale_t', prefix: '#include <xlocale.h>')
- cdata.set('LOCALE_T_IN_XLOCALE', 1)
-endif
-
# Check if the C compiler understands typeof or a variant. Define
# HAVE_TYPEOF if so, and define 'typeof' to the actual key word.
foreach kw : ['typeof', '__typeof__', 'decltype']
@@ -2583,30 +2575,6 @@ int main(void)
endif
endforeach
-
-# Try to find a declaration for wcstombs_l(). It might be in stdlib.h
-# (following the POSIX requirement for wcstombs()), or in locale.h, or in
-# xlocale.h. If it's in the latter, define WCSTOMBS_L_IN_XLOCALE.
-wcstombs_l_test = '''
-#include <stdlib.h>
-#include <locale.h>
-@0@
-
-void main(void)
-{
-#ifndef wcstombs_l
- (void) wcstombs_l;
-#endif
-}
-'''
-if (not cc.compiles(wcstombs_l_test.format(''),
- name: 'wcstombs_l') and
- cc.compiles(wcstombs_l_test.format('#include <xlocale.h>'),
- name: 'wcstombs_l in xlocale.h'))
- cdata.set('WCSTOMBS_L_IN_XLOCALE', 1)
-endif
-
-
# MSVC doesn't cope well with defining restrict to __restrict, the spelling it
# understands, because it conflicts with __declspec(restrict). Therefore we
# define pg_restrict to the appropriate definition, which presumably won't
@@ -2658,7 +2626,6 @@ endif
# XXX: Might be worth conditioning some checks on the OS, to avoid doing
# unnecessary checks over and over, particularly on windows.
func_checks = [
- ['_configthreadlocale', {'skip': host_system != 'windows'}],
['backtrace_symbols', {'dependencies': [execinfo_dep]}],
['clock_gettime', {'dependencies': [rt_dep], 'define': false}],
['copyfile'],
@@ -2690,6 +2657,8 @@ func_checks = [
['shm_open', {'dependencies': [rt_dep], 'define': false}],
['shm_unlink', {'dependencies': [rt_dep], 'define': false}],
['shmget', {'dependencies': [cygipc_dep], 'define': false}],
+ ['snprintf_l'],
+ ['strtod_l'],
['socket', {'dependencies': [socket_dep], 'define': false}],
['strchrnul'],
['strerror_r', {'dependencies': [thread_dep]}],
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 0e9b108e66..c0586edab1 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -376,6 +376,9 @@
/* Define to 1 if you have the `setproctitle_fast' function. */
#undef HAVE_SETPROCTITLE_FAST
+/* Define to 1 if you have the `snprintf_l' function. */
+#undef HAVE_SNPRINTF_L
+
/* Define to 1 if the system has the type `socklen_t'. */
#undef HAVE_SOCKLEN_T
@@ -421,6 +424,9 @@
/* Define to 1 if you have the `strsignal' function. */
#undef HAVE_STRSIGNAL
+/* Define to 1 if you have the `strtod_l' function. */
+#undef HAVE_STRTOD_L
+
/* Define to 1 if the system has the type `struct option'. */
#undef HAVE_STRUCT_OPTION
@@ -556,9 +562,6 @@
/* Define to 1 if your compiler understands __builtin_unreachable. */
#undef HAVE__BUILTIN_UNREACHABLE
-/* Define to 1 if you have the `_configthreadlocale' function. */
-#undef HAVE__CONFIGTHREADLOCALE
-
/* Define to 1 if you have __cpuid. */
#undef HAVE__CPUID
@@ -577,9 +580,6 @@
/* Define to the appropriate printf length modifier for 64-bit ints. */
#undef INT64_MODIFIER
-/* Define to 1 if `locale_t' requires <xlocale.h>. */
-#undef LOCALE_T_IN_XLOCALE
-
/* Define as the maximum alignment requirement of any C data type. */
#undef MAXIMUM_ALIGNOF
@@ -766,9 +766,6 @@
/* Define to 1 to build with ZSTD support. (--with-zstd) */
#undef USE_ZSTD
-/* Define to 1 if `wcstombs_l' requires <xlocale.h>. */
-#undef WCSTOMBS_L_IN_XLOCALE
-
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
significant byte first (like Motorola and SPARC, unlike Intel). */
#if defined AC_APPLE_UNIVERSAL_BUILD
diff --git a/src/include/port.h b/src/include/port.h
index c740005267..26a78320f8 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -15,6 +15,11 @@
#include <ctype.h>
+#include <locale.h>
+#ifdef HAVE_XLOCALE_H
+#include <xlocale.h>
+#endif
+
/*
* Windows has enough specialized port stuff that we push most of it off
* into another file.
@@ -217,6 +222,30 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2,
extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0);
extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2);
+/* Acessor for a thread-safe process-lifetime "C" locale. */
+extern locale_t pg_get_c_locale(void);
+
+#ifndef HAVE_STRTOD_L
+/*
+ * POSIX doesn't define this function, but we can implement it with thread-safe
+ * save-and-restore. Not all systems have uselocale(), but the ones that don't
+ * have strtod_l().
+ */
+static inline double
+strtod_l(const char *nptr, char **endptr, locale_t loc)
+{
+#ifdef WIN32
+ return _strtod_l(nptr, endptr, loc);
+#else
+ locale_t save = uselocale(loc);
+ double result = strtod(nptr, endptr);
+
+ uselocale(save);
+ return result;
+#endif
+}
+#endif
+
#ifndef WIN32
/*
* We add a pg_ prefix as a warning that the Windows implementations have the
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index f41d33975b..9651f3c3ab 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -12,7 +12,7 @@
#ifndef _PG_LOCALE_
#define _PG_LOCALE_
-#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE)
+#ifdef HAVE_XLOCALE_H
#include <xlocale.h>
#endif
#ifdef USE_ICU
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
index 8afb1f0a26..eac1a2e9b5 100644
--- a/src/interfaces/ecpg/ecpglib/connect.c
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -10,10 +10,6 @@
#include "ecpgtype.h"
#include "sqlca.h"
-#ifdef HAVE_USELOCALE
-locale_t ecpg_clocale = (locale_t) 0;
-#endif
-
static pthread_mutex_t connections_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_key_t actual_connection_key;
static pthread_once_t actual_connection_key_once = PTHREAD_ONCE_INIT;
@@ -483,39 +479,6 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
/* add connection to our list */
pthread_mutex_lock(&connections_mutex);
- /*
- * ... but first, make certain we have created ecpg_clocale. Rely on
- * holding connections_mutex to ensure this is done by only one thread.
- */
-#ifdef HAVE_USELOCALE
- if (!ecpg_clocale)
- {
- ecpg_clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
- if (!ecpg_clocale)
- {
- pthread_mutex_unlock(&connections_mutex);
- ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
- ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
- if (host)
- ecpg_free(host);
- if (port)
- ecpg_free(port);
- if (options)
- ecpg_free(options);
- if (realname)
- ecpg_free(realname);
- if (dbname)
- ecpg_free(dbname);
- if (conn_keywords)
- ecpg_free(conn_keywords);
- if (conn_values)
- ecpg_free(conn_values);
- free(this);
- return false;
- }
- }
-#endif
-
if (connection_name != NULL)
this->name = ecpg_strdup(connection_name, lineno);
else
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fa56276758..228df49052 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -466,7 +466,7 @@ ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
pval++;
if (!check_special_value(pval, &dres, &scan_length))
- dres = strtod(pval, &scan_length);
+ dres = strtod_l(pval, &scan_length, pg_get_c_locale());
if (isarray && *scan_length == '"')
scan_length++;
diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c
index ad279e245c..855322e037 100644
--- a/src/interfaces/ecpg/ecpglib/descriptor.c
+++ b/src/interfaces/ecpg/ecpglib/descriptor.c
@@ -475,46 +475,9 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
memset(&stmt, 0, sizeof stmt);
stmt.lineno = lineno;
- /* Make sure we do NOT honor the locale for numeric input */
- /* since the database gives the standard decimal point */
- /* (see comments in execute.c) */
-#ifdef HAVE_USELOCALE
-
- /*
- * To get here, the above PQnfields() test must have found nonzero
- * fields. One needs a connection to create such a descriptor. (EXEC
- * SQL SET DESCRIPTOR can populate the descriptor's "items", but it
- * can't change the descriptor's PQnfields().) Any successful
- * connection initializes ecpg_clocale.
- */
- Assert(ecpg_clocale);
- stmt.oldlocale = uselocale(ecpg_clocale);
-#else
-#ifdef HAVE__CONFIGTHREADLOCALE
- stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
-#endif
- stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
- setlocale(LC_NUMERIC, "C");
-#endif
-
/* desperate try to guess something sensible */
stmt.connection = ecpg_get_connection(NULL);
ecpg_store_result(ECPGresult, index, &stmt, &data_var);
-
-#ifdef HAVE_USELOCALE
- if (stmt.oldlocale != (locale_t) 0)
- uselocale(stmt.oldlocale);
-#else
- if (stmt.oldlocale)
- {
- setlocale(LC_NUMERIC, stmt.oldlocale);
- ecpg_free(stmt.oldlocale);
- }
-#ifdef HAVE__CONFIGTHREADLOCALE
- if (stmt.oldthreadlocale != -1)
- (void) _configthreadlocale(stmt.oldthreadlocale);
-#endif
-#endif
}
else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
diff --git a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h
index 01b4309a71..40988d5357 100644
--- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h
+++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h
@@ -13,9 +13,6 @@
#ifndef CHAR_BIT
#include <limits.h>
#endif
-#ifdef LOCALE_T_IN_XLOCALE
-#include <xlocale.h>
-#endif
enum COMPAT_MODE
{
@@ -59,10 +56,6 @@ struct ECPGtype_information_cache
enum ARRAY_TYPE isarray;
};
-#ifdef HAVE_USELOCALE
-extern locale_t ecpg_clocale; /* LC_NUMERIC=C */
-#endif
-
/* structure to store one statement */
struct statement
{
@@ -76,14 +69,6 @@ struct statement
bool questionmarks;
struct variable *inlist;
struct variable *outlist;
-#ifdef HAVE_USELOCALE
- locale_t oldlocale;
-#else
- char *oldlocale;
-#ifdef HAVE__CONFIGTHREADLOCALE
- int oldthreadlocale;
-#endif
-#endif
int nparams;
char **paramvalues;
int *paramlengths;
diff --git a/src/interfaces/ecpg/ecpglib/execute.c b/src/interfaces/ecpg/ecpglib/execute.c
index 04d0b40c53..806d4779fa 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -101,9 +101,6 @@ free_statement(struct statement *stmt)
free_variable(stmt->outlist);
ecpg_free(stmt->command);
ecpg_free(stmt->name);
-#ifndef HAVE_USELOCALE
- ecpg_free(stmt->oldlocale);
-#endif
ecpg_free(stmt);
}
@@ -1973,40 +1970,6 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
if (stmt == NULL)
return false;
- /*
- * Make sure we do NOT honor the locale for numeric input/output since the
- * database wants the standard decimal point. If available, use
- * uselocale() for this because it's thread-safe. Windows doesn't have
- * that, but it usually does have _configthreadlocale(). In some versions
- * of MinGW, _configthreadlocale() exists but always returns -1 --- so
- * treat that situation as if the function doesn't exist.
- */
-#ifdef HAVE_USELOCALE
-
- /*
- * Since ecpg_init() succeeded, we have a connection. Any successful
- * connection initializes ecpg_clocale.
- */
- Assert(ecpg_clocale);
- stmt->oldlocale = uselocale(ecpg_clocale);
- if (stmt->oldlocale == (locale_t) 0)
- {
- ecpg_do_epilogue(stmt);
- return false;
- }
-#else
-#ifdef HAVE__CONFIGTHREADLOCALE
- stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
-#endif
- stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
- if (stmt->oldlocale == NULL)
- {
- ecpg_do_epilogue(stmt);
- return false;
- }
- setlocale(LC_NUMERIC, "C");
-#endif
-
/*
* If statement type is ECPGst_prepnormal we are supposed to prepare the
* statement before executing them
@@ -2213,24 +2176,6 @@ ecpg_do_epilogue(struct statement *stmt)
if (stmt == NULL)
return;
-#ifdef HAVE_USELOCALE
- if (stmt->oldlocale != (locale_t) 0)
- uselocale(stmt->oldlocale);
-#else
- if (stmt->oldlocale)
- setlocale(LC_NUMERIC, stmt->oldlocale);
-#ifdef HAVE__CONFIGTHREADLOCALE
-
- /*
- * This is a bit trickier than it looks: if we failed partway through
- * statement initialization, oldthreadlocale could still be 0. But that's
- * okay because a call with 0 is defined to be a no-op.
- */
- if (stmt->oldthreadlocale != -1)
- (void) _configthreadlocale(stmt->oldthreadlocale);
-#endif
-#endif
-
free_statement(stmt);
}
diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c
index ed08088bfe..6aff989e0f 100644
--- a/src/interfaces/ecpg/pgtypeslib/dt_common.c
+++ b/src/interfaces/ecpg/pgtypeslib/dt_common.c
@@ -1216,7 +1216,7 @@ DecodeNumber(int flen, char *str, int fmask,
return DecodeNumberField(flen, str, (fmask | DTK_DATE_M),
tmask, tm, fsec, is2digits);
- *fsec = strtod(cp, &cp);
+ *fsec = strtod_l(cp, &cp, pg_get_c_locale());
if (*cp != '\0')
return -1;
}
@@ -2028,7 +2028,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
{
double frac;
- frac = strtod(cp, &cp);
+ frac = strtod_l(cp, &cp, pg_get_c_locale());
if (*cp != '\0')
return -1;
*fsec = frac * 1000000;
@@ -2052,7 +2052,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
{
double time;
- time = strtod(cp, &cp);
+ time = strtod_l(cp, &cp, pg_get_c_locale());
if (*cp != '\0')
return -1;
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
index 936a688381..8fc34acf68 100644
--- a/src/interfaces/ecpg/pgtypeslib/interval.c
+++ b/src/interfaces/ecpg/pgtypeslib/interval.c
@@ -60,7 +60,7 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
return DTERR_BAD_FORMAT;
errno = 0;
- val = strtod(str, endptr);
+ val = strtod_l(str, endptr, pg_get_c_locale());
/* did we not see anything that looks like a double? */
if (*endptr == str || errno != 0)
return DTERR_BAD_FORMAT;
@@ -455,7 +455,7 @@ DecodeInterval(char **field, int *ftype, int nf, /* int range, */
else if (*cp == '.')
{
errno = 0;
- fval = strtod(cp, &cp);
+ fval = strtod_l(cp, &cp, pg_get_c_locale());
if (*cp != '\0' || errno != 0)
return DTERR_BAD_FORMAT;
diff --git a/src/interfaces/ecpg/pgtypeslib/numeric.c b/src/interfaces/ecpg/pgtypeslib/numeric.c
index 35e7b92da4..f53fdb9f10 100644
--- a/src/interfaces/ecpg/pgtypeslib/numeric.c
+++ b/src/interfaces/ecpg/pgtypeslib/numeric.c
@@ -1455,7 +1455,7 @@ numericvar_to_double(numeric *var, double *dp)
* strtod does not reset errno to 0 in case of success.
*/
errno = 0;
- val = strtod(tmp, &endptr);
+ val = strtod_l(tmp, &endptr, pg_get_c_locale());
if (errno == ERANGE)
{
free(tmp);
diff --git a/src/port/Makefile b/src/port/Makefile
index db7c02117b..03b55bd26b 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -42,6 +42,7 @@ OBJS = \
bsearch_arg.o \
chklocale.o \
inet_net_ntop.o \
+ locale.o \
noblock.o \
path.o \
pg_bitutils.o \
diff --git a/src/port/locale.c b/src/port/locale.c
new file mode 100644
index 0000000000..ca463257ef
--- /dev/null
+++ b/src/port/locale.c
@@ -0,0 +1,73 @@
+/*-------------------------------------------------------------------------
+ *
+ * locale.c
+ * Helper routines for thread-safe system locale usage.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/port/locale.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+#include <locale.h>
+#ifdef HAVE_XLOCALE_H
+#include <xlocale.h>
+#endif
+
+#ifndef WIN32
+#include <pthread.h>
+#else
+#include <synchapi.h>
+#endif
+
+static locale_t c_locale;
+
+#ifndef WIN32
+static void
+init_c_locale_once(void)
+{
+ c_locale = newlocale(LC_ALL, "C", NULL);
+}
+#else
+static BOOL
+init_c_locale_once(PINIT_ONCE once, PVOID parameter, PVOID *context)
+{
+ c_locale = _create_locale(LC_ALL, "C");
+ return true;
+}
+#endif
+
+locale_t
+pg_get_c_locale(void)
+{
+ /*
+ * Fast path if already initialized. This assumes that we can read a
+ * locale_t (in practice, a pointer) without tearing in a multi-threaded
+ * program.
+ */
+ if (c_locale != (locale_t) 0)
+ return c_locale;
+
+ /* Make a locale_t. It will live until process exit. */
+ {
+#ifndef WIN32
+ static pthread_once_t once = PTHREAD_ONCE_INIT;
+
+ pthread_once(&once, init_c_locale_once);
+#else
+ static INIT_ONCE once;
+ InitOnceExecuteOnce(&once, init_c_locale_once, NULL, NULL);
+#endif
+ }
+
+ Assert(c_locale != (locale_t) 0);
+
+ return c_locale;
+}
diff --git a/src/port/meson.build b/src/port/meson.build
index ff54b7b53e..e21961fd73 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -5,6 +5,7 @@ pgport_sources = [
'chklocale.c',
'inet_net_ntop.c',
'noblock.c',
+ 'locale.c',
'path.c',
'pg_bitutils.c',
'pg_strong_random.c',
diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index 884f0262dd..c122e0deb7 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -35,6 +35,7 @@
#include <math.h>
+
/*
* We used to use the platform's NL_ARGMAX here, but that's a bad idea,
* first because the point of this module is to remove platform dependencies
@@ -109,6 +110,15 @@
#undef vprintf
#undef printf
+#ifdef WIN32
+/*
+ * This is only good enough to claim support for the usage we make in this
+ * module, so keep this claim of HAVE_SNPRINTF_L local.
+ */
+#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__)
+#define HAVE_SNPRINTF_L
+#endif
+
/*
* Info about where the formatted output is going.
*
@@ -1184,6 +1194,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
* according to == but not according to memcmp.
*/
static const double dzero = 0.0;
+#ifndef HAVE_SNPRINTF_L
+ locale_t save_locale = uselocale(pg_get_c_locale());
+#endif
if (adjust_sign((value < 0.0 ||
(value == 0.0 &&
@@ -1205,7 +1218,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
fmt[2] = '*';
fmt[3] = type;
fmt[4] = '\0';
+#ifdef HAVE_SNPRINTF_L
+ vallen = snprintf_l(convert, sizeof(convert), pg_get_c_locale(), fmt, prec, value);
+#else
vallen = snprintf(convert, sizeof(convert), fmt, prec, value);
+#endif
}
else
{
@@ -1214,6 +1231,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
fmt[2] = '\0';
vallen = snprintf(convert, sizeof(convert), fmt, value);
}
+
+#ifndef HAVE_SNPRINTF_L
+ uselocale(save_locale);
+#endif
+
if (vallen < 0)
goto fail;
@@ -1336,12 +1358,23 @@ pg_strfromd(char *str, size_t count, int precision, double value)
}
else
{
+#ifndef HAVE_SNPRINTF_L
+ locale_t save_locale;
+#endif
+
fmt[0] = '%';
fmt[1] = '.';
fmt[2] = '*';
fmt[3] = 'g';
fmt[4] = '\0';
+#ifdef HAVE_SNPRINTF_L
+ vallen = snprintf_l(convert, sizeof(convert), pg_get_c_locale(), fmt, precision, value);
+#else
+ save_locale = uselocale(pg_get_c_locale());
vallen = snprintf(convert, sizeof(convert), fmt, precision, value);
+ uselocale(save_locale);
+#endif
+
if (vallen < 0)
{
target.failed = true;
--
2.39.2