Skip to content

Commit 8eb4a93

Browse files
committed
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. 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
1 parent f4593bd commit 8eb4a93

File tree

7 files changed

+90
-9
lines changed

7 files changed

+90
-9
lines changed

configure

+1-1
Original file line numberDiff line numberDiff line change
@@ -15209,7 +15209,7 @@ fi
1520915209
LIBS_including_readline="$LIBS"
1521015210
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
1521115211

15212-
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
15212+
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
1521315213
do :
1521415214
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
1521515215
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"

configure.in

+1
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,7 @@ AC_CHECK_FUNCS(m4_normalize([
16181618
strsignal
16191619
symlink
16201620
sync_file_range
1621+
uselocale
16211622
utime
16221623
utimes
16231624
wcstombs_l

src/include/pg_config.h.in

+3
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,9 @@
691691
/* Define to 1 if the system has the type `unsigned long long int'. */
692692
#undef HAVE_UNSIGNED_LONG_LONG_INT
693693

694+
/* Define to 1 if you have the `uselocale' function. */
695+
#undef HAVE_USELOCALE
696+
694697
/* Define to 1 if you have the `utime' function. */
695698
#undef HAVE_UTIME
696699

src/include/pg_config.h.win32

+3
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,9 @@
545545
/* Define to 1 if you have the `unsetenv' function. */
546546
/* #undef HAVE_UNSETENV */
547547

548+
/* Define to 1 if you have the `uselocale' function. */
549+
/* #undef HAVE_USELOCALE */
550+
548551
/* Define to 1 if you have the `utime' function. */
549552
#define HAVE_UTIME 1
550553

src/interfaces/ecpg/ecpglib/descriptor.c

+30-7
Original file line numberDiff line numberDiff line change
@@ -483,22 +483,45 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
483483
if (data_var.type != ECPGt_EORT)
484484
{
485485
struct statement stmt;
486-
char *oldlocale;
486+
487+
memset(&stmt, 0, sizeof stmt);
488+
stmt.lineno = lineno;
487489

488490
/* Make sure we do NOT honor the locale for numeric input */
489491
/* since the database gives the standard decimal point */
490-
oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
492+
/* (see comments in execute.c) */
493+
#ifdef HAVE_USELOCALE
494+
stmt.clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
495+
if (stmt.clocale != (locale_t) 0)
496+
stmt.oldlocale = uselocale(stmt.clocale);
497+
#else
498+
#ifdef WIN32
499+
stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
500+
#endif
501+
stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
491502
setlocale(LC_NUMERIC, "C");
492-
493-
memset(&stmt, 0, sizeof stmt);
494-
stmt.lineno = lineno;
503+
#endif
495504

496505
/* desperate try to guess something sensible */
497506
stmt.connection = ecpg_get_connection(NULL);
498507
ecpg_store_result(ECPGresult, index, &stmt, &data_var);
499508

500-
setlocale(LC_NUMERIC, oldlocale);
501-
ecpg_free(oldlocale);
509+
#ifdef HAVE_USELOCALE
510+
if (stmt.oldlocale != (locale_t) 0)
511+
uselocale(stmt.oldlocale);
512+
if (stmt.clocale)
513+
freelocale(stmt.clocale);
514+
#else
515+
if (stmt.oldlocale)
516+
{
517+
setlocale(LC_NUMERIC, stmt.oldlocale);
518+
ecpg_free(stmt.oldlocale);
519+
}
520+
#ifdef WIN32
521+
if (stmt.oldthreadlocale != -1)
522+
_configthreadlocale(stmt.oldthreadlocale);
523+
#endif
524+
#endif
502525
}
503526
else if (data_var.ind_type != ECPGt_NO_INDICATOR && data_var.ind_pointer != NULL)
504527

src/interfaces/ecpg/ecpglib/ecpglib_extern.h

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#ifndef CHAR_BIT
1313
#include <limits.h>
1414
#endif
15+
#ifdef LOCALE_T_IN_XLOCALE
16+
#include <xlocale.h>
17+
#endif
1518

1619
enum COMPAT_MODE
1720
{
@@ -61,7 +64,15 @@ struct statement
6164
bool questionmarks;
6265
struct variable *inlist;
6366
struct variable *outlist;
67+
#ifdef HAVE_USELOCALE
68+
locale_t clocale;
69+
locale_t oldlocale;
70+
#else
6471
char *oldlocale;
72+
#ifdef WIN32
73+
int oldthreadlocale;
74+
#endif
75+
#endif
6576
int nparams;
6677
char **paramvalues;
6778
PGresult *results;

src/interfaces/ecpg/ecpglib/execute.c

+41-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,12 @@ free_statement(struct statement *stmt)
102102
free_variable(stmt->outlist);
103103
ecpg_free(stmt->command);
104104
ecpg_free(stmt->name);
105+
#ifdef HAVE_USELOCALE
106+
if (stmt->clocale)
107+
freelocale(stmt->clocale);
108+
#else
105109
ecpg_free(stmt->oldlocale);
110+
#endif
106111
ecpg_free(stmt);
107112
}
108113

@@ -1771,15 +1776,40 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
17711776

17721777
/*
17731778
* Make sure we do NOT honor the locale for numeric input/output since the
1774-
* database wants the standard decimal point
1779+
* database wants the standard decimal point. If available, use
1780+
* uselocale() for this because it's thread-safe. Windows doesn't have
1781+
* that, but it does have _configthreadlocale().
17751782
*/
1783+
#ifdef HAVE_USELOCALE
1784+
stmt->clocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
1785+
if (stmt->clocale == (locale_t) 0)
1786+
{
1787+
ecpg_do_epilogue(stmt);
1788+
return false;
1789+
}
1790+
stmt->oldlocale = uselocale(stmt->clocale);
1791+
if (stmt->oldlocale == (locale_t) 0)
1792+
{
1793+
ecpg_do_epilogue(stmt);
1794+
return false;
1795+
}
1796+
#else
1797+
#ifdef WIN32
1798+
stmt->oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
1799+
if (stmt->oldthreadlocale == -1)
1800+
{
1801+
ecpg_do_epilogue(stmt);
1802+
return false;
1803+
}
1804+
#endif
17761805
stmt->oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), lineno);
17771806
if (stmt->oldlocale == NULL)
17781807
{
17791808
ecpg_do_epilogue(stmt);
17801809
return false;
17811810
}
17821811
setlocale(LC_NUMERIC, "C");
1812+
#endif
17831813

17841814
#ifdef ENABLE_THREAD_SAFETY
17851815
ecpg_pthreads_init();
@@ -1982,8 +2012,18 @@ ecpg_do_epilogue(struct statement *stmt)
19822012
if (stmt == NULL)
19832013
return;
19842014

2015+
#ifdef HAVE_USELOCALE
2016+
if (stmt->oldlocale != (locale_t) 0)
2017+
uselocale(stmt->oldlocale);
2018+
#else
19852019
if (stmt->oldlocale)
2020+
{
19862021
setlocale(LC_NUMERIC, stmt->oldlocale);
2022+
#ifdef WIN32
2023+
_configthreadlocale(stmt->oldthreadlocale);
2024+
#endif
2025+
}
2026+
#endif
19872027

19882028
free_statement(stmt);
19892029
}

0 commit comments

Comments
 (0)