This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
locale.c: Use mnemonic
[perl5.git] / locale.c
index b0bca5e..bc507d9 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -23,8 +23,8 @@
 /* utility functions for handling locale-specific stuff like what
  * character represents the decimal point.
  *
- * All C programs have an underlying locale.  Perl generally doesn't pay any
- * attention to it except within the scope of a 'use locale'.  For most
+ * All C programs have an underlying locale.  Perl code generally doesn't pay
+ * any attention to it except within the scope of a 'use locale'.  For most
  * categories, it accomplishes this by just using different operations if it is
  * in such scope than if not.  However, various libc functions called by Perl
  * are affected by the LC_NUMERIC category, so there are macros in perl.h that
 
 #include "EXTERN.h"
 #define PERL_IN_LOCALE_C
+#include "perl_langinfo.h"
 #include "perl.h"
 
-#ifdef I_LANGINFO
-#   include <langinfo.h>
-#endif
-
 #include "reentr.h"
 
 /* If the environment says to, we can output debugging information during
  * initialization.  This is done before option parsing, and before any thread
  * creation, so can be a file-level static */
 #ifdef DEBUGGING
-# ifdef PERL_GLOBAL_STRUCT
+#  ifdef PERL_GLOBAL_STRUCT
   /* no global syms allowed */
-#  define debug_initialization 0
-#  define DEBUG_INITIALIZATION_set(v)
-# else
+#    define debug_initialization 0
+#    define DEBUG_INITIALIZATION_set(v)
+#  else
 static bool debug_initialization = FALSE;
-#  define DEBUG_INITIALIZATION_set(v) (debug_initialization = v)
-# endif
+#    define DEBUG_INITIALIZATION_set(v) (debug_initialization = v)
+#  endif
 #endif
 
+/* strlen() of a literal string constant.  XXX We might want this more general,
+ * but using it in just this file for now */
+#define STRLENs(s)  (sizeof("" s "") - 1)
+
+/* Is the C string input 'name' "C" or "POSIX"?  If so, and 'name' is the
+ * return of setlocale(), then this is extremely likely to be the C or POSIX
+ * locale.  However, the output of setlocale() is documented to be opaque, but
+ * the odds are extremely small that it would return these two strings for some
+ * other locale.  Note that VMS in these two locales includes many non-ASCII
+ * characters as controls and punctuation (below are hex bytes):
+ *   cntrl:  84-97 9B-9F
+ *   punct:  A1-A3 A5 A7-AB B0-B3 B5-B7 B9-BD BF-CF D1-DD DF-EF F1-FD
+ * Oddly, none there are listed as alphas, though some represent alphabetics
+ * http://www.nntp.perl.org/group/perl.perl5.porters/2013/02/msg198753.html */
+#define isNAME_C_OR_POSIX(name)                                              \
+                             (   (name) != NULL                              \
+                              && (( *(name) == 'C' && (*(name + 1)) == '\0') \
+                                   || strEQ((name), "POSIX")))
+
 #ifdef USE_LOCALE
 
 /*
@@ -103,37 +119,184 @@ S_stdize_locale(pTHX_ char *locs)
     return locs;
 }
 
-#endif
+/* Two parallel arrays; first the locale categories Perl uses on this system;
+ * the second array is their names.  These arrays are in mostly arbitrary
+ * order. */
+
+const int categories[] = {
+
+#    ifdef USE_LOCALE_NUMERIC
+                             LC_NUMERIC,
+#    endif
+#    ifdef USE_LOCALE_CTYPE
+                             LC_CTYPE,
+#    endif
+#    ifdef USE_LOCALE_COLLATE
+                             LC_COLLATE,
+#    endif
+#    ifdef USE_LOCALE_TIME
+                             LC_TIME,
+#    endif
+#    ifdef USE_LOCALE_MESSAGES
+                             LC_MESSAGES,
+#    endif
+#    ifdef USE_LOCALE_MONETARY
+                             LC_MONETARY,
+#    endif
+#    ifdef LC_ALL
+                             LC_ALL,
+#    endif
+                            -1  /* Placeholder because C doesn't allow a
+                                   trailing comma, and it would get complicated
+                                   with all the #ifdef's */
+};
+
+/* The top-most real element is LC_ALL */
+
+const char * category_names[] = {
+
+#    ifdef USE_LOCALE_NUMERIC
+                                 "LC_NUMERIC",
+#    endif
+#    ifdef USE_LOCALE_CTYPE
+                                 "LC_CTYPE",
+#    endif
+#    ifdef USE_LOCALE_COLLATE
+                                 "LC_COLLATE",
+#    endif
+#    ifdef USE_LOCALE_TIME
+                                 "LC_TIME",
+#    endif
+#    ifdef USE_LOCALE_MESSAGES
+                                 "LC_MESSAGES",
+#    endif
+#    ifdef USE_LOCALE_MONETARY
+                                 "LC_MONETARY",
+#    endif
+#    ifdef LC_ALL
+                                 "LC_ALL",
+#    endif
+                                 NULL  /* Placeholder */
+                            };
+
+#  ifdef LC_ALL
+
+    /* On systems with LC_ALL, it is kept in the highest index position.  (-2
+     * to account for the final unused placeholder element.) */
+#    define NOMINAL_LC_ALL_INDEX (C_ARRAY_LENGTH(categories) - 2)
 
-void
-Perl_set_numeric_radix(pTHX)
+#  else
+
+    /* On systems without LC_ALL, we pretend it is there, one beyond the real
+     * top element, hence in the unused placeholder element. */
+#    define NOMINAL_LC_ALL_INDEX (C_ARRAY_LENGTH(categories) - 1)
+
+#  endif
+
+/* Pretending there is an LC_ALL element just above allows us to avoid most
+ * special cases.  Most loops through these arrays in the code below are
+ * written like 'for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++)'.  They will work
+ * on either type of system.  But the code must be written to not access the
+ * element at 'LC_ALL_INDEX' except on platforms that have it.  This can be
+ * checked for at compile time by using the #define LC_ALL_INDEX which is only
+ * defined if we do have LC_ALL. */
+
+/* Now create LC_foo_INDEX #defines for just those categories on this system */
+#  ifdef USE_LOCALE_NUMERIC
+#    define LC_NUMERIC_INDEX            0
+#    define _DUMMY_NUMERIC              LC_NUMERIC_INDEX
+#  else
+#    define _DUMMY_NUMERIC              -1
+#  endif
+#  ifdef USE_LOCALE_CTYPE
+#    define LC_CTYPE_INDEX              _DUMMY_NUMERIC + 1
+#    define _DUMMY_CTYPE                LC_CTYPE_INDEX
+#  else
+#    define _DUMMY_CTYPE                _DUMMY_NUMERIC
+#  endif
+#  ifdef USE_LOCALE_COLLATE
+#    define LC_COLLATE_INDEX            _DUMMY_CTYPE + 1
+#    define _DUMMY_COLLATE              LC_COLLATE_INDEX
+#  else
+#    define _DUMMY_COLLATE              _DUMMY_COLLATE
+#  endif
+#  ifdef USE_LOCALE_TIME
+#    define LC_TIME_INDEX               _DUMMY_COLLATE + 1
+#    define _DUMMY_TIME                 LC_TIME_INDEX
+#  else
+#    define _DUMMY_TIME                 _DUMMY_COLLATE
+#  endif
+#  ifdef USE_LOCALE_MESSAGES
+#    define LC_MESSAGES_INDEX           _DUMMY_TIME + 1
+#    define _DUMMY_MESSAGES             LC_MESSAGES_INDEX
+#  else
+#    define _DUMMY_MESSAGES             _DUMMY_TIME
+#  endif
+#  ifdef USE_LOCALE_MONETARY
+#    define LC_MONETARY_INDEX           _DUMMY_MESSAGES + 1
+#    define _DUMMY_MONETARY             LC_MONETARY_INDEX
+#  else
+#    define _DUMMY_MONETARY             _DUMMY_MESSAGES
+#  endif
+#  ifdef LC_ALL
+#    define LC_ALL_INDEX                _DUMMY_MONETARY + 1
+#  endif
+#endif /* ifdef USE_LOCALE */
+
+/* Windows requres a customized base-level setlocale() */
+#  ifdef WIN32
+#    define my_setlocale(cat, locale) win32_setlocale(cat, locale)
+#  else
+#    define my_setlocale(cat, locale) setlocale(cat, locale)
+#  endif
+
+/* Just placeholders for now.  "_c" is intended to be called when the category
+ * is a constant known at compile time; "_r", not known until run time  */
+#  define do_setlocale_c(category, locale) my_setlocale(category, locale)
+#  define do_setlocale_r(category, locale) my_setlocale(category, locale)
+
+STATIC void
+S_set_numeric_radix(pTHX_ const bool use_locale)
 {
-#ifdef USE_LOCALE_NUMERIC
-# ifdef HAS_LOCALECONV
-    const struct lconv* const lc = localeconv();
+    /* If 'use_locale' is FALSE, set to use a dot for the radix character.  If
+     * TRUE, use the radix character derived from the current locale */
 
-    if (lc && lc->decimal_point) {
-       if (lc->decimal_point[0] == '.' && lc->decimal_point[1] == 0) {
-           SvREFCNT_dec(PL_numeric_radix_sv);
-           PL_numeric_radix_sv = NULL;
-       }
-       else {
-           if (PL_numeric_radix_sv)
-               sv_setpv(PL_numeric_radix_sv, lc->decimal_point);
-           else
-               PL_numeric_radix_sv = newSVpv(lc->decimal_point, 0);
-            if (! is_invariant_string((U8 *) lc->decimal_point, 0)
-                && is_utf8_string((U8 *) lc->decimal_point, 0)
+#if defined(USE_LOCALE_NUMERIC) && (   defined(HAS_LOCALECONV)              \
+                                    || defined(HAS_NL_LANGINFO))
+
+    /* We only set up the radix SV if we are to use a locale radix ... */
+    if (use_locale) {
+        const char * radix = my_nl_langinfo(PERL_RADIXCHAR, FALSE);
+                                          /* FALSE => already in dest locale */
+
+        /* ... and the character being used isn't a dot */
+        if (strNE(radix, ".")) {
+            if (PL_numeric_radix_sv) {
+                sv_setpv(PL_numeric_radix_sv, radix);
+            }
+            else {
+                PL_numeric_radix_sv = newSVpv(radix, 0);
+            }
+
+            if ( !  is_utf8_invariant_string(
+                     (U8 *) SvPVX(PL_numeric_radix_sv), SvCUR(PL_numeric_radix_sv))
+                &&  is_utf8_string(
+                     (U8 *) SvPVX(PL_numeric_radix_sv), SvCUR(PL_numeric_radix_sv))
                 && _is_cur_LC_category_utf8(LC_NUMERIC))
             {
-               SvUTF8_on(PL_numeric_radix_sv);
+                SvUTF8_on(PL_numeric_radix_sv);
             }
-       }
+            goto done;
+        }
     }
-    else
-       PL_numeric_radix_sv = NULL;
 
-#ifdef DEBUGGING
+    SvREFCNT_dec(PL_numeric_radix_sv);
+    PL_numeric_radix_sv = NULL;
+
+  done: ;
+
+#  ifdef DEBUGGING
+
     if (DEBUG_L_TEST || debug_initialization) {
         PerlIO_printf(Perl_debug_log, "Locale radix is '%s', ?UTF-8=%d\n",
                                           (PL_numeric_radix_sv)
@@ -143,38 +306,30 @@ Perl_set_numeric_radix(pTHX)
                                            ? cBOOL(SvUTF8(PL_numeric_radix_sv))
                                            : 0);
     }
-#endif
 
-# endif /* HAS_LOCALECONV */
-#endif /* USE_LOCALE_NUMERIC */
+#  endif
+#endif /* USE_LOCALE_NUMERIC and can find the radix char */
+
 }
 
-/* Is the C string input 'name' "C" or "POSIX"?  If so, and 'name' is the
- * return of setlocale(), then this is extremely likely to be the C or POSIX
- * locale.  However, the output of setlocale() is documented to be opaque, but
- * the odds are extremely small that it would return these two strings for some
- * other locale.  Note that VMS in these two locales includes many non-ASCII
- * characters as controls and punctuation (below are hex bytes):
- *   cntrl:  00-1F 7F 84-97 9B-9F
- *   punct:  21-2F 3A-40 5B-60 7B-7E A1-A3 A5 A7-AB B0-B3 B5-B7 B9-BD BF-CF D1-DD DF-EF F1-FD
- * Oddly, none there are listed as alphas, though some represent alphabetics
- * http://www.nntp.perl.org/group/perl.perl5.porters/2013/02/msg198753.html */
-#define isNAME_C_OR_POSIX(name) ((name) != NULL                                 \
-                                  && ((*(name) == 'C' && (*(name + 1)) == '\0') \
-                                       || strEQ((name), "POSIX")))
 
 void
 Perl_new_numeric(pTHX_ const char *newnum)
 {
-#ifdef USE_LOCALE_NUMERIC
+
+#ifndef USE_LOCALE_NUMERIC
+
+    PERL_UNUSED_ARG(newnum);
+
+#else
 
     /* Called after all libc setlocale() calls affecting LC_NUMERIC, to tell
      * core Perl this and that 'newnum' is the name of the new locale.
      * It installs this locale as the current underlying default.
      *
      * The default locale and the C locale can be toggled between by use of the
-     * set_numeric_local() and set_numeric_standard() functions, which should
-     * probably not be called directly, but only via macros like
+     * set_numeric_underlying() and set_numeric_standard() functions, which
+     * should probably not be called directly, but only via macros like
      * SET_NUMERIC_STANDARD() in perl.h.
      *
      * The toggling is necessary mainly so that a non-dot radix decimal point
@@ -183,7 +338,7 @@ Perl_new_numeric(pTHX_ const char *newnum)
      *
      * This sets several interpreter-level variables:
      * PL_numeric_name  The underlying locale's name: a copy of 'newnum'
-     * PL_numeric_local A boolean indicating if the toggled state is such
+     * PL_numeric_underlying  A boolean indicating if the toggled state is such
      *                  that the current locale is the program's underlying
      *                  locale
      * PL_numeric_standard An int indicating if the toggled state is such
@@ -205,14 +360,14 @@ Perl_new_numeric(pTHX_ const char *newnum)
        Safefree(PL_numeric_name);
        PL_numeric_name = NULL;
        PL_numeric_standard = TRUE;
-       PL_numeric_local = TRUE;
+       PL_numeric_underlying = TRUE;
        return;
     }
 
     save_newnum = stdize_locale(savepv(newnum));
 
     PL_numeric_standard = isNAME_C_OR_POSIX(save_newnum);
-    PL_numeric_local = TRUE;
+    PL_numeric_underlying = TRUE;
 
     if (! PL_numeric_name || strNE(PL_numeric_name, save_newnum)) {
        Safefree(PL_numeric_name);
@@ -227,69 +382,83 @@ Perl_new_numeric(pTHX_ const char *newnum)
      * need the underlying locale change to it temporarily). */
     set_numeric_standard();
 
-    set_numeric_radix();
-
-#else
-    PERL_UNUSED_ARG(newnum);
 #endif /* USE_LOCALE_NUMERIC */
+
 }
 
 void
 Perl_set_numeric_standard(pTHX)
 {
+
 #ifdef USE_LOCALE_NUMERIC
+
     /* Toggle the LC_NUMERIC locale to C.  Most code should use the macros like
      * SET_NUMERIC_STANDARD() in perl.h instead of calling this directly.  The
      * macro avoids calling this routine if toggling isn't necessary according
      * to our records (which could be wrong if some XS code has changed the
      * locale behind our back) */
 
-    setlocale(LC_NUMERIC, "C");
+    do_setlocale_c(LC_NUMERIC, "C");
     PL_numeric_standard = TRUE;
-    PL_numeric_local = isNAME_C_OR_POSIX(PL_numeric_name);
-    set_numeric_radix();
-#ifdef DEBUGGING
+    PL_numeric_underlying = isNAME_C_OR_POSIX(PL_numeric_name);
+    set_numeric_radix(0);
+
+#  ifdef DEBUGGING
+
     if (DEBUG_L_TEST || debug_initialization) {
         PerlIO_printf(Perl_debug_log,
-                          "Underlying LC_NUMERIC locale now is C\n");
+                          "LC_NUMERIC locale now is standard C\n");
     }
-#endif
 
+#  endif
 #endif /* USE_LOCALE_NUMERIC */
+
 }
 
 void
-Perl_set_numeric_local(pTHX)
+Perl_set_numeric_underlying(pTHX)
 {
+
 #ifdef USE_LOCALE_NUMERIC
+
     /* Toggle the LC_NUMERIC locale to the current underlying default.  Most
-     * code should use the macros like SET_NUMERIC_LOCAL() in perl.h instead of
-     * calling this directly.  The macro avoids calling this routine if
-     * toggling isn't necessary according to our records (which could be wrong
-     * if some XS code has changed the locale behind our back) */
+     * code should use the macros like SET_NUMERIC_UNDERLYING() in perl.h
+     * instead of calling this directly.  The macro avoids calling this routine
+     * if toggling isn't necessary according to our records (which could be
+     * wrong if some XS code has changed the locale behind our back) */
 
-    setlocale(LC_NUMERIC, PL_numeric_name);
+    do_setlocale_c(LC_NUMERIC, PL_numeric_name);
     PL_numeric_standard = isNAME_C_OR_POSIX(PL_numeric_name);
