This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
locale.c: Make a common idiom into a macro
[perl5.git] / locale.c
index 82b8414..332be8a 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -93,7 +93,6 @@ void
 Perl_set_numeric_radix(pTHX)
 {
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
 # ifdef HAS_LOCALECONV
     const struct lconv* const lc = localeconv();
 
@@ -109,7 +108,7 @@ Perl_set_numeric_radix(pTHX)
                PL_numeric_radix_sv = newSVpv(lc->decimal_point, 0);
             if (! is_ascii_string((U8 *) lc->decimal_point, 0)
                 && is_utf8_string((U8 *) lc->decimal_point, 0)
-                && is_cur_LC_category_utf8(LC_NUMERIC))
+                && _is_cur_LC_category_utf8(LC_NUMERIC))
             {
                SvUTF8_on(PL_numeric_radix_sv);
             }
@@ -117,10 +116,30 @@ Perl_set_numeric_radix(pTHX)
     }
     else
        PL_numeric_radix_sv = NULL;
+
+    DEBUG_L(PerlIO_printf(Perl_debug_log, "Locale radix is %s\n",
+                                          (PL_numeric_radix_sv)
+                                          ? lc->decimal_point
+                                          : "NULL"));
+
 # endif /* HAS_LOCALECONV */
 #endif /* USE_LOCALE_NUMERIC */
 }
 
+/* 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)
 {
@@ -142,9 +161,12 @@ Perl_new_numeric(pTHX_ const char *newnum)
      * This sets several interpreter-level variables:
      * PL_numeric_name  The default locale's name: a copy of 'newnum'
      * PL_numeric_local A boolean indicating if the toggled state is such
-     *                  that the current locale is the default locale
-     * PL_numeric_standard A boolean indicating if the toggled state is such
-     *                  that the current locale is the C locale
+     *                  that the current locale is the program's underlying
+     *                  locale
+     * PL_numeric_standard An int indicating if the toggled state is such
+     *                  that the current locale is the C locale.  If non-zero,
+     *                  it is in C; if > 1, it means it may not be toggled away
+     *                  from C.
      * Note that both of the last two variables can be true at the same time,
      * if the underlying locale is C.  (Toggling is a no-op under these
      * circumstances.)
@@ -155,7 +177,6 @@ Perl_new_numeric(pTHX_ const char *newnum)
      * POSIX::setlocale() */
 
     char *save_newnum;
-    dVAR;
 
     if (! newnum) {
        Safefree(PL_numeric_name);
@@ -171,11 +192,18 @@ Perl_new_numeric(pTHX_ const char *newnum)
        PL_numeric_name = save_newnum;
     }
 
-    PL_numeric_standard = ((*save_newnum == 'C' && save_newnum[1] == '\0')
-                            || strEQ(save_newnum, "POSIX"));
+    PL_numeric_standard = isNAME_C_OR_POSIX(save_newnum);
     PL_numeric_local = TRUE;
+
+    /* Keep LC_NUMERIC in the C locale.  This is for XS modules, so they don't
+     * have to worry about the radix being a non-dot.  (Core operations that
+     * need the underlying locale change to it temporarily). */
+    set_numeric_standard();
+
     set_numeric_radix();
 
+#else
+    PERL_UNUSED_ARG(newnum);
 #endif /* USE_LOCALE_NUMERIC */
 }
 
