On Mon, Sep 15, 2014 at 12:51:14AM -0400, Noah Misch wrote:
> 1. Fork, call setlocale(LC_x, "") in the child, pass back the effective locale
>    name through a pipe, and pass that name to setlocale() in the original
>    process.  The short-lived child will get the extra threads, and the
>    postmaster will remain clean.

Here's an implementation thereof covering both backend and frontend use of
setlocale().  A setlocale() wrapper, pg_setlocale(), injects use of the child
process where necessary.  I placed the new function in src/common/exec.c,
because every executable already needed that file's code and translatable
strings for set_pglocale_pgservice() and callees.  (Some executables do miss
exec.c translations; I did not update them.)  src/port/chklocale.c was closer
in subject matter, but libpq imports it; this function does not belong in
libpq.  Also, this function would benefit from a frontend ereport()
implementation, which is likely to land in libpgcommon if it lands at all.
The runtime changes are conditional on __darwin__ but not on --enable-nls.
NLS builds are the production norm; I'd like non-NLS builds to consistently
exercise this code rather than shave its bytes and cycles.

pg_setlocale() relies on main() setting all six LC_<category> environment
variables.  The second attached patch seals the cracks in main()'s
longstanding attempt to do so.  This improves preexisting user-visible
behavior in weird cases: "LANG=pt_BR.utf8 LC_ALL=invalid postgres -D nosuch"
will now print untranslated text, not pt_BR text.

Documentation for each of lc_monetary, lc_numeric and lc_time says "If this
variable is set to the empty string (which is the default) then the value is
inherited from the execution environment of the server in a system-dependent
way."  Not so; by design, setlocale(LC_x, "") just re-selects the last value L
passed to pg_perm_setlocale(LC_x, L).  I have not touched this; I mention it
for the sake of the archives.
commit cd27ff7 (HEAD)
Author:     Noah Misch <n...@leadboat.com>
AuthorDate: Fri Oct 10 04:04:03 2014 -0400
Commit:     Noah Misch <n...@leadboat.com>
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 <dns_sd.h>
 #endif
 
+#ifdef HAVE_PTHREAD_IS_THREADED_NP
+#include <pthread.h>
+#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 
<pgsql-b...@postgresql.org>.")));
+#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 <limits.h>
 #include <signal.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #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 <pwd.h> 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)
commit 6525617
Author:     Noah Misch <n...@leadboat.com>
AuthorDate: Fri Oct 10 04:03:41 2014 -0400
Commit:     Noah Misch <n...@leadboat.com>
CommitDate: Fri Oct 10 04:03:41 2014 -0400

    Always set the six locale category environment variables in main().
    
    Typical server invocations already achieved that.  Invalid locale
    settings in the initial postmaster environment interfered, as could
    malloc() failure.  Setting "LC_MESSAGES=pt_BR.utf8 LC_ALL=invalid" in
    the postmaster environment will now choose C-locale messages, not
    Brazilian Portuguese messages.  Most localized programs, including all
    PostgreSQL frontend executables, do likewise.  Users are unlikely to
    observe changes involving locale categories other than LC_MESSAGES.
    CheckMyDatabase() ensures that we successfully set LC_COLLATE and
    LC_CTYPE; main() sets the remaining three categories to locale "C",
    which almost cannot fail.  Back-patch to 9.0 (all supported versions).

diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index c51b391..7e6cd0e 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -43,6 +43,7 @@ const char *progname;
 
 
 static void startup_hacks(const char *progname);
+static void init_locale(int category, const char *locale);
 static void help(const char *progname);
 static void check_root(const char *progname);
 
@@ -115,31 +116,31 @@ main(int argc, char *argv[])
                char       *env_locale;
 
                if ((env_locale = getenv("LC_COLLATE")) != NULL)
-                       pg_perm_setlocale(LC_COLLATE, env_locale);
+                       init_locale(LC_COLLATE, env_locale);
                else
-                       pg_perm_setlocale(LC_COLLATE, "");
+                       init_locale(LC_COLLATE, "");
 
                if ((env_locale = getenv("LC_CTYPE")) != NULL)
-                       pg_perm_setlocale(LC_CTYPE, env_locale);
+                       init_locale(LC_CTYPE, env_locale);
                else
-                       pg_perm_setlocale(LC_CTYPE, "");
+                       init_locale(LC_CTYPE, "");
        }
 #else
-       pg_perm_setlocale(LC_COLLATE, "");
-       pg_perm_setlocale(LC_CTYPE, "");
+       init_locale(LC_COLLATE, "");
+       init_locale(LC_CTYPE, "");
 #endif
 
 #ifdef LC_MESSAGES
-       pg_perm_setlocale(LC_MESSAGES, "");
+       init_locale(LC_MESSAGES, "");
 #endif
 
        /*
         * We keep these set to "C" always, except transiently in pg_locale.c; 
see
         * that file for explanations.
         */
-       pg_perm_setlocale(LC_MONETARY, "C");
-       pg_perm_setlocale(LC_NUMERIC, "C");
-       pg_perm_setlocale(LC_TIME, "C");
+       init_locale(LC_MONETARY, "C");
+       init_locale(LC_NUMERIC, "C");
+       init_locale(LC_TIME, "C");
 
        /*
         * Now that we have absorbed as much as we wish to from the locale
@@ -272,6 +273,23 @@ startup_hacks(const char *progname)
 
 
 /*
+ * Make the initial permanent setting for a locale category.  If that fails,
+ * perhaps due to LC_foo=invalid in the environment, use locale C.  If even
+ * that fails, perhaps due to out-of-memory, the entire startup fails with it.
+ * When this returns, we are guaranteed to have a setting for the given
+ * category's environment variable.
+ */
+static void
+init_locale(int category, const char *locale)
+{
+       if (pg_perm_setlocale(category, locale) == NULL &&
+               pg_perm_setlocale(category, "C") == NULL)
+               elog(FATAL, "could not adopt C locale");
+}
+
+
+
+/*
  * Help display should match the options accepted by PostmasterMain()
  * and PostgresMain().
  *
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to