This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
locale.c: Refactor Ultrix code
[perl5.git] / locale.c
index 3d68958..fdb4d4b 100644 (file)
--- a/locale.c
+++ b/locale.c
 /* 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
-  /* no global syms allowed */
-#    define debug_initialization 0
-#    define DEBUG_INITIALIZATION_set(v)
-#  else
+#if ! defined(DEBUGGING) || defined(PERL_GLOBAL_STRUCT)
+#  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
 
-/* strlen() of a literal string constant.  XXX We might want this more general,
- * but using it in just this file for now */
+/* strlen() of a literal string constant.  We might want this more general,
+ * but using it in just this file for now.  A problem with more generality is
+ * the compiler warnings about comparing unlike signs */
 #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
 
 /*
@@ -104,8 +117,174 @@ S_stdize_locale(pTHX_ char *locs)
     return locs;
 }
 
+/* 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)
+
+#  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. */
+
+STATIC const char *
+S_category_name(const int category)
+{
+    unsigned int i;
+
+#ifdef LC_ALL
+
+    if (category == LC_ALL) {
+        return "LC_ALL";
+    }
+
 #endif
 
+    for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++) {
+        if (category == categories[i]) {
+            return category_names[i];
+        }
+    }
+
+    {
+        const char suffix[] = " (unknown)";
+        int temp = category;
+        Size_t length = sizeof(suffix) + 1;
+        char * unknown;
+        dTHX;
+
+        if (temp < 0) {
+            length++;
+            temp = - temp;
+        }
+
+        /* Calculate the number of digits */
+        while (temp >= 10) {
+            temp /= 10;
+            length++;
+        }
+
+        Newx(unknown, length, char);
+        my_snprintf(unknown, length, "%d%s", category, suffix);
+        SAVEFREEPV(unknown);
+        return unknown;
+    }
+}
+
+/* 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)
@@ -131,9 +310,10 @@ S_set_numeric_radix(pTHX_ const bool use_locale)
     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, ".")) {
+            const U8 * first_variant;
+
             if (PL_numeric_radix_sv) {
                 sv_setpv(PL_numeric_radix_sv, radix);
             }
@@ -141,10 +321,17 @@ S_set_numeric_radix(pTHX_ const bool use_locale)
                 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))
+            /* If there is a byte variant under UTF-8, and if the remainder of
+             * the string starting there is valid UTF-8, and we are in a UTF-8
+             * locale, then mark the radix as being in UTF-8 */
+            if ( !  is_utf8_invariant_string_loc(
+                                            (U8 *) SvPVX(PL_numeric_radix_sv),
+                                            SvCUR(PL_numeric_radix_sv),
+                                            &first_variant)
+                &&  is_utf8_string(first_variant,
+                                   SvCUR(PL_numeric_radix_sv)
+                                 - ((char *) first_variant
+                                 - SvPVX(PL_numeric_radix_sv)))
                 && _is_cur_LC_category_utf8(LC_NUMERIC))
             {
                 SvUTF8_on(PL_numeric_radix_sv);
@@ -175,20 +362,6 @@ S_set_numeric_radix(pTHX_ const bool use_locale)
 
 }
 