@@ -183,18 +211,18 @@ void
 Perl_set_numeric_standard(pTHX)
 {
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     /* Toggle the LC_NUMERIC locale to C, if not already there.  Probably
      * should use the macros like SET_NUMERIC_STANDARD() in perl.h instead of
      * calling this directly. */
 
-    if (! PL_numeric_standard) {
+    if (_NOT_IN_NUMERIC_STANDARD) {
        setlocale(LC_NUMERIC, "C");
        PL_numeric_standard = TRUE;
        PL_numeric_local = FALSE;
        set_numeric_radix();
     }
+    DEBUG_L(PerlIO_printf(Perl_debug_log,
+                          "Underlying LC_NUMERIC locale now is C\n"));
 
 #endif /* USE_LOCALE_NUMERIC */
 }
@@ -203,18 +231,19 @@ void
 Perl_set_numeric_local(pTHX)
 {
 #ifdef USE_LOCALE_NUMERIC
-    dVAR;
-
     /* Toggle the LC_NUMERIC locale to the current underlying default, if not
      * already there.  Probably should use the macros like SET_NUMERIC_LOCAL()
      * in perl.h instead of calling this directly. */
 
-    if (! PL_numeric_local) {
+    if (_NOT_IN_NUMERIC_LOCAL) {
        setlocale(LC_NUMERIC, PL_numeric_name);
        PL_numeric_standard = FALSE;
        PL_numeric_local = TRUE;
        set_numeric_radix();
     }
+    DEBUG_L(PerlIO_printf(Perl_debug_log,
+                          "Underlying LC_NUMERIC locale now is %s\n",
+                          PL_numeric_name));
 
 #endif /* USE_LOCALE_NUMERIC */
 }
@@ -243,7 +272,7 @@ Perl_new_ctype(pTHX_ const char *newctype)
 
     PERL_ARGS_ASSERT_NEW_CTYPE;
 
-    PL_in_utf8_CTYPE_locale = is_cur_LC_category_utf8(LC_CTYPE);
+    PL_in_utf8_CTYPE_locale = _is_cur_LC_category_utf8(LC_CTYPE);
 
     /* A UTF-8 locale gets standard rules.  But note that code still has to
      * handle this specially because of the three problematic code points */
@@ -280,8 +309,6 @@ Perl_new_collate(pTHX_ const char *newcoll)
      * should be called directly only from this file and from
      * POSIX::setlocale() */
 
-    dVAR;
-
     if (! newcoll) {
        if (PL_collation_name) {
            ++PL_collation_ix;
@@ -298,8 +325,7 @@ Perl_new_collate(pTHX_ const char *newcoll)
        ++PL_collation_ix;
        Safefree(PL_collation_name);
        PL_collation_name = stdize_locale(savepv(newcoll));
-       PL_collation_standard = ((*newcoll == 'C' && newcoll[1] == '\0')
-                                || strEQ(newcoll, "POSIX"));
+       PL_collation_standard = isNAME_C_OR_POSIX(newcoll);
 
        {
          /*  2: at most so many chars ('a', 'b'). */
@@ -317,25 +343,164 @@ Perl_new_collate(pTHX_ const char *newcoll)
        }
     }
 
+#else
+    PERL_UNUSED_ARG(newcoll);
 #endif /* USE_LOCALE_COLLATE */
 }
 
+#ifdef WIN32
+
+char *
+Perl_my_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. */
+
+    bool override_LC_ALL = 0;
+    char * result;
+
+    if (locale && strEQ(locale, "")) {
+#   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 (! locale) {
+                locale = PerlEnv_getenv("LANG");
+                if (! locale) {
+                    locale = "";
+                }
+            }
+#   ifdef LC_ALL
+        }
+#   endif
+    }
+
+    result = setlocale(category, locale);
+
+    if (! override_LC_ALL)  {
+        return result;
+    }
+
+    /* Here the input locale was LC_ALL, and we have set it to what is in the
+     * LANG variable or the system default if there is no LANG.  But these have
+     * 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);
+    }
+#   endif
+#   ifdef USE_LOCALE_CTYPE
+    result = PerlEnv_getenv("LC_CTYPE");
+    if (result && strNE(result, "")) {
+        setlocale(LC_CTYPE, result);
+    }
+#   endif
+#   ifdef USE_LOCALE_COLLATE
+    result = PerlEnv_getenv("LC_COLLATE");
+    if (result && strNE(result, "")) {
+        setlocale(LC_COLLATE, result);
+    }
+#   endif
+#   ifdef USE_LOCALE_MONETARY
+    result = PerlEnv_getenv("LC_MONETARY");
+    if (result && strNE(result, "")) {
+        setlocale(LC_MONETARY, result);
+    }
+#   endif
+#   ifdef USE_LOCALE_NUMERIC
+    result = PerlEnv_getenv("LC_NUMERIC");
+    if (result && strNE(result, "")) {
+        setlocale(LC_NUMERIC, result);
+    }
+#   endif
+#   ifdef USE_LOCALE_MESSAGES
+    result = PerlEnv_getenv("LC_MESSAGES");
+    if (result && strNE(result, "")) {
+        setlocale(LC_MESSAGES, result);
+    }
+#   endif
+
+    return setlocale(LC_ALL, NULL);
+
+}
+
+#endif
+
+
 /*
  * Initialize locale awareness.
  */
 int
 Perl_init_i18nl10n(pTHX_ int printwarn)
 {
-    int ok = 1;
-    /* returns
+    /* 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 C locale,
-     *   -1 = fallback to C locale failed
+     *    0 = fallback to a locale of lower priority
+     *   -1 = fallback to all locales failed, not even to the C locale
      */
 
-#if defined(USE_LOCALE)
-    dVAR;
+    int ok = 1;
 
+#if defined(USE_LOCALE)
 #ifdef USE_LOCALE_CTYPE
     char *curctype   = NULL;
 #endif /* USE_LOCALE_CTYPE */
@@ -348,26 +513,43 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 #ifdef __GLIBC__
     char * const language   = 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;
     char * const lc_all     = PerlEnv_getenv("LC_ALL");
     char * const lang       = PerlEnv_getenv("LANG");
     bool setlocale_failure = FALSE;
+    unsigned int i;
+    char *p;
+    const bool locwarn = (printwarn > 1 ||
+                    (printwarn &&
+                     (!(p = PerlEnv_getenv("PERL_BADLANG")) || atoi(p))));
+    bool done = FALSE;
+#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
 
-#ifdef LOCALE_ENVIRON_REQUIRED
+#ifndef LOCALE_ENVIRON_REQUIRED
+    PERL_UNUSED_VAR(done);
+#else
 
     /*
      * Ultrix setlocale(..., "") fails if there are no environment
      * variables from which to get a locale name.
      */
 
-    bool done = FALSE;
-
 #   ifdef LC_ALL
     if (lang) {
-       if (setlocale(LC_ALL, setlocale_init))
+       if (my_setlocale(LC_ALL, setlocale_init))
            done = TRUE;
        else
            setlocale_failure = TRUE;
@@ -376,7 +558,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 #       ifdef USE_LOCALE_CTYPE
        Safefree(curctype);
        if (! (curctype =
-              setlocale(LC_CTYPE,
+              my_setlocale(LC_CTYPE,
                         (!done && (lang || PerlEnv_getenv("LC_CTYPE")))
                                    ? setlocale_init : NULL)))
            setlocale_failure = TRUE;
@@ -386,7 +568,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 #       ifdef USE_LOCALE_COLLATE
        Safefree(curcoll);
        if (! (curcoll =
-              setlocale(LC_COLLATE,
+              my_setlocale(LC_COLLATE,
                         (!done && (lang || PerlEnv_getenv("LC_COLLATE")))
                                   ? setlocale_init : NULL)))
            setlocale_failure = TRUE;
@@ -396,174 +578,318 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 #       ifdef USE_LOCALE_NUMERIC
        Safefree(curnum);
        if (! (curnum =
-              setlocale(LC_NUMERIC,
+              my_setlocale(LC_NUMERIC,
                         (!done && (lang || PerlEnv_getenv("LC_NUMERIC")))
                                  ? setlocale_init : NULL)))
            setlocale_failure = TRUE;
        else
            curnum = savepv(curnum);
 #       endif /* USE_LOCALE_NUMERIC */
+#       ifdef USE_LOCALE_MESSAGES
+       if (! my_setlocale(LC_MESSAGES,
+                        (!done && (lang || PerlEnv_getenv("LC_MESSAGES")))
+                                 ? setlocale_init : NULL))
+        {
+           setlocale_failure = TRUE;
+        }
+#       endif /* USE_LOCALE_MESSAGES */
+#       ifdef USE_LOCALE_MONETARY
+       if (! my_setlocale(LC_MONETARY,
+                        (!done && (lang || PerlEnv_getenv("LC_MONETARY")))
+                                 ? setlocale_init : NULL))
+        {
+           setlocale_failure = TRUE;
+        }
+#       endif /* USE_LOCALE_MONETARY */
     }
 
 #   endif /* LC_ALL */
 
 #endif /* !LOCALE_ENVIRON_REQUIRED */
 
+    /* We try each locale in the list until we get one that works, or exhaust
+     * the list */
+    trial_locales[0] = setlocale_init;
+    trial_locales_count = 1;
+    for (i= 0; i < trial_locales_count; i++) {
+        const char * trial_locale = trial_locales[i];
+
+        if (i > 0) {
+
+            /* XXX This is to preserve old behavior for LOCALE_ENVIRON_REQUIRED
+             * when i==0, but I (khw) don't think that behavior makes much
+             * sense */
+            setlocale_failure = FALSE;
+
+#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, "")) {
+                unsigned int j;
+
+                /* Note that this may change the locale, but we are going to do
+                 * that anyway just below */
+                system_default_locale = setlocale(LC_ALL, "");
+
+                /* Skip if invalid or it's already on the list of locales to
+                 * try */
+                if (! system_default_locale) {
+                    goto next_iteration;
+                }
+                for (j = 0; j < trial_locales_count; j++) {
+                    if (strEQ(system_default_locale, trial_locales[j])) {
+                        goto next_iteration;
+                    }
+                }
+
+                trial_locale = system_default_locale;
+            }
+#  endif /* WIN32 */
+#endif /* SYSTEM_DEFAULT_LOCALE */
+        }
+
 #ifdef LC_ALL
-    if (! setlocale(LC_ALL, setlocale_init))
-       setlocale_failure = TRUE;
+        if (! my_setlocale(LC_ALL, trial_locale)) {
+            setlocale_failure = TRUE;
+        }
+        else {
+            /* Since LC_ALL succeeded, it should have changed all the other
+             * categories it can to its value; so we massage things so that the
+             * 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
+             * the POSIX locale. */
+            trial_locale = NULL;
+        }
 #endif /* LC_ALL */
 
-    if (!setlocale_failure) {
+        if (!setlocale_failure) {
 #ifdef USE_LOCALE_CTYPE
-       Safefree(curctype);
-       if (! (curctype = setlocale(LC_CTYPE, setlocale_init)))
-           setlocale_failure = TRUE;
-       else
-           curctype = savepv(curctype);
+            Safefree(curctype);
+            if (! (curctype = my_setlocale(LC_CTYPE, trial_locale)))
+                setlocale_failure = TRUE;
+            else
+                curctype = savepv(curctype);
 #endif /* USE_LOCALE_CTYPE */
 #ifdef USE_LOCALE_COLLATE
-       Safefree(curcoll);
-       if (! (curcoll = setlocale(LC_COLLATE, setlocale_init)))
-           setlocale_failure = TRUE;
-       else
-           curcoll = savepv(curcoll);
+            Safefree(curcoll);
+            if (! (curcoll = my_setlocale(LC_COLLATE, trial_locale)))
+                setlocale_failure = TRUE;
+            else
+                curcoll = savepv(curcoll);
 #endif /* USE_LOCALE_COLLATE */
 #ifdef USE_LOCALE_NUMERIC
-       Safefree(curnum);
-       if (! (curnum = setlocale(LC_NUMERIC, setlocale_init)))
-           setlocale_failure = TRUE;
-       else
-           curnum = savepv(curnum);
+            Safefree(curnum);
+            if (! (curnum = my_setlocale(LC_NUMERIC, trial_locale)))
+                setlocale_failure = TRUE;
+            else
+                curnum = savepv(curnum);
 #endif /* USE_LOCALE_NUMERIC */
-    }
+#ifdef USE_LOCALE_MESSAGES
+            if (! (my_setlocale(LC_MESSAGES, trial_locale)))
+                setlocale_failure = TRUE;
+#endif /* USE_LOCALE_MESSAGES */
+#ifdef USE_LOCALE_MONETARY
+            if (! (my_setlocale(LC_MONETARY, trial_locale)))
+                setlocale_failure = TRUE;
+#endif /* USE_LOCALE_MONETARY */
+
+            if (! setlocale_failure) {  /* Success */
+                break;
+            }
+        }
 
-    if (setlocale_failure) {
-       char *p;
-       const bool locwarn = (printwarn > 1 ||
-                       (printwarn &&
-                        (!(p = PerlEnv_getenv("PERL_BADLANG")) || atoi(p))));
+        /* Here, something failed; will need to try a fallback. */
+        ok = 0;
 
-       if (locwarn) {
+        if (i == 0) {
+            unsigned int j;
+
+            if (locwarn) { /* Output failure info only on the first one */
 #ifdef LC_ALL
 
-           PerlIO_printf(Perl_error_log,
-              "perl: warning: Setting locale failed.\n");
+                PerlIO_printf(Perl_error_log,
+                "perl: warning: Setting locale failed.\n");
 
 #else /* !LC_ALL */
 
-           PerlIO_printf(Perl_error_log,
-              "perl: warning: Setting locale failed for the categories:\n\t");
+                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 ");
+                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 ");
+                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 ");
+                if (! curnum)
+                    PerlIO_printf(Perl_error_log, "LC_NUMERIC ");
 #endif /* USE_LOCALE_NUMERIC */
-           PerlIO_printf(Perl_error_log, "\n");
+                PerlIO_printf(Perl_error_log, "and possibly others\n");
 
 #endif /* LC_ALL */
 
-           PerlIO_printf(Perl_error_log,
-               "perl: warning: Please check that your locale settings:\n");
+                PerlIO_printf(Perl_error_log,
+                    "perl: warning: Please check that your locale settings:\n");
 
 #ifdef __GLIBC__
-           PerlIO_printf(Perl_error_log,
-                         "\tLANGUAGE = %c%s%c,\n",
-                         language ? '"' : '(',
-                         language ? language : "unset",
-                         language ? '"' : ')');
+                PerlIO_printf(Perl_error_log,
+                            "\tLANGUAGE = %c%s%c,\n",
+                            language ? '"' : '(',
+                            language ? language : "unset",
+                            language ? '"' : ')');
 #endif
 
-           PerlIO_printf(Perl_error_log,
-                         "\tLC_ALL = %c%s%c,\n",
-                         lc_all ? '"' : '(',
-                         lc_all ? lc_all : "unset",
-                         lc_all ? '"' : ')');
+                PerlIO_printf(Perl_error_log,
+                            "\tLC_ALL = %c%s%c,\n",
+                            lc_all ? '"' : '(',
+                            lc_all ? lc_all : "unset",
+                            lc_all ? '"' : ')');
 
 #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;
+                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);
+                }
+                }
 #else
-           PerlIO_printf(Perl_error_log,
-                         "\t(possibly more locale environment variables)\n");
+                PerlIO_printf(Perl_error_log,
+                            "\t(possibly more locale environment variables)\n");
 #endif
 
-           PerlIO_printf(Perl_error_log,
-                         "\tLANG = %c%s%c\n",
-                         lang ? '"' : '(',
-                         lang ? lang : "unset",
-                         lang ? '"' : ')');
+                PerlIO_printf(Perl_error_log,
+                            "\tLANG = %c%s%c\n",
+                            lang ? '"' : '(',
+                            lang ? lang : "unset",
+                            lang ? '"' : ')');
 
-           PerlIO_printf(Perl_error_log,
-                         "    are supported and installed on your system.\n");
-       }
+                PerlIO_printf(Perl_error_log,
+                            "    are supported and installed on your system.\n");
+            }
 
-#ifdef LC_ALL
+            /* Calculate what fallback locales to try.  We have avoided this
+             * until we have to, becuase failure is quite unlikely.  This will
+             * usually change the upper bound of the loop we are in.
+             *
+             * Since the system's default way of setting the locale has not
+             * found one that works, We use Perl's defined ordering: LC_ALL,
+             * LANG, and the C locale.  We don't try the same locale twice, so
+             * don't add to the list if already there.  (On POSIX systems, the
+             * LC_ALL element will likely be a repeat of the 0th element "",
+             * but there's no harm done by doing it explicitly */
+            if (lc_all) {
+                for (j = 0; j < trial_locales_count; j++) {
+                    if (strEQ(lc_all, trial_locales[j])) {
+                        goto done_lc_all;
+                    }
+                }
+                trial_locales[trial_locales_count++] = lc_all;
+            }
+          done_lc_all:
 
-       if (setlocale(LC_ALL, "C")) {
-           if (locwarn)
-               PerlIO_printf(Perl_error_log,
-      "perl: warning: Falling back to the standard locale (\"C\").\n");
-           ok = 0;
-       }
-       else {
-           if (locwarn)
-               PerlIO_printf(Perl_error_log,
-      "perl: warning: Failed to fall back to the standard locale (\"C\").\n");
-           ok = -1;
-       }
+            if (lang) {
+                for (j = 0; j < trial_locales_count; j++) {
+                    if (strEQ(lang, trial_locales[j])) {
+                        goto done_lang;
+                    }
+                }
+                trial_locales[trial_locales_count++] = lang;
+            }
+          done_lang:
+
+#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
+             * fallback possibility).  The "" is the same as the 0th element of
+             * the array, but the code at the loop above knows to treat it
+             * differently when not the 0th */
+            trial_locales[trial_locales_count++] = "";
+#endif
 
-#else /* ! LC_ALL */
+            for (j = 0; j < trial_locales_count; j++) {
+                if (strEQ("C", trial_locales[j])) {
+                    goto done_C;
+                }
+            }
+            trial_locales[trial_locales_count++] = "C";
 
-       if (0
-#ifdef USE_LOCALE_CTYPE
-           || !(curctype || setlocale(LC_CTYPE, "C"))
-#endif /* USE_LOCALE_CTYPE */
-#ifdef USE_LOCALE_COLLATE
-           || !(curcoll || setlocale(LC_COLLATE, "C"))
-#endif /* USE_LOCALE_COLLATE */
-#ifdef USE_LOCALE_NUMERIC
-           || !(curnum || setlocale(LC_NUMERIC, "C"))
-#endif /* USE_LOCALE_NUMERIC */
-           )
-       {
-           if (locwarn)
-               PerlIO_printf(Perl_error_log,
-      "perl: warning: Cannot fall back to the standard locale (\"C\").\n");
-           ok = -1;
-       }
+          done_C: ;
+        }   /* end of first time through the loop */
+
+#ifdef WIN32
+      next_iteration: ;
+#endif
+
+    }   /* end of looping through the trial locales */
+
+    if (ok < 1) {   /* If we tried to fallback */
+        const char* msg;
+        if (! setlocale_failure) {  /* fallback succeeded */
+           msg = "Falling back to";
+        }
+        else {  /* fallback failed */
 
-#endif /* ! LC_ALL */
+            /* We dropped off the end of the loop, so have to decrement i to
+             * get back to the value the last time through */
+            i--;
 
+            ok = -1;
+            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));
+            Safefree(curctype);
+            curctype = savepv(setlocale(LC_CTYPE, NULL));
 #endif /* USE_LOCALE_CTYPE */
 #ifdef USE_LOCALE_COLLATE