-    PL_numeric_local = TRUE;
-    set_numeric_radix();
-#ifdef DEBUGGING
+    PL_numeric_underlying = TRUE;
+    set_numeric_radix(1);
+
+#  ifdef DEBUGGING
+
     if (DEBUG_L_TEST || debug_initialization) {
         PerlIO_printf(Perl_debug_log,
-                          "Underlying LC_NUMERIC locale now is %s\n",
+                          "LC_NUMERIC locale now is %s\n",
                           PL_numeric_name);
     }
-#endif
 
+#  endif
 #endif /* USE_LOCALE_NUMERIC */
+
 }
 
 /*
  * Set up for a new ctype locale.
  */
-void
-Perl_new_ctype(pTHX_ const char *newctype)
+STATIC void
+S_new_ctype(pTHX_ const char *newctype)
 {
-#ifdef USE_LOCALE_CTYPE
+
+#ifndef USE_LOCALE_CTYPE
+
+    PERL_ARGS_ASSERT_NEW_CTYPE;
+    PERL_UNUSED_ARG(newctype);
+    PERL_UNUSED_CONTEXT;
+
+#else
 
     /* Called after all libc setlocale() calls affecting LC_CTYPE, to tell
      * core Perl this and that 'newctype' is the name of the new locale.
@@ -328,8 +497,9 @@ Perl_new_ctype(pTHX_ const char *newctype)
          * NUL */
         char bad_chars_list[ (94 * 4) + (3 * 5) + 1 ];
 
-        bool check_for_problems = ckWARN_d(WARN_LOCALE); /* No warnings means
-                                                            no check */
+        /* Don't check for problems if we are suppressing the warnings */
+        bool check_for_problems = ckWARN_d(WARN_LOCALE)
+                               || UNLIKELY(DEBUG_L_TEST);
         bool multi_byte_locale = FALSE;     /* Assume is a single-byte locale
                                                to start */
         unsigned int bad_count = 0;         /* Count of bad characters */
@@ -351,10 +521,10 @@ Perl_new_ctype(pTHX_ const char *newctype)
              * nowadays.  It isn't a problem for most controls to be changed
              * into something else; we check only \n and \t, though perhaps \r
              * could be an issue as well. */
-            if (check_for_problems
+            if (    check_for_problems
                 && (isGRAPH_A(i) || isBLANK_A(i) || i == '\n'))
             {
-                if ((isALPHANUMERIC_A(i) && ! isALPHANUMERIC_LC(i))
+                if ((    isALPHANUMERIC_A(i) && ! isALPHANUMERIC_LC(i))
                      || (isPUNCT_A(i) && ! isPUNCT_LC(i))
                      || (isBLANK_A(i) && ! isBLANK_LC(i))
                      || (i == '\n' && ! isCNTRL_LC(i)))
@@ -383,10 +553,15 @@ Perl_new_ctype(pTHX_ const char *newctype)
             }
         }
 
-#ifdef MB_CUR_MAX
+#  ifdef MB_CUR_MAX
+
         /* We only handle single-byte locales (outside of UTF-8 ones; so if
          * this locale requires more than one byte, there are going to be
          * problems. */
+        DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+                 "%s:%d: check_for_problems=%d, MB_CUR_MAX=%d\n",
+                 __FILE__, __LINE__, check_for_problems, (int) MB_CUR_MAX));
+
         if (check_for_problems && MB_CUR_MAX > 1
 
                /* Some platforms return MB_CUR_MAX > 1 for even the "C"
@@ -399,7 +574,8 @@ Perl_new_ctype(pTHX_ const char *newctype)
         {
             multi_byte_locale = TRUE;
         }
-#endif
+
+#  endif
 
         if (bad_count || multi_byte_locale) {
             PL_warn_locale = Perl_newSVpvf(aTHX_
@@ -418,11 +594,12 @@ Perl_new_ctype(pTHX_ const char *newctype)
                               ? bad_chars_list
                               : ""
                             );
-            /* If we are actually in the scope of the locale, output the
-             * message now.  Otherwise we save it to be output at the first
-             * operation using this locale, if that actually happens.  Most
-             * programs don't use locales, so they are immune to bad ones */
-            if (IN_LC(LC_CTYPE)) {
+            /* If we are actually in the scope of the locale or are debugging,
+             * output the message now.  If not in that scope, we save the
+             * message to be output at the first operation using this locale,
+             * if that actually happens.  Most programs don't use locales, so
+             * they are immune to bad ones.  */
+            if (IN_LC(LC_CTYPE) || UNLIKELY(DEBUG_L_TEST)) {
 
                 /* We have to save 'newctype' because the setlocale() just
                  * below may destroy it.  The next setlocale() further down
@@ -430,22 +607,24 @@ Perl_new_ctype(pTHX_ const char *newctype)
                  * here is transparent to this function's caller */
                 const char * const badlocale = savepv(newctype);
 
-                setlocale(LC_CTYPE, "C");
+                do_setlocale_c(LC_CTYPE, "C");
 
                 /* The '0' below suppresses a bogus gcc compiler warning */
                 Perl_warner(aTHX_ packWARN(WARN_LOCALE), SvPVX(PL_warn_locale), 0);
-                setlocale(LC_CTYPE, badlocale);
+
+                do_setlocale_c(LC_CTYPE, badlocale);
                 Safefree(badlocale);
-                SvREFCNT_dec_NN(PL_warn_locale);
-                PL_warn_locale = NULL;
+
+                if (IN_LC(LC_CTYPE)) {
+                    SvREFCNT_dec_NN(PL_warn_locale);
+                    PL_warn_locale = NULL;
+                }
             }
         }
     }
 
 #endif /* USE_LOCALE_CTYPE */
-    PERL_ARGS_ASSERT_NEW_CTYPE;
-    PERL_UNUSED_ARG(newctype);
-    PERL_UNUSED_CONTEXT;
+
 }
 
 void
@@ -474,19 +653,20 @@ Perl__warn_problematic_locale()
 
 }
 
-void
-Perl_new_collate(pTHX_ const char *newcoll)
+STATIC void
+S_new_collate(pTHX_ const char *newcoll)
 {
-#ifdef USE_LOCALE_COLLATE
+
+#ifndef USE_LOCALE_COLLATE
+
+    PERL_UNUSED_ARG(newcoll);
+    PERL_UNUSED_CONTEXT;
+
+#else
 
     /* Called after all libc setlocale() calls affecting LC_COLLATE, to tell
      * core Perl this and that 'newcoll' is the name of the new locale.
      *
-     * Any code changing the locale (outside this file) should use
-     * POSIX::setlocale, which calls this function.  Therefore this function
-     * should be called directly only from this file and from
-     * POSIX::setlocale().
-     *
      * The design of locale collation is that every locale change is given an
      * index 'PL_collation_ix'.  The first time a string particpates in an
      * operation that requires collation while locale collation is active, it
@@ -512,7 +692,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
        PL_collxfrm_base = 0;
        PL_collxfrm_mult = 2;
         PL_in_utf8_COLLATE_locale = FALSE;
-        *PL_strxfrm_min_char = '\0';
+        PL_strxfrm_NUL_replacement = '\0';
         PL_strxfrm_max_cp = 0;
        return;
     }
@@ -528,7 +708,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
         }
 
         PL_in_utf8_COLLATE_locale = _is_cur_LC_category_utf8(LC_COLLATE);
-        *PL_strxfrm_min_char = '\0';
+        PL_strxfrm_NUL_replacement = '\0';
         PL_strxfrm_max_cp = 0;
 
         /* A locale collation definition includes primary, secondary, tertiary,
@@ -547,7 +727,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
          * This has the desired effect that strcmp() will look at the secondary
          * or tertiary weights only if the strings compare equal at all higher
          * priority weights.  The spaces shown here, like in
-         *  "A¹B¹C¹ A²B²C² "
+         *  "A¹B¹C¹ A²B²C² "
          * are not just for readability.  In the general case, these must
          * actually be bytes, which we will call here 'separator weights'; and
          * they must be smaller than any other weight value, but since these
@@ -674,106 +854,89 @@ Perl_new_collate(pTHX_ const char *newcoll)
                 PL_collxfrm_base = base + 1;
             }
 
-#ifdef DEBUGGING
+#  ifdef DEBUGGING
+
             if (DEBUG_L_TEST || debug_initialization) {
                 PerlIO_printf(Perl_debug_log,
-                    "%s:%d: ?UTF-8 locale=%d; x_len_shorter=%"UVuf", "
-                    "x_len_longer=%"UVuf","
-                    " collate multipler=%"UVuf", collate base=%"UVuf"\n",
+                    "%s:%d: ?UTF-8 locale=%d; x_len_shorter=%zu, "
+                    "x_len_longer=%zu,"
+                    " collate multipler=%zu, collate base=%zu\n",
                     __FILE__, __LINE__,
                     PL_in_utf8_COLLATE_locale,
                     x_len_shorter, x_len_longer,
                     PL_collxfrm_mult, PL_collxfrm_base);
             }
-#endif
+#  endif
+
        }
     }
 
-#else
-    PERL_UNUSED_ARG(newcoll);
 #endif /* USE_LOCALE_COLLATE */
+
 }
 
 #ifdef WIN32
 
-char *
-Perl_my_setlocale(pTHX_ int category, const char* locale)
+STATIC char *
+S_win32_setlocale(pTHX_ int category, const char* locale)
 {
     /* This, for Windows, emulates POSIX setlocale() behavior.  There is no
-     * difference unless the input locale is "", which means on Windows to get
-     * the machine default, which is set via the computer's "Regional and
-     * Language Options" (or its current equivalent).  In POSIX, it instead
-     * means to find the locale from the user's environment.  This routine
-     * looks in the environment, and, if anything is found, uses that instead
-     * of going to the machine default.  If there is no environment override,
-     * the machine default is used, as normal, by calling the real setlocale()
-     * with "".  The POSIX behavior is to use the LC_ALL variable if set;
-     * otherwise to use the particular category's variable if set; otherwise to
-     * use the LANG variable. */
+     * difference between the two unless the input locale is "", which normally
+     * means on Windows to get the machine default, which is set via the
+     * computer's "Regional and Language Options" (or its current equivalent).
+     * In POSIX, it instead means to find the locale from the user's
+     * environment.  This routine changes the Windows behavior to first look in
+     * the environment, and, if anything is found, use that instead of going to
+     * the machine default.  If there is no environment override, the machine
+     * default is used, by calling the real setlocale() with "".
+     *
+     * The POSIX behavior is to use the LC_ALL variable if set; otherwise to
+     * use the particular category's variable if set; otherwise to use the LANG
+     * variable. */
 
     bool override_LC_ALL = FALSE;
     char * result;
+    unsigned int i;
 
     if (locale && strEQ(locale, "")) {
-#   ifdef LC_ALL
+
+#  ifdef LC_ALL
+
         locale = PerlEnv_getenv("LC_ALL");
         if (! locale) {
-#endif
-            switch (category) {
-#   ifdef LC_ALL
-                case LC_ALL:
-                    override_LC_ALL = TRUE;
-                    break;  /* We already know its variable isn't set */
-#   endif
-#   ifdef USE_LOCALE_TIME
-                case LC_TIME:
-                    locale = PerlEnv_getenv("LC_TIME");
-                    break;
-#   endif
-#   ifdef USE_LOCALE_CTYPE
-                case LC_CTYPE:
-                    locale = PerlEnv_getenv("LC_CTYPE");
-                    break;
-#   endif
-#   ifdef USE_LOCALE_COLLATE
-                case LC_COLLATE:
-                    locale = PerlEnv_getenv("LC_COLLATE");
-                    break;
-#   endif
-#   ifdef USE_LOCALE_MONETARY
-                case LC_MONETARY:
-                    locale = PerlEnv_getenv("LC_MONETARY");
-                    break;
-#   endif
-#   ifdef USE_LOCALE_NUMERIC
-                case LC_NUMERIC:
-                    locale = PerlEnv_getenv("LC_NUMERIC");
-                    break;
-#   endif
-#   ifdef USE_LOCALE_MESSAGES
-                case LC_MESSAGES:
-                    locale = PerlEnv_getenv("LC_MESSAGES");
-                    break;
-#   endif
-                default:
-                    /* This is a category, like PAPER_SIZE that we don't
-                     * know about; and so can't provide a wrapper. */
-                    break;
+            if (category ==  LC_ALL) {
+                override_LC_ALL = TRUE;
             }
-            if (! locale) {
+            else {
+
+#  endif
+
+                for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++) {
+                    if (category == categories[i]) {
+                        locale = PerlEnv_getenv(category_names[i]);
+                        goto found_locale;
+                    }
+                }
+
                 locale = PerlEnv_getenv("LANG");
                 if (! locale) {
                     locale = "";
                 }
+
+              found_locale: ;
+
+#  ifdef LC_ALL
+
             }
-#   ifdef LC_ALL
         }
-#   endif
+
+#  endif
+
     }
 
     result = setlocale(category, locale);
     DEBUG_L(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n", __FILE__, __LINE__,
-                            _setlocale_debug_string(category, locale, result)));
+                            setlocale_debug_string(category, locale, result)));
 
     if (! override_LC_ALL)  {
         return result;
@@ -784,272 +947,923 @@ Perl_my_setlocale(pTHX_ int category, const char* locale)
      * lower priority than the other LC_foo variables, so override it for each
      * one that is set.  (If they are set to "", it means to use the same thing
      * we just set LC_ALL to, so can skip) */
-#   ifdef USE_LOCALE_TIME
-    result = PerlEnv_getenv("LC_TIME");
-    if (result && strNE(result, "")) {
-        setlocale(LC_TIME, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                    __FILE__, __LINE__,
-                    _setlocale_debug_string(LC_TIME, result, "not captured")));
-    }
-#   endif
-#   ifdef USE_LOCALE_CTYPE
-    result = PerlEnv_getenv("LC_CTYPE");
-    if (result && strNE(result, "")) {
-        setlocale(LC_CTYPE, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                    __FILE__, __LINE__,
-                    _setlocale_debug_string(LC_CTYPE, result, "not captured")));
-    }
-#   endif
-#   ifdef USE_LOCALE_COLLATE
-    result = PerlEnv_getenv("LC_COLLATE");
-    if (result && strNE(result, "")) {
-        setlocale(LC_COLLATE, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                  __FILE__, __LINE__,
-                  _setlocale_debug_string(LC_COLLATE, result, "not captured")));
-    }
-#   endif
-#   ifdef USE_LOCALE_MONETARY
-    result = PerlEnv_getenv("LC_MONETARY");
-    if (result && strNE(result, "")) {
-        setlocale(LC_MONETARY, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                 __FILE__, __LINE__,
-                 _setlocale_debug_string(LC_MONETARY, result, "not captured")));
-    }
-#   endif
-#   ifdef USE_LOCALE_NUMERIC
-    result = PerlEnv_getenv("LC_NUMERIC");
-    if (result && strNE(result, "")) {
-        setlocale(LC_NUMERIC, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                 __FILE__, __LINE__,
-                 _setlocale_debug_string(LC_NUMERIC, result, "not captured")));
-    }
-#   endif
-#   ifdef USE_LOCALE_MESSAGES
-    result = PerlEnv_getenv("LC_MESSAGES");
-    if (result && strNE(result, "")) {
-        setlocale(LC_MESSAGES, result);
-        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
-                 __FILE__, __LINE__,
-                 _setlocale_debug_string(LC_MESSAGES, result, "not captured")));
-    }
-#   endif
+
+    for (i = 0; i < LC_ALL_INDEX; i++) {
+        result = PerlEnv_getenv(category_names[i]);
+        if (result && strNE(result, "")) {
+            setlocale(categories[i], result);
+            DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
+                __FILE__, __LINE__,
+                setlocale_debug_string(categories[i], result, "not captured")));
+        }
+    }
 
     result = setlocale(LC_ALL, NULL);
     DEBUG_L(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
                                __FILE__, __LINE__,
-                               _setlocale_debug_string(LC_ALL, NULL, result)));
+                               setlocale_debug_string(LC_ALL, NULL, result)));
 
     return result;
 }
 
 #endif
 
