]> granicus.if.org Git - postgresql/commitdiff
Avoid thread-safety problem in ecpglib.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Jan 2019 17:07:02 +0000 (12:07 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Jan 2019 17:07:02 +0000 (12:07 -0500)
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.

This should get back-patched, but let's see what the buildfarm
thinks of it first.

Michael Meskes and Tom Lane; thanks also to Takayuki Tsunakawa.

Discussion: https://postgr.es/m/31420.1547783697@sss.pgh.pa.us

configure
configure.in
src/include/pg_config.h.in
src/include/pg_config.h.win32
src/interfaces/ecpg/ecpglib/descriptor.c
src/interfaces/ecpg/ecpglib/ecpglib_extern.h
src/interfaces/ecpg/ecpglib/execute.c

index 7602e65416ee53a0cbc804e61dbd1fa4e5004496..1e69edacde66dbdef7d34b958dd318c85e0d3383 100755 (executable)
--- a/configure
+++ b/configure
@@ -15209,7 +15209,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in cbrt clock_gettime copyfile fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul strsignal symlink sync_file_range utime utimes wcstombs_l
+for ac_func in cbrt clock_gettime copyfile fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul strsignal symlink sync_file_range uselocale utime utimes 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"
index d599ad85cc827a4a2f8b22bfa1b672ce55f891eb..556186cf2d43e6dcbba232fd1651f09b1554a2ba 100644 (file)
@@ -1618,6 +1618,7 @@ AC_CHECK_FUNCS(m4_normalize([
        strsignal
        symlink
        sync_file_range
+       uselocale
        utime
        utimes
        wcstombs_l
index 9d99816eae97fb4b96e8923214b86afbc613ded9..2c899a15692b06f5431362df1c47b91b9c9c7554 100644 (file)
 /* 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
 
index 8a560ef0f39d98527429a1e4731dff2320ecf280..396443386a255108bed3d5969dd0712bc42c4325 100644 (file)
 /* 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
 
index 186f92cc3ed07d0a6423adb964cc0fe7d169ab5b..71cef1517247cf8b61faea68a1d6e34f0939bee4 100644 (file)
@@ -483,22 +483,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 WIN32
+               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 WIN32
+               if (stmt.oldthreadlocale != -1)
+                       _configthreadlocale(stmt.oldthreadlocale);
+#endif
+#endif
        }
        else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
 
index 1c9bce1456d03164307862f206c28a96bc37e394..41851d59007466ba02418ea75be4e7e368c71677 100644 (file)
@@ -12,6 +12,9 @@
 #ifndef CHAR_BIT
 #include <limits.h>
 #endif
+#ifdef LOCALE_T_IN_XLOCALE
+#include <xlocale.h>
+#endif
 
 enum COMPAT_MODE
 {
@@ -61,7 +64,15 @@ struct statement
        bool            questionmarks;
        struct variable *inlist;
        struct variable *outlist;
+#ifdef HAVE_USELOCALE
+       locale_t        clocale;
+       locale_t        oldlocale;
+#else
        char       *oldlocale;
+#ifdef WIN32
+       int                     oldthreadlocale;
+#endif
+#endif
        int                     nparams;
        char      **paramvalues;
        PGresult   *results;
index 3f5034e7924a87daf408851be60211fa1429e87e..81aaf10f0886a8a4f958c753101d54f14e4737a7 100644 (file)
@@ -102,7 +102,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);
 }
 
@@ -1771,8 +1776,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 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 WIN32
+       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)
        {
@@ -1780,6 +1809,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();
@@ -1982,8 +2012,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 WIN32
+               _configthreadlocale(stmt->oldthreadlocale);
+#endif
+       }
+#endif
 
        free_statement(stmt);
 }