-       Safefree(curcoll);
-       curcoll = savepv(setlocale(LC_COLLATE, NULL));
+            Safefree(curcoll);
+            curcoll = savepv(setlocale(LC_COLLATE, NULL));
 #endif /* USE_LOCALE_COLLATE */
 #ifdef USE_LOCALE_NUMERIC
-       Safefree(curnum);
-       curnum = savepv(setlocale(LC_NUMERIC, NULL));
+            Safefree(curnum);
+            curnum = savepv(setlocale(LC_NUMERIC, NULL));
 #endif /* USE_LOCALE_NUMERIC */
-    }
-    else {
+        }
+
+        if (locwarn) {
+            const char * description;
+            const char * name = "";
+            if (strEQ(trial_locales[i], "C")) {
+                description = "the standard locale";
+                name = "C";
+            }
+#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 */
+            else {
+                description = "a fallback locale";
+                name = trial_locales[i];
+            }
+            if (name && strNE(name, "")) {
+                PerlIO_printf(Perl_error_log,
+                    "perl: warning: %s %s (\"%s\").\n", msg, description, name);
+            }
+            else {
+                PerlIO_printf(Perl_error_log,
+                                   "perl: warning: %s %s.\n", msg, description);
+            }
+        }
+    } /* End of tried to fallback */
 
 #ifdef USE_LOCALE_CTYPE
     new_ctype(curctype);