-
-/*
- * Initialize locale awareness.
- */
-int
-Perl_init_i18nl10n(pTHX_ int printwarn)
+char *
+Perl_setlocale(int category, const char * locale)
 {
-    /* printwarn is
-     *
-     *    0 if not to output warning when setup locale is bad
-     *    1 if to output warning based on value of PERL_BADLANG
-     *    >1 if to output regardless of PERL_BADLANG
-     *
-     * returns
-     *    1 = set ok or not applicable,
-     *    0 = fallback to a locale of lower priority
-     *   -1 = fallback to all locales failed, not even to the C locale
-     *
-     * Under -DDEBUGGING, if the environment variable PERL_DEBUG_LOCALE_INIT is
-     * set, debugging information is output.
-     *
-     * This looks more complicated than it is, mainly due to the #ifdefs.
-     *
-     * We try to set LC_ALL to the value determined by the environment.  If
-     * there is no LC_ALL on this platform, we try the individual categories we
-     * know about.  If this works, we are done.
-     *
-     * But if it doesn't work, we have to do something else.  We search the
-     * environment variables ourselves instead of relying on the system to do
-     * it.  We look at, in order, LC_ALL, LANG, a system default locale (if we
-     * think there is one), and the ultimate fallback "C".  This is all done in
-     * the same loop as above to avoid duplicating code, but it makes things
-     * more complex.  After the original failure, we add the fallback
-     * possibilities to the list of locales to try, and iterate the loop
-     * through them all until one succeeds.
-     *
-     * On Ultrix, the locale MUST come from the environment, so there is
-     * preliminary code to set it.  I (khw) am not sure that it is necessary,
-     * and that this couldn't be folded into the loop, but barring any real
-     * platforms to test on, it's staying as-is
-     *
-     * A slight complication is that in embedded Perls, the locale may already
-     * be set-up, and we don't want to get it from the normal environment
-     * variables.  This is handled by having a special environment variable
-     * indicate we're in this situation.  We simply set setlocale's 2nd
-     * parameter to be a NULL instead of "".  That indicates to setlocale that
-     * it is not to change anything, but to return the current value,
-     * effectively initializing perl's db to what the locale already is.
-     *
-     * We play the same trick with NULL if a LC_ALL succeeds.  We call
-     * setlocale() on the individual categores with NULL to get their existing
-     * values for our db, instead of trying to change them.
-     * */
+    /* This wraps POSIX::setlocale() */
 
-    int ok = 1;
+    char * retval;
+    char * newlocale;
+    dTHX;
 
-#if defined(USE_LOCALE)
-#ifdef USE_LOCALE_CTYPE
-    char *curctype   = NULL;
-#endif /* USE_LOCALE_CTYPE */
-#ifdef USE_LOCALE_COLLATE
-    char *curcoll    = NULL;
-#endif /* USE_LOCALE_COLLATE */
 #ifdef USE_LOCALE_NUMERIC
-    char *curnum     = NULL;
-#endif /* USE_LOCALE_NUMERIC */
-#ifdef __GLIBC__
-    const char * const language   = savepv(PerlEnv_getenv("LANGUAGE"));
-#endif
 
-    /* NULL uses the existing already set up locale */
-    const char * const setlocale_init = (PerlEnv_getenv("PERL_SKIP_LOCALE_INIT"))
-                                        ? NULL
-                                        : "";
-    const char* trial_locales[5];   /* 5 = 1 each for "", LC_ALL, LANG, "", C */
-    unsigned int trial_locales_count;
-    const char * const lc_all     = savepv(PerlEnv_getenv("LC_ALL"));
-    const char * const lang       = savepv(PerlEnv_getenv("LANG"));
-    bool setlocale_failure = FALSE;
-    unsigned int i;
-    char *p;
+    /* A NULL locale means only query what the current one is.  We
+     * have the LC_NUMERIC name saved, because we are normally switched
+     * into the C locale for it.  Switch back so an LC_ALL query will yield
+     * the correct results; all other categories don't require special
+     * handling */
+    if (locale == NULL) {
+        if (category == LC_NUMERIC) {
+            return savepv(PL_numeric_name);
+        }
 
-    /* A later getenv() could zap this, so only use here */
-    const char * const bad_lang_use_once = PerlEnv_getenv("PERL_BADLANG");
+#  ifdef LC_ALL
 
-    const bool locwarn = (printwarn > 1
-                          || (printwarn
-                              && (! bad_lang_use_once
-                                  || (
-                                    /* disallow with "" or "0" */
-                                    *bad_lang_use_once
-                                    && strNE("0", bad_lang_use_once)))));
-    bool done = FALSE;
-    char * sl_result;   /* return from setlocale() */
-    char * locale_param;
-#ifdef WIN32
-    /* In some systems you can find out the system default locale
-     * and use that as the fallback locale. */
-#   define SYSTEM_DEFAULT_LOCALE
-#endif
-#ifdef SYSTEM_DEFAULT_LOCALE
-    const char *system_default_locale = NULL;
-#endif
+        else if (category == LC_ALL) {
+            SET_NUMERIC_UNDERLYING();
+        }
 
-#ifdef DEBUGGING
-    DEBUG_INITIALIZATION_set((PerlEnv_getenv("PERL_DEBUG_LOCALE_INIT"))
-                           ? TRUE
-                           : FALSE);
-#   define DEBUG_LOCALE_INIT(category, locale, result)                      \
-       STMT_START {                                                        \
-               if (debug_initialization) {                                 \
+#  endif
+
+    }
+
+#endif
+
+    /* Save retval since subsequent setlocale() calls may overwrite it. */
+    retval = savepv(do_setlocale_r(category, locale));
+
+    DEBUG_L(PerlIO_printf(Perl_debug_log,
+        "%s:%d: %s\n", __FILE__, __LINE__,
+            setlocale_debug_string(category, locale, retval)));
+    if (! retval) {
+        /* Should never happen that a query would return an error, but be
+         * sure and reset to C locale */
+        if (locale == 0) {
+            SET_NUMERIC_STANDARD();
+        }
+
+        return NULL;
+    }
+
+    /* If locale == NULL, we are just querying the state, but may have switched
+     * to NUMERIC_UNDERLYING.  Switch back before returning. */
+    if (locale == NULL) {
+        SET_NUMERIC_STANDARD();
+        return retval;
+    }
+
+    /* Now that have switched locales, we have to update our records to
+     * correspond. */
+
+    switch (category) {
+
+#ifdef USE_LOCALE_CTYPE
+
+        case LC_CTYPE:
+            new_ctype(retval);
+            break;
+
+#endif
+#ifdef USE_LOCALE_COLLATE
+
+        case LC_COLLATE:
+            new_collate(retval);
+            break;
+
+#endif
+#ifdef USE_LOCALE_NUMERIC
+
+        case LC_NUMERIC:
+            new_numeric(retval);
+            break;
+
+#endif
+#ifdef LC_ALL
+
+        case LC_ALL:
+
+            /* LC_ALL updates all the things we care about.  The values may not
+             * be the same as 'retval', as the locale "" may have set things
+             * individually */
+
+#  ifdef USE_LOCALE_CTYPE
+
+            newlocale = do_setlocale_c(LC_CTYPE, NULL);
+            new_ctype(newlocale);
+
+#  endif /* USE_LOCALE_CTYPE */
+#  ifdef USE_LOCALE_COLLATE
+
+            newlocale = do_setlocale_c(LC_COLLATE, NULL);
+            new_collate(newlocale);
+
+#  endif
+#  ifdef USE_LOCALE_NUMERIC
+
+            newlocale = do_setlocale_c(LC_NUMERIC, NULL);
+            new_numeric(newlocale);
+
+#  endif /* USE_LOCALE_NUMERIC */
+#endif /* LC_ALL */
+
+        default:
+            break;
+    }
+
+    return retval;
+
+
+}
+
+PERL_STATIC_INLINE const char *
+S_save_to_buffer(const char * string, char **buf, Size_t *buf_size, const Size_t offset)
+{
+    /* Copy the NUL-terminated 'string' to 'buf' + 'offset'.  'buf' has size 'buf_size',
+     * growing it if necessary */
+
+    const Size_t string_size = strlen(string) + offset + 1;
+
+    PERL_ARGS_ASSERT_SAVE_TO_BUFFER;
+
+    if (*buf_size == 0) {
+        Newx(*buf, string_size, char);
+        *buf_size = string_size;
+    }
+    else if (string_size > *buf_size) {
+        Renew(*buf, string_size, char);
+        *buf_size = string_size;
+    }
+
+    Copy(string, *buf + offset, string_size - offset, char);
+    return *buf;
+}
+
+/*
+
+=head1 Locale-related functions and macros
+
+=for apidoc Perl_langinfo
+
+This is an (almost Âª) drop-in replacement for the system C<L<nl_langinfo(3)>>,
+taking the same C<item> parameter values, and returning the same information.
+But it is more thread-safe than regular C<nl_langinfo()>, and hides the quirks
+of Perl's locale handling from your code, and can be used on systems that lack
+a native C<nl_langinfo>.
+
+Expanding on these:
+
+=over
+
+=item *
+
+It delivers the correct results for the C<RADIXCHAR> and C<THOUSESEP> items,
+without you having to write extra code.  The reason for the extra code would be
+because these are from the C<LC_NUMERIC> locale category, which is normally
+kept set to the C locale by Perl, no matter what the underlying locale is
+supposed to be, and so to get the expected results, you have to temporarily
+toggle into the underlying locale, and later toggle back.  (You could use
+plain C<nl_langinfo> and C<L</STORE_LC_NUMERIC_FORCE_TO_UNDERLYING>> for this
+but then you wouldn't get the other advantages of C<Perl_langinfo()>; not
+keeping C<LC_NUMERIC> in the C locale would break a lot of CPAN, which is
+expecting the radix (decimal point) character to be a dot.)
+
+=item *
+
+Depending on C<item>, it works on systems that don't have C<nl_langinfo>, hence
+makes your code more portable.  Of the fifty-some possible items specified by
+the POSIX 2008 standard,
+L<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/langinfo.h.html>,
+only two are completely unimplemented.  It uses various techniques to recover
+the other items, including calling C<L<localeconv(3)>>, and C<L<strftime(3)>>,
+both of which are specified in C89, so should be always be available.  Later
+C<strftime()> versions have additional capabilities; C<""> is returned for
+those not available on your system.
+
+The details for those items which may differ from what this emulation returns
+and what a native C<nl_langinfo()> would return are:
+
+=over
+
+=item C<CODESET>
+
+=item C<ERA>
+
+Unimplemented, so returns C<"">.
+
+=item C<YESEXPR>
+
+=item C<NOEXPR>
+
+Only the values for English are returned.  Earlier POSIX standards also
+specified C<YESSTR> and C<NOSTR>, but these have been removed from POSIX 2008,
+and aren't supported by C<Perl_langinfo>.
+
+=item C<D_FMT>
+
+Always evaluates to C<%x>, the locale's appropriate date representation.
+
+=item C<T_FMT>
+
+Always evaluates to C<%X>, the locale's appropriate time representation.
+
+=item C<D_T_FMT>
+
+Always evaluates to C<%c>, the locale's appropriate date and time
+representation.
+
+=item C<CRNCYSTR>
+
+The return may be incorrect for those rare locales where the currency symbol
+replaces the radix character.
+Send email to L<mailto:perlbug@perl.org> if you have examples of it needing
+to work differently.
+
+=item C<ALT_DIGITS>
+
+Currently this gives the same results as Linux does.
+Send email to L<mailto:perlbug@perl.org> if you have examples of it needing
+to work differently.
+
+=item C<ERA_D_FMT>
+
+=item C<ERA_T_FMT>
+
+=item C<ERA_D_T_FMT>
+
+=item C<T_FMT_AMPM>
+
+These are derived by using C<strftime()>, and not all versions of that function
+know about them.  C<""> is returned for these on such systems.
+
+=back
+
+When using C<Perl_langinfo> on systems that don't have a native
+C<nl_langinfo()>, you must
+
+ #include "perl_langinfo.h"
+
+before the C<perl.h> C<#include>.  You can replace your C<langinfo.h>
+C<#include> with this one.  (Doing it this way keeps out the symbols that plain
+C<langinfo.h> imports into the namespace for code that doesn't need it.)
+
+You also should not use the bare C<langinfo.h> item names, but should preface
+them with C<PERL_>, so use C<PERL_RADIXCHAR> instead of plain C<RADIXCHAR>.
+The C<PERL_I<foo>> versions will also work for this function on systems that do
+have a native C<nl_langinfo>.
+
+=item *
+
+It is thread-friendly, returning its result in a buffer that won't be
+overwritten by another thread, so you don't have to code for that possibility.
+The buffer can be overwritten by the next call to C<nl_langinfo> or
+C<Perl_langinfo> in the same thread.
+
+=item *
+
+ª It returns S<C<const char *>>, whereas plain C<nl_langinfo()> returns S<C<char
+*>>, but you are (only by documentation) forbidden to write into the buffer.
+By declaring this C<const>, the compiler enforces this restriction.  The extra
+C<const> is why this isn't an unequivocal drop-in replacement for
+C<nl_langinfo>.
+
+=back
+
+The original impetus for C<Perl_langinfo()> was so that code that needs to
+find out the current currency symbol, floating point radix character, or digit
+grouping separator can use, on all systems, the simpler and more
+thread-friendly C<nl_langinfo> API instead of C<L<localeconv(3)>> which is a
+pain to make thread-friendly.  For other fields returned by C<localeconv>, it
+is better to use the methods given in L<perlcall> to call
+L<C<POSIX::localeconv()>|POSIX/localeconv>, which is thread-friendly.
+
+=cut
+
+*/
+
+const char *
+#ifdef HAS_NL_LANGINFO
+Perl_langinfo(const nl_item item)
+#else
+Perl_langinfo(const int item)
+#endif
+{
+    return my_nl_langinfo(item, TRUE);
+}
+
+const char *
+#ifdef HAS_NL_LANGINFO
+S_my_nl_langinfo(const nl_item item, bool toggle)
+#else
+S_my_nl_langinfo(const int item, bool toggle)
+#endif
+{
+    dTHX;
+
+#if defined(HAS_NL_LANGINFO) /* nl_langinfo() is available.  */
+#if   ! defined(HAS_POSIX_2008_LOCALE)
+
+    /* Here, use plain nl_langinfo(), switching to the underlying LC_NUMERIC
+     * for those items dependent on it.  This must be copied to a buffer before
+     * switching back, as some systems destroy the buffer when setlocale() is
+     * called */
+
+    LOCALE_LOCK;
+
+    if (toggle) {
+        if (item == PERL_RADIXCHAR || item == PERL_THOUSEP) {
+            do_setlocale_c(LC_NUMERIC, PL_numeric_name);
+        }
+        else {
+            toggle = FALSE;
+        }
+    }
+
+    save_to_buffer(nl_langinfo(item), &PL_langinfo_buf, &PL_langinfo_bufsize, 0);
+
+    if (toggle) {
+        do_setlocale_c(LC_NUMERIC, "C");
+    }
+
+    LOCALE_UNLOCK;
+
+    return PL_langinfo_buf;
+
+#  else /* Use nl_langinfo_l(), avoiding both a mutex and changing the locale */
+
+    bool do_free = FALSE;
+    locale_t cur = uselocale((locale_t) 0);
+
+    if (cur == LC_GLOBAL_LOCALE) {
+        cur = duplocale(LC_GLOBAL_LOCALE);
+        do_free = TRUE;
+    }
+
+    if (   toggle
+        && (item == PERL_RADIXCHAR || item == PERL_THOUSEP))
+    {
+        cur = newlocale(LC_NUMERIC_MASK, PL_numeric_name, cur);
+        do_free = TRUE;
+    }
+
+    save_to_buffer(nl_langinfo_l(item, cur),
+                   &PL_langinfo_buf, &PL_langinfo_bufsize, 0);
+    if (do_free) {
+        freelocale(cur);
+    }
+
+    return PL_langinfo_buf;
+
+#    endif
+#else   /* Below, emulate nl_langinfo as best we can */
+#  ifdef HAS_LOCALECONV
+
+    const struct lconv* lc;
+
+#  endif
+#  ifdef HAS_STRFTIME
+
+    struct tm tm;
+    bool return_format = FALSE; /* Return the %format, not the value */
+    const char * format;
+
+#  endif
+
+    /* We copy the results to a per-thread buffer, even if not multi-threaded.
+     * This is in part to simplify this code, and partly because we need a
+     * buffer anyway for strftime(), and partly because a call of localeconv()
+     * could otherwise wipe out the buffer, and the programmer would not be
+     * expecting this, as this is a nl_langinfo() substitute after all, so s/he
+     * might be thinking their localeconv() is safe until another localeconv()
+     * call. */
+
+    switch (item) {
+        Size_t len;
+        const char * retval;
+
+        /* These 2 are unimplemented */
+        case PERL_CODESET:
+        case PERL_ERA:         /* For use with strftime() %E modifier */
+
+        default:
+            return "";
+
+        /* We use only an English set, since we don't know any more */
+        case PERL_YESEXPR:   return "^[+1yY]";
+        case PERL_NOEXPR:    return "^[-0nN]";
+
+#  ifdef HAS_LOCALECONV
+
+        case PERL_CRNCYSTR:
+
+            LOCALE_LOCK;
+
+            lc = localeconv();
+            if (! lc || ! lc->currency_symbol || strEQ("", lc->currency_symbol))
+            {
+                LOCALE_UNLOCK;
+                return "";
+            }
+
+            /* Leave the first spot empty to be filled in below */
+            save_to_buffer(lc->currency_symbol, &PL_langinfo_buf,
+                           &PL_langinfo_bufsize, 1);
+            if (lc->mon_decimal_point && strEQ(lc->mon_decimal_point, ""))
+            { /*  khw couldn't figure out how the localedef specifications
+                  would show that the $ should replace the radix; this is
+                  just a guess as to how it might work.*/
+                *PL_langinfo_buf = '.';
+            }
+            else if (lc->p_cs_precedes) {
+                *PL_langinfo_buf = '-';
+            }
+            else {
+                *PL_langinfo_buf = '+';
+            }
+
+            LOCALE_UNLOCK;
+            break;
+
+        case PERL_RADIXCHAR:
+        case PERL_THOUSEP:
+
+            LOCALE_LOCK;
+
+            if (toggle) {
+                do_setlocale_c(LC_NUMERIC, PL_numeric_name);
+            }
+
+            lc = localeconv();
+            if (! lc) {
+                retval = "";
+            }
+            else {
+                retval = (item == PERL_RADIXCHAR)
+                         ? lc->decimal_point
+                         : lc->thousands_sep;
+                if (! retval) {
+                    retval = "";
+                }
+            }
+
+            save_to_buffer(retval, &PL_langinfo_buf, &PL_langinfo_bufsize, 0);
+
+            if (toggle) {
+                do_setlocale_c(LC_NUMERIC, "C");
+            }
+
+            LOCALE_UNLOCK;
+
+            break;
+
+#  endif
+#  ifdef HAS_STRFTIME
+
+        /* These are defined by C89, so we assume that strftime supports them,
+         * and so are returned unconditionally; they may not be what the locale
+         * actually says, but should give good enough results for someone using
+         * them as formats (as opposed to trying to parse them to figure out
+         * what the locale says).  The other format items are actually tested to
+         * verify they work on the platform */
+        case PERL_D_FMT:         return "%x";
+        case PERL_T_FMT:         return "%X";
+        case PERL_D_T_FMT:       return "%c";
+
+        /* These formats are only available in later strfmtime's */
+        case PERL_ERA_D_FMT: case PERL_ERA_T_FMT: case PERL_ERA_D_T_FMT:
+        case PERL_T_FMT_AMPM:
+
+        /* The rest can be gotten from most versions of strftime(). */
+        case PERL_ABDAY_1: case PERL_ABDAY_2: case PERL_ABDAY_3:
+        case PERL_ABDAY_4: case PERL_ABDAY_5: case PERL_ABDAY_6:
+        case PERL_ABDAY_7:
+        case PERL_ALT_DIGITS:
+        case PERL_AM_STR: case PERL_PM_STR:
+        case PERL_ABMON_1: case PERL_ABMON_2: case PERL_ABMON_3:
+        case PERL_ABMON_4: case PERL_ABMON_5: case PERL_ABMON_6:
+        case PERL_ABMON_7: case PERL_ABMON_8: case PERL_ABMON_9:
+        case PERL_ABMON_10: case PERL_ABMON_11: case PERL_ABMON_12:
+        case PERL_DAY_1: case PERL_DAY_2: case PERL_DAY_3: case PERL_DAY_4:
+        case PERL_DAY_5: case PERL_DAY_6: case PERL_DAY_7:
+        case PERL_MON_1: case PERL_MON_2: case PERL_MON_3: case PERL_MON_4:
+        case PERL_MON_5: case PERL_MON_6: case PERL_MON_7: case PERL_MON_8:
+        case PERL_MON_9: case PERL_MON_10: case PERL_MON_11: case PERL_MON_12:
+
+            LOCALE_LOCK;
+
+            init_tm(&tm);   /* Precaution against core dumps */
+            tm.tm_sec = 30;
+            tm.tm_min = 30;
+            tm.tm_hour = 6;
+            tm.tm_year = 2017 - 1900;
+            tm.tm_wday = 0;
+            tm.tm_mon = 0;
+            switch (item) {
+                default:
+                    LOCALE_UNLOCK;
+                    Perl_croak(aTHX_ "panic: %s: %d: switch case: %d problem",
+                                             __FILE__, __LINE__, item);
+                    NOT_REACHED; /* NOTREACHED */
+
+                case PERL_PM_STR: tm.tm_hour = 18;
+                case PERL_AM_STR:
+                    format = "%p";
+                    break;
+
+                case PERL_ABDAY_7: tm.tm_wday++;
+                case PERL_ABDAY_6: tm.tm_wday++;
+                case PERL_ABDAY_5: tm.tm_wday++;
+                case PERL_ABDAY_4: tm.tm_wday++;
+                case PERL_ABDAY_3: tm.tm_wday++;
+                case PERL_ABDAY_2: tm.tm_wday++;
+                case PERL_ABDAY_1:
+                    format = "%a";
+                    break;
+
+                case PERL_DAY_7: tm.tm_wday++;
+                case PERL_DAY_6: tm.tm_wday++;
+                case PERL_DAY_5: tm.tm_wday++;
+                case PERL_DAY_4: tm.tm_wday++;
+                case PERL_DAY_3: tm.tm_wday++;
+                case PERL_DAY_2: tm.tm_wday++;
+                case PERL_DAY_1:
+                    format = "%A";
+                    break;
+
+                case PERL_ABMON_12: tm.tm_mon++;
+                case PERL_ABMON_11: tm.tm_mon++;
+                case PERL_ABMON_10: tm.tm_mon++;
+                case PERL_ABMON_9: tm.tm_mon++;
+                case PERL_ABMON_8: tm.tm_mon++;
+                case PERL_ABMON_7: tm.tm_mon++;
+                case PERL_ABMON_6: tm.tm_mon++;
+                case PERL_ABMON_5: tm.tm_mon++;
+                case PERL_ABMON_4: tm.tm_mon++;
+                case PERL_ABMON_3: tm.tm_mon++;
+                case PERL_ABMON_2: tm.tm_mon++;
+                case PERL_ABMON_1:
+                    format = "%b";
+                    break;
+
+                case PERL_MON_12: tm.tm_mon++;
+                case PERL_MON_11: tm.tm_mon++;
+                case PERL_MON_10: tm.tm_mon++;
+                case PERL_MON_9: tm.tm_mon++;
+                case PERL_MON_8: tm.tm_mon++;
+                case PERL_MON_7: tm.tm_mon++;
+                case PERL_MON_6: tm.tm_mon++;
+                case PERL_MON_5: tm.tm_mon++;
+                case PERL_MON_4: tm.tm_mon++;
+                case PERL_MON_3: tm.tm_mon++;
+                case PERL_MON_2: tm.tm_mon++;
+                case PERL_MON_1:
+                    format = "%B";
+                    break;
+
+                case PERL_T_FMT_AMPM:
+                    format = "%r";
+                    return_format = TRUE;
+                    break;
+
+                case PERL_ERA_D_FMT:
+                    format = "%Ex";
+                    return_format = TRUE;
+                    break;
+
+                case PERL_ERA_T_FMT:
+                    format = "%EX";
+                    return_format = TRUE;
+                    break;
+
+                case PERL_ERA_D_T_FMT:
+                    format = "%Ec";
+                    return_format = TRUE;
+                    break;
+
+                case PERL_ALT_DIGITS:
+                    tm.tm_wday = 0;
+                    format = "%Ow";    /* Find the alternate digit for 0 */
+                    break;
+            }
+
+            /* We can't use my_strftime() because it doesn't look at tm_wday  */
+            while (0 == strftime(PL_langinfo_buf, PL_langinfo_bufsize,
+                                 format, &tm))
+            {
+                /* A zero return means one of:
+                 *  a)  there wasn't enough space in PL_langinfo_buf
+                 *  b)  the format, like a plain %p, returns empty
+                 *  c)  it was an illegal format, though some implementations of
+                 *      strftime will just return the illegal format as a plain
+                 *      character sequence.
+                 *
+                 *  To quickly test for case 'b)', try again but precede the
+                 *  format with a plain character.  If that result is still
+                 *  empty, the problem is either 'a)' or 'c)' */
+
+                Size_t format_size = strlen(format) + 1;
+                Size_t mod_size = format_size + 1;
+                char * mod_format;
+                char * temp_result;
+
+                Newx(mod_format, mod_size, char);
+                Newx(temp_result, PL_langinfo_bufsize, char);
+                *mod_format = '\a';
+                my_strlcpy(mod_format + 1, format, mod_size);
+                len = strftime(temp_result,
+                               PL_langinfo_bufsize,
+                               mod_format, &tm);
+                Safefree(mod_format);
+                Safefree(temp_result);
+
+                /* If 'len' is non-zero, it means that we had a case like %p
+                 * which means the current locale doesn't use a.m. or p.m., and
+                 * that is valid */
+                if (len == 0) {
+
+                    /* Here, still didn't work.  If we get well beyond a
+                     * reasonable size, bail out to prevent an infinite loop. */
+
+                    if (PL_langinfo_bufsize > 100 * format_size) {
+                        *PL_langinfo_buf = '\0';
+                    }
+                    else { /* Double the buffer size to retry;  Add 1 in case
+                              original was 0, so we aren't stuck at 0. */
+                        PL_langinfo_bufsize *= 2;
+                        PL_langinfo_bufsize++;
+                        Renew(PL_langinfo_buf, PL_langinfo_bufsize, char);
+                        continue;
+                    }
+                }
+
+                break;
+            }
+
+            /* Here, we got a result.
+             *
+             * If the item is 'ALT_DIGITS', PL_langinfo_buf contains the
+             * alternate format for wday 0.  If the value is the same as the
+             * normal 0, there isn't an alternate, so clear the buffer. */
+            if (   item == PERL_ALT_DIGITS
+                && strEQ(PL_langinfo_buf, "0"))
+            {
+                *PL_langinfo_buf = '\0';
+            }
+
+            /* ALT_DIGITS is problematic.  Experiments on it showed that
+             * strftime() did not always work properly when going from alt-9 to
+             * alt-10.  Only a few locales have this item defined, and in all
+             * of them on Linux that khw was able to find, nl_langinfo() merely
+             * returned the alt-0 character, possibly doubled.  Most Unicode
+             * digits are in blocks of 10 consecutive code points, so that is
+             * sufficient information for those scripts, as we can infer alt-1,
+             * alt-2, ....  But for a Japanese locale, a CJK ideographic 0 is
+             * returned, and the CJK digits are not in code point order, so you
+             * can't really infer anything.  The localedef for this locale did
+             * specify the succeeding digits, so that strftime() works properly
+             * on them, without needing to infer anything.  But the
+             * nl_langinfo() return did not give sufficient information for the
+             * caller to understand what's going on.  So until there is
+             * evidence that it should work differently, this returns the alt-0
+             * string for ALT_DIGITS.
+             *
+             * wday was chosen because its range is all a single digit.  Things
+             * like tm_sec have two digits as the minimum: '00' */
+
+            LOCALE_UNLOCK;
+
+            /* If to return the format, not the value, overwrite the buffer
+             * with it.  But some strftime()s will keep the original format if
+             * illegal, so change those to "" */
+            if (return_format) {
+                if (strEQ(PL_langinfo_buf, format)) {
+                    *PL_langinfo_buf = '\0';
+                }
+                else {
+                    save_to_buffer(format, &PL_langinfo_buf,
+                                    &PL_langinfo_bufsize, 0);
+                }
+            }
+
+            break;
+
+#  endif
+
+    }
+
+    return PL_langinfo_buf;
+
+#endif
+
+}
+
+/*
+ * Initialize locale awareness.
+ */
+int
+Perl_init_i18nl10n(pTHX_ int printwarn)
+{
+    /* printwarn is
+     *
+     *    0 if not to output warning when setup locale is bad
+     *    1 if to output warning based on value of PERL_BADLANG
+     *    >1 if to output regardless of PERL_BADLANG
+     *
+     * returns
+     *    1 = set ok or not applicable,
+     *    0 = fallback to a locale of lower priority
+     *   -1 = fallback to all locales failed, not even to the C locale
+     *
+     * Under -DDEBUGGING, if the environment variable PERL_DEBUG_LOCALE_INIT is
+     * set, debugging information is output.
+     *
+     * This looks more complicated than it is, mainly due to the #ifdefs.
+     *
+     * We try to set LC_ALL to the value determined by the environment.  If
+     * there is no LC_ALL on this platform, we try the individual categories we
+     * know about.  If this works, we are done.
+     *
+     * But if it doesn't work, we have to do something else.  We search the
+     * environment variables ourselves instead of relying on the system to do
+     * it.  We look at, in order, LC_ALL, LANG, a system default locale (if we
+     * think there is one), and the ultimate fallback "C".  This is all done in
+     * the same loop as above to avoid duplicating code, but it makes things
+     * more complex.  The 'trial_locales' array is initialized with just one
+     * element; it causes the behavior described in the paragraph above this to
+     * happen.  If that fails, we add elements to 'trial_locales', and do extra
+     * loop iterations to cause the behavior described in this paragraph.
+     *
+     * On Ultrix, the locale MUST come from the environment, so there is
+     * preliminary code to set it.  I (khw) am not sure that it is necessary,
+     * and that this couldn't be folded into the loop, but barring any real
+     * platforms to test on, it's staying as-is
+     *
+     * A slight complication is that in embedded Perls, the locale may already
+     * be set-up, and we don't want to get it from the normal environment
+     * variables.  This is handled by having a special environment variable
+     * indicate we're in this situation.  We simply set setlocale's 2nd
+     * parameter to be a NULL instead of "".  That indicates to setlocale that
+     * it is not to change anything, but to return the current value,
+     * effectively initializing perl's db to what the locale already is.
+     *
+     * We play the same trick with NULL if a LC_ALL succeeds.  We call
+     * setlocale() on the individual categores with NULL to get their existing
+     * values for our db, instead of trying to change them.
+     * */
+
+    int ok = 1;
+
+#ifndef USE_LOCALE
+
+    PERL_UNUSED_ARG(printwarn);
+
+#else  /* USE_LOCALE */
+#  ifdef __GLIBC__
+
+    const char * const language   = savepv(PerlEnv_getenv("LANGUAGE"));
+
+#  endif
+
+    /* NULL uses the existing already set up locale */
+    const char * const setlocale_init = (PerlEnv_getenv("PERL_SKIP_LOCALE_INIT"))
+                                        ? NULL
+                                        : "";
+    const char* trial_locales[5];   /* 5 = 1 each for "", LC_ALL, LANG, "", C */
+    unsigned int trial_locales_count;
+    const char * const lc_all     = savepv(PerlEnv_getenv("LC_ALL"));
+    const char * const lang       = savepv(PerlEnv_getenv("LANG"));
+    bool setlocale_failure = FALSE;
+    unsigned int i;
+
+    /* A later getenv() could zap this, so only use here */
+    const char * const bad_lang_use_once = PerlEnv_getenv("PERL_BADLANG");
+
+    const bool locwarn = (printwarn > 1
+                          || (          printwarn
+                              && (    ! bad_lang_use_once
+                                  || (
+                                         /* disallow with "" or "0" */
+                                         *bad_lang_use_once
+                                       && strNE("0", bad_lang_use_once)))));
+    bool done = FALSE;
+    char * sl_result[NOMINAL_LC_ALL_INDEX + 1];   /* setlocale() return vals;
+                                                     not copied so must be
+                                                     looked at immediately */
+    char * curlocales[NOMINAL_LC_ALL_INDEX + 1];  /* current locale for given
+                                                     category; should have been
+                                                     copied so aren't volatile
+                                                   */
+    char * locale_param;
+
+#  ifdef WIN32
+
+    /* In some systems you can find out the system default locale
+     * and use that as the fallback locale. */
+#    define SYSTEM_DEFAULT_LOCALE
+#  endif
+#  ifdef SYSTEM_DEFAULT_LOCALE
+
+    const char *system_default_locale = NULL;
+
+#  endif
+
+#  ifndef DEBUGGING
+#    define DEBUG_LOCALE_INIT(a,b,c)
+#  else
+
+    DEBUG_INITIALIZATION_set(cBOOL(PerlEnv_getenv("PERL_DEBUG_LOCALE_INIT")));
+
+#    define DEBUG_LOCALE_INIT(category, locale, result)                     \
+       STMT_START {                                                        \
+               if (debug_initialization) {                                 \
                     PerlIO_printf(Perl_debug_log,                           \
                                   "%s:%d: %s\n",                            \
                                   __FILE__, __LINE__,                       \
-                                  _setlocale_debug_string(category,         \
+                                  setlocale_debug_string(category,          \
                                                           locale,           \
                                                           result));         \
                 }                                                           \
        } STMT_END
-#else
-#   define DEBUG_LOCALE_INIT(a,b,c)
-#endif
 
-#ifndef LOCALE_ENVIRON_REQUIRED
+/* Make sure the parallel arrays are properly set up */
+#    ifdef USE_LOCALE_NUMERIC
+    assert(categories[LC_NUMERIC_INDEX] == LC_NUMERIC);
+    assert(strEQ(category_names[LC_NUMERIC_INDEX], "LC_NUMERIC"));
+#    endif
+#    ifdef USE_LOCALE_CTYPE
+    assert(categories[LC_CTYPE_INDEX] == LC_CTYPE);
+    assert(strEQ(category_names[LC_CTYPE_INDEX], "LC_CTYPE"));
+#    endif
+#    ifdef USE_LOCALE_COLLATE
+    assert(categories[LC_COLLATE_INDEX] == LC_COLLATE);
+    assert(strEQ(category_names[LC_COLLATE_INDEX], "LC_COLLATE"));
+#    endif
+#    ifdef USE_LOCALE_TIME
+    assert(categories[LC_TIME_INDEX] == LC_TIME);
+    assert(strEQ(category_names[LC_TIME_INDEX], "LC_TIME"));
+#    endif
+#    ifdef USE_LOCALE_MESSAGES
+    assert(categories[LC_MESSAGES_INDEX] == LC_MESSAGES);
+    assert(strEQ(category_names[LC_MESSAGES_INDEX], "LC_MESSAGES"));
+#    endif
+#    ifdef USE_LOCALE_MONETARY
+    assert(categories[LC_MONETARY_INDEX] == LC_MONETARY);
+    assert(strEQ(category_names[LC_MONETARY_INDEX], "LC_MONETARY"));
+#    endif
+#    ifdef LC_ALL
+    assert(categories[LC_ALL_INDEX] == LC_ALL);
+    assert(strEQ(category_names[LC_ALL_INDEX], "LC_ALL"));
+    assert(NOMINAL_LC_ALL_INDEX == LC_ALL_INDEX);
+#    endif
+#  endif    /* DEBUGGING */
+#  ifndef LOCALE_ENVIRON_REQUIRED
+
     PERL_UNUSED_VAR(done);
     PERL_UNUSED_VAR(locale_param);
-#else
+
+#  else
 
     /*
      * Ultrix setlocale(..., "") fails if there are no environment
      * variables from which to get a locale name.
      */
 
-#   ifdef LC_ALL
+#    ifdef LC_ALL
+
     if (lang) {
-       sl_result = my_setlocale(LC_ALL, setlocale_init);
-        DEBUG_LOCALE_INIT(LC_ALL, setlocale_init, sl_result);
-       if (sl_result)
+       sl_result[LC_ALL_INDEX] = do_setlocale_c(LC_ALL, setlocale_init);
+        DEBUG_LOCALE_INIT(LC_ALL, setlocale_init, sl_result[LC_ALL_INDEX]);
+       if (sl_result[LC_ALL_INDEX])
            done = TRUE;
        else
            setlocale_failure = TRUE;
     }
     if (! setlocale_failure) {
-#       ifdef USE_LOCALE_CTYPE
-        locale_param = (! done && (lang || PerlEnv_getenv("LC_CTYPE")))
-                       ? setlocale_init
-                       : NULL;
-       curctype = my_setlocale(LC_CTYPE, locale_param);
-        DEBUG_LOCALE_INIT(LC_CTYPE, locale_param, sl_result);
-       if (! curctype)
-           setlocale_failure = TRUE;
-       else
-           curctype = savepv(curctype);
-#       endif /* USE_LOCALE_CTYPE */
-#       ifdef USE_LOCALE_COLLATE
-        locale_param = (! done && (lang || PerlEnv_getenv("LC_COLLATE")))
-                       ? setlocale_init
-                       : NULL;
-       curcoll = my_setlocale(LC_COLLATE, locale_param);
-        DEBUG_LOCALE_INIT(LC_COLLATE, locale_param, sl_result);
-       if (! curcoll)
-           setlocale_failure = TRUE;
-       else
-           curcoll = savepv(curcoll);
-#       endif /* USE_LOCALE_COLLATE */
-#       ifdef USE_LOCALE_NUMERIC
-        locale_param = (! done && (lang || PerlEnv_getenv("LC_NUMERIC")))
-                       ? setlocale_init
-                       : NULL;
-       curnum = my_setlocale(LC_NUMERIC, locale_param);
-        DEBUG_LOCALE_INIT(LC_NUMERIC, locale_param, sl_result);
-       if (! curnum)
-           setlocale_failure = TRUE;
-       else
-           curnum = savepv(curnum);
-#       endif /* USE_LOCALE_NUMERIC */
-#       ifdef USE_LOCALE_MESSAGES
-        locale_param = (! done && (lang || PerlEnv_getenv("LC_MESSAGES")))
-                       ? setlocale_init
-                       : NULL;
-       sl_result = my_setlocale(LC_MESSAGES, locale_param);
-        DEBUG_LOCALE_INIT(LC_MESSAGES, locale_param, sl_result);
-       if (! sl_result)
-           setlocale_failure = TRUE;
-        }
-#       endif /* USE_LOCALE_MESSAGES */
-#       ifdef USE_LOCALE_MONETARY
-        locale_param = (! done && (lang || PerlEnv_getenv("LC_MONETARY")))
-                       ? setlocale_init
-                       : NULL;
-       sl_result = my_setlocale(LC_MONETARY, locale_param);
-        DEBUG_LOCALE_INIT(LC_MONETARY, locale_param, sl_result);
-       if (! sl_result) {
-           setlocale_failure = TRUE;
+        for (i = 0; i < LC_ALL_INDEX; i++) {
+            locale_param = (! done && (lang || PerlEnv_getenv(category_names[i])))
+                           ? setlocale_init
+                           : NULL;
+            sl_result[i] = do_setlocale_r(categories[i], locale_param);
+            if (! sl_result[i]) {
+                setlocale_failure = TRUE;
+            }
+            DEBUG_LOCALE_INIT(categories[i], locale_param, sl_result[i]);
         }
-#       endif /* USE_LOCALE_MONETARY */
     }
 
-#   endif /* LC_ALL */
-
-#endif /* !LOCALE_ENVIRON_REQUIRED */
+#    endif /* LC_ALL */
+#  endif /* LOCALE_ENVIRON_REQUIRED */
 
     /* We try each locale in the list until we get one that works, or exhaust
      * the list.  Normally the loop is executed just once.  But if setting the
@@ -1057,6 +1871,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
      * will execute the loop multiple times */
     trial_locales[0] = setlocale_init;
     trial_locales_count = 1;
+
     for (i= 0; i < trial_locales_count; i++) {
         const char * trial_locale = trial_locales[i];
 
@@ -1067,8 +1882,9 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
              * sense */
             setlocale_failure = FALSE;
 
-#ifdef SYSTEM_DEFAULT_LOCALE
-#  ifdef WIN32
+#  ifdef SYSTEM_DEFAULT_LOCALE
+#    ifdef WIN32
+
             /* On Windows machines, an entry of "" after the 0th means to use
              * the system default locale, which we now proceed to get. */
             if (strEQ(trial_locale, "")) {
@@ -1076,10 +1892,10 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 
                 /* Note that this may change the locale, but we are going to do
                  * that anyway just below */
-                system_default_locale = setlocale(LC_ALL, "");
+                system_default_locale = do_setlocale_c(LC_ALL, "");
                 DEBUG_LOCALE_INIT(LC_ALL, "", system_default_locale);
 
-                /* Skip if invalid or it's already on the list of locales to
+                /* Skip if invalid or if it's already on the list of locales to
                  * try */
                 if (! system_default_locale) {
                     goto next_iteration;
@@ -1092,14 +1908,15 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 
                 trial_locale = system_default_locale;
             }
-#  endif /* WIN32 */
-#endif /* SYSTEM_DEFAULT_LOCALE */
+#    endif /* WIN32 */
+#  endif /* SYSTEM_DEFAULT_LOCALE */
         }
 
-#ifdef LC_ALL
-        sl_result = my_setlocale(LC_ALL, trial_locale);
-        DEBUG_LOCALE_INIT(LC_ALL, trial_locale, sl_result);
-        if (! sl_result) {
+#  ifdef LC_ALL
+
+        sl_result[LC_ALL_INDEX] = do_setlocale_c(LC_ALL, trial_locale);
+        DEBUG_LOCALE_INIT(LC_ALL, trial_locale, sl_result[LC_ALL_INDEX]);
+        if (! sl_result[LC_ALL_INDEX]) {
             setlocale_failure = TRUE;
         }
         else {
@@ -1108,55 +1925,26 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
              * setlocales below just return their category's current values.
              * This adequately handles the case in NetBSD where LC_COLLATE may
              * not be defined for a locale, and setting it individually will
-             * fail, whereas setting LC_ALL suceeds, leaving LC_COLLATE set to
+             * fail, whereas setting LC_ALL succeeds, leaving LC_COLLATE set to
              * the POSIX locale. */
             trial_locale = NULL;
         }
-#endif /* LC_ALL */
 
-        if (!setlocale_failure) {
-#ifdef USE_LOCALE_CTYPE
-            Safefree(curctype);
-            curctype = my_setlocale(LC_CTYPE, trial_locale);
-            DEBUG_LOCALE_INIT(LC_CTYPE, trial_locale, curctype);
-            if (! curctype)
-                setlocale_failure = TRUE;
-            else
-                curctype = savepv(curctype);
-#endif /* USE_LOCALE_CTYPE */
-#ifdef USE_LOCALE_COLLATE
-            Safefree(curcoll);
-            curcoll = my_setlocale(LC_COLLATE, trial_locale);
-            DEBUG_LOCALE_INIT(LC_COLLATE, trial_locale, curcoll);
-            if (! curcoll)
-                setlocale_failure = TRUE;
-            else
-                curcoll = savepv(curcoll);
-#endif /* USE_LOCALE_COLLATE */
-#ifdef USE_LOCALE_NUMERIC
-            Safefree(curnum);
-            curnum = my_setlocale(LC_NUMERIC, trial_locale);
-            DEBUG_LOCALE_INIT(LC_NUMERIC, trial_locale, curnum);
-            if (! curnum)
-                setlocale_failure = TRUE;
-            else
-                curnum = savepv(curnum);
-#endif /* USE_LOCALE_NUMERIC */
-#ifdef USE_LOCALE_MESSAGES
-            sl_result = my_setlocale(LC_MESSAGES, trial_locale);
-            DEBUG_LOCALE_INIT(LC_MESSAGES, trial_locale, sl_result);
-            if (! (sl_result))
-                setlocale_failure = TRUE;
-#endif /* USE_LOCALE_MESSAGES */
-#ifdef USE_LOCALE_MONETARY
-            sl_result = my_setlocale(LC_MONETARY, trial_locale);
-            DEBUG_LOCALE_INIT(LC_MONETARY, trial_locale, sl_result);
-            if (! (sl_result))
-                setlocale_failure = TRUE;
-#endif /* USE_LOCALE_MONETARY */
+#  endif /* LC_ALL */
 
-            if (! setlocale_failure) {  /* Success */
-                break;
+        if (! setlocale_failure) {
+            unsigned int j;
+            for (j = 0; j < NOMINAL_LC_ALL_INDEX; j++) {
+                curlocales[j]
+                        = savepv(do_setlocale_r(categories[j], trial_locale));
+                if (! curlocales[j]) {
+                    setlocale_failure = TRUE;
+                }
+                DEBUG_LOCALE_INIT(categories[j], trial_locale, curlocales[j]);
+            }
+
+            if (! setlocale_failure) {  /* All succeeded */
+                break;  /* Exit trial_locales loop */
             }
         }
 
@@ -1167,41 +1955,41 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
             unsigned int j;
 
             if (locwarn) { /* Output failure info only on the first one */
-#ifdef LC_ALL
+
+#  ifdef LC_ALL
 
                 PerlIO_printf(Perl_error_log,
                 "perl: warning: Setting locale failed.\n");
 
-#else /* !LC_ALL */
+#  else /* !LC_ALL */
 
                 PerlIO_printf(Perl_error_log,
                 "perl: warning: Setting locale failed for the categories:\n\t");
-#  ifdef USE_LOCALE_CTYPE
-                if (! curctype)
-                    PerlIO_printf(Perl_error_log, "LC_CTYPE ");
-#  endif /* USE_LOCALE_CTYPE */
-#  ifdef USE_LOCALE_COLLATE
-                if (! curcoll)
-                    PerlIO_printf(Perl_error_log, "LC_COLLATE ");
-#  endif /* USE_LOCALE_COLLATE */
-#  ifdef USE_LOCALE_NUMERIC
-                if (! curnum)
-                    PerlIO_printf(Perl_error_log, "LC_NUMERIC ");
-#  endif /* USE_LOCALE_NUMERIC */
+
+                for (j = 0; j < NOMINAL_LC_ALL_INDEX; j++) {
+                    if (! curlocales[j]) {
+                        PerlIO_printf(Perl_error_log, category_names[j]);
+                    }
+                    else {
+                        Safefree(curlocales[j]);
+                    }
+                }
+
                 PerlIO_printf(Perl_error_log, "and possibly others\n");
 
-#endif /* LC_ALL */
+#  endif /* LC_ALL */
 
                 PerlIO_printf(Perl_error_log,
                     "perl: warning: Please check that your locale settings:\n");
 
-#ifdef __GLIBC__
+#  ifdef __GLIBC__
+
                 PerlIO_printf(Perl_error_log,
                             "\tLANGUAGE = %c%s%c,\n",
                             language ? '"' : '(',
                             language ? language : "unset",
                             language ? '"' : ')');
-#endif
+#  endif
 
                 PerlIO_printf(Perl_error_log,
                             "\tLC_ALL = %c%s%c,\n",
@@ -1209,21 +1997,38 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
                             lc_all ? lc_all : "unset",
                             lc_all ? '"' : ')');
 
-#if defined(USE_ENVIRON_ARRAY)
+#  if defined(USE_ENVIRON_ARRAY)
+
                 {
-                char **e;
-                for (e = environ; *e; e++) {
-                    if (strnEQ(*e, "LC_", 3)
-                            && strnNE(*e, "LC_ALL=", 7)
-                            && (p = strchr(*e, '=')))
-                        PerlIO_printf(Perl_error_log, "\t%.*s = \"%s\",\n",
-                                        (int)(p - *e), *e, p + 1);
-                }
+                    char **e;
+
+                    /* Look through the environment for any variables of the
+                     * form qr/ ^ LC_ [A-Z]+ = /x, except LC_ALL which was
+                     * already handled above.  These are assumed to be locale
+                     * settings.  Output them and their values. */
+                    for (e = environ; *e; e++) {
+                        const STRLEN prefix_len = sizeof("LC_") - 1;
+                        STRLEN uppers_len;
+
+                        if (     strBEGINs(*e, "LC_")
+                            && ! strBEGINs(*e, "LC_ALL=")
+                            && (uppers_len = strspn(*e + prefix_len,
+                                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
+                            && ((*e)[prefix_len + uppers_len] == '='))
+                        {
+                            PerlIO_printf(Perl_error_log, "\t%.*s = \"%s\",\n",
+                                (int) (prefix_len + uppers_len), *e,
+                                *e + prefix_len + uppers_len + 1);
+                        }
+                    }
                 }
-#else
+
+#  else
+
                 PerlIO_printf(Perl_error_log,
                             "\t(possibly more locale environment variables)\n");
-#endif
+
+#  endif
 
                 PerlIO_printf(Perl_error_log,
                             "\tLANG = %c%s%c\n",
@@ -1270,7 +2075,8 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
             }
           done_lang:
 
-#if defined(WIN32) && defined(LC_ALL)
+#  if defined(WIN32) && defined(LC_ALL)
+
             /* For Windows, we also try the system default locale before "C".
              * (If there exists a Windows without LC_ALL we skip this because
              * it gets too complicated.  For those, the "C" is the next
@@ -1278,7 +2084,8 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
              * the array, but the code at the loop above knows to treat it
              * differently when not the 0th */
             trial_locales[trial_locales_count++] = "";
-#endif
+
+#  endif
 
             for (j = 0; j < trial_locales_count; j++) {
                 if (strEQ("C", trial_locales[j])) {
@@ -1290,9 +2097,11 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
           done_C: ;
         }   /* end of first time through the loop */
 
-#ifdef WIN32
+#  ifdef WIN32
+
       next_iteration: ;
-#endif
+
+#  endif
 
     }   /* end of looping through the trial locales */
 
@@ -1302,6 +2111,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
            msg = "Falling back to";
         }
         else {  /* fallback failed */
+            unsigned int j;
 
             /* We dropped off the end of the loop, so have to decrement i to
              * get back to the value the last time through */
@@ -1311,21 +2121,12 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
             msg = "Failed to fall back to";
 
             /* To continue, we should use whatever values we've got */
-#ifdef USE_LOCALE_CTYPE
-            Safefree(curctype);
-            curctype = savepv(setlocale(LC_CTYPE, NULL));
-            DEBUG_LOCALE_INIT(LC_CTYPE, NULL, curctype);
-#endif /* USE_LOCALE_CTYPE */
-#ifdef USE_LOCALE_COLLATE
-            Safefree(curcoll);
-            curcoll = savepv(setlocale(LC_COLLATE, NULL));
-            DEBUG_LOCALE_INIT(LC_COLLATE, NULL, curcoll);
-#endif /* USE_LOCALE_COLLATE */
-#ifdef USE_LOCALE_NUMERIC
-            Safefree(curnum);
-            curnum = savepv(setlocale(LC_NUMERIC, NULL));
-            DEBUG_LOCALE_INIT(LC_NUMERIC, NULL, curnum);
-#endif /* USE_LOCALE_NUMERIC */
+
+            for (j = 0; j < NOMINAL_LC_ALL_INDEX; j++) {
+                Safefree(curlocales[j]);
+                curlocales[j] = savepv(do_setlocale_r(categories[j], NULL));
+                DEBUG_LOCALE_INIT(categories[j], NULL, curlocales[j]);
+            }
         }
 
         if (locwarn) {
@@ -1335,14 +2136,18 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
                 description = "the standard locale";
                 name = "C";
             }
-#ifdef SYSTEM_DEFAULT_LOCALE
+
+#  ifdef SYSTEM_DEFAULT_LOCALE
+
             else if (strEQ(trial_locales[i], "")) {
                 description = "the system default locale";
                 if (system_default_locale) {
                     name = system_default_locale;
                 }
             }
-#endif /* SYSTEM_DEFAULT_LOCALE */
+
+#  endif /* SYSTEM_DEFAULT_LOCALE */
+
             else {
                 description = "a fallback locale";
                 name = trial_locales[i];
@@ -1358,19 +2163,31 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
         }
     } /* End of tried to fallback */
 
-#ifdef USE_LOCALE_CTYPE
-    new_ctype(curctype);
-#endif /* USE_LOCALE_CTYPE */
+    /* Done with finding the locales; update our records */
+
+#  ifdef USE_LOCALE_CTYPE
+
+    new_ctype(curlocales[LC_CTYPE_INDEX]);
+
+#  endif
+#  ifdef USE_LOCALE_COLLATE
+
+    new_collate(curlocales[LC_COLLATE_INDEX]);
+
+#  endif
+#  ifdef USE_LOCALE_NUMERIC
 
-#ifdef USE_LOCALE_COLLATE
-    new_collate(curcoll);
-#endif /* USE_LOCALE_COLLATE */
+    new_numeric(curlocales[LC_NUMERIC_INDEX]);
 
-#ifdef USE_LOCALE_NUMERIC
-    new_numeric(curnum);
-#endif /* USE_LOCALE_NUMERIC */
+#  endif
+
+
+    for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++) {
+        Safefree(curlocales[i]);
+    }
+
+#  if defined(USE_PERLIO) && defined(USE_LOCALE_CTYPE)
 
-#if defined(USE_PERLIO) && defined(USE_LOCALE_CTYPE)
     /* Set PL_utf8locale to TRUE if using PerlIO _and_ the current LC_CTYPE
      * locale is UTF-8.  If PL_utf8locale and PL_unicode (set by -C or by
      * $ENV{PERL_UNICODE}) are true, perl.c:S_parse_body() will turn on the
@@ -1387,32 +2204,23 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
         if (PL_unicode & PERL_UNICODE_UTF8CACHEASSERT_FLAG)
             PL_utf8cache = -1;
     }
-#endif
 
-#ifdef USE_LOCALE_CTYPE
-    Safefree(curctype);
-#endif /* USE_LOCALE_CTYPE */
-#ifdef USE_LOCALE_COLLATE
-    Safefree(curcoll);
-#endif /* USE_LOCALE_COLLATE */
-#ifdef USE_LOCALE_NUMERIC
-    Safefree(curnum);
-#endif /* USE_LOCALE_NUMERIC */
+#  endif
+#  ifdef __GLIBC__
 
-#ifdef __GLIBC__
     Safefree(language);
-#endif
+
+#  endif
 
     Safefree(lc_all);
     Safefree(lang);
 
-#else  /* !USE_LOCALE */
-    PERL_UNUSED_ARG(printwarn);
 #endif /* USE_LOCALE */
-
 #ifdef DEBUGGING
+
     /* So won't continue to output stuff */
     DEBUG_INITIALIZATION_set(FALSE);
+
 #endif
 
     return ok;
@@ -1453,6 +2261,8 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
 
     /* If this locale has defective collation, skip */
     if (PL_collxfrm_base == 0 && PL_collxfrm_mult == 0) {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                      "_mem_collxfrm: locale's collation is defective\n"));
         goto bad;
     }
 
@@ -1460,134 +2270,138 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
      * This will give as good as possible results on strings that don't
      * otherwise contain that character, but otherwise there may be
      * less-than-perfect results with that character and NUL.  This is
-     * unavoidable unless we replace strxfrm with our own implementation.
-     *
-     * XXX This code may be overkill.  khw wrote it before realizing that if
-     * you change a NUL into some other character, that that may change the
-     * strxfrm results if that character is part of a sequence with other
-     * characters for weight calculations.  To minimize the chances of this,
-     * now the replacement is restricted to another control (likely to be
-     * \001).  But the full generality has been retained.
-     *
-     * This is one of the few places in the perl core, where we can use
-     * standard functions like strlen() and strcat().  It's because we're
-     * looking for NULs. */
-    if (s_strlen < len) {
+     * unavoidable unless we replace strxfrm with our own implementation. */
+    if (UNLIKELY(s_strlen < len)) {   /* Only execute if there is an embedded
+                                         NUL */
         char * e = s + len;
         char * sans_nuls;
-        STRLEN cur_min_char_len;
-
-        /* If we don't know what control character sorts lowest for this
-         * locale, find it */
-        if (*PL_strxfrm_min_char == '\0') {
+        STRLEN sans_nuls_len;
+        int try_non_controls;
+        char this_replacement_char[] = "?\0";   /* Room for a two-byte string,
+                                                   making sure 2nd byte is NUL.
+                                                 */
+        STRLEN this_replacement_len;
+
+        /* If we don't know what non-NUL control character sorts lowest for
+         * this locale, find it */
+        if (PL_strxfrm_NUL_replacement == '\0') {
             int j;
-#ifdef DEBUGGING
-            U8     cur_min_cp = 1;  /* The code point that sorts lowest, so far */
-#endif
-            char * cur_min_x = NULL;    /* And its xfrm, (except it also
+            char * cur_min_x = NULL;    /* The min_char's xfrm, (except it also
                                            includes the collation index
                                            prefixed. */
 
             DEBUG_Lv(PerlIO_printf(Perl_debug_log, "Looking to replace NUL\n"));
-            /* Look through all legal code points (NUL isn't) */
-            for (j = 1; j < 256; j++) {
-                char * x;       /* j's xfrm plus collation index */
-                STRLEN x_len;   /* length of 'x' */
-                STRLEN trial_len = 1;
-
-                /* Create a 1 byte string of the current code point, but with
-                 * room to be 2 bytes */
-                char cur_source[] = { (char) j, '\0' , '\0' };
-
-                if (PL_in_utf8_COLLATE_locale) {
-                    if (! isCNTRL_L1(j)) {
+
+            /* Unlikely, but it may be that no control will work to replace
+             * NUL, in which case we instead look for any character.  Controls
+             * are preferred because collation order is, in general, context
+             * sensitive, with adjoining characters affecting the order, and
+             * controls are less likely to have such interactions, allowing the
+             * NUL-replacement to stand on its own.  (Another way to look at it
+             * is to imagine what would happen if the NUL were replaced by a
+             * combining character; it wouldn't work out all that well.) */
+            for (try_non_controls = 0;
+                 try_non_controls < 2;
+                 try_non_controls++)
+            {
+                /* Look through all legal code points (NUL isn't) */
+                for (j = 1; j < 256; j++) {
+                    char * x;       /* j's xfrm plus collation index */
+                    STRLEN x_len;   /* length of 'x' */
+                    STRLEN trial_len = 1;
+                    char cur_source[] = { '\0', '\0' };
+
+                    /* Skip non-controls the first time through the loop.  The
+                     * controls in a UTF-8 locale are the L1 ones */
+                    if (! try_non_controls && (PL_in_utf8_COLLATE_locale)
+                                               ? ! isCNTRL_L1(j)
+                                               : ! isCNTRL_LC(j))
+                    {
                         continue;
                     }
 
-                    /* If needs to be 2 bytes, find them */
-                    if (! UVCHR_IS_INVARIANT(j)) {
-                        char * d = cur_source;
-                        append_utf8_from_native_byte((U8) j, (U8 **) &d);
-                        trial_len = 2;
+                    /* Create a 1-char string of the current code point */
+                    cur_source[0] = (char) j;
+
+                    /* Then transform it */
+                    x = _mem_collxfrm(cur_source, trial_len, &x_len,
+                                      0 /* The string is not in UTF-8 */);
+
+                    /* Ignore any character that didn't successfully transform.
+                     * */
+                    if (! x) {
+                        continue;
                     }
-                }
-                else if (! isCNTRL_LC(j)) {
-                    continue;
-                }
 
-                /* Then transform it */
-                x = _mem_collxfrm(cur_source, trial_len, &x_len,
-                                  PL_in_utf8_COLLATE_locale);
+                    /* If this character's transformation is lower than
+                     * the current lowest, this one becomes the lowest */
+                    if (   cur_min_x == NULL
+                        || strLT(x         + COLLXFRM_HDR_LEN,
+                                 cur_min_x + COLLXFRM_HDR_LEN))
+                    {
+                        PL_strxfrm_NUL_replacement = j;
+                        cur_min_x = x;
+                    }
+                    else {
+                        Safefree(x);
+                    }
+                } /* end of loop through all 255 characters */
 
-                /* If something went wrong (which it shouldn't), just
-                 * ignore this code point */
-                if (   x_len == 0
-                    || strlen(x + COLLXFRM_HDR_LEN) < x_len)
-                {
-                    continue;
+                /* Stop looking if found */
+                if (cur_min_x) {
+                    break;
                 }
 
-                /* If this character's transformation is lower than
-                 * the current lowest, this one becomes the lowest */
-                if (   cur_min_x == NULL
-                    || strLT(x         + COLLXFRM_HDR_LEN,
-                             cur_min_x + COLLXFRM_HDR_LEN))
-                {
-                    PL_strxfrm_min_char[0] = cur_source[0];
-                    PL_strxfrm_min_char[1] = cur_source[1];
-                    PL_strxfrm_min_char[2] = cur_source[2];
-                    cur_min_x = x;
-#ifdef DEBUGGING
-                    cur_min_cp = j;
-#endif
-                }
-                else {
-                    Safefree(x);
-                }
-            } /* end of loop through all bytes */
+                /* Unlikely, but possible, if there aren't any controls that
+                 * work in the locale, repeat the loop, looking for any
+                 * character that works */
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                "_mem_collxfrm: No control worked.  Trying non-controls\n"));
+            } /* End of loop to try first the controls, then any char */
 
-            /* Unlikely, but possible, if there aren't any controls in the
-             * locale, arbitrarily use \001 */
-            if (cur_min_x == NULL) {
-                STRLEN x_len;   /* temporary */
-                cur_min_x = _mem_collxfrm("\001", 1, &x_len,
-                                          PL_in_utf8_COLLATE_locale);
-                /* cur_min_cp was already initialized to 1 */
+            if (! cur_min_x) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                    "_mem_collxfrm: Couldn't find any character to replace"
+                    " embedded NULs in locale %s with", PL_collation_name));
+                goto bad;
             }
 
             DEBUG_L(PerlIO_printf(Perl_debug_log,
-                    "_mem_collxfrm: lowest collating non-NUL control in the "
-                    "0-255 range in locale %s is 0x%02X\n",
-                    PL_collation_name,
-                    cur_min_cp));
-            if (DEBUG_Lv_TEST) {
-                unsigned i;
-                PerlIO_printf(Perl_debug_log, "Its xfrm is");
-                for (i = 0; i < strlen(cur_min_x + COLLXFRM_HDR_LEN); i ++) {
-                    PerlIO_printf(Perl_debug_log, " %02x",
-                                (U8) *(cur_min_x + COLLXFRM_HDR_LEN + i));
-                }
-                PerlIO_printf(Perl_debug_log, "\n");
-            }
+                    "_mem_collxfrm: Replacing embedded NULs in locale %s with "
+                    "0x%02X\n", PL_collation_name, PL_strxfrm_NUL_replacement));
 
             Safefree(cur_min_x);
+        } /* End of determining the character that is to replace NULs */
+
+        /* If the replacement is variant under UTF-8, it must match the
+         * UTF8-ness as the original */
+        if ( ! UVCHR_IS_INVARIANT(PL_strxfrm_NUL_replacement) && utf8) {
+            this_replacement_char[0] =
+                                UTF8_EIGHT_BIT_HI(PL_strxfrm_NUL_replacement);
+            this_replacement_char[1] =
+                                UTF8_EIGHT_BIT_LO(PL_strxfrm_NUL_replacement);
+            this_replacement_len = 2;
+        }
+        else {
+            this_replacement_char[0] = PL_strxfrm_NUL_replacement;
+            /* this_replacement_char[1] = '\0' was done at initialization */
+            this_replacement_len = 1;
         }
 
         /* The worst case length for the replaced string would be if every
          * character in it is NUL.  Multiply that by the length of each
          * replacement, and allow for a trailing NUL */
-        cur_min_char_len = strlen(PL_strxfrm_min_char);
-        Newx(sans_nuls, (len * cur_min_char_len) + 1, char);
+        sans_nuls_len = (len * this_replacement_len) + 1;
+        Newx(sans_nuls, sans_nuls_len, char);
         *sans_nuls = '\0';
 
-
         /* Replace each NUL with the lowest collating control.  Loop until have
          * exhausted all the NULs */
         while (s + s_strlen < e) {
-            strcat(sans_nuls, s);
+            my_strlcat(sans_nuls, s, sans_nuls_len);
 
             /* Do the actual replacement */
-            strcat(sans_nuls, PL_strxfrm_min_char);
+            my_strlcat(sans_nuls, this_replacement_char, sans_nuls_len);
 
             /* Move past the input NUL */
             s += s_strlen + 1;
@@ -1595,12 +2409,12 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
         }
 
         /* And add anything that trails the final NUL */
-        strcat(sans_nuls, s);
+        my_strlcat(sans_nuls, s, sans_nuls_len);
 
         /* Switch so below we transform this modified string */
         s = sans_nuls;
         len = strlen(s);
-    }
+    } /* End of replacing NULs */
 
     /* Make sure the UTF8ness of the string and locale match */
     if (utf8 != PL_in_utf8_COLLATE_locale) {
@@ -1659,17 +2473,17 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                     for (j = 1; j < 256; j++) {
                         char * x;
                         STRLEN x_len;
+                        char cur_source[] = { '\0', '\0' };
 
-                        /* Create a 1-char string of the current code point. */
-                        char cur_source[] = { (char) j, '\0' };
+                        /* Create a 1-char string of the current code point */
+                        cur_source[0] = (char) j;
 
                         /* Then transform it */
                         x = _mem_collxfrm(cur_source, 1, &x_len, FALSE);
 
                         /* If something went wrong (which it shouldn't), just
                          * ignore this code point */
-                        if (x_len == 0) {
-                            Safefree(x);
+                        if (! x) {
                             continue;
                         }
 
@@ -1687,23 +2501,19 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                         }
                     }
 
+                    if (! cur_max_x) {
+                        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                            "_mem_collxfrm: Couldn't find any character to"
+                            " replace above-Latin1 chars in locale %s with",
+                            PL_collation_name));
+                        goto bad;
+                    }
+
                     DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "_mem_collxfrm: highest 1-byte collating character"
                             " in locale %s is 0x%02X\n",
                             PL_collation_name,
                             PL_strxfrm_max_cp));
-                    if (DEBUG_Lv_TEST) {
-                        unsigned i;
-                        PerlIO_printf(Perl_debug_log, "Its xfrm is ");
-                        for (i = 0;
-                             i < strlen(cur_max_x + COLLXFRM_HDR_LEN);
-                             i++)
-                        {
-                            PerlIO_printf(Perl_debug_log, " %02x",
-                                        (U8) cur_max_x[i + COLLXFRM_HDR_LEN]);
-                        }
-                        PerlIO_printf(Perl_debug_log, "\n");
-                    }
 
                     Safefree(cur_max_x);
                 }