-/* 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")))
 
 void
 Perl_new_numeric(pTHX_ const char *newnum)
@@ -200,7 +373,7 @@ Perl_new_numeric(pTHX_ const char *newnum)
 
 #else
 
-    /* Called after all libc setlocale() calls affecting LC_NUMERIC, to tell
+    /* Called after each libc setlocale() call 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.
      *
@@ -284,7 +457,7 @@ Perl_set_numeric_standard(pTHX)
 
     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
@@ -313,7 +486,7 @@ Perl_set_numeric_underlying(pTHX)
 
     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);
     }
 
@@ -337,7 +510,7 @@ S_new_ctype(pTHX_ const char *newctype)
 
 #else
 
-    /* Called after all libc setlocale() calls affecting LC_CTYPE, to tell
+    /* Called after each libc setlocale() call affecting LC_CTYPE, to tell
      * core Perl this and that 'newctype' is the name of the new locale.
      *
      * This function sets up the folding arrays for all 256 bytes, assuming
@@ -382,10 +555,10 @@ S_new_ctype(pTHX_ const char *newctype)
         unsigned int bad_count = 0;         /* Count of bad characters */
 
         for (i = 0; i < 256; i++) {
-            if (isUPPER_LC((U8) i))
-                PL_fold_locale[i] = (U8) toLOWER_LC((U8) i);
-            else if (isLOWER_LC((U8) i))
-                PL_fold_locale[i] = (U8) toUPPER_LC((U8) i);
+            if (isupper(i))
+                PL_fold_locale[i] = (U8) tolower(i);
+            else if (islower(i))
+                PL_fold_locale[i] = (U8) toupper(i);
             else
                 PL_fold_locale[i] = (U8) i;
 
@@ -401,10 +574,19 @@ S_new_ctype(pTHX_ const char *newctype)
             if (    check_for_problems
                 && (isGRAPH_A(i) || isBLANK_A(i) || i == '\n'))
             {
-                if ((    isALPHANUMERIC_A(i) && ! isALPHANUMERIC_LC(i))
-                     || (isPUNCT_A(i) && ! isPUNCT_LC(i))
-                     || (isBLANK_A(i) && ! isBLANK_LC(i))
-                     || (i == '\n' && ! isCNTRL_LC(i)))
+                if (   cBOOL(isalnum(i)) != cBOOL(isALPHANUMERIC(i))
+                    || cBOOL(isalpha(i)) != cBOOL(isALPHA_A(i))
+                    || cBOOL(isdigit(i)) != cBOOL(isDIGIT_A(i))
+                    || cBOOL(isgraph(i)) != cBOOL(isGRAPH_A(i))
+                    || cBOOL(islower(i)) != cBOOL(isLOWER_A(i))
+                    || cBOOL(isprint(i)) != cBOOL(isPRINT_A(i))
+                    || cBOOL(ispunct(i)) != cBOOL(isPUNCT_A(i))
+                    || cBOOL(isspace(i)) != cBOOL(isSPACE_A(i))
+                    || cBOOL(isupper(i)) != cBOOL(isUPPER_A(i))
+                    || cBOOL(isxdigit(i))!= cBOOL(isXDIGIT_A(i))
+                    || tolower(i) != (int) toLOWER_A(i)
+                    || toupper(i) != (int) toUPPER_A(i)
+                    || (i == '\n' && ! isCNTRL_LC(i)))
                 {
                     if (bad_count) {    /* Separate multiple entries with a
                                            blank */
@@ -517,11 +699,9 @@ Perl__warn_problematic_locale()
      * _CHECK_AND_WARN_PROBLEMATIC_LOCALE */
 
     if (PL_warn_locale) {
-        /*GCC_DIAG_IGNORE(-Wformat-security);   Didn't work */
         Perl_ck_warner(aTHX_ packWARN(WARN_LOCALE),
                              SvPVX(PL_warn_locale),
                              0 /* dummy to avoid compiler warning */ );
-        /* GCC_DIAG_RESTORE; */
         SvREFCNT_dec_NN(PL_warn_locale);
         PL_warn_locale = NULL;
     }
@@ -541,7 +721,7 @@ S_new_collate(pTHX_ const char *newcoll)
 
 #else
 
-    /* Called after all libc setlocale() calls affecting LC_COLLATE, to tell
+    /* Called after each libc setlocale() call affecting LC_COLLATE, to tell
      * core Perl this and that 'newcoll' is the name of the new locale.
      *
      * The design of locale collation is that every locale change is given an
@@ -773,6 +953,7 @@ S_win32_setlocale(pTHX_ int category, const char* locale)
 
     bool override_LC_ALL = FALSE;
     char * result;
+    unsigned int i;
 
     if (locale && strEQ(locale, "")) {
 
@@ -780,73 +961,30 @@ S_win32_setlocale(pTHX_ int category, const char* locale)
 
         locale = PerlEnv_getenv("LC_ALL");
         if (! locale) {
+            if (category ==  LC_ALL) {
+                override_LC_ALL = TRUE;
+            }
+            else {
 
 #  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
+                for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++) {
+                    if (category == categories[i]) {
+                        locale = PerlEnv_getenv(category_names[i]);
+                        goto found_locale;
+                    }
+                }
 
-                default:
-                    /* This is a category, like PAPER_SIZE that we don't
-                     * know about; and so can't provide a wrapper. */
-                    break;
-            }
-            if (! locale) {
                 locale = PerlEnv_getenv("LANG");
                 if (! locale) {
                     locale = "";
                 }
-            }
+
+              found_locale: ;
 
 #  ifdef LC_ALL
 
+            }
         }
 
 #  endif
@@ -867,73 +1005,16 @@ S_win32_setlocale(pTHX_ int category, const char* locale)
      * 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")));
+    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")));
+        }
     }
 