@@ -577,18 +903,14 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     new_numeric(curnum);
 #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 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 PerlIO :utf8 layer
-        on STDIN, STDOUT, STDERR, _and_ the default open discipline.
-      */
-        PL_utf8locale = is_cur_LC_category_utf8(LC_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
+     * PerlIO :utf8 layer on STDIN, STDOUT, STDERR, _and_ the default open
+     * discipline.  */
+    PL_utf8locale = _is_cur_LC_category_utf8(LC_CTYPE);
+
     /* Set PL_unicode to $ENV{PERL_UNICODE} if using PerlIO.
        This is an alternative to using the -C command line switch
        (the -C if present will override this). */
@@ -610,11 +932,14 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
     Safefree(curnum);
 #endif /* USE_LOCALE_NUMERIC */
 
+#else  /* !USE_LOCALE */
+    PERL_UNUSED_ARG(printwarn);
 #endif /* USE_LOCALE */
 
     return ok;
 }
 
+
 #ifdef USE_LOCALE_COLLATE
 
 /*
@@ -628,7 +953,6 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
 char *
 Perl_mem_collxfrm(pTHX_ const char *s, STRLEN len, STRLEN *xlen)
 {
-    dVAR;
     char *xbuf;
     STRLEN xAlloc, xin, xout; /* xalloc is a reserved word in VC */
 
