From 512110458d6916efb7515770d6c4f4b7477b1d22 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Thu, 16 Nov 2023 15:53:51 +1300
Subject: [PATCH] ecpg: Use thread-safe _l() functions if possible.

In order to use snprintf("%g") and strtod() with the C locale in certain
parts of the ECPG code, we call uselocale() around those sections.

Two operating systems don't have uselocale(), but our fallbacks were
non-ideal.  For NetBSD we'd use setlocale(), which is not thread-safe
and dangerous, and for Windows we'd either use setlocale() or a Windows
API that is egregiously different and hard to maintain, depending on the
compiler tool chain.

We can remove all of that and use not-yet-standardized [v]s[n]printf_l()
and strtod_l(), if available.  Those two operating systems, along with
the other BSDs and macOS have them.  Glibc has strtod_l() but not yet
the other.

Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech
---
 configure                                    |  13 +-
 configure.ac                                 |   5 +-
 meson.build                                  |   5 +-
 src/include/pg_config.h.in                   |  15 +-
 src/interfaces/ecpg/ecpglib/connect.c        |  55 ++++----
 src/interfaces/ecpg/ecpglib/data.c           |   3 +-
 src/interfaces/ecpg/ecpglib/descriptor.c     |  29 +---
 src/interfaces/ecpg/ecpglib/ecpglib_extern.h |  11 --
 src/interfaces/ecpg/ecpglib/execute.c        |  54 +-------
 src/interfaces/ecpg/include/pgtypes.h        |   6 +
 src/interfaces/ecpg/include/pgtypes_format.h |  26 ++++
 src/interfaces/ecpg/pgtypeslib/common.c      | 138 ++++++++++++++++++-
 src/interfaces/ecpg/pgtypeslib/exports.txt   |   6 +
 src/interfaces/ecpg/pgtypeslib/interval.c    |   5 +-
 src/interfaces/ecpg/pgtypeslib/numeric.c     |   3 +-
 src/tools/msvc/Solution.pm                   |   5 +-
 16 files changed, 233 insertions(+), 146 deletions(-)
 create mode 100644 src/interfaces/ecpg/include/pgtypes_format.h

diff --git a/configure b/configure
index c064115038..91c9865ae3 100755
--- a/configure
+++ b/configure
@@ -15539,7 +15539,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 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 getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal strtod_l syncfs sync_file_range vsnprintf_l vsprintf_l 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"
@@ -16309,17 +16309,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 f220b379b3..67ecb83447 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1782,9 +1782,11 @@ AC_CHECK_FUNCS(m4_normalize([
 	setproctitle_fast
 	strchrnul
 	strsignal
+	strtod_l
 	syncfs
 	sync_file_range
-	uselocale
+	vsnprintf_l
+	vsprintf_l
 	wcstombs_l
 ]))
 
@@ -1868,7 +1870,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 286d7e4269..e24277b473 100644
--- a/meson.build
+++ b/meson.build
@@ -2417,7 +2417,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'],
@@ -2456,9 +2455,11 @@ func_checks = [
   ['strlcpy'],
   ['strnlen'],
   ['strsignal'],
+  ['strtod_l'],
   ['sync_file_range'],
   ['syncfs'],
-  ['uselocale'],
+  ['vsnprintf_l'],
+  ['vsprintf_l'],
   ['wcstombs_l'],
 ]
 
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index d8a2985567..ea047e85cf 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -429,6 +429,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
 
@@ -495,9 +498,6 @@
 /* Define to 1 if you have the <unistd.h> header file. */
 #undef HAVE_UNISTD_H
 
-/* Define to 1 if you have the `uselocale' function. */
-#undef HAVE_USELOCALE
-
 /* Define to 1 if you have BSD UUID support. */
 #undef HAVE_UUID_BSD
 
@@ -516,6 +516,12 @@
 /* Define to 1 if your compiler knows the visibility("hidden") attribute. */
 #undef HAVE_VISIBILITY_ATTRIBUTE
 
