Skip to content

Commit c9ff6cb

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. (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 8eb4a93 and ee27584. Michael Meskes and Tom Lane; thanks also to Takayuki Tsunakawa. Discussion: https://postgr.es/m/31420.1547783697@sss.pgh.pa.us
1 parent 6f090c1 commit c9ff6cb

File tree

7 files changed

+134
-10
lines changed

7 files changed

+134
-10
lines changed

configure

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12474,7 +12474,7 @@ fi
1247412474
LIBS_including_readline="$LIBS"
1247512475
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
1247612476

12477-
for ac_func in cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs wcstombs_l
12477+
for ac_func in cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower uselocale utime utimes wcstombs wcstombs_l
1247812478
do :
1247912479
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
1248012480
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -13229,6 +13229,17 @@ fi
1322913229

1323013230
# Win32 (really MinGW) support
1323113231
if test "$PORTNAME" = "win32"; then
13232+
for ac_func in _configthreadlocale
13233+
do :
13234+
ac_fn_c_check_func "$LINENO" "_configthreadlocale" "ac_cv_func__configthreadlocale"
13235+
if test "x$ac_cv_func__configthreadlocale" = xyes; then :
13236+
cat >>confdefs.h <<_ACEOF
13237+
#define HAVE__CONFIGTHREADLOCALE 1
13238+
_ACEOF
13239+
13240+
fi
13241+
done
13242+
1323213243
ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday"
1323313244
if test "x$ac_cv_func_gettimeofday" = xyes; then :
1323413245
$as_echo "#define HAVE_GETTIMEOFDAY 1" >>confdefs.h

configure.in

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,33 @@ PGAC_FUNC_WCSTOMBS_L
14371437
LIBS_including_readline="$LIBS"
14381438
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
14391439

1440-
AC_CHECK_FUNCS([cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate pstat pthread_is_threaded_np readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs wcstombs_l])
1440+
AC_CHECK_FUNCS(m4_normalize([
1441+
cbrt
1442+
dlopen
1443+
fdatasync
1444+
getifaddrs
1445+
getpeerucred
1446+
getrlimit
1447+
mbstowcs_l
1448+
memmove
1449+
poll
1450+
posix_fallocate
1451+
pstat
1452+
pthread_is_threaded_np
1453+
readlink
1454+
setproctitle
1455+
setsid
1456+
shm_open
1457+
sigprocmask
1458+
symlink
1459+
sync_file_range
1460+
towlower
1461+
uselocale
1462+
utime
1463+
utimes
1464+
wcstombs
1465+
wcstombs_l
1466+
]))
14411467

14421468
AC_REPLACE_FUNCS(fseeko)
14431469
case $host_os in
@@ -1602,6 +1628,7 @@ fi
16021628

16031629
# Win32 (really MinGW) support
16041630
if test "$PORTNAME" = "win32"; then
1631+
AC_CHECK_FUNCS(_configthreadlocale)
16051632
AC_REPLACE_FUNCS(gettimeofday)
16061633
AC_LIBOBJ(dirmod)
16071634
AC_LIBOBJ(kill)

src/include/pg_config.h.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,9 @@
630630
/* Define to 1 if the system has the type `unsigned long long int'. */
631631
#undef HAVE_UNSIGNED_LONG_LONG_INT
632632

633+
/* Define to 1 if you have the `uselocale' function. */
634+
#undef HAVE_USELOCALE
635+
633636
/* Define to 1 if you have the `utime' function. */
634637
#undef HAVE_UTIME
635638

@@ -681,6 +684,9 @@
681684
/* Define to 1 if your compiler understands __builtin_unreachable. */
682685
#undef HAVE__BUILTIN_UNREACHABLE
683686

687+
/* Define to 1 if you have the `_configthreadlocale' function. */
688+
#undef HAVE__CONFIGTHREADLOCALE
689+
684690
/* Define to 1 if your compiler understands _Static_assert. */
685691
#undef HAVE__STATIC_ASSERT
686692

src/include/pg_config.h.win32

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@
509509
/* Define to 1 if you have the `unsetenv' function. */
510510
/* #undef HAVE_UNSETENV */
511511

512+
/* Define to 1 if you have the `uselocale' function. */
513+
/* #undef HAVE_USELOCALE */
514+
512515
/* Define to 1 if you have the `utime' function. */
513516
#define HAVE_UTIME 1
514517

@@ -545,6 +548,9 @@
545548
/* Define to 1 if your compiler understands __builtin_unreachable. */
546549
/* #undef HAVE__BUILTIN_UNREACHABLE */
547550

551+
/* Define to 1 if you have the `_configthreadlocale' function. */
552+
#define HAVE__CONFIGTHREADLOCALE 1
553+
548554
/* Define to 1 if your compiler understands _Static_assert. */
549555
/* #undef HAVE__STATIC_ASSERT */
550556

src/interfaces/ecpg/ecpglib/descriptor.c

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -482,22 +482,45 @@ ECPGget_desc(int lineno, const char *desc_name, int index,...)
482482
if (data_var.type != ECPGt_EORT)
483483
{
484484
struct statement stmt;
485-
char *oldlocale;
485+
486+
memset(&stmt, 0, sizeof stmt);
487+
stmt.lineno = lineno;
486488

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

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

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

src/interfaces/ecpg/ecpglib/execute.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,12 @@ free_statement(struct statement * stmt)
104104
free_variable(stmt->outlist);
105105
ecpg_free(stmt->command);
106106
ecpg_free(stmt->name);
107+
#ifdef HAVE_USELOCALE
108+
if (stmt->clocale)
109+
freelocale(stmt->clocale);
110+
#else
107111
ecpg_free(stmt->oldlocale);
112+
#endif
108113
ecpg_free(stmt);
109114
}
110115

@@ -1772,15 +1777,40 @@ ecpg_do_prologue(int lineno, const int compat, const int force_indicator,
17721777

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

17851815
#ifdef ENABLE_THREAD_SAFETY
17861816
ecpg_pthreads_init();
@@ -1983,8 +2013,18 @@ ecpg_do_epilogue(struct statement * stmt)
19832013
if (stmt == NULL)
19842014
return;
19852015

2016+
#ifdef HAVE_USELOCALE
2017+
if (stmt->oldlocale != (locale_t) 0)
2018+
uselocale(stmt->oldlocale);
2019+
#else
19862020
if (stmt->oldlocale)
2021+
{
19872022
setlocale(LC_NUMERIC, stmt->oldlocale);
2023+
#ifdef HAVE__CONFIGTHREADLOCALE
2024+
_configthreadlocale(stmt->oldthreadlocale);
2025+
#endif
2026+
}
2027+
#endif
19882028

19892029
free_statement(stmt);
19902030
}

src/interfaces/ecpg/ecpglib/extern.h

Lines changed: 11 additions & 0 deletions
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
{
@@ -60,7 +63,15 @@ struct statement
6063
bool questionmarks;
6164
struct variable *inlist;
6265
struct variable *outlist;
66+
#ifdef HAVE_USELOCALE
67+
locale_t clocale;
68+
locale_t oldlocale;
69+
#else
6370
char *oldlocale;
71+
#ifdef HAVE__CONFIGTHREADLOCALE
72+
int oldthreadlocale;
73+
#endif
74+
#endif
6475
int nparams;
6576
char **paramvalues;
6677
PGresult *results;

0 commit comments

Comments
 (0)