@@ -680,8 +1004,8 @@ Perl_mem_collxfrm(pTHX_ const char *s, STRLEN len, STRLEN *xlen)
 
 #ifdef USE_LOCALE
 
-STATIC bool
-S_is_cur_LC_category_utf8(pTHX_ int category)
+bool
+Perl__is_cur_LC_category_utf8(pTHX_ int category)
 {
     /* Returns TRUE if the current locale for 'category' is UTF-8; FALSE
      * otherwise. 'category' may not be LC_ALL.  If the platform doesn't have
@@ -699,19 +1023,24 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
     /* First dispose of the trivial cases */
     save_input_locale = setlocale(category, NULL);
     if (! save_input_locale) {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                              "Could not find current locale for category %d\n",
+                              category));
         return FALSE;   /* XXX maybe should croak */
     }
     save_input_locale = stdize_locale(savepv(save_input_locale));
-    if ((*save_input_locale == 'C' && save_input_locale[1] == '\0')
-        || strEQ(save_input_locale, "POSIX"))
-    {
+    if (isNAME_C_OR_POSIX(save_input_locale)) {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                              "Current locale for category %d is %s\n",
+                              category, save_input_locale));
         Safefree(save_input_locale);
         return FALSE;
     }
 
-#if defined(USE_LOCALE_CTYPE) && (defined(MB_CUR_MAX) || defined(HAS_NL_LANGINFO) && defined(CODESET))
+#if defined(USE_LOCALE_CTYPE)    \
+    && (defined(MB_CUR_MAX) || (defined(HAS_NL_LANGINFO) && defined(CODESET)))
 
