aboutsummaryrefslogtreecommitdiff
path: root/src/port/snprintf.c
diff options
context:
space:
mode:
authorPeter Eisentraut <peter@eisentraut.org>2025-03-28 16:15:57 +0100
committerPeter Eisentraut <peter@eisentraut.org>2025-03-28 16:18:36 +0100
commit8e993bff5326b00ced137c837fce7cd1e0ecae14 (patch)
treea5e4b23dfe58e730a0c86f1e1c85574bf37430fa /src/port/snprintf.c
parent2247281c470502c799fc2153a3583c025e894a9c (diff)
downloadpostgresql-8e993bff5326b00ced137c837fce7cd1e0ecae14.tar.gz
postgresql-8e993bff5326b00ced137c837fce7cd1e0ecae14.zip
Tidy up locale thread safety in ECPG library.
Remove setlocale() and _configthreadlocal() as 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. It was probably broken on some systems (NetBSD), and the code was also quite messy and complicated, with obsolete configure tests (Windows). It was also arguably broken, or at least had unstated environmental requirements, if pgtypeslib code was called directly. Instead, introduce PG_C_LOCALE to refer to the "C" locale as a locale_t value. It maps to the special constant LC_C_LOCALE when defined by libc (macOS, NetBSD), or otherwise uses a process-lifetime locale_t that is allocated on first use, just as ECPG previously did itself. The new replacement might be more widely useful. Then change the float parsing and printing code to pass that to _l() functions where appropriate. Unfortunately the portability of those functions is a bit complicated. First, many obvious and useful _l() functions are missing from POSIX, though most standard libraries define some of them anyway. Second, although the thread-safe save/restore technique can be used to replace the missing ones, Windows and NetBSD refused to implement standard uselocale(). They might have a point: "wide scope" uselocale() is hard to combine with other code and error-prone, especially in library code. Luckily they have the _l() functions we want so far anyway. So we have to be prepared for both ways of doing things: 1. In ECPG, use strtod_l() for parsing, and supply a port.h replacement using uselocale() over a limited scope if missing. 2. Inside our own snprintf.c, use three different approaches to format floats. For frontend code, call libc's snprintf_l(), or wrap libc's snprintf() in uselocale() if it's missing. For backend code, snprintf.c can keep assuming that the global locale's LC_NUMERIC is "C" and call libc's snprintf() without change, for now. (It might eventually be possible to call our in-tree Ryƫ routines to display floats in snprintf.c, given the C-locale-always remit of our in-tree snprintf(), but this patch doesn't risk changing anything that complicated.) Author: Thomas Munro <thomas.munro@gmail.com> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: Tristan Partin <tristan@partin.io> Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi> Discussion: https://postgr.es/m/CWZBBRR6YA8D.8EHMDRGLCKCD%40neon.tech
Diffstat (limited to 'src/port/snprintf.c')
-rw-r--r--src/port/snprintf.c55
1 files changed, 55 insertions, 0 deletions
diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index f8f2018ea0c..376d5c906a0 100644
--- a/src/port/snprintf.c
+++ b/src/port/snprintf.c
@@ -109,6 +109,36 @@
#undef vprintf
#undef printf
+#if defined(FRONTEND)
+/*
+ * Frontend code might be multi-threaded. When calling the system snprintf()
+ * for floats, we have to use either the non-standard snprintf_l() variant, or
+ * save-and-restore the calling thread's locale using uselocale(), depending on
+ * availability.
+ */
+#if defined(HAVE_SNPRINTF_L)
+/* At least macOS and the BSDs have the snprintf_l() extension. */
+#define USE_SNPRINTF_L
+#elif defined(WIN32)
+/* Windows has a version with a different name and argument order. */
+#define snprintf_l(str, size, loc, format, ...) _snprintf_l(str, size, format, loc, __VA_ARGS__)
+#define USE_SNPRINTF_L
+#else
+/* We have to do a thread-safe save-and-restore around snprintf(). */
+#define NEED_USE_LOCALE
+#endif
+#else
+/*
+ * Backend code doesn't need to worry about locales here, because LC_NUMERIC is
+ * set to "C" in main() and doesn't change. Plain old snprintf() is always OK
+ * without uselocale().
+ *
+ * XXX If the backend were multithreaded, we would have to be 100% certain that
+ * no one is calling setlocale() concurrently, even transiently, to be able to
+ * keep this small optimization.
+ */
+#endif
+
/*
* Info about where the formatted output is going.
*
@@ -1220,6 +1250,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
* according to == but not according to memcmp.
*/
static const double dzero = 0.0;
+#ifdef NEED_USE_LOCALE
+ locale_t save_locale = uselocale(PG_C_LOCALE);
+#endif
if (adjust_sign((value < 0.0 ||
(value == 0.0 &&
@@ -1241,7 +1274,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
fmt[2] = '*';
fmt[3] = type;
fmt[4] = '\0';
+#ifdef USE_SNPRINTF_L
+ vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, prec, value);
+#else
vallen = snprintf(convert, sizeof(convert), fmt, prec, value);
+#endif
}
else
{
@@ -1250,6 +1287,11 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
fmt[2] = '\0';
vallen = snprintf(convert, sizeof(convert), fmt, value);
}
+
+#ifdef NEED_USE_LOCALE
+ uselocale(save_locale);
+#endif
+
if (vallen < 0)
goto fail;
@@ -1372,12 +1414,25 @@ pg_strfromd(char *str, size_t count, int precision, double value)
}
else
{
+#ifdef NEED_USE_LOCALE
+ locale_t save_locale = uselocale(PG_C_LOCALE);
+#endif
+
fmt[0] = '%';
fmt[1] = '.';
fmt[2] = '*';
fmt[3] = 'g';
fmt[4] = '\0';
+#ifdef USE_SNPRINTF_L
+ vallen = snprintf_l(convert, sizeof(convert), PG_C_LOCALE, fmt, precision, value);
+#else
vallen = snprintf(convert, sizeof(convert), fmt, precision, value);
+#endif
+
+#ifdef NEED_USE_LOCALE
+ uselocale(save_locale);
+#endif
+
if (vallen < 0)
{
target.failed = true;