-#  endif
-
     result = setlocale(LC_ALL, NULL);
     DEBUG_L(PerlIO_printf(Perl_debug_log, "%s:%d: %s\n",
                                __FILE__, __LINE__,
@@ -955,11 +1036,10 @@ Perl_setlocale(int category, const char * locale)
 
 #ifdef USE_LOCALE_NUMERIC
 
-    /* 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 */
+    /* 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.  For an LC_ALL query, switch back to get the correct
+     * results.  All other categories don't require special handling */
     if (locale == NULL) {
         if (category == LC_NUMERIC) {
             return savepv(PL_numeric_name);
@@ -977,7 +1057,8 @@ Perl_setlocale(int category, const char * locale)
 
 #endif
 
-    retval = do_setlocale_r(category, locale);
+    /* 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__,
@@ -992,9 +1073,6 @@ Perl_setlocale(int category, const char * locale)
         return NULL;
     }
 
-    /* Save retval since subsequent setlocale() calls may overwrite it. */
-    retval = savepv(retval);
-
     /* If locale == NULL, we are just querying the state, but may have switched
      * to NUMERIC_UNDERLYING.  Switch back before returning. */
     if (locale == NULL) {
@@ -1142,11 +1220,15 @@ Unimplemented, so returns C<"">.
 
 =item C<YESEXPR>
 
+=item C<YESSTR>
+
 =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<NOSTR>
+
+Only the values for English are returned.  C<YESSTR> and C<NOSTR> have been
+removed from POSIX 2008, and are retained for backwards compatibility.  Your
+platform's C<nl_langinfo> may not support them.
 
 =item C<D_FMT>
 
@@ -1276,8 +1358,6 @@ S_my_nl_langinfo(const int item, bool toggle)
 
     LOCALE_UNLOCK;
 
-    return PL_langinfo_buf;
-
 #  else /* Use nl_langinfo_l(), avoiding both a mutex and changing the locale */
 
     bool do_free = FALSE;
@@ -1301,9 +1381,19 @@ S_my_nl_langinfo(const int item, bool toggle)
         freelocale(cur);
     }
 
+#  endif
+
+    if (strEQ(PL_langinfo_buf, "")) {
+        if (item == PERL_YESSTR) {
+            return "yes";
+        }
+        if (item == PERL_NOSTR) {
+            return "no";
+        }
+    }
+
     return PL_langinfo_buf;
 
-#    endif
 #else   /* Below, emulate nl_langinfo as best we can */
 #  ifdef HAS_LOCALECONV
 
@@ -1339,7 +1429,9 @@ S_my_nl_langinfo(const int item, bool toggle)
 
         /* We use only an English set, since we don't know any more */
         case PERL_YESEXPR:   return "^[+1yY]";
+        case PERL_YESSTR:    return "yes";
         case PERL_NOEXPR:    return "^[-0nN]";
+        case PERL_NOSTR:     return "no";
 
 #  ifdef HAS_LOCALECONV
 
@@ -1347,6 +1439,9 @@ S_my_nl_langinfo(const int item, bool toggle)
 
             LOCALE_LOCK;
 
+            /* We don't bother with localeconv_l() because any system that has
+             * it is likely to also have nl_langinfo() */
+
             lc = localeconv();
             if (! lc || ! lc->currency_symbol || strEQ("", lc->currency_symbol))
             {
@@ -1386,29 +1481,13 @@ S_my_nl_langinfo(const int item, bool toggle)
             if (! lc) {
                 retval = "";
             }
-            else switch (item) {
-                case PERL_RADIXCHAR:
-                    if (! lc->decimal_point) {
-                        retval = "";
-                    }
-                    else {
-                        retval = lc->decimal_point;
-                    }
-                    break;
-
-                case PERL_THOUSEP:
-                    if (! lc->thousands_sep || strEQ("", lc->thousands_sep)) {
-                        retval = "";
-                    }
-                    else {
-                        retval = lc->thousands_sep;
-                    }
-                    break;
-
-                default:
-                    LOCALE_UNLOCK;
-                    Perl_croak(aTHX_ "panic: %s: %d: switch case: %d problem",
-                                            __FILE__, __LINE__, item);
+            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);
@@ -1723,21 +1802,6 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     PERL_UNUSED_ARG(printwarn);
 
 #else  /* 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"));
@@ -1759,15 +1823,18 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     const char * const bad_lang_use_once = PerlEnv_getenv("PERL_BADLANG");
 
     const bool locwarn = (printwarn > 1
-                          || (printwarn
-                              && (! bad_lang_use_once
+                          || (          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;
+                                         /* disallow with "" or "0" */
+                                         *bad_lang_use_once
+                                       && strNE("0", bad_lang_use_once)))));
+    /* setlocale() return vals; not copied so must be looked at immediately */
+    const char * sl_result[NOMINAL_LC_ALL_INDEX + 1];
+
+    /* current locale for given category; should have been copied so aren't
+     * volatile */
+    const char * curlocales[NOMINAL_LC_ALL_INDEX + 1];
 
 #  ifdef WIN32
 
@@ -1780,7 +1847,10 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     const char *system_default_locale = NULL;
 
 #  endif
-#  ifdef DEBUGGING
+
+#  ifndef DEBUGGING
+#    define DEBUG_LOCALE_INIT(a,b,c)
+#  else
 
     DEBUG_INITIALIZATION_set(cBOOL(PerlEnv_getenv("PERL_DEBUG_LOCALE_INIT")));
 
@@ -1796,102 +1866,75 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
                 }                                                           \
        } STMT_END
 
-#  else
-#    define DEBUG_LOCALE_INIT(a,b,c)
-#  endif
-
-#  ifndef LOCALE_ENVIRON_REQUIRED
-
-    PERL_UNUSED_VAR(done);
-    PERL_UNUSED_VAR(locale_param);
-
-#  else
+/* 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 */
+#  ifdef LOCALE_ENVIRON_REQUIRED
 
     /*
      * Ultrix setlocale(..., "") fails if there are no environment
      * variables from which to get a locale name.
      */
 
-#    ifdef LC_ALL
+#    ifndef LC_ALL
+#      error Ultrix without LC_ALL not implemented
+#    else
 
+    {
+        bool done = FALSE;
     if (lang) {
-       sl_result = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(LC_MONETARY, locale_param);
-        DEBUG_LOCALE_INIT(LC_MONETARY, locale_param, sl_result);
-       if (! sl_result) {
-           setlocale_failure = TRUE;
+        const char * locale_param;
+        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 /* 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
@@ -1911,7 +1954,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
             setlocale_failure = FALSE;
 
 #  ifdef SYSTEM_DEFAULT_LOCALE
-#    ifdef WIN32
+#    ifdef WIN32    /* Note that assumes Win32 has LC_ALL */
 
             /* On Windows machines, an entry of "" after the 0th means to use
              * the system default locale, which we now proceed to get. */
@@ -1938,13 +1981,14 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
             }
 #    endif /* WIN32 */
 #  endif /* SYSTEM_DEFAULT_LOCALE */
-        }
+
+        }   /* For i > 0 */
 
 #  ifdef LC_ALL
 
-        sl_result = do_setlocale_c(LC_ALL, trial_locale);
-        DEBUG_LOCALE_INIT(LC_ALL, trial_locale, sl_result);
-        if (! sl_result) {
+        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 {
@@ -1960,60 +2004,19 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 
 #  endif /* LC_ALL */
 
-        if (!setlocale_failure) {
-
-#  ifdef USE_LOCALE_CTYPE
-
-            Safefree(curctype);
-            curctype = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(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 = do_setlocale_c(LC_MONETARY, trial_locale);
-            DEBUG_LOCALE_INIT(LC_MONETARY, trial_locale, sl_result);
-            if (! (sl_result))
-                setlocale_failure = TRUE;
-
-#  endif /* USE_LOCALE_MONETARY */
+        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) {  /* Success */
-                break;
+            if (! setlocale_failure) {  /* All succeeded */
+                break;  /* Exit trial_locales loop */
             }
         }
 
@@ -2035,23 +2038,14 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
                 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");
 
@@ -2189,6 +2183,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 */
@@ -2199,28 +2194,11 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 
             /* To continue, we should use whatever values we've got */
 
-#  ifdef USE_LOCALE_CTYPE
-
-            Safefree(curctype);
-            curctype = savepv(do_setlocale_c(LC_CTYPE, NULL));
-            DEBUG_LOCALE_INIT(LC_CTYPE, NULL, curctype);
-
-#  endif /* USE_LOCALE_CTYPE */
-#  ifdef USE_LOCALE_COLLATE
-
-            Safefree(curcoll);
-            curcoll = savepv(do_setlocale_c(LC_COLLATE, NULL));
-            DEBUG_LOCALE_INIT(LC_COLLATE, NULL, curcoll);
-
-#  endif /* USE_LOCALE_COLLATE */
-#  ifdef USE_LOCALE_NUMERIC
-
-            Safefree(curnum);
-            curnum = savepv(do_setlocale_c(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) {
@@ -2257,21 +2235,29 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
         }
     } /* End of tried to fallback */
 
+    /* Done with finding the locales; update our records */
+
 #  ifdef USE_LOCALE_CTYPE
 
-    new_ctype(curctype);
+    new_ctype(curlocales[LC_CTYPE_INDEX]);
 
-#  endif /* USE_LOCALE_CTYPE */
+#  endif
 #  ifdef USE_LOCALE_COLLATE
 
-    new_collate(curcoll);
+    new_collate(curlocales[LC_COLLATE_INDEX]);
 
-#  endif /* USE_LOCALE_COLLATE */
+#  endif
 #  ifdef USE_LOCALE_NUMERIC
 
-    new_numeric(curnum);
+    new_numeric(curlocales[LC_NUMERIC_INDEX]);
+
+#  endif
+
+
+    for (i = 0; i < NOMINAL_LC_ALL_INDEX; i++) {
+        Safefree(curlocales[i]);
+    }
 
-#  endif /* USE_LOCALE_NUMERIC */
 #  if defined(USE_PERLIO) && defined(USE_LOCALE_CTYPE)
 
     /* Set PL_utf8locale to TRUE if using PerlIO _and_ the current LC_CTYPE
@@ -2292,22 +2278,6 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     }
 
 #  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 */
-
 #  ifdef __GLIBC__
 
     Safefree(language);
@@ -2476,7 +2446,7 @@ Perl__mem_collxfrm(pTHX_ const char *input_string,
         } /* 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 */
+         * UTF8-ness of the original */
         if ( ! UVCHR_IS_INVARIANT(PL_strxfrm_NUL_replacement) && utf8) {
             this_replacement_char[0] =
                                 UTF8_EIGHT_BIT_HI(PL_strxfrm_NUL_replacement);
@@ -2932,7 +2902,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
      * English, it comes down to if the locale's name ends in something like
      * "UTF-8".  It errs on the side of not being a UTF-8 locale. */
 
-    char *save_input_locale = NULL;
+    const char *save_input_locale = NULL;
     STRLEN final_pos;
 
 #  ifdef LC_ALL
@@ -3002,9 +2972,8 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
          * should give the correct results */
 
 #    if defined(HAS_NL_LANGINFO) && defined(CODESET)
-     /* The task is easiest if has this POSIX 2001 function */
 
-        {
+        { /* The task is easiest if the platform has this POSIX 2001 function */
             const char *codeset = my_nl_langinfo(PERL_CODESET, FALSE);
                                           /* FALSE => already in dest locale */
 
@@ -3018,8 +2987,10 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
                     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",
@@ -3039,7 +3010,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
          * Unicode code point.  Since UTF-8 is the only non-single byte
          * encoding we handle, we just say any such encoding is UTF-8, and if
          * turns out to be wrong, other things will fail */
-        is_utf8 = MB_CUR_MAX >= 4;
+        is_utf8 = (unsigned) MB_CUR_MAX >= STRLENs(MAX_UNICODE_UTF8);
 
         DEBUG_L(PerlIO_printf(Perl_debug_log,
                               "\tMB_CUR_MAX=%d; ?UTF8 locale=%d\n",
@@ -3055,17 +3026,22 @@ 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));
             }
         }
 
@@ -3364,7 +3340,7 @@ Perl__is_cur_LC_category_utf8(pTHX_ int category)
 
     final_pos = strlen(save_input_locale) - 1;
     if (final_pos >= 3) {
-        char *name = save_input_locale;
+        const char *name = save_input_locale;
 
         /* Find next 'U' or 'u' and look from there */
         while ((name += strcspn(name, "Uu") + 1)
@@ -3482,18 +3458,53 @@ Perl_my_strerror(pTHX_ const int errnum)
 
 #  if defined(HAS_POSIX_2008_LOCALE) && defined(HAS_STRERROR_L)
 
-    /* This function is trivial if we have strerror_l() */
+    /* 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 = strerror(errnum);
+        errstr = savepv(strerror(errnum));
     }
     else {
-        errstr = strerror_l(errnum, PL_C_locale_obj);
+        errstr = savepv(strerror_l(errnum, PL_C_locale_obj));
     }
 
-    errstr = savepv(errstr);
+#    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 */
 
-#  else /* Doesn't have strerror_l(). */
+    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
 
@@ -3501,7 +3512,7 @@ Perl_my_strerror(pTHX_ const int errnum)
 
 #    else
 
-    char * save_locale = NULL;
+    const char * save_locale = NULL;
     bool locale_is_C = FALSE;
 
     /* We have a critical section to prevent another thread from changing the
@@ -3676,65 +3687,9 @@ S_setlocale_debug_string(const int category,        /* category number,
     static char ret[128] = "If you can read this, thank your buggy C"
                            " library strlcpy(), and change your hints file"
                            " to undef it";
-    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
-
-    }
 
+    my_strlcpy(ret, "setlocale(", sizeof(ret));
+    my_strlcat(ret, category_name(category), sizeof(ret));
     my_strlcat(ret, ", ", sizeof(ret));
 
     if (locale) {