-    { /* Next try MB_CUR_MAX or nl_langinfo if available */
+    { /* Next try nl_langinfo or MB_CUR_MAX if available */
 
         char *save_ctype_locale = NULL;
         bool is_utf8;
@@ -721,6 +1050,8 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
             /* Get the current LC_CTYPE locale */
             save_ctype_locale = stdize_locale(savepv(setlocale(LC_CTYPE, NULL)));
             if (! save_ctype_locale) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                               "Could not find current locale for LC_CTYPE\n"));
                 goto cant_use_nllanginfo;
             }
 
@@ -733,29 +1064,61 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
                 save_ctype_locale = NULL;
             }
             else if (! setlocale(LC_CTYPE, save_input_locale)) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                                    "Could not change LC_CTYPE locale to %s\n",
+                                    save_input_locale));
                 Safefree(save_ctype_locale);
                 goto cant_use_nllanginfo;
             }
         }
 
+        DEBUG_L(PerlIO_printf(Perl_debug_log, "Current LC_CTYPE locale=%s\n",
+                                              save_input_locale));
+
         /* Here the current LC_CTYPE is set to the locale of the category whose
-         * information is desired.  This means that MB_CUR_MAX and
-         * nl_langinfo() should give the correct results */
+         * information is desired.  This means that nl_langinfo() and MB_CUR_MAX
+         * should give the correct results */
 