@@ -1718,13 +2528,14 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                 {
                     STRLEN i;
                     STRLEN d= 0;
+                    char * e = (char *) t + len;
 
                     for (i = 0; i < len; i+= UTF8SKIP(t + i)) {
                         U8 cur_char = t[i];
                         if (UTF8_IS_INVARIANT(cur_char)) {
                             s[d++] = cur_char;
                         }
-                        else if (UTF8_IS_DOWNGRADEABLE_START(cur_char)) {
+                        else if (UTF8_IS_NEXT_CHAR_DOWNGRADEABLE(t + i, e)) {
                             s[d++] = EIGHT_BIT_UTF8_TO_NATIVE(cur_char, t[i+1]);
                         }
                         else {  /* Replace illegal cp with highest collating
@@ -1757,8 +2568,11 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
            + PL_collxfrm_base
            + (PL_collxfrm_mult * length_in_chars);
     Newx(xbuf, xAlloc, char);
-    if (UNLIKELY(! xbuf))
+    if (UNLIKELY(! xbuf)) {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                      "_mem_collxfrm: Couldn't malloc %zu bytes\n", xAlloc));
        goto bad;
+    }
 
     /* Store the collation id */
     *(U32*)xbuf = PL_collation_ix;
@@ -1766,6 +2580,7 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
     /* Then the transformation of the input.  We loop until successful, or we
      * give up */
     for (;;) {
+
         *xlen = strxfrm(xbuf + COLLXFRM_HDR_LEN, s, xAlloc - COLLXFRM_HDR_LEN);
 
         /* If the transformed string occupies less space than we told strxfrm()
@@ -1773,6 +2588,15 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
          * string. */
         if (*xlen < xAlloc - COLLXFRM_HDR_LEN) {
 
+            /* Some systems include a trailing NUL in the returned length.
+             * Ignore it, using a loop in case multiple trailing NULs are
+             * returned. */
+            while (   (*xlen) > 0
+                   && *(xbuf + COLLXFRM_HDR_LEN + (*xlen) - 1) == '\0')
+            {
+                (*xlen)--;
+            }
+
             /* If the first try didn't get it, it means our prediction was low.
              * Modify the coefficients so that we predict a larger value in any
              * future transformations */
@@ -1788,19 +2612,23 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                                      : PL_collxfrm_mult;
 
                 DEBUG_Lv(PerlIO_printf(Perl_debug_log,
-                    "%s: %d: initial size of %"UVuf" bytes for a length "
-                    "%"UVuf" string was insufficient, %"UVuf" needed\n",
+                    "%s: %d: initial size of %zu bytes for a length "
+                    "%zu string was insufficient, %zu needed\n",
                     __FILE__, __LINE__,
-                    (UV) computed_guess, (UV) length_in_chars, (UV) needed));
+                    computed_guess, length_in_chars, needed));
 
                 /* If slope increased, use it, but discard this result for
                  * length 1 strings, as we can't be sure that it's a real slope
                  * change */
                 if (length_in_chars > 1 && new_m  > PL_collxfrm_mult) {
-#ifdef DEBUGGING
+
+#  ifdef DEBUGGING
+
                     STRLEN old_m = PL_collxfrm_mult;
                     STRLEN old_b = PL_collxfrm_base;
-#endif
+
+#  endif
+
                     PL_collxfrm_mult = new_m;
                     PL_collxfrm_base = 1;   /* +1 For trailing NUL */
                     computed_guess = PL_collxfrm_base
@@ -1810,20 +2638,20 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                     }
 
                     DEBUG_Lv(PerlIO_printf(Perl_debug_log,
-                        "%s: %d: slope is now %"UVuf"; was %"UVuf", base "
-                        "is now %"UVuf"; was %"UVuf"\n",
+                        "%s: %d: slope is now %zu; was %zu, base "
+                        "is now %zu; was %zu\n",
                         __FILE__, __LINE__,
-                        (UV) PL_collxfrm_mult, (UV) old_m,
-                        (UV) PL_collxfrm_base, (UV) old_b));
+                        PL_collxfrm_mult, old_m,
+                        PL_collxfrm_base, old_b));
                 }
                 else {  /* Slope didn't change, but 'b' did */
                     const STRLEN new_b = needed
                                         - computed_guess
                                         + PL_collxfrm_base;
                     DEBUG_Lv(PerlIO_printf(Perl_debug_log,
-                        "%s: %d: base is now %"UVuf"; was %"UVuf"\n",
+                        "%s: %d: base is now %zu; was %zu\n",
                         __FILE__, __LINE__,
-                        (UV) new_b, (UV) PL_collxfrm_base));
+                        new_b, PL_collxfrm_base));
                     PL_collxfrm_base = new_b;
                 }
             }