+/* Define to 1 if you have the `vsnprintf_l' function. */
+#undef HAVE_VSNPRINTF_L
+
+/* Define to 1 if you have the `vsprintf_l' function. */
+#undef HAVE_VSPRINTF_L
+
 /* Define to 1 if you have the `wcstombs_l' function. */
 #undef HAVE_WCSTOMBS_L
 
@@ -561,9 +567,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
 
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
index 8afb1f0a26..cf2083713c 100644
--- a/src/interfaces/ecpg/ecpglib/connect.c
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -8,12 +8,9 @@
 #include "ecpglib.h"
 #include "ecpglib_extern.h"
 #include "ecpgtype.h"
+#include "pgtypes.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;
@@ -484,37 +481,31 @@ ECPGconnect(int lineno, int c, const char *name, const char *user, const char *p
 	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.
+	 * ... but first, make certain pgtypes' locale is set up.  Rely on holding
+	 * connections_mutex to ensure this is done by only one thread.
 	 */
-#ifdef HAVE_USELOCALE
-	if (!ecpg_clocale)
+	if (PGTYPESinit() < 0)
 	{
-		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;
-		}
+		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);
diff --git a/src/interfaces/ecpg/ecpglib/data.c b/src/interfaces/ecpg/ecpglib/data.c
index fa56276758..0f3ab36575 100644
--- a/src/interfaces/ecpg/ecpglib/data.c
+++ b/src/interfaces/ecpg/ecpglib/data.c
@@ -10,6 +10,7 @@
 #include "ecpglib_extern.h"
 #include "ecpgtype.h"
 #include "pgtypes_date.h"
+#include "pgtypes_format.h"
 #include "pgtypes_interval.h"
 #include "pgtypes_numeric.h"
 #include "pgtypes_timestamp.h"
@@ -466,7 +467,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 = PGTYPESstrtod(pval, &scan_length);
 
 					if (isarray && *scan_length == '"')
 						scan_length++;
diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c
index ad279e245c..e7d4cdf971 100644
--- a/src/interfaces/ecpg/ecpglib/descriptor.c
+++ b/src/interfaces/ecpg/ecpglib/descriptor.c
@@ -12,6 +12,7 @@
 #include "ecpglib.h"
 #include "ecpglib_extern.h"
 #include "ecpgtype.h"
+#include "pgtypes_format.h"
 #include "sql3types.h"
 #include "sqlca.h"
 #include "sqlda.h"
@@ -478,43 +479,21 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
 		/* 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.
+		 * connection calls PGTYPESinit().
 		 */
-		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
+		PGTYPESbegin_clocale(&stmt.oldlocale);
 
 		/* 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
+		PGTYPESend_clocale(stmt.oldlocale);
 	}
 	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..ae4c49d87e 100644
--- a/src/interfaces/ecpg/ecpglib/ecpglib_extern.h
+++ b/src/interfaces/ecpg/ecpglib/ecpglib_extern.h
@@ -59,10 +59,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 +72,7 @@ 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..a9afc88a61 100644
--- a/src/interfaces/ecpg/ecpglib/execute.c
+++ b/src/interfaces/ecpg/ecpglib/execute.c
@@ -24,6 +24,7 @@
 #include "ecpglib_extern.h"
 #include "ecpgtype.h"
 #include "pgtypes_date.h"
+#include "pgtypes_format.h"
 #include "pgtypes_interval.h"
 #include "pgtypes_numeric.h"
 #include "pgtypes_timestamp.h"
@@ -101,9 +102,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);
 }
 
@@ -465,7 +463,7 @@ sprintf_double_value(char *ptr, double value, const char *delim)
 			sprintf(ptr, "%s%s", "Infinity", delim);
 	}
 	else
-		sprintf(ptr, "%.15g%s", value, delim);
+		PGTYPESsprintf(ptr, "%.15g%s", value, delim);
 }
 
 static void
@@ -481,7 +479,7 @@ sprintf_float_value(char *ptr, float value, const char *delim)
 			sprintf(ptr, "%s%s", "Infinity", delim);
 	}
 	else
-		sprintf(ptr, "%.15g%s", value, delim);
+		PGTYPESsprintf(ptr, "%.15g%s", value, delim);
 }
 
 static char *
@@ -1975,37 +1973,13 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
 
 	/*
 	 * 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.
+	 * database wants the standard decimal point.
 	 */
