0001-ecpg-Use-thread-safe-_l-functions-if-possible.patch
application/octet-stream
Filename: 0001-ecpg-Use-thread-safe-_l-functions-if-possible.patch
Type: application/octet-stream
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 0001
Subject: ecpg: Use thread-safe _l() functions if possible.
| File | + | − |
|---|---|---|
| configure | 1 | 12 |
| configure.ac | 3 | 2 |
| meson.build | 3 | 2 |
| src/include/pg_config.h.in | 9 | 6 |
| src/interfaces/ecpg/ecpglib/connect.c | 23 | 32 |
| src/interfaces/ecpg/ecpglib/data.c | 2 | 1 |
| src/interfaces/ecpg/ecpglib/descriptor.c | 4 | 25 |
| src/interfaces/ecpg/ecpglib/ecpglib_extern.h | 0 | 11 |
| src/interfaces/ecpg/ecpglib/execute.c | 6 | 48 |
| src/interfaces/ecpg/include/pgtypes_format.h | 26 | 0 |
| src/interfaces/ecpg/include/pgtypes.h | 6 | 0 |
| src/interfaces/ecpg/pgtypeslib/common.c | 136 | 2 |
| src/interfaces/ecpg/pgtypeslib/exports.txt | 6 | 0 |
| src/interfaces/ecpg/pgtypeslib/interval.c | 3 | 2 |
| src/interfaces/ecpg/pgtypeslib/numeric.c | 2 | 1 |
| src/tools/msvc/Solution.pm | 3 | 2 |
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)