diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2019-01-21 23:18:58 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2019-01-21 23:18:58 -0500 |
commit | 6106f9dd71ff5f80fdaa41aa0e39f550967e2040 (patch) | |
tree | fc8d8a13108ee18a6c598d9c55e9c4f4ac298453 /src | |
parent | 5aa03f99fe7cfb1072849960465a040e834a25e2 (diff) | |
download | postgresql-6106f9dd71ff5f80fdaa41aa0e39f550967e2040.tar.gz postgresql-6106f9dd71ff5f80fdaa41aa0e39f550967e2040.zip |
Avoid thread-safety problem in ecpglib.
ecpglib attempts to force the LC_NUMERIC locale to "C" while reading
server output, to avoid problems with strtod() and related functions.
Historically it's just issued setlocale() calls to do that, but that
has major problems if we're in a threaded application. setlocale()
itself is not required by POSIX to be thread-safe (and indeed is not,
on recent OpenBSD). Moreover, its effects are process-wide, so that
we could cause unexpected results in other threads, or another thread
could change our setting.
On platforms having uselocale(), which is required by POSIX:2008,
we can avoid these problems by using uselocale() instead. Windows
goes its own way as usual, but we can make it safe by using
_configthreadlocale(). Platforms having neither continue to use the
old code, but that should be pretty much nobody among current systems.
(Subsequent buildfarm results show that recent NetBSD versions still
lack uselocale(), but it's not a big problem because they also do not
support non-"C" settings for LC_NUMERIC.)
Back-patch of commits 8eb4a9312 and ee27584c4.
Michael Meskes and Tom Lane; thanks also to Takayuki Tsunakawa.
Discussion: https://postgr.es/m/31420.1547783697@sss.pgh.pa.us
Diffstat (limited to 'src')
-rw-r--r-- | src/include/pg_config.h.in | 6 | ||||
-rw-r--r-- | src/include/pg_config.h.win32 | 6 | ||||
-rw-r--r-- | src/interfaces/ecpg/ecpglib/descriptor.c | 37 | ||||
-rw-r--r-- | src/interfaces/ecpg/ecpglib/execute.c | 42 | ||||
-rw-r--r-- | src/interfaces/ecpg/ecpglib/extern.h | 11 |
5 files changed, 94 insertions, 8 deletions
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index a7c1874c226..3c82718bf99 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -644,6 +644,9 @@ /* Define to 1 if the system has the type `unsigned long long int'. */ #undef HAVE_UNSIGNED_LONG_LONG_INT +/* Define to 1 if you have the `uselocale' function. */ +#undef HAVE_USELOCALE + /* Define to 1 if you have the `utime' function. */ #undef HAVE_UTIME @@ -701,6 +704,9 @@ /* 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/include/pg_config.h.win32 b/src/include/pg_config.h.win32 index a86261f5bbf..9c2d98f12fc 100644 --- a/src/include/pg_config.h.win32 +++ b/src/include/pg_config.h.win32 @@ -494,6 +494,9 @@ /* Define to 1 if you have the `unsetenv' function. */ /* #undef HAVE_UNSETENV */ +/* Define to 1 if you have the `uselocale' function. */ +/* #undef HAVE_USELOCALE */ + /* Define to 1 if you have the `utime' function. */ #define HAVE_UTIME 1 @@ -536,6 +539,9 @@ /* Define to 1 if your compiler understands __builtin_unreachable. */ /* #undef HAVE__BUILTIN_UNREACHABLE */ +/* Define to 1 if you have the `_configthreadlocale' function. */ +#define HAVE__CONFIGTHREADLOCALE 1 + /* Define to 1 if you have __cpuid. */ #define HAVE__CPUID 1 diff --git a/src/interfaces/ecpg/ecpglib/descriptor.c b/src/interfaces/ecpg/ecpglib/descriptor.c index 72b0646aa6b..cf8657e9c00 100644 --- a/src/interfaces/ecpg/ecpglib/descriptor.c +++ b/src/interfaces/ecpg/ecpglib/descriptor.c @@ -482,22 +482,45 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...) if (data_var.type != ECPGt_EORT) { struct statement stmt; - char *oldlocale; + + 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 */ - oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); + /* (see comments in execute.c) */ +#ifdef HAVE_USELOCALE + stmt.clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (stmt.clocale != (locale_t) 0) + stmt.oldlocale = uselocale(stmt.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"); - - memset(&stmt, 0, sizeof stmt); - stmt.lineno = lineno; +#endif /* desperate try to guess something sensible */ stmt.connection = ecpg_get_connection(NULL); ecpg_store_result(ECPGresult, index, &stmt, &data_var); - setlocale(LC_NUMERIC, oldlocale); - ecpg_free(oldlocale); +#ifdef HAVE_USELOCALE + if (stmt.oldlocale != (locale_t) 0) + uselocale(stmt.oldlocale); + if (stmt.clocale) + freelocale(stmt.clocale); +#else + if (stmt.oldlocale) + { + setlocale(LC_NUMERIC, stmt.oldlocale); + ecpg_free(stmt.oldlocale); + } +#ifdef HAVE__CONFIGTHREADLOCALE + if (stmt.oldthreadlocale != -1) + _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/execute.c b/src/interfaces/ecpg/ecpglib/execute.c index 6f20cc412a2..adc4470d9a2 100644 --- a/src/interfaces/ecpg/ecpglib/execute.c +++ b/src/interfaces/ecpg/ecpglib/execute.c @@ -103,7 +103,12 @@ free_statement(struct statement *stmt) free_variable(stmt->outlist); ecpg_free(stmt->command); ecpg_free(stmt->name); +#ifdef HAVE_USELOCALE + if (stmt->clocale) + freelocale(stmt->clocale); +#else ecpg_free(stmt->oldlocale); +#endif ecpg_free(stmt); } @@ -1778,8 +1783,32 @@ 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 + * 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(). */ +#ifdef HAVE_USELOCALE + stmt->clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (stmt->clocale == (locale_t) 0) + { + ecpg_do_epilogue(stmt); + return false; + } + stmt->oldlocale = uselocale(stmt->clocale); + if (stmt->oldlocale == (locale_t) 0) + { + ecpg_do_epilogue(stmt); + return false; + } +#else +#ifdef HAVE__CONFIGTHREADLOCALE + stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + if (stmt->oldthreadlocale == -1) + { + ecpg_do_epilogue(stmt); + return false; + } +#endif stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno); if (stmt->oldlocale == NULL) { @@ -1787,6 +1816,7 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator, return false; } setlocale(LC_NUMERIC, "C"); +#endif #ifdef ENABLE_THREAD_SAFETY ecpg_pthreads_init(); @@ -1989,8 +2019,18 @@ 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 + _configthreadlocale(stmt->oldthreadlocale); +#endif + } +#endif free_statement(stmt); } diff --git a/src/interfaces/ecpg/ecpglib/extern.h b/src/interfaces/ecpg/ecpglib/extern.h index 91c7367b8b0..4a524392046 100644 --- a/src/interfaces/ecpg/ecpglib/extern.h +++ b/src/interfaces/ecpg/ecpglib/extern.h @@ -12,6 +12,9 @@ #ifndef CHAR_BIT #include <limits.h> #endif +#ifdef LOCALE_T_IN_XLOCALE +#include <xlocale.h> +#endif enum COMPAT_MODE { @@ -60,7 +63,15 @@ struct statement bool questionmarks; struct variable *inlist; struct variable *outlist; +#ifdef HAVE_USELOCALE + locale_t clocale; + locale_t oldlocale; +#else char *oldlocale; +#ifdef HAVE__CONFIGTHREADLOCALE + int oldthreadlocale; +#endif +#endif int nparams; char **paramvalues; PGresult *results; |