-#   ifdef MB_CUR_MAX
+#   if defined(HAS_NL_LANGINFO) && defined(CODESET)
+        {
+            char *codeset = savepv(nl_langinfo(CODESET));
+            if (codeset && strNE(codeset, "")) {
 
-        /* If we switched LC_CTYPE, switch back */
-        if (save_ctype_locale) {
-            setlocale(LC_CTYPE, save_ctype_locale);
-            Safefree(save_ctype_locale);
+                /* If we switched LC_CTYPE, switch back */
+                if (save_ctype_locale) {
+                    setlocale(LC_CTYPE, save_ctype_locale);
+                    Safefree(save_ctype_locale);
+                }
+
+                is_utf8 = foldEQ(codeset, STR_WITH_LEN("UTF-8"))
+                        || 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;
+            }
+            Safefree(codeset);
         }
 
+#   endif
+#   ifdef MB_CUR_MAX
+
+        /* Here, either we don't have nl_langinfo, or it didn't return a
+         * codeset.  Try MB_CUR_MAX */
+
         /* Standard UTF-8 needs at least 4 bytes to represent the maximum
          * 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;
 
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                              "\tMB_CUR_MAX=%d; ?UTF8 locale=%d\n",
+                                   (int) MB_CUR_MAX,      is_utf8));
+
         Safefree(save_input_locale);
 
 #       ifdef HAS_MBTOWC
@@ -766,39 +1129,30 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
          * result */
         if (is_utf8) {
             wchar_t wc;
-            if (mbtowc(&wc, HYPHEN_UTF8, strlen(HYPHEN_UTF8))
+            GCC_DIAG_IGNORE(-Wunused-result);
+            (void) mbtowc(&wc, NULL, 0);    /* Reset any shift state */
+            GCC_DIAG_RESTORE;
+            errno = 0;
+            if ((size_t)mbtowc(&wc, HYPHEN_UTF8, strlen(HYPHEN_UTF8))
                                                         != strlen(HYPHEN_UTF8)
                 || wc != (wchar_t) 0x2010)
             {
                 is_utf8 = FALSE;
+                DEBUG_L(PerlIO_printf(Perl_debug_log, "\thyphen=U+%x\n", 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));
             }
         }
-
 #       endif
 
-        return is_utf8;
-#else
-#   if defined(HAS_NL_LANGINFO) && defined(CODESET)
-        {
-            char *codeset = savepv(nl_langinfo(CODESET));
-            if (codeset) {
-
-                /* If we switched LC_CTYPE, switch back */
-                if (save_ctype_locale) {
-                    setlocale(LC_CTYPE, save_ctype_locale);
-                    Safefree(save_ctype_locale);
-                }
-
-                is_utf8 = foldEQ(codeset, STR_WITH_LEN("UTF-8"))
-                        || foldEQ(codeset, STR_WITH_LEN("UTF8"));
-
-                Safefree(codeset);
-                Safefree(save_input_locale);
-                return is_utf8;
-            }
+        /* If we switched LC_CTYPE, switch back */
+        if (save_ctype_locale) {
+            setlocale(LC_CTYPE, save_ctype_locale);
+            Safefree(save_ctype_locale);
         }
 
-#   endif
+        return is_utf8;
 #   endif
     }
 
@@ -831,9 +1185,15 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
             }
             if (*(name) == '8') {
                 Safefree(save_input_locale);
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                                      "Locale %s ends with UTF-8 in name\n",
+                                      save_input_locale));
                 return TRUE;
             }
         }
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                              "Locale %s doesn't end with UTF-8 in name\n",
+                                save_input_locale));
     }
 
 #ifdef WIN32