@@ -1831,13 +2659,17 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
             break;
         }
 
-        if (UNLIKELY(*xlen >= PERL_INT_MAX))
+        if (UNLIKELY(*xlen >= PERL_INT_MAX)) {
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                  "_mem_collxfrm: Needed %zu bytes, max permissible is %u\n",
+                  *xlen, PERL_INT_MAX));
             goto bad;
+        }
 
         /* A well-behaved strxfrm() returns exactly how much space it needs
-         * (not including the trailing NUL) when it fails due to not enough
-         * space being provided.  Assume that this is the case unless it's been
-         * proven otherwise */
+         * (usually not including the trailing NUL) when it fails due to not
+         * enough space being provided.  Assume that this is the case unless
+         * it's been proven otherwise */
         if (LIKELY(PL_strxfrm_is_behaved) && first_time) {
             xAlloc = *xlen + COLLXFRM_HDR_LEN + 1;
         }
@@ -1850,66 +2682,48 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
                 *      (Many versions of cygwin fit this.  When the buffer size
                 *      isn't sufficient, they return the input size instead of
                 *      how much is needed.)
-                * Increase the buffer size by a fixed percentage and try again. */
+                * Increase the buffer size by a fixed percentage and try again.
+                * */
             xAlloc += (xAlloc / 4) + 1;
             PL_strxfrm_is_behaved = FALSE;
 
