commit cd27ff7 (HEAD) Author: Noah Misch AuthorDate: Fri Oct 10 04:04:03 2014 -0400 Commit: Noah Misch CommitDate: Fri Oct 10 04:04:03 2014 -0400 When setlocale(LC_x, "") will start a thread, run it in a child process. Only Darwin, --enable-nls builds use a setlocale() that can start a thread. Buildfarm member orangutan experienced BackendList corruption on account of different postmaster threads executing signal handlers simultaneously. Furthermore, a multithreaded postmaster risks undefined behavior from sigprocmask() and fork(). Introduce pg_setlocale(), a wrapper around setlocale(). When the corresponding raw setlocale() call could start a thread, it forks, retrieves the setlocale() return value from the child, and uses that non-"" locale value to make an equivalent setlocale() call in the original process. The short-lived child does become multithreaded, but it uses no feature for which that poses a problem. Use of pg_setlocale() in the frontend protects forking executables, such as pg_dump, from undefined behavior. As the usage guideline comment implies, whether to call setlocale() or pg_setlocale() is a mere style question for most code sites. Opt for pg_setlocale() at indifferent code sites, leaving raw setlocale() calls in src/interfaces, in pg_setlocale() itself, and in callees of pg_setlocale(). Back-patch to 9.0 (all supported versions). diff --git a/configure b/configure index f0580ce..ee08963 100755 --- a/configure +++ b/configure @@ -11298,7 +11298,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs wcstombs_l +for ac_func in cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat pthread_is_threaded_np readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs 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" diff --git a/configure.in b/configure.in index 527b076..2f8adfc 100644 --- a/configure.in +++ b/configure.in @@ -1257,7 +1257,7 @@ PGAC_FUNC_GETTIMEOFDAY_1ARG LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -AC_CHECK_FUNCS([cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs wcstombs_l]) +AC_CHECK_FUNCS([cbrt dlopen fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll pstat pthread_is_threaded_np readlink setproctitle setsid shm_open sigprocmask symlink sync_file_range towlower utime utimes wcstombs wcstombs_l]) AC_REPLACE_FUNCS(fseeko) case $host_os in diff --git a/contrib/pg_upgrade/check.c b/contrib/pg_upgrade/check.c index bbfcab7..524d95d 100644 --- a/contrib/pg_upgrade/check.c +++ b/contrib/pg_upgrade/check.c @@ -1051,7 +1051,7 @@ get_canonical_locale_name(int category, const char *locale) char *res; /* get the current setting, so we can restore it. */ - save = setlocale(category, NULL); + save = pg_setlocale(category, NULL); if (!save) pg_fatal("failed to get the current locale\n"); @@ -1059,7 +1059,7 @@ get_canonical_locale_name(int category, const char *locale) save = pg_strdup(save); /* set the locale with setlocale, to see if it accepts it. */ - res = setlocale(category, locale); + res = pg_setlocale(category, locale); if (!res) pg_fatal("failed to get system locale name for \"%s\"\n", locale); @@ -1067,7 +1067,7 @@ get_canonical_locale_name(int category, const char *locale) res = pg_strdup(res); /* restore old value. */ - if (!setlocale(category, save)) + if (!pg_setlocale(category, save)) pg_fatal("failed to restore old locale \"%s\"\n", save); pg_free(save); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 6220a8e..0017b5f 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -87,6 +87,10 @@ #include #endif +#ifdef HAVE_PTHREAD_IS_THREADED_NP +#include +#endif + #include "access/transam.h" #include "access/xlog.h" #include "bootstrap/bootstrap.h" @@ -1656,6 +1660,15 @@ ServerLoop(void) last_touch_time = now; } +#ifdef HAVE_PTHREAD_IS_THREADED_NP + + /* + * With assertions enabled, check regularly for appearance of + * additional threads. All builds check softly in ExitPostmaster(). + */ + Assert(pthread_is_threaded_np() == 0); +#endif + /* * If we already sent SIGQUIT to children and they are slow to shut * down, it's time to send them SIGKILL. This doesn't happen @@ -4733,6 +4746,22 @@ SubPostmasterMain(int argc, char *argv[]) static void ExitPostmaster(int status) { +#ifdef HAVE_PTHREAD_IS_THREADED_NP + + /* + * The postmaster calls sigprocmask() and calls fork() without an + * immediate exec(). Both practices have undefined behavior in a + * multithreaded program. Diagnose this on platforms providing a + * convenient means to do so. However, a multithreaded postmaster is the + * normal case on Windows, which offers neither fork() nor sigprocmask(). + */ + if (pthread_is_threaded_np() != 0) + ereport(LOG, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg_internal("postmaster became multithreaded"), + errdetail("Please report this to ."))); +#endif + /* should cleanup shared memory and kill all backends */ /* diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 94bb5a4..870e7bc 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -34,13 +34,13 @@ * mind in future: on some platforms, the locale functions return pointers * to static data that will be overwritten by any later locale function. * Thus, for example, the obvious-looking sequence - * save = setlocale(category, NULL); - * if (!setlocale(category, value)) + * save = pg_setlocale(category, NULL); + * if (!pg_setlocale(category, value)) * fail = true; - * setlocale(category, save); - * DOES NOT WORK RELIABLY: on some platforms the second setlocale() call + * pg_setlocale(category, save); + * DOES NOT WORK RELIABLY: on some platforms the second pg_setlocale() call * will change the memory save is pointing at. To do this sort of thing - * safely, you *must* pstrdup what setlocale returns the first time. + * safely, you *must* pstrdup what pg_setlocale returns the first time. * * FYI, The Open Group locale standard is defined here: * @@ -131,16 +131,16 @@ static char *IsoLocaleName(const char *); /* MSVC specific */ /* * pg_perm_setlocale * - * This wraps the libc function setlocale(), with two additions. First, when - * changing LC_CTYPE, update gettext's encoding for the current message - * domain. GNU gettext automatically tracks LC_CTYPE on most platforms, but - * not on Windows. Second, if the operation is successful, the corresponding - * LC_XXX environment variable is set to match. By setting the environment - * variable, we ensure that any subsequent use of setlocale(..., "") will - * preserve the settings made through this routine. Of course, LC_ALL must - * also be unset to fully ensure that, but that has to be done elsewhere after - * all the individual LC_XXX variables have been set correctly. (Thank you - * Perl for making this kluge necessary.) + * This wraps pg_setlocale(), with two additions. First, when changing + * LC_CTYPE, update gettext's encoding for the current message domain. GNU + * gettext automatically tracks LC_CTYPE on most platforms, but not on + * Windows. Second, if the operation is successful, the corresponding LC_XXX + * environment variable is set to match. By setting the environment variable, + * we ensure that any subsequent use of setlocale(..., "") will preserve the + * settings made through this routine. Of course, LC_ALL must also be unset + * to fully ensure that, but that has to be done elsewhere after all the + * individual LC_XXX variables have been set correctly. (Thank you Perl for + * making this kluge necessary.) */ char * pg_perm_setlocale(int category, const char *locale) @@ -150,7 +150,7 @@ pg_perm_setlocale(int category, const char *locale) char *envbuf; #ifndef WIN32 - result = setlocale(category, locale); + result = pg_setlocale(category, locale); #else /* @@ -168,7 +168,7 @@ pg_perm_setlocale(int category, const char *locale) } else #endif - result = setlocale(category, locale); + result = pg_setlocale(category, locale); #endif /* WIN32 */ if (result == NULL) @@ -258,7 +258,7 @@ check_locale(int category, const char *locale, char **canonname) if (canonname) *canonname = NULL; /* in case of failure */ - save = setlocale(category, NULL); + save = pg_setlocale(category, NULL); if (!save) return false; /* won't happen, we hope */ @@ -266,14 +266,14 @@ check_locale(int category, const char *locale, char **canonname) save = pstrdup(save); /* set the locale with setlocale, to see if it accepts it. */ - res = setlocale(category, locale); + res = pg_setlocale(category, locale); /* save canonical name if requested. */ if (res && canonname) *canonname = pstrdup(res); /* restore old value. */ - if (!setlocale(category, save)) + if (!pg_setlocale(category, save)) elog(WARNING, "failed to restore old locale \"%s\"", save); pfree(save); @@ -454,11 +454,11 @@ PGLC_localeconv(void) free_struct_lconv(&CurrentLocaleConv); /* Save user's values of monetary and numeric locales */ - save_lc_monetary = setlocale(LC_MONETARY, NULL); + save_lc_monetary = pg_setlocale(LC_MONETARY, NULL); if (save_lc_monetary) save_lc_monetary = pstrdup(save_lc_monetary); - save_lc_numeric = setlocale(LC_NUMERIC, NULL); + save_lc_numeric = pg_setlocale(LC_NUMERIC, NULL); if (save_lc_numeric) save_lc_numeric = pstrdup(save_lc_numeric); @@ -486,16 +486,16 @@ PGLC_localeconv(void) */ /* save user's value of ctype locale */ - save_lc_ctype = setlocale(LC_CTYPE, NULL); + save_lc_ctype = pg_setlocale(LC_CTYPE, NULL); if (save_lc_ctype) save_lc_ctype = pstrdup(save_lc_ctype); /* use numeric to set the ctype */ - setlocale(LC_CTYPE, locale_numeric); + pg_setlocale(LC_CTYPE, locale_numeric); #endif /* Get formatting information for numeric */ - setlocale(LC_NUMERIC, locale_numeric); + pg_setlocale(LC_NUMERIC, locale_numeric); extlconv = localeconv(); encoding = pg_get_encoding_from_locale(locale_numeric, true); @@ -505,11 +505,11 @@ PGLC_localeconv(void) #ifdef WIN32 /* use monetary to set the ctype */ - setlocale(LC_CTYPE, locale_monetary); + pg_setlocale(LC_CTYPE, locale_monetary); #endif /* Get formatting information for monetary */ - setlocale(LC_MONETARY, locale_monetary); + pg_setlocale(LC_MONETARY, locale_monetary); extlconv = localeconv(); encoding = pg_get_encoding_from_locale(locale_monetary, true); @@ -532,14 +532,14 @@ PGLC_localeconv(void) /* Try to restore internal settings */ if (save_lc_monetary) { - if (!setlocale(LC_MONETARY, save_lc_monetary)) + if (!pg_setlocale(LC_MONETARY, save_lc_monetary)) elog(WARNING, "failed to restore old locale"); pfree(save_lc_monetary); } if (save_lc_numeric) { - if (!setlocale(LC_NUMERIC, save_lc_numeric)) + if (!pg_setlocale(LC_NUMERIC, save_lc_numeric)) elog(WARNING, "failed to restore old locale"); pfree(save_lc_numeric); } @@ -548,7 +548,7 @@ PGLC_localeconv(void) /* Try to restore internal ctype settings */ if (save_lc_ctype) { - if (!setlocale(LC_CTYPE, save_lc_ctype)) + if (!pg_setlocale(LC_CTYPE, save_lc_ctype)) elog(WARNING, "failed to restore old locale"); pfree(save_lc_ctype); } @@ -640,7 +640,7 @@ cache_locale_time(void) elog(DEBUG3, "cache_locale_time() executed; locale: \"%s\"", locale_time); /* save user's value of time locale */ - save_lc_time = setlocale(LC_TIME, NULL); + save_lc_time = pg_setlocale(LC_TIME, NULL); if (save_lc_time) save_lc_time = pstrdup(save_lc_time); @@ -656,15 +656,15 @@ cache_locale_time(void) */ /* save user's value of ctype locale */ - save_lc_ctype = setlocale(LC_CTYPE, NULL); + save_lc_ctype = pg_setlocale(LC_CTYPE, NULL); if (save_lc_ctype) save_lc_ctype = pstrdup(save_lc_ctype); /* use lc_time to set the ctype */ - setlocale(LC_CTYPE, locale_time); + pg_setlocale(LC_CTYPE, locale_time); #endif - setlocale(LC_TIME, locale_time); + pg_setlocale(LC_TIME, locale_time); timenow = time(NULL); timeinfo = localtime(&timenow); @@ -707,7 +707,7 @@ cache_locale_time(void) /* try to restore internal settings */ if (save_lc_time) { - if (!setlocale(LC_TIME, save_lc_time)) + if (!pg_setlocale(LC_TIME, save_lc_time)) elog(WARNING, "failed to restore old locale"); pfree(save_lc_time); } @@ -716,7 +716,7 @@ cache_locale_time(void) /* try to restore internal ctype settings */ if (save_lc_ctype) { - if (!setlocale(LC_CTYPE, save_lc_ctype)) + if (!pg_setlocale(LC_CTYPE, save_lc_ctype)) elog(WARNING, "failed to restore old locale"); pfree(save_lc_ctype); } @@ -945,7 +945,7 @@ lc_collate_is_c(Oid collation) if (result >= 0) return (bool) result; - localeptr = setlocale(LC_COLLATE, NULL); + localeptr = pg_setlocale(LC_COLLATE, NULL); if (!localeptr) elog(ERROR, "invalid LC_COLLATE setting"); @@ -995,7 +995,7 @@ lc_ctype_is_c(Oid collation) if (result >= 0) return (bool) result; - localeptr = setlocale(LC_CTYPE, NULL); + localeptr = pg_setlocale(LC_CTYPE, NULL); if (!localeptr) elog(ERROR, "invalid LC_CTYPE setting"); diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c index 665ac10..11f46f3 100644 --- a/src/backend/utils/mb/mbutils.c +++ b/src/backend/utils/mb/mbutils.c @@ -984,7 +984,7 @@ pg_bind_textdomain_codeset(const char *domainname) int new_msgenc; #ifndef WIN32 - const char *ctype = setlocale(LC_CTYPE, NULL); + const char *ctype = pg_setlocale(LC_CTYPE, NULL); if (pg_strcasecmp(ctype, "C") == 0 || pg_strcasecmp(ctype, "POSIX") == 0) #endif diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index c8ff2cb..2744a75 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2487,12 +2487,12 @@ locale_date_order(const char *locale) result = DATEORDER_MDY; /* default */ - save = setlocale(LC_TIME, NULL); + save = pg_setlocale(LC_TIME, NULL); if (!save) return result; save = pg_strdup(save); - setlocale(LC_TIME, locale); + pg_setlocale(LC_TIME, locale); memset(&testtime, 0, sizeof(testtime)); testtime.tm_mday = 22; @@ -2501,7 +2501,7 @@ locale_date_order(const char *locale) res = my_strftime(buf, sizeof(buf), "%x", &testtime); - setlocale(LC_TIME, save); + pg_setlocale(LC_TIME, save); free(save); if (res == 0) @@ -2545,7 +2545,7 @@ check_locale_name(int category, const char *locale, char **canonname) if (canonname) *canonname = NULL; /* in case of failure */ - save = setlocale(category, NULL); + save = pg_setlocale(category, NULL); if (!save) { fprintf(stderr, _("%s: setlocale() failed\n"), @@ -2557,14 +2557,14 @@ check_locale_name(int category, const char *locale, char **canonname) save = pg_strdup(save); /* set the locale with setlocale, to see if it accepts it. */ - res = setlocale(category, locale); + res = pg_setlocale(category, locale); /* save canonical name if requested. */ if (res && canonname) *canonname = pg_strdup(res); /* restore old value. */ - if (!setlocale(category, save)) + if (!pg_setlocale(category, save)) { fprintf(stderr, _("%s: failed to restore old locale \"%s\"\n"), progname, save); diff --git a/src/common/exec.c b/src/common/exec.c index 037bef2..4f364d8 100644 --- a/src/common/exec.c +++ b/src/common/exec.c @@ -20,12 +20,17 @@ #include "postgres_fe.h" #endif +#include #include #include #include #include #ifndef FRONTEND +#include "miscadmin.h" +#endif + +#ifndef FRONTEND /* We use only 3- and 4-parameter elog calls in this file, for simplicity */ /* NOTE: caller must provide gettext call around str! */ #define log_error(str, param) elog(LOG, str, param) @@ -556,7 +561,7 @@ set_pglocale_pgservice(const char *argv0, const char *app) /* don't set LC_ALL in the backend */ if (strcmp(app, PG_TEXTDOMAIN("postgres")) != 0) - setlocale(LC_ALL, ""); + pg_setlocale(LC_ALL, ""); if (find_my_exec(argv0, my_exec_path) < 0) return; @@ -586,6 +591,223 @@ set_pglocale_pgservice(const char *argv0, const char *app) } } +#ifndef pg_setlocale +static char *setlocale_native_forked(int category); + +/* + * pg_setlocale + * + * Usage Guidelines + * ---------------- + * + * In the backend, it is always safe to replace a plain setlocale() call with + * a pg_setlocale() call. Use of pg_setlocale() is mandatory in code that can + * run before main() finishes initializing the process locale. After that, + * pg_setlocale() degenerates to plain setlocale(), and either is acceptable. + * + * In the frontend, pg_setlocale() has three noteworthy side effects: + * + * 1. Calls exit(EXIT_FAILURE) on internal error, much like pg_malloc(). + * 2. Forks a child that calls a non-async-signal-safe function. If the + * original process was multithreaded, behavior is undefined. + * 3. The process will receive SIGCHLD for one already-waited child, much as + * it does during system(). + * + * src/bin tolerates those effects. src/interfaces libraries do not; they + * must use plain setlocale(). + * + * Background + * ---------- + * + * On Darwin, libintl's replacement setlocale() calls CFLocaleCopyCurrent() + * when its second argument is "" and every relevant environment variable is + * unset or empty. CFLocaleCopyCurrent() makes the process multithreaded, + * which raises several programming hazards we'd just as soon not face. This + * function addresses the problem by retrieving the locale string in a + * short-lived child process, where the extra thread is acceptable. + * + * libintl's replacement newlocale() behaves likewise. All our newlocale() + * calls happen in the backend, after main() has initialized the process + * locale. So long as that is true, they require no wrapper. + */ +char * +pg_setlocale(int category, const char *locale) +{ + const char *env_lang; + + /* Plain setlocale() is fine for non-"" locale arguments. */ + if (locale == NULL || locale[0] != '\0') + goto degenerate; + +#ifndef FRONTEND + + /* + * After main()'s series of pg_perm_setlocale() calls, every locale + * category environment variable has a non-empty value. Recognizing this + * case is no mere optimization, because the slow path's use of + * ereport(FATAL) is unacceptable during normal operation. + */ + if (MyProcPid != 0) + goto degenerate; +#endif + + /* + * With LANG available, libintl_setlocale(category, "") never calls + * CFLocaleCopyCurrent(). Optimize that common case. + */ + env_lang = getenv("LANG"); + if (env_lang != NULL && env_lang[0] != '\0') + goto degenerate; + + return setlocale_native_forked(category); +degenerate: + return setlocale(category, locale); +} + +static char * +setlocale_native_forked(int category) +{ +#ifdef FRONTEND + + /* + * In place of an ugly #ifdef FRONTEND variation of each ereport(), squash + * most of them to a generic message. + */ +#define ereport_f(elevel, rest) \ + do { \ + fprintf(stderr, _("could not determine native locale\n")); \ + exit(EXIT_FAILURE); \ + } while (0) +#else +#define ereport_f(elevel, rest) ereport(elevel, rest) +#endif + + int pipefd[2]; + sigset_t block_chld; + sigset_t old_sigs; + pid_t pid; + pid_t waited_pid; + int exit_status; + + /* + * The longest locale strings come from setlocale(LC_ALL, ""), reaching + * 101 bytes on Darwin under environment variable settings such as + * "LANG=fr_BE.ISO8859-15 LC_NUMERIC=it_CH.ISO8859-1". That's comfortably + * below PIPE_BUF of 512. + */ + char locbuf[PIPE_BUF]; + int rlen; + + if (pipe(pipefd) != 0) + ereport_f(FATAL, + (errcode_for_file_access(), + errmsg("could not create locale retrieval pipe: %m"))); + + /* Block SIGCHLD so we can get an exit status. */ + sigemptyset(&block_chld); + sigaddset(&block_chld, SIGCHLD); + sigprocmask(SIG_BLOCK, &block_chld, &old_sigs); + + /* + * Run the child. + */ + pid = fork(); + if (pid < 0) + { + /* + * This is the most likely non-bug error, so furnish a precise message + * in both backend and frontend. + */ +#ifdef FRONTEND + fprintf(stderr, _("could not fork for locale retrieval: %s\n"), + strerror(errno)); + exit(EXIT_FAILURE); +#else + ereport(FATAL, + (errmsg("could not fork for locale retrieval: %m"))); +#endif + } + else if (pid == 0) + { + char *result; + int status = 0; + + /* + * Limit the child's actions to setlocale() and to system calls. This + * shields other PostgreSQL facilities from the need to contemplate + * implications of executing within this special-case child process. + * It makes unnecessary the additional actions of fork_process() and + * the post-fork actions of BackendStartup(). These restrictions do + * have us miss precise logging for a failed system call, but these + * calls almost cannot fail. + * + * When we would otherwise write() enough bytes to possibly block, + * truncate instead. The parent process notices this truncation and + * raises an error. Since we ensure that write() won't block, don't + * bother closing the read end of the pipe. (This also prevents + * SIGPIPE, which typically still has SIG_DFL.) Close the write end + * to detect EIO, though EIO is likely impossible when closing a pipe. + */ + + result = setlocale(category, ""); + + if (result != NULL) + { + int wlen = Min(strlen(result), sizeof(locbuf)); + + if (write(pipefd[1], result, wlen) != wlen) + status = 1; + } + if (close(pipefd[1]) != 0) + status = 1; + + _exit(status); + } + + /* Wait for the child to exit. */ + waited_pid = waitpid(pid, &exit_status, 0); + sigprocmask(SIG_SETMASK, &old_sigs, NULL); + if (waited_pid != pid) + ereport_f(FATAL, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("waitpid() failed: %m"))); + if (exit_status != 0) + ereport_f(FATAL, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not determine native locale: %s", + wait_result_to_str(exit_status)))); + + /* + * Retrieve the child's work. Child writes nothing if setlocale() returns + * NULL; otherwise, it writes the returned string, unterminated. (This + * assumes setlocale() never returns "".) + */ + if (close(pipefd[1]) != 0) + ereport_f(FATAL, + (errcode_for_file_access(), + errmsg("could not close locale retrieval pipe: %m"))); + rlen = read(pipefd[0], locbuf, sizeof(locbuf)); + if (rlen < 0) + ereport_f(FATAL, + (errcode_for_file_access(), + errmsg("could not read from locale retrieval pipe: %m"))); + else if (rlen == sizeof(locbuf)) + ereport_f(FATAL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("native locale setting is too long (maximum %d bytes)", + (int) sizeof(locbuf)))); + if (close(pipefd[0]) != 0) + ereport_f(FATAL, + (errcode_for_file_access(), + errmsg("could not close locale retrieval pipe: %m"))); + + if (rlen == 0) + return NULL; + locbuf[rlen] = '\0'; + return setlocale(category, locbuf); +} +#endif + #ifdef WIN32 /* diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index ddcf4b0..933736f 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -391,6 +391,9 @@ /* Define to 1 if the PS_STRINGS thing exists. */ #undef HAVE_PS_STRINGS +/* Define to 1 if you have the `pthread_is_threaded_np' function. */ +#undef HAVE_PTHREAD_IS_THREADED_NP + /* Define to 1 if you have the header file. */ #undef HAVE_PWD_H diff --git a/src/include/port.h b/src/include/port.h index 9f8465e..824a072 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -213,6 +213,12 @@ extern char *pgwin32_setlocale(int category, const char *locale); #define setlocale(a,b) pgwin32_setlocale(a,b) #endif /* WIN32 */ +#ifdef __darwin__ +extern char *pg_setlocale(int category, const char *locale); +#else +#define pg_setlocale(category, locale) setlocale(category, locale) +#endif + /* Portable prompt handling */ extern char *simple_prompt(const char *prompt, int maxlen, bool echo); diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 1d025d4..36853d1 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -734,15 +734,15 @@ plperl_init_interp(void) *save_numeric, *save_time; - loc = setlocale(LC_COLLATE, NULL); + loc = pg_setlocale(LC_COLLATE, NULL); save_collate = loc ? pstrdup(loc) : NULL; - loc = setlocale(LC_CTYPE, NULL); + loc = pg_setlocale(LC_CTYPE, NULL); save_ctype = loc ? pstrdup(loc) : NULL; - loc = setlocale(LC_MONETARY, NULL); + loc = pg_setlocale(LC_MONETARY, NULL); save_monetary = loc ? pstrdup(loc) : NULL; - loc = setlocale(LC_NUMERIC, NULL); + loc = pg_setlocale(LC_NUMERIC, NULL); save_numeric = loc ? pstrdup(loc) : NULL; - loc = setlocale(LC_TIME, NULL); + loc = pg_setlocale(LC_TIME, NULL); save_time = loc ? pstrdup(loc) : NULL; #define PLPERL_RESTORE_LOCALE(name, saved) \ @@ -3902,7 +3902,7 @@ plperl_inline_callback(void *arg) static char * setlocale_perl(int category, char *locale) { - char *RETVAL = setlocale(category, locale); + char *RETVAL = pg_setlocale(category, locale); if (RETVAL) { @@ -3917,7 +3917,7 @@ setlocale_perl(int category, char *locale) #ifdef LC_ALL if (category == LC_ALL) - newctype = setlocale(LC_CTYPE, NULL); + newctype = pg_setlocale(LC_CTYPE, NULL); else #endif newctype = RETVAL; @@ -3935,7 +3935,7 @@ setlocale_perl(int category, char *locale) #ifdef LC_ALL if (category == LC_ALL) - newcoll = setlocale(LC_COLLATE, NULL); + newcoll = pg_setlocale(LC_COLLATE, NULL); else #endif newcoll = RETVAL; @@ -3954,7 +3954,7 @@ setlocale_perl(int category, char *locale) #ifdef LC_ALL if (category == LC_ALL) - newnum = setlocale(LC_NUMERIC, NULL); + newnum = pg_setlocale(LC_NUMERIC, NULL); else #endif newnum = RETVAL; diff --git a/src/port/chklocale.c b/src/port/chklocale.c index 588dfd9..a58c39a 100644 --- a/src/port/chklocale.c +++ b/src/port/chklocale.c @@ -278,6 +278,13 @@ pg_codepage_to_encoding(UINT cp) * * If running in the backend and write_message is false, this function must * cope with the possibility that elog() and palloc() are not yet usable. + * + * This departs from the pg_setlocale() usage guidelines. It should use + * pg_setlocale() when pg_bind_textdomain_codeset() calls it before the + * postmaster locale is fully initialized. However, it should use plain + * setlocale() when libpq calls it. The difference doesn't matter for a NULL + * locale argument. Those callers pass NULL ctype, making the question + * academic. Use plain setlocale() to reduce libpq code footprint. */ int pg_get_encoding_from_locale(const char *ctype, bool write_message)