@@ -845,6 +1205,9 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
         && *(save_input_locale + final_pos - 3) == '5'
         && *(save_input_locale + final_pos - 4) == '6')
     {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                        "Locale %s ends with 10056 in name, is UTF-8 locale\n",
+                        save_input_locale));
         Safefree(save_input_locale);
         return TRUE;
     }
@@ -852,6 +1215,9 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
 
     /* Other common encodings are the ISO 8859 series, which aren't UTF-8 */
     if (instr(save_input_locale, "8859")) {
+        DEBUG_L(PerlIO_printf(Perl_debug_log,
+                             "Locale %s has 8859 in name, not UTF-8 locale\n",
+                             save_input_locale));
         Safefree(save_input_locale);
         return FALSE;
     }
@@ -881,11 +1247,16 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
             save_monetary_locale = stdize_locale(savepv(setlocale(LC_MONETARY,
                                                                   NULL)));
             if (! save_monetary_locale) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                            "Could not find current locale for LC_MONETARY\n"));
                 goto cant_use_monetary;
             }
 
             if (strNE(save_monetary_locale, save_input_locale)) {
                 if (! setlocale(LC_MONETARY, save_input_locale)) {
+                    DEBUG_L(PerlIO_printf(Perl_debug_log,
+                                "Could not change LC_MONETARY locale to %s\n",
+                                                            save_input_locale));
                     Safefree(save_monetary_locale);
                     goto cant_use_monetary;
                 }
@@ -897,9 +1268,13 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
 
         if (lc && lc->currency_symbol) {
             if (! is_utf8_string((U8 *) lc->currency_symbol, 0)) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log,
+                            "Currency symbol for %s is not legal UTF-8\n",
+                                        save_input_locale));
                 illegal_utf8 = TRUE;
             }
             else if (is_ascii_string((U8 *) lc->currency_symbol, 0)) {
+                DEBUG_L(PerlIO_printf(Perl_debug_log, "Currency symbol for %s contains only ASCII; can't use for determining if UTF-8 locale\n", save_input_locale));
                 only_ascii = TRUE;
             }
         }
@@ -917,6 +1292,9 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
          * UTF-8.  (We can't really tell if the locale is UTF-8 or not if the
          * symbol is just a '$', so we err on the side of it not being UTF-8)
          * */
+        DEBUG_L(PerlIO_printf(Perl_debug_log, "\tis_utf8=%d\n", (illegal_utf8)
+                                                               ? FALSE
+                                                               : ! only_ascii));
         return (illegal_utf8)
                 ? FALSE
                 : ! only_ascii;
@@ -1003,12 +1381,68 @@ S_is_cur_LC_category_utf8(pTHX_ int category)
 
 #endif
 
+    DEBUG_L(PerlIO_printf(Perl_debug_log,
+                          "Assuming locale %s is not a UTF-8 locale\n",
+                                    save_input_locale));
     Safefree(save_input_locale);
     return FALSE;
 }
 
 #endif
 
+
+bool
+Perl__is_in_locale_category(pTHX_ const bool compiling, const int category)
+{
+    dVAR;
+    /* Internal function which returns if we are in the scope of a pragma that
+     * enables the locale category 'category'.  'compiling' should indicate if
+     * this is during the compilation phase (TRUE) or not (FALSE). */
+
+    const COP * const cop = (compiling) ? &PL_compiling : PL_curcop;
+
+    SV *categories = cop_hints_fetch_pvs(cop, "locale", 0);
+    if (! categories || categories == &PL_sv_placeholder) {
+        return FALSE;
+    }
+
+    /* The pseudo-category 'not_characters' is -1, so just add 1 to each to get
+     * a valid unsigned */
+    assert(category >= -1);
+    return cBOOL(SvUV(categories) & (1U << (category + 1)));
+}
+
+char *
+Perl_my_strerror(pTHX_ const int errnum) {
+
+    /* Uses C locale for the error text unless within scope of 'use locale' for
+     * LC_MESSAGES */
+
+#ifdef USE_LOCALE_MESSAGES
+    if (! IN_LC(LC_MESSAGES)) {
+        char * save_locale = setlocale(LC_MESSAGES, NULL);
+        if (! isNAME_C_OR_POSIX(save_locale)) {
+            char *errstr;
+
+            /* The next setlocale likely will zap this, so create a copy */
+            save_locale = savepv(save_locale);
+
+            setlocale(LC_MESSAGES, "C");
+
+            /* This points to the static space in Strerror, with all its
+             * limitations */
+            errstr = Strerror(errnum);
+
+            setlocale(LC_MESSAGES, save_locale);
+            Safefree(save_locale);
+            return errstr;
+        }
+    }
+#endif
+
+    return Strerror(errnum);
+}
+
 /*
  * Local variables:
  * c-indentation-style: bsd