-#ifdef DEBUGGING
+#  ifdef DEBUGGING
+
             if (DEBUG_Lv_TEST || debug_initialization) {
                 PerlIO_printf(Perl_debug_log,
                 "_mem_collxfrm required more space than previously calculated"
-                " for locale %s, trying again with new guess=%d+%"UVuf"\n",
+                " for locale %s, trying again with new guess=%d+%zu\n",
                 PL_collation_name, (int) COLLXFRM_HDR_LEN,
-                (UV) xAlloc - COLLXFRM_HDR_LEN);
+                xAlloc - COLLXFRM_HDR_LEN);
             }
-#endif
+
+#  endif
+
         }
 
         Renew(xbuf, xAlloc, char);
-        if (UNLIKELY(! xbuf))
+        if (UNLIKELY(! xbuf)) {
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                      "_mem_collxfrm: Couldn't realloc %zu bytes\n", xAlloc));
             goto bad;
+        }
 
         first_time = FALSE;
     }
 
 
-#ifdef DEBUGGING
+#  ifdef DEBUGGING
+
     if (DEBUG_Lv_TEST || debug_initialization) {
-        unsigned i;
-        char * t = s;
-        bool prev_was_printable = TRUE;
-        bool first_time = TRUE;
-        PerlIO_printf(Perl_debug_log,
-            "_mem_collxfrm[%d]: returning %"UVuf" for locale %s string '",
-            PL_collation_ix, *xlen, PL_collation_name);
-        while (t < s + len ) {
-            UV cp = (utf8)
-                    ?  utf8_to_uvchr_buf((U8 *) t, s + len, NULL)
-                    : * (U8 *) t;
-            if (isPRINT(cp)) {
-                if (! prev_was_printable) {
-                    PerlIO_printf(Perl_debug_log, " ");
-                }
-                PerlIO_printf(Perl_debug_log, "%c", (U8) cp);
-                prev_was_printable = TRUE;
-            }
-            else {
-                if (! first_time) {
-                    PerlIO_printf(Perl_debug_log, " ");
-                }
-                PerlIO_printf(Perl_debug_log, "%02"UVXf"", cp);
-                prev_was_printable = FALSE;
-            }
-            t += (utf8) ? UTF8SKIP(t) : 1;
-            first_time = FALSE;
-        }
-        PerlIO_printf(Perl_debug_log, "'\nIts xfrm is");
-        for (i = COLLXFRM_HDR_LEN; i < *xlen + COLLXFRM_HDR_LEN; i++) {
-            PerlIO_printf(Perl_debug_log, " %02x", (U8) xbuf[i]);
-        }
-        PerlIO_printf(Perl_debug_log, "\n");
+
+        print_collxfrm_input_and_return(s, s + len, xlen, utf8);
+        PerlIO_printf(Perl_debug_log, "Its xfrm is:");
+        PerlIO_printf(Perl_debug_log, "%s\n",
+                      _byte_dump_string((U8 *) xbuf + COLLXFRM_HDR_LEN,
+                       *xlen, 1));
     }