-#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)
+	if (PGTYPESbegin_clocale(&stmt->oldlocale) < 0)
 	{
 		ecpg_do_epilogue(stmt);
 		return false;
 	}
-	setlocale(LC_NUMERIC, "C");
-#endif
 
 	/*
 	 * If statement type is ECPGst_prepnormal we are supposed to prepare the
@@ -2213,23 +2187,7 @@ 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
+	PGTYPESend_clocale(stmt->oldlocale);
 
 	free_statement(stmt);
 }
diff --git a/src/interfaces/ecpg/include/pgtypes.h b/src/interfaces/ecpg/include/pgtypes.h
index dbf759b45f..18251d74c9 100644
--- a/src/interfaces/ecpg/include/pgtypes.h
+++ b/src/interfaces/ecpg/include/pgtypes.h
@@ -10,6 +10,12 @@ extern "C"
 
 extern void PGTYPESchar_free(char *ptr);
 
+/*
+ * Initialize.  Not thread-safe and should only be called in one thread at a
+ * time, but idempotent.  Returns 0 on success, -1 on failure and sets errno.
+ */
+extern int	PGTYPESinit(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/interfaces/ecpg/include/pgtypes_format.h b/src/interfaces/ecpg/include/pgtypes_format.h
new file mode 100644
index 0000000000..d6dd06d361
--- /dev/null
+++ b/src/interfaces/ecpg/include/pgtypes_format.h
@@ -0,0 +1,26 @@
+/*
+ * We would like to be able to convert between strings and doubles using the C
+ * locale.  We can set the thread's current locale with uselocale() and use
+ * snprintf() and strtod() according to the POSIX standard, but some systems
+ * haven't implemented uselocale() yet.  We can use snprintf_l() and
+ * strtod_l() on some systems, but POSIX hasn't standardized those yet.  All
+ * systems we target can do one or the other, so provide a simple abstraction.
+ */
+
+#ifndef PGTYPES_FORMAT_H
+#define PGTYPES_FORMAT_H
+
+#if defined(LOCALE_T_IN_XLOCALE)
+#include <xlocale.h>
+#else
+#include <locale.h>
+#endif
+
+extern int PGTYPESbegin_clocale(locale_t *old_locale);
+extern void PGTYPESend_clocale(locale_t old_locale);
+
+extern double PGTYPESstrtod(const char *str, char **endptr);
+extern int PGTYPESsprintf(char *str, const char *format, ...) pg_attribute_printf(2, 3);
+extern int PGTYPESsnprintf(char *str, size_t size, const char *format, ...) pg_attribute_printf(3, 4);
+
+#endif
diff --git a/src/interfaces/ecpg/pgtypeslib/common.c b/src/interfaces/ecpg/pgtypeslib/common.c
index 8972229ca2..37ba35bc7a 100644
--- a/src/interfaces/ecpg/pgtypeslib/common.c
+++ b/src/interfaces/ecpg/pgtypeslib/common.c
@@ -3,8 +3,142 @@
 #include "postgres_fe.h"
 
 #include "pgtypes.h"
+#include "pgtypes_format.h"
 #include "pgtypeslib_extern.h"
 
+/*
+ * Use wcstombs_l's header as a clue about where to find the other extra _l
+ * functions.
+ */
+#if defined(LOCALE_T_IN_XLOCALE) || defined(WCSTOMBS_L_IN_XLOCALE)
+#include <xlocale.h>
+#else
+#include <locale.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static locale_t	PGTYPESclocale = (locale_t) 0;
+
+int
+PGTYPESinit(void)
+{
+	/* Already called? */
+	if (PGTYPESclocale != (locale_t) 0)
+		return 0;
+
+#ifdef WIN32
+	PGTYPESclocale = _create_locale(LC_ALL, "C");
+#else
+	PGTYPESclocale = newlocale(LC_ALL_MASK, "C", (locale_t) 0);
+#endif
+	if (PGTYPESclocale == (locale_t) 0)
+		return -1;
+	return -0;
+}
+
+/*
+ * If any of these _l() functions are missing, we need to mess with the
+ * thread-local locale.
+ */
+#if !defined(HAVE_STRTOD_L) || !defined(HAVE_VSPRINTF_L) || !defined(HAVE_VSNPRINTF_L)
+#if defined(WIN32)
+/* Windows has all of these in slightly scrambled form. */
+#else
+/* At least one of these functions is missing.  We'll do the save/restore. */
+#define PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE
+#endif
+#endif
+
+/*
+ * Before running code that might do conversions, call this to change the
+ * thread's current locale on platforms that need it.  This is done as a
+ * separate function rather than inside individual conversion wrappers for
+ * some batching effect, avoiding repeated save/restore.
+ *
+ * PGTYPESinit() must have been called, or this will have no effect.
+ */
+int
+PGTYPESbegin_clocale(locale_t *old_locale)
+{
+#ifdef PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE
+	/*
+	 * On this platform, at least one of the functions below expects us to
+	 * have changed the thread's locale.  The caller provides space for us to
+	 * store the current locale, so we can change it back later.
+	 */
+	*old_locale = uselocale(PGTYPESclocale);
+	return (*old_locale == (locale_t) 0) ? -1 : 0;
+#else
+	/*
+	 * Dummy value.  We have _l() variants of the functions we need, and we
+	 * might not even have uselocale() on this platform.
+	 */
+	*old_locale = (locale_t) 0;
+	return 0;
+#endif
+}
+
+/*
+ * Restore the current thread's locale.  Call with the value returned by
+ * PGTYPESbegin_clocale().  Does nothing on platforms with all the required
+ * _l() functions.
+ */
+void
+PGTYPESend_clocale(locale_t old_locale)
+{
+#ifdef PGTYPES_MUST_SAVE_RESTORE_THREAD_LOCALE
+	/* Put it back. */
+	uselocale(old_locale);
+#endif
+}
+
+double
+PGTYPESstrtod(const char *str, char **endptr)
+{
+#if defined(WIN32)
+	return _strtod_l(str, endptr, PGTYPESclocale);
+#elif defined(HAVE_STRTOD_L)
+	return strtod_l(str, endptr, PGTYPESclocale);
+#else
+	return strtod(str, endptr);
+#endif
+}
+
+int
+PGTYPESsprintf(char *str, const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+
+#if defined(WIN32)
+	return _vsprintf_l(str, format, PGTYPESclocale, args);
+#elif defined(HAVE_VSPRINTF_L)
+	return vsprintf_l(str, PGTYPESclocale, format, args);
+#else
+	return vsprintf(str, format, args);
+#endif
+}
+
+int
+PGTYPESsnprintf(char *str, size_t size, const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+
+#if defined(WIN32)
+	return _vsnprintf_l(str, size, format, PGTYPESclocale, args);
+#elif defined(HAVE_VSPRINTF_L)
+	return vsnprintf_l(str, size, PGTYPESclocale, format, args);
+#else
+	return vsnprintf(str, size, format, args);
+#endif
+}
+
 /* Return value is zero-filled. */
 char *
 pgtypes_alloc(long size)
@@ -81,8 +215,8 @@ pgtypes_fmt_replace(union un_fmt_comb replace_val, int replace_type, char **outp
 				switch (replace_type)
 				{
 					case PGTYPES_TYPE_DOUBLE_NF:
-						i = snprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS,
-									 "%0.0g", replace_val.double_val);
+						i = PGTYPESsnprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS,
+											"%0.0g", replace_val.double_val);
 						break;
 					case PGTYPES_TYPE_INT64:
 						i = snprintf(t, PGTYPES_FMT_NUM_MAX_DIGITS,
diff --git a/src/interfaces/ecpg/pgtypeslib/exports.txt b/src/interfaces/ecpg/pgtypeslib/exports.txt
index 2d5ec17656..3fe02bc60a 100644
--- a/src/interfaces/ecpg/pgtypeslib/exports.txt
+++ b/src/interfaces/ecpg/pgtypeslib/exports.txt
@@ -46,3 +46,9 @@ PGTYPEStimestamp_sub            43
 PGTYPEStimestamp_sub_interval   44
 PGTYPEStimestamp_to_asc         45
 PGTYPESchar_free                46
+PGTYPESinit                     47
+PGTYPESsprintf                  48
+PGTYPESsnprintf                 49
+PGTYPESstrtod                   50
+PGTYPESbegin_clocale            51
+PGTYPESend_clocale              52
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
index 936a688381..124ed5e1c2 100644
--- a/src/interfaces/ecpg/pgtypeslib/interval.c
+++ b/src/interfaces/ecpg/pgtypeslib/interval.c
@@ -13,6 +13,7 @@
 #include "common/string.h"
 #include "dt.h"
 #include "pgtypes_error.h"
+#include "pgtypes_format.h"
 #include "pgtypes_interval.h"
 #include "pgtypeslib_extern.h"
 
@@ -60,7 +61,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 = PGTYPESstrtod(str, endptr);
 	/* did we not see anything that looks like a double? */
 	if (*endptr == str || errno != 0)
 		return DTERR_BAD_FORMAT;
@@ -455,7 +456,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 				else if (*cp == '.')
 				{
 					errno = 0;
-					fval = strtod(cp, &cp);
+					fval = PGTYPESstrtod(cp, &cp);
 					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..a8bc19cc8f 100644
--- a/src/interfaces/ecpg/pgtypeslib/numeric.c
+++ b/src/interfaces/ecpg/pgtypeslib/numeric.c
@@ -7,6 +7,7 @@
 #include <limits.h>
 
 #include "pgtypes_error.h"
+#include "pgtypes_format.h"
 #include "pgtypes_numeric.h"
 #include "pgtypeslib_extern.h"
 
@@ -1414,7 +1415,7 @@ PGTYPESnumeric_from_double(double d, numeric *dst)
 	numeric    *tmp;
 	int			i;
 
-	if (sprintf(buffer, "%.*g", DBL_DIG, d) <= 0)
+	if (PGTYPESsprintf(buffer, "%.*g", DBL_DIG, d) <= 0)
 		return -1;
 
 	if ((tmp = PGTYPESnumeric_from_asc(buffer, NULL)) == NULL)
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 98a5b5d872..6c550f3d37 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -339,6 +339,7 @@ sub GenerateFiles
 		HAVE_STRLCPY => undef,
 		HAVE_STRNLEN => 1,
 		HAVE_STRSIGNAL => undef,
+		HAVE_STRTOD_L => undef,
 		HAVE_STRUCT_OPTION => undef,
 		HAVE_STRUCT_SOCKADDR_SA_LEN => undef,
 		HAVE_STRUCT_TM_TM_ZONE => undef,
@@ -361,7 +362,6 @@ sub GenerateFiles
 		HAVE_UINT8 => undef,
 		HAVE_UNION_SEMUN => undef,
 		HAVE_UNISTD_H => 1,
-		HAVE_USELOCALE => undef,
 		HAVE_UUID_BSD => undef,
 		HAVE_UUID_E2FS => undef,
 		HAVE_UUID_OSSP => undef,
@@ -369,6 +369,8 @@ sub GenerateFiles
 		HAVE_UUID_UUID_H => undef,
 		HAVE_WCSTOMBS_L => undef,
 		HAVE_VISIBILITY_ATTRIBUTE => undef,
+		HAVE_VSNPRINTF_L => undef,
+		HAVE_VSPRINTF_L => undef,
 		HAVE_X509_GET_SIGNATURE_INFO => undef,
 		HAVE_X86_64_POPCNTQ => undef,
 		HAVE__BOOL => undef,
@@ -383,7 +385,6 @@ sub GenerateFiles
 		HAVE__BUILTIN_POPCOUNT => undef,
 		HAVE__BUILTIN_TYPES_COMPATIBLE_P => undef,
 		HAVE__BUILTIN_UNREACHABLE => undef,
-		HAVE__CONFIGTHREADLOCALE => 1,
 		HAVE__CPUID => 1,
 		HAVE__GET_CPUID => undef,
 		HAVE__STATIC_ASSERT => undef,
-- 
2.39.3 (Apple Git-145)