-#endif
+
+#  endif
 
     /* Free up unneeded space; retain ehough for trailing NUL */
     Renew(xbuf, COLLXFRM_HDR_LEN + *xlen + 1, char);
@@ -1926,15 +2740,81 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
         Safefree(s);
     }
     *xlen = 0;
-#ifdef DEBUGGING
+
+#  ifdef DEBUGGING
+
     if (DEBUG_Lv_TEST || debug_initialization) {
-        PerlIO_printf(Perl_debug_log, "_mem_collxfrm[%d] returning NULL\n",
-                                      PL_collation_ix);
+        print_collxfrm_input_and_return(s, s + len, NULL, utf8);
     }
-#endif
+
+#  endif
+
     return NULL;
 }
 
+#  ifdef DEBUGGING
+
+STATIC void
+S_print_collxfrm_input_and_return(pTHX_
+                                  const char * const s,
+                                  const char * const e,
+                                  const STRLEN * const xlen,
+                                  const bool is_utf8)
+{
+
+    PERL_ARGS_ASSERT_PRINT_COLLXFRM_INPUT_AND_RETURN;
+
+    PerlIO_printf(Perl_debug_log, "_mem_collxfrm[%" UVuf "]: returning ",
+                                                        (UV)PL_collation_ix);
+    if (xlen) {
+        PerlIO_printf(Perl_debug_log, "%zu", *xlen);
+    }
+    else {
+        PerlIO_printf(Perl_debug_log, "NULL");
+    }
+    PerlIO_printf(Perl_debug_log, " for locale '%s', string='",
+                                                            PL_collation_name);
+    print_bytes_for_locale(s, e, is_utf8);
+
+    PerlIO_printf(Perl_debug_log, "'\n");
+}
+
+STATIC void
+S_print_bytes_for_locale(pTHX_
+                    const char * const s,
+                    const char * const e,
+                    const bool is_utf8)
+{
+    const char * t = s;
+    bool prev_was_printable = TRUE;
+    bool first_time = TRUE;
+
+    PERL_ARGS_ASSERT_PRINT_BYTES_FOR_LOCALE;
+
+    while (t < e) {
+        UV cp = (is_utf8)
+                ?  utf8_to_uvchr_buf((U8 *) t, e, NULL)
+                : * (U8 *) t;
+        if (isPRINT(cp)) {
+            if (! prev_was_printable) {
+                PerlIO_printf(Perl_debug_log, " ");
+            }
+            PerlIO_printf(Perl_debug_log, "%c", (U8) cp);
+            prev_was_printable = TRUE;
+        }
+        else {
+            if (! first_time) {
+                PerlIO_printf(Perl_debug_log, " ");
+            }
+            PerlIO_printf(Perl_debug_log, "%02" UVXf, cp);
+            prev_was_printable = FALSE;
+        }
+        t += (is_utf8) ? UTF8SKIP(t) : 1;
+        first_time = FALSE;
+    }
+}
+
+#  endif   /* #ifdef DEBUGGING */
 #endif /* USE_LOCALE_COLLATE */
 
 #ifdef USE_LOCALE
@@ -1953,12 +2833,14 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
     char *save_input_locale = NULL;
     STRLEN final_pos;
 
-#ifdef LC_ALL
+#  ifdef LC_ALL
+
     assert(category != LC_ALL);
-#endif
+
+#  endif
 
     /* First dispose of the trivial cases */
-    save_input_locale = setlocale(category, NULL);
+    save_input_locale = do_setlocale_r(category, NULL);
     if (! save_input_locale) {
         DEBUG_L(PerlIO_printf(Perl_debug_log,
                               "Could not find current locale for category %d\n",
@@ -1974,7 +2856,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         return FALSE;
     }
 
-#if defined(USE_LOCALE_CTYPE)    \
+#  if defined(USE_LOCALE_CTYPE)    \
     && (defined(MB_CUR_MAX) || (defined(HAS_NL_LANGINFO) && defined(CODESET)))
 
     { /* Next try nl_langinfo or MB_CUR_MAX if available */
@@ -1985,7 +2867,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         if (category != LC_CTYPE) { /* These work only on LC_CTYPE */
 
             /* Get the current LC_CTYPE locale */
-            save_ctype_locale = setlocale(LC_CTYPE, NULL);
+            save_ctype_locale = do_setlocale_c(LC_CTYPE, NULL);
             if (! save_ctype_locale) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                                "Could not find current locale for LC_CTYPE\n"));
@@ -2001,7 +2883,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 Safefree(save_ctype_locale);
                 save_ctype_locale = NULL;
             }
-            else if (! setlocale(LC_CTYPE, save_input_locale)) {
+            else if (! do_setlocale_c(LC_CTYPE, save_input_locale)) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                                     "Could not change LC_CTYPE locale to %s\n",
                                     save_input_locale));
@@ -2017,32 +2899,38 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
          * information is desired.  This means that nl_langinfo() and MB_CUR_MAX
          * should give the correct results */
 
-#   if defined(HAS_NL_LANGINFO) && defined(CODESET)
+#    if defined(HAS_NL_LANGINFO) && defined(CODESET)
+     /* The task is easiest if has this POSIX 2001 function */
+
         {
-            char *codeset = nl_langinfo(CODESET);
-            if (codeset && strNE(codeset, "")) {
-                codeset = savepv(codeset);
+            const char *codeset = my_nl_langinfo(PERL_CODESET, FALSE);
+                                          /* FALSE => already in dest locale */
+
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                            "\tnllanginfo returned CODESET '%s'\n", codeset));
 
+            if (codeset && strNE(codeset, "")) {
                 /* If we switched LC_CTYPE, switch back */
                 if (save_ctype_locale) {
-                    setlocale(LC_CTYPE, save_ctype_locale);
+                    do_setlocale_c(LC_CTYPE, save_ctype_locale);
                     Safefree(save_ctype_locale);
                 }
 
-                is_utf8 = foldEQ(codeset, STR_WITH_LEN("UTF-8"))
-                        || foldEQ(codeset, STR_WITH_LEN("UTF8"));
+                is_utf8 = (   (   strlen(codeset) == STRLENs("UTF-8")
+                               && foldEQ(codeset, STR_WITH_LEN("UTF-8")))
+                           || (   strlen(codeset) == STRLENs("UTF8")
+                               && foldEQ(codeset, STR_WITH_LEN("UTF8"))));
 
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                        "\tnllanginfo returned CODESET '%s'; ?UTF8 locale=%d\n",
                                                      codeset,         is_utf8));
-                Safefree(codeset);
                 Safefree(save_input_locale);
                 return is_utf8;
             }
         }
 
-#   endif
-#   ifdef MB_CUR_MAX
+#    endif
+#    ifdef MB_CUR_MAX
 
         /* Here, either we don't have nl_langinfo, or it didn't return a
          * codeset.  Try MB_CUR_MAX */
@@ -2059,7 +2947,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         Safefree(save_input_locale);
 
-#       ifdef HAS_MBTOWC
+#      ifdef HAS_MBTOWC
 
         /* ... But, most system that have MB_CUR_MAX will also have mbtowc(),
          * since they are both in the C99 standard.  We can feed a known byte
@@ -2067,34 +2955,42 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
          * result */
         if (is_utf8) {
             wchar_t wc;
+            int len;
+
             PERL_UNUSED_RESULT(mbtowc(&wc, NULL, 0));/* Reset any shift state */
             errno = 0;
-            if ((size_t)mbtowc(&wc, HYPHEN_UTF8, strlen(HYPHEN_UTF8))
-                                                        != strlen(HYPHEN_UTF8)
-                || wc != (wchar_t) 0x2010)
+            len = mbtowc(&wc, STR_WITH_LEN(REPLACEMENT_CHARACTER_UTF8));
+
+
+            if (   len != STRLENs(REPLACEMENT_CHARACTER_UTF8)
+                || wc != (wchar_t) UNICODE_REPLACEMENT)
             {
                 is_utf8 = FALSE;
-                DEBUG_L(PerlIO_printf(Perl_debug_log, "\thyphen=U+%x\n", (unsigned int)wc));
+                DEBUG_L(PerlIO_printf(Perl_debug_log, "\replacement=U+%x\n",
+                                                            (unsigned int)wc));
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                         "\treturn from mbtowc=%d; errno=%d; ?UTF8 locale=0\n",
-                        mbtowc(&wc, HYPHEN_UTF8, strlen(HYPHEN_UTF8)), errno));
+                                               len,      errno));
             }
         }
-#       endif
+
+#      endif
 
         /* If we switched LC_CTYPE, switch back */
         if (save_ctype_locale) {
-            setlocale(LC_CTYPE, save_ctype_locale);
+            do_setlocale_c(LC_CTYPE, save_ctype_locale);
             Safefree(save_ctype_locale);
         }
 
         return is_utf8;
-#   endif
+
+#    endif
+
     }
 
   cant_use_nllanginfo:
 
-#else   /* nl_langinfo should work if available, so don't bother compiling this
+#  else   /* nl_langinfo should work if available, so don't bother compiling this
            fallback code.  The final fallback of looking at the name is
            compiled, and will be executed if nl_langinfo fails */
 
@@ -2105,8 +3001,9 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
      * too, as the odds of a non-UTF8 string being valid UTF-8 are quite small
      * */
 
-#ifdef HAS_LOCALECONV
-#   ifdef USE_LOCALE_MONETARY
+#    ifdef HAS_LOCALECONV
+#      ifdef USE_LOCALE_MONETARY
+
     {
         char *save_monetary_locale = NULL;
         bool only_ascii = FALSE;
@@ -2118,7 +3015,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         if (category != LC_MONETARY) {
 
-            save_monetary_locale = setlocale(LC_MONETARY, NULL);
+            save_monetary_locale = do_setlocale_c(LC_MONETARY, NULL);
             if (! save_monetary_locale) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not find current locale for LC_MONETARY\n"));
@@ -2130,7 +3027,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 Safefree(save_monetary_locale);
                 save_monetary_locale = NULL;
             }
-            else if (! setlocale(LC_MONETARY, save_input_locale)) {
+            else if (! do_setlocale_c(LC_MONETARY, save_input_locale)) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not change LC_MONETARY locale to %s\n",
                                                         save_input_locale));
@@ -2145,7 +3042,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         lc = localeconv();
         if (! lc
             || ! lc->currency_symbol
-            || is_invariant_string((U8 *) lc->currency_symbol, 0))
+            || is_utf8_invariant_string((U8 *) lc->currency_symbol, 0))
         {
             DEBUG_L(PerlIO_printf(Perl_debug_log, "Couldn't get currency symbol for %s, or contains only ASCII; can't use for determining if UTF-8 locale\n", save_input_locale));
             only_ascii = TRUE;
@@ -2156,7 +3053,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         /* If we changed it, restore LC_MONETARY to its original locale */
         if (save_monetary_locale) {
-            setlocale(LC_MONETARY, save_monetary_locale);
+            do_setlocale_c(LC_MONETARY, save_monetary_locale);
             Safefree(save_monetary_locale);
         }
 
@@ -2173,10 +3070,10 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
     }
   cant_use_monetary:
 
-#   endif /* USE_LOCALE_MONETARY */
-#endif /* HAS_LOCALECONV */
+#      endif /* USE_LOCALE_MONETARY */
+#    endif /* HAS_LOCALECONV */
 
-#if defined(HAS_STRFTIME) && defined(USE_LOCALE_TIME)
+#    if defined(HAS_STRFTIME) && defined(USE_LOCALE_TIME)
 
 /* Still haven't found a non-ASCII string to disambiguate UTF-8 or not.  Try
  * the names of the months and weekdays, timezone, and am/pm indicator */
@@ -2195,7 +3092,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         if (category != LC_TIME) {
 
-            save_time_locale = setlocale(LC_TIME, NULL);
+            save_time_locale = do_setlocale_c(LC_TIME, NULL);
             if (! save_time_locale) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not find current locale for LC_TIME\n"));
@@ -2207,7 +3104,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 Safefree(save_time_locale);
                 save_time_locale = NULL;
             }
-            else if (! setlocale(LC_TIME, save_input_locale)) {
+            else if (! do_setlocale_c(LC_TIME, save_input_locale)) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not change LC_TIME locale to %s\n",
                                                         save_input_locale));
@@ -2224,8 +3121,10 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         for (i = 0; i < 7 + 12; i++) {  /* 7 days; 12 months */
             formatted_time = my_strftime("%A %B %Z %p",
-                                    0, 0, hour, dom, month, 112, 0, 0, is_dst);
-            if (! formatted_time || is_invariant_string((U8 *) formatted_time, 0)) {
+                            0, 0, hour, dom, month, 2012 - 1900, 0, 0, is_dst);
+            if ( ! formatted_time
+                || is_utf8_invariant_string((U8 *) formatted_time, 0))
+            {
 
                 /* Here, we didn't find a non-ASCII.  Try the next time through
                  * with the complemented dst and am/pm, and try with the next
@@ -2244,7 +3143,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
              * false otherwise.  But first, restore LC_TIME to its original
              * locale if we changed it */
             if (save_time_locale) {
-                setlocale(LC_TIME, save_time_locale);
+                do_setlocale_c(LC_TIME, save_time_locale);
                 Safefree(save_time_locale);
             }
 
@@ -2259,16 +3158,16 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
          * ASCII.  Go on to the next test.  If we changed it, restore LC_TIME
          * to its original locale */
         if (save_time_locale) {
-            setlocale(LC_TIME, save_time_locale);
+            do_setlocale_c(LC_TIME, save_time_locale);
             Safefree(save_time_locale);
         }
         DEBUG_L(PerlIO_printf(Perl_debug_log, "All time-related words for %s contain only ASCII; can't use for determining if UTF-8 locale\n", save_input_locale));
     }
   cant_use_time:
 
-#endif
+#    endif
 
-#if 0 && defined(USE_LOCALE_MESSAGES) && defined(HAS_SYS_ERRLIST)
+#    if 0 && defined(USE_LOCALE_MESSAGES) && defined(HAS_SYS_ERRLIST)
 
 /* This code is ifdefd out because it was found to not be necessary in testing
  * on our dromedary test machine, which has over 700 locales.  There, this
@@ -2293,7 +3192,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         if (category != LC_MESSAGES) {
 
-            save_messages_locale = setlocale(LC_MESSAGES, NULL);
+            save_messages_locale = do_setlocale_c(LC_MESSAGES, NULL);
             if (! save_messages_locale) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not find current locale for LC_MESSAGES\n"));
@@ -2305,7 +3204,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 Safefree(save_messages_locale);
                 save_messages_locale = NULL;
             }
-            else if (! setlocale(LC_MESSAGES, save_input_locale)) {
+            else if (! do_setlocale_c(LC_MESSAGES, save_input_locale)) {
                 DEBUG_L(PerlIO_printf(Perl_debug_log,
                             "Could not change LC_MESSAGES locale to %s\n",
                                                         save_input_locale));
@@ -2326,7 +3225,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                 break;
             }
             errmsg = savepv(errmsg);
-            if (! is_invariant_string((U8 *) errmsg, 0)) {
+            if (! is_utf8_invariant_string((U8 *) errmsg, 0)) {
                 non_ascii = TRUE;
                 is_utf8 = is_utf8_string((U8 *) errmsg, 0);
                 break;
@@ -2336,7 +3235,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
         /* And, if we changed it, restore LC_MESSAGES to its original locale */
         if (save_messages_locale) {
-            setlocale(LC_MESSAGES, save_messages_locale);
+            do_setlocale_c(LC_MESSAGES, save_messages_locale);
             Safefree(save_messages_locale);
         }
 
@@ -2355,12 +3254,12 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
     }
   cant_use_messages:
 
-#endif
-
-#endif /* the code that is compiled when no nl_langinfo */
+#    endif
+#  endif /* the code that is compiled when no nl_langinfo */
 
-#ifndef EBCDIC  /* On os390, even if the name ends with "UTF-8', it isn't a
+#  ifndef EBCDIC  /* On os390, even if the name ends with "UTF-8', it isn't a
                    UTF-8 locale */
+
     /* As a last resort, look at the locale name to see if it matches
      * qr/UTF -?  * 8 /ix, or some other common locale names.  This "name", the
      * return of setlocale(), is actually defined to be opaque, so we can't
@@ -2376,7 +3275,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         while ((name += strcspn(name, "Uu") + 1)
                                             <= save_input_locale + final_pos - 2)
         {
-            if (!isALPHA_FOLD_NE(*name, 't')
+            if (   isALPHA_FOLD_NE(*name, 't')
                 || isALPHA_FOLD_NE(*(name + 1), 'f'))
             {
                 continue;
@@ -2400,29 +3299,26 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                               "Locale %s doesn't end with UTF-8 in name\n",
                                 save_input_locale));
     }
-#endif
 
-#ifdef WIN32
+#  endif
+#  ifdef WIN32
+
     /* http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756.aspx */
-    if (final_pos >= 4
-        && *(save_input_locale + final_pos - 0) == '1'
-        && *(save_input_locale + final_pos - 1) == '0'
-        && *(save_input_locale + final_pos - 2) == '0'
-        && *(save_input_locale + final_pos - 3) == '5'
-        && *(save_input_locale + final_pos - 4) == '6')
-    {
+    if (memENDs(save_input_locale, final_pos, "65001")) {
         DEBUG_L(PerlIO_printf(Perl_debug_log,
-                        "Locale %s ends with 10056 in name, is UTF-8 locale\n",
+                        "Locale %s ends with 65001 in name, is UTF-8 locale\n",
                         save_input_locale));
         Safefree(save_input_locale);
         return TRUE;
     }
-#endif
+
+#  endif
 
     /* Other common encodings are the ISO 8859 series, which aren't UTF-8.  But
      * since we are about to return FALSE anyway, there is no point in doing
      * this extra work */
-#if 0
+
+#  if 0
     if (instr(save_input_locale, "8859")) {
         DEBUG_L(PerlIO_printf(Perl_debug_log,
                              "Locale %s has 8859 in name, not UTF-8 locale\n",
@@ -2430,7 +3326,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
         Safefree(save_input_locale);
         return FALSE;
     }
-#endif
+#  endif
 
     DEBUG_L(PerlIO_printf(Perl_debug_log,
                           "Assuming locale %s is not a UTF-8 locale\n",
@@ -2476,88 +3372,184 @@ Perl_my_strerror(pTHX_ const int errnum)
      * to the C locale */
 
     char *errstr;
+    dVAR;
+
+#ifndef USE_LOCALE_MESSAGES
+
+    /* If platform doesn't have messages category, we don't do any switching to
+     * the C locale; we just use whatever strerror() returns */
+
+    errstr = savepv(Strerror(errnum));
+
+#else   /* Has locale messages */
 
-#ifdef USE_LOCALE_MESSAGES  /* If platform doesn't have messages category, we
-                               don't do any switching to the C locale; we just
-                               use whatever strerror() returns */
     const bool within_locale_scope = IN_LC(LC_MESSAGES);
 
-    dVAR;
+#  if defined(HAS_POSIX_2008_LOCALE) && defined(HAS_STRERROR_L)
 
-#  ifdef USE_THREAD_SAFE_LOCALE
-    locale_t save_locale;
-#  else
-    char * save_locale;
-    bool locale_is_C;
+    /* This function is trivial if we don't have to worry about thread safety
+     * and have strerror_l(), as it handles the switch of locales so we don't
+     * have to deal with that.  We don't have to worry about thread safety if
+     * this is an unthreaded build, or if strerror_r() is also available.  Both
+     * it and strerror_l() are thread-safe.  Plain strerror() isn't thread
+     * safe.  But on threaded builds when strerror_r() is available, the
+     * apparent call to strerror() below is actually a macro that
+     * behind-the-scenes calls strerror_r().
+     */
+
+#    if ! defined(USE_ITHREADS) || defined(HAS_STRERROR_R)
+
+    if (within_locale_scope) {
+        errstr = savepv(strerror(errnum));
+    }
+    else {
+        errstr = savepv(strerror_l(errnum, PL_C_locale_obj));
+    }
+
+#    else
+
+    /* Here we have strerror_l(), but not strerror_r() and we are on a
+     * threaded-build.  We use strerror_l() for everything, constructing a
+     * locale to pass to it if necessary */
+
+    bool do_free = FALSE;
+    locale_t locale_to_use;
+
+    if (within_locale_scope) {
+        locale_to_use = uselocale((locale_t) 0);
+        if (locale_to_use == LC_GLOBAL_LOCALE) {
+            locale_to_use = duplocale(LC_GLOBAL_LOCALE);
+            do_free = TRUE;
+        }
+    }
+    else {  /* Use C locale if not within 'use locale' scope */
+        locale_to_use = PL_C_locale_obj;
+    }
+
+    errstr = savepv(strerror_l(errnum, locale_to_use));
+
+    if (do_free) {
+        freelocale(locale_to_use);
+    }
+
+#    endif
+#  else /* Doesn't have strerror_l() */
+
+#    ifdef USE_POSIX_2008_LOCALE
+
+    locale_t save_locale = NULL;
+
+#    else
+
+    char * save_locale = NULL;
+    bool locale_is_C = FALSE;
 
     /* We have a critical section to prevent another thread from changing the
      * locale out from under us (or zapping the buffer returned from
      * setlocale() ) */
     LOCALE_LOCK;
 
-#  endif
+#    endif
 
+    DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+                            "my_strerror called with errnum %d\n", errnum));
     if (! within_locale_scope) {
+        errno = 0;
 
-#  ifdef USE_THREAD_SAFE_LOCALE /* Use the thread-safe locale functions */
+#  ifdef USE_POSIX_2008_LOCALE /* Use the thread-safe locale functions */
 
+        DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+                                    "Not within locale scope, about to call"
+                                    " uselocale(0x%p)\n", PL_C_locale_obj));
         save_locale = uselocale(PL_C_locale_obj);
+        if (! save_locale) {
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                                    "uselocale failed, errno=%d\n", errno));
+        }
+        else {
+            DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+                                    "uselocale returned 0x%p\n", save_locale));
+        }
 
-#  else    /* Not thread-safe build */
+#    else    /* Not thread-safe build */
 
-        save_locale = setlocale(LC_MESSAGES, NULL);
-        locale_is_C = isNAME_C_OR_POSIX(save_locale);
+        save_locale = do_setlocale_c(LC_MESSAGES, NULL);
+        if (! save_locale) {
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                                  "setlocale failed, errno=%d\n", errno));
+        }
+        else {
+            locale_is_C = isNAME_C_OR_POSIX(save_locale);
 
-        /* Switch to the C locale if not already in it */
-        if (! locale_is_C) {
+            /* Switch to the C locale if not already in it */
+            if (! locale_is_C) {
 
-            /* The setlocale() just below likely will zap 'save_locale', so
-             * create a copy.  */
-            save_locale = savepv(save_locale);
-            setlocale(LC_MESSAGES, "C");
+                /* The setlocale() just below likely will zap 'save_locale', so
+                 * create a copy.  */
+                save_locale = savepv(save_locale);
+                do_setlocale_c(LC_MESSAGES, "C");
+            }
         }
 
-#  endif
+#    endif
 
     }   /* end of ! within_locale_scope */
-
-#endif
-
-    errstr = Strerror(errnum);
-    if (errstr) {
-        errstr = savepv(errstr);
-        SAVEFREEPV(errstr);
+    else {
+        DEBUG_Lv(PerlIO_printf(Perl_debug_log, "%s: %d: WITHIN locale scope\n",
+                                               __FILE__, __LINE__));
     }
 
-#ifdef USE_LOCALE_MESSAGES
+    DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+             "Any locale change has been done; about to call Strerror\n"));
+    errstr = savepv(Strerror(errnum));
 
     if (! within_locale_scope) {
+        errno = 0;
 
-#  ifdef USE_THREAD_SAFE_LOCALE
+#  ifdef USE_POSIX_2008_LOCALE
 
-        uselocale(save_locale);
+        DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+                    "%s: %d: not within locale scope, restoring the locale\n",
+                    __FILE__, __LINE__));
+        if (save_locale && ! uselocale(save_locale)) {
+            DEBUG_L(PerlIO_printf(Perl_debug_log,
+                          "uselocale restore failed, errno=%d\n", errno));
+        }
     }
 
-#  else
+#    else
 
-        if (! locale_is_C) {
-            setlocale(LC_MESSAGES, save_locale);
+        if (save_locale && ! locale_is_C) {
+            if (! do_setlocale_c(LC_MESSAGES, save_locale)) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                      "setlocale restore failed, errno=%d\n", errno));
+            }
             Safefree(save_locale);
         }
     }
 
     LOCALE_UNLOCK;
 
-#  endif
+#    endif
+#  endif /* End of doesn't have strerror_l */
+#endif   /* End of does have locale messages */
+
+#ifdef DEBUGGING
+
+    if (DEBUG_Lv_TEST) {
+        PerlIO_printf(Perl_debug_log, "Strerror returned; saving a copy: '");
+        print_bytes_for_locale(errstr, errstr + strlen(errstr), 0);
+        PerlIO_printf(Perl_debug_log, "'\n");
+    }
+
 #endif
 
+    SAVEFREEPV(errstr);
     return errstr;
 }
 
 /*
 
-=head1 Locale-related functions and macros
-
 =for apidoc sync_locale
 
 Changing the program's locale should be avoided by XS code.  Nevertheless,
@@ -2571,26 +3563,42 @@ to do so, before returning to Perl.
 void
 Perl_sync_locale(pTHX)
 {
+    char * newlocale;
 
 #ifdef USE_LOCALE_CTYPE
-    new_ctype(setlocale(LC_CTYPE, NULL));
-#endif /* USE_LOCALE_CTYPE */
 
+    newlocale = do_setlocale_c(LC_CTYPE, NULL);
+    DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+        "%s:%d: %s\n", __FILE__, __LINE__,
+        setlocale_debug_string(LC_CTYPE, NULL, newlocale)));
+    new_ctype(newlocale);
+
+#endif /* USE_LOCALE_CTYPE */
 #ifdef USE_LOCALE_COLLATE
-    new_collate(setlocale(LC_COLLATE, NULL));
-#endif
 
+    newlocale = do_setlocale_c(LC_COLLATE, NULL);
+    DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+        "%s:%d: %s\n", __FILE__, __LINE__,
+        setlocale_debug_string(LC_COLLATE, NULL, newlocale)));
+    new_collate(newlocale);
+
+#endif
 #ifdef USE_LOCALE_NUMERIC
-    set_numeric_local();    /* Switch from "C" to underlying LC_NUMERIC */
-    new_numeric(setlocale(LC_NUMERIC, NULL));
+
+    newlocale = do_setlocale_c(LC_NUMERIC, NULL);
+    DEBUG_Lv(PerlIO_printf(Perl_debug_log,
+        "%s:%d: %s\n", __FILE__, __LINE__,
+        setlocale_debug_string(LC_NUMERIC, NULL, newlocale)));
+    new_numeric(newlocale);
+
 #endif /* USE_LOCALE_NUMERIC */
 
 }
 
 #if defined(DEBUGGING) && defined(USE_LOCALE)
 
-char *
-Perl__setlocale_debug_string(const int category,        /* category number,
+STATIC char *
+S_setlocale_debug_string(const int category,        /* category number,
                                                            like LC_ALL */
                             const char* const locale,   /* locale name */
 
@@ -2605,51 +3613,37 @@ Perl__setlocale_debug_string(const int category,        /* category number,
 
     /* initialise to a non-null value to keep it out of BSS and so keep
      * -DPERL_GLOBAL_STRUCT_PRIVATE happy */
-    static char ret[128] = "x";
+    static char ret[128] = "If you can read this, thank your buggy C"
+                           " library strlcpy(), and change your hints file"
+                           " to undef it";
+    unsigned int i;
+
+#  ifdef LC_ALL
+
+    const unsigned int highest_index = LC_ALL_INDEX;
+
+#  else
+
+    const unsigned int highest_index = NOMINAL_LC_ALL_INDEX - 1;
+
+#endif
+
 
     my_strlcpy(ret, "setlocale(", sizeof(ret));
 
-    switch (category) {
-        default:
-            my_snprintf(ret, sizeof(ret), "%s? %d", ret, category);
-            break;
-#   ifdef LC_ALL
-        case LC_ALL:
-            my_strlcat(ret, "LC_ALL", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_CTYPE
-        case LC_CTYPE:
-            my_strlcat(ret, "LC_CTYPE", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_NUMERIC
-        case LC_NUMERIC:
-            my_strlcat(ret, "LC_NUMERIC", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_COLLATE
-        case LC_COLLATE:
-            my_strlcat(ret, "LC_COLLATE", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_TIME
-        case LC_TIME:
-            my_strlcat(ret, "LC_TIME", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_MONETARY
-        case LC_MONETARY:
-            my_strlcat(ret, "LC_MONETARY", sizeof(ret));
-            break;
-#   endif
-#   ifdef LC_MESSAGES
-        case LC_MESSAGES:
-            my_strlcat(ret, "LC_MESSAGES", sizeof(ret));
-            break;
-#   endif
+    /* Look for category in our list, and if found, add its name */
+    for (i = 0; i <= highest_index; i++) {
+        if (category == categories[i]) {
+            my_strlcat(ret, category_names[i], sizeof(ret));
+            goto found_category;
+        }
     }
 
+    /* Unknown category to us */
+    my_snprintf(ret, sizeof(ret), "%s? %d", ret, category);
+
+  found_category:
+
     my_strlcat(ret, ", ", sizeof(ret));
 
     if (locale) {