diff mbox series

locale: Add LOCPATH diagnostics to the locale program

Message ID 87sgu8eozk.fsf@oldenburg2.str.redhat.com
State New
Headers show
Series locale: Add LOCPATH diagnostics to the locale program | expand

Commit Message

Florian Weimer April 23, 2019, 3:10 p.m. UTC
The implementation of quote_string is based on support_quote_blob.

2019-04-23  Florian Weimer  <fweimer@redhat.com>

	locale: Add LOCPATH diagnostics to the locale program.
	* locale/programs/locale.c (setlocale_failed): New variable.
	(try_setlocale): New function.
	(quote_string): Likewise.
	(setlocale_diagnostics): Likewise.
	(main): Call try_setlocale instead of setlocale.  Call
	setlocale_diagnostics.
	* locale/Makefile (tests-special): Add tst-locale-locpath.out.
	(tst-locale-locpath.out): New target.
	* locale/tst-locale-locpath.sh: New file.

Comments

Carlos O'Donell April 23, 2019, 4:08 p.m. UTC | #1
On 4/23/19 11:10 AM, Florian Weimer wrote:
> The implementation of quote_string is based on support_quote_blob.
> 

LGTM.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>


> 2019-04-23  Florian Weimer  <fweimer@redhat.com>
> 
> 	locale: Add LOCPATH diagnostics to the locale program.
> 	* locale/programs/locale.c (setlocale_failed): New variable.
> 	(try_setlocale): New function.
> 	(quote_string): Likewise.
> 	(setlocale_diagnostics): Likewise.
> 	(main): Call try_setlocale instead of setlocale.  Call
> 	setlocale_diagnostics.
> 	* locale/Makefile (tests-special): Add tst-locale-locpath.out.
> 	(tst-locale-locpath.out): New target.
> 	* locale/tst-locale-locpath.sh: New file.
> 
> diff --git a/locale/Makefile b/locale/Makefile
> index 764e751c36..6822b795dd 100644
> --- a/locale/Makefile
> +++ b/locale/Makefile
> @@ -28,6 +28,7 @@ routines	= setlocale findlocale loadlocale loadarchive \
>   		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
>   		  newlocale duplocale freelocale uselocale
>   tests		= tst-C-locale tst-locname tst-duplocale
> +tests-special	= $(objpfx)tst-locale-locpath.out

OK.

>   categories	= ctype messages monetary numeric time paper name \
>   		  address telephone measurement identification collate
>   aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
> @@ -107,3 +108,7 @@ cpp-srcs-left := $(localedef-modules) $(localedef-aux) $(locale-modules) \
>   		 $(lib-modules)
>   lib := locale-programs
>   include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
> +
> +$(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale
> +	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper)' '$(test-wrapper-env)' > $@; \
> +	$(evaluate-test)

OK.

> diff --git a/locale/programs/locale.c b/locale/programs/locale.c
> index 8af0d78a77..6eae3175bb 100644
> --- a/locale/programs/locale.c
> +++ b/locale/programs/locale.c
> @@ -173,6 +173,9 @@ static int write_archive_locales (void **all_datap, char *linebuf);
>   static void write_charmaps (void);
>   static void show_locale_vars (void);
>   static void show_info (const char *name);
> +static void try_setlocale (int category, const char *category_name);
> +static char *quote_string (const char *input);
> +static void setlocale_diagnostics (void);

OK.

>   
>   
>   int
> @@ -186,10 +189,8 @@ main (int argc, char *argv[])
>   
>     /* Set locale.  Do not set LC_ALL because the other categories must
>        not be affected (according to POSIX.2).  */
> -  if (setlocale (LC_CTYPE, "") == NULL)
> -    error (0, errno, gettext ("Cannot set LC_CTYPE to default locale"));
> -  if (setlocale (LC_MESSAGES, "") == NULL)
> -    error (0, errno, gettext ("Cannot set LC_MESSAGES to default locale"));
> +  try_setlocale (LC_CTYPE, "LC_CTYPE");
> +  try_setlocale (LC_MESSAGES, "LC_MESSAGES");

OK.

>   
>     /* Initialize the message catalog.  */
>     textdomain (PACKAGE);
> @@ -200,9 +201,8 @@ main (int argc, char *argv[])
>     /* `-a' requests the names of all available locales.  */
>     if (do_all != 0)
>       {
> -      if (setlocale (LC_COLLATE, "") == NULL)
> -	error (0, errno,
> -	       gettext ("Cannot set LC_COLLATE to default locale"));
> +      setlocale_diagnostics ();
> +      try_setlocale (LC_COLLATE, "LC_COLLATE");

OK.

>         write_locales ();
>         exit (EXIT_SUCCESS);
>       }
> @@ -211,14 +211,15 @@ main (int argc, char *argv[])
>        used for the -f argument to localedef(1).  */
>     if (do_charmaps != 0)
>       {
> +      setlocale_diagnostics ();

OK.

>         write_charmaps ();
>         exit (EXIT_SUCCESS);
>       }
>   
>     /* Specific information about the current locale are requested.
>        Change to this locale now.  */
> -  if (setlocale (LC_ALL, "") == NULL)
> -    error (0, errno, gettext ("Cannot set LC_ALL to default locale"));
> +  try_setlocale (LC_ALL, "LC_ALL");
> +  setlocale_diagnostics ();

OK.

>   
>     /* If no real argument is given we have to print the contents of the
>        current locale definition variables.  These are LANG and the LC_*.  */
> @@ -983,3 +984,121 @@ show_info (const char *name)
>        For testing and perhaps advanced use allow some more symbols.  */
>     locale_special (name, show_category_name, show_keyword_name);
>   }
> +
> +/* Set to true by try_setlocale if setlocale fails.  Used by
> +   setlocale_diagnostics.  */
> +static bool setlocale_failed;

OK.

> +
> +/* Call setlocale, with non-fatal error reporting.  */

OK. Like this much better, lets you get through all the problems and then
print some better diagnostics.

> +static void
> +try_setlocale (int category, const char *category_name)
> +{
> +  if (setlocale (category, "") == NULL)
> +    {
> +      error (0, errno, gettext ("Cannot set %s to default locale"),
> +	     category_name);
> +      setlocale_failed = true;
> +    }
> +}
> +
> +/* Return a quoted version of the passed string, or NULL on error.  */
> +static char *
> +quote_string (const char *input)
> +{
> +  char *buffer;
> +  size_t length;
> +  FILE *stream = open_memstream (&buffer, &length);
> +  if (stream == NULL)
> +    return NULL;
> +
> +  while (true)
> +    {
> +      unsigned char ch = *input++;
> +      if (ch == '\0')
> +	break;
> +
> +      /* Use C backslash escapes for those control characters for
> +         which they are defined.  */
> +      switch (ch)
> +        {
> +          case '\a':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('a', stream);
> +            break;
> +          case '\b':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('b', stream);
> +            break;
> +          case '\f':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('f', stream);
> +            break;
> +          case '\n':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('n', stream);
> +            break;
> +          case '\r':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('r', stream);
> +            break;
> +          case '\t':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('t', stream);
> +            break;
> +          case '\v':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked ('v', stream);
> +            break;
> +          case '\\':
> +          case '\'':
> +          case '\"':
> +            putc_unlocked ('\\', stream);
> +            putc_unlocked (ch, stream);
> +            break;
> +        default:
> +          if (ch < ' ' || ch > '~')
> +            /* Use octal sequences because they are fixed width,
> +               unlike hexadecimal sequences.  */
> +            fprintf (stream, "\\%03o", ch);
> +          else
> +            putc_unlocked (ch, stream);
> +        }
> +    }
> +
> +  if (ferror (stream))
> +    {
> +      fclose (stream);
> +      free (buffer);
> +      return NULL;
> +    }
> +  if (fclose (stream) != 0)
> +    {
> +      free (buffer);
> +      return NULL;
> +    }
> +
> +  return buffer;
> +}

OK.

> +
> +/* Print additional information if there was a setlocale error (during
> +   try_setlocale).  */

OK. Great, we print LOCPATH value as part of the diagnostics to help users.

> +static void
> +setlocale_diagnostics (void)
> +{
> +  if (setlocale_failed)
> +    {
> +      const char *locpath = getenv ("LOCPATH");

OK.

> +      if (locpath != NULL)
> +	{
> +	  char *quoted = quote_string (locpath);
> +	  if (quoted != NULL)
> +	    fprintf (stderr,
> +		     gettext ("\
> +warning: The LOCPATH variable is set to \"%s\"\n"),
> +		     quoted);
> +	  else
> +	    fputs ("warning: The LOCPATH variable is set\n", stderr);
> +	  free (quoted);
> +	}
> +    }
> +}
> diff --git a/locale/tst-locale-locpath.sh b/locale/tst-locale-locpath.sh
> new file mode 100644
> index 0000000000..b83de90a39
> --- /dev/null
> +++ b/locale/tst-locale-locpath.sh
> @@ -0,0 +1,83 @@
> +#!/bin/sh
> +# Test that locale prints LOCPATH on failure.

OK.

> +# Copyright (C) 2019 Free Software Foundation, Inc.
> +# This file is part of the GNU C Library.
> +
> +# The GNU C Library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +
> +# The GNU C Library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with the GNU C Library; if not, see
> +# <http://www.gnu.org/licenses/>.
> +
> +set -ex
> +
> +common_objpfx=$1
> +test_wrapper_env=$2
> +run_program_env=$3
> +
> +LIBPATH="$common_objpfx"
> +
> +testroot="${common_objpfx}locale/tst-locale-locpath-directory"
> +cleanup () {
> +    rm -rf "$testroot"
> +}
> +trap cleanup 0
> +
> +rm -rf "$testroot"
> +mkdir -p $testroot
> +
> +unset LANG
> +
> +${test_wrapper_env} \
> +${run_program_env} LC_ALL=invalid-locale LOCPATH=does-not-exist \
> +${common_objpfx}elf/ld.so --library-path "$LIBPATH" \
> +  "${common_objpfx}locale/locale" \
> +  > "$testroot/stdout" 2> "$testroot/stderr"

OK, test running locale.

> +
> +echo "* standard error"
> +cat "$testroot/stderr"
> +echo "* standard output"
> +cat "$testroot/stdout"
> +
> +cat > "$testroot/stderr-expected" <<EOF
> +${common_objpfx}locale/locale: Cannot set LC_CTYPE to default locale: No such file or directory
> +${common_objpfx}locale/locale: Cannot set LC_MESSAGES to default locale: No such file or directory
> +${common_objpfx}locale/locale: Cannot set LC_ALL to default locale: No such file or directory
> +warning: The LOCPATH variable is set to "does-not-exist"
> +EOF
> +
> +cat > "$testroot/stdout-expected" <<EOF
> +LANG=
> +LC_CTYPE="invalid-locale"
> +LC_NUMERIC="invalid-locale"
> +LC_TIME="invalid-locale"
> +LC_COLLATE="invalid-locale"
> +LC_MONETARY="invalid-locale"
> +LC_MESSAGES="invalid-locale"
> +LC_PAPER="invalid-locale"
> +LC_NAME="invalid-locale"
> +LC_ADDRESS="invalid-locale"
> +LC_TELEPHONE="invalid-locale"
> +LC_MEASUREMENT="invalid-locale"
> +LC_IDENTIFICATION="invalid-locale"
> +LC_ALL=invalid-locale
> +EOF
> +
> +errors=0
> +if ! cmp -s "$testroot/stderr-expected" "$testroot/stderr" ; then
> +    echo "error: standard error not correct"
> +    errors=1
> +fi
> +if ! cmp -s "$testroot/stdout-expected" "$testroot/stdout" ; then
> +    echo "error: standard output not correct"
> +    errors=1
> +fi
> +exit $errors

OK.

>
Joseph Myers April 23, 2019, 9:57 p.m. UTC | #2
On Tue, 23 Apr 2019, Florian Weimer wrote:

> diff --git a/locale/Makefile b/locale/Makefile
> index 764e751c36..6822b795dd 100644
> --- a/locale/Makefile
> +++ b/locale/Makefile
> @@ -28,6 +28,7 @@ routines	= setlocale findlocale loadlocale loadarchive \
>  		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
>  		  newlocale duplocale freelocale uselocale
>  tests		= tst-C-locale tst-locname tst-duplocale
> +tests-special	= $(objpfx)tst-locale-locpath.out
>  categories	= ctype messages monetary numeric time paper name \
>  		  address telephone measurement identification collate
>  aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
> @@ -107,3 +108,7 @@ cpp-srcs-left := $(localedef-modules) $(localedef-aux) $(locale-modules) \
>  		 $(lib-modules)
>  lib := locale-programs
>  include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
> +
> +$(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale
> +	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper)' '$(test-wrapper-env)' > $@; \
> +	$(evaluate-test)

This is missing run-built-tests conditionals so wrongly tries to run the 
test unconditionally for cross-compilation.

https://sourceware.org/ml/libc-testresults/2019-q2/msg00081.html
Carlos O'Donell April 23, 2019, 9:59 p.m. UTC | #3
On 4/23/19 5:57 PM, Joseph Myers wrote:
> On Tue, 23 Apr 2019, Florian Weimer wrote:
> 
>> diff --git a/locale/Makefile b/locale/Makefile
>> index 764e751c36..6822b795dd 100644
>> --- a/locale/Makefile
>> +++ b/locale/Makefile
>> @@ -28,6 +28,7 @@ routines	= setlocale findlocale loadlocale loadarchive \
>>   		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
>>   		  newlocale duplocale freelocale uselocale
>>   tests		= tst-C-locale tst-locname tst-duplocale
>> +tests-special	= $(objpfx)tst-locale-locpath.out
>>   categories	= ctype messages monetary numeric time paper name \
>>   		  address telephone measurement identification collate
>>   aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
>> @@ -107,3 +108,7 @@ cpp-srcs-left := $(localedef-modules) $(localedef-aux) $(locale-modules) \
>>   		 $(lib-modules)
>>   lib := locale-programs
>>   include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
>> +
>> +$(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale
>> +	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper)' '$(test-wrapper-env)' > $@; \
>> +	$(evaluate-test)
> 
> This is missing run-built-tests conditionals so wrongly tries to run the
> test unconditionally for cross-compilation.
> 
> https://sourceware.org/ml/libc-testresults/2019-q2/msg00081.html
> 

I'll watch for that in future reviews.

Thanks for catching this.
Florian Weimer April 24, 2019, 6:16 a.m. UTC | #4
* Joseph Myers:

> On Tue, 23 Apr 2019, Florian Weimer wrote:
>
>> diff --git a/locale/Makefile b/locale/Makefile
>> index 764e751c36..6822b795dd 100644
>> --- a/locale/Makefile
>> +++ b/locale/Makefile
>> @@ -28,6 +28,7 @@ routines	= setlocale findlocale loadlocale loadarchive \
>>  		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
>>  		  newlocale duplocale freelocale uselocale
>>  tests		= tst-C-locale tst-locname tst-duplocale
>> +tests-special	= $(objpfx)tst-locale-locpath.out
>>  categories	= ctype messages monetary numeric time paper name \
>>  		  address telephone measurement identification collate
>>  aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
>> @@ -107,3 +108,7 @@ cpp-srcs-left := $(localedef-modules) $(localedef-aux) $(locale-modules) \
>>  		 $(lib-modules)
>>  lib := locale-programs
>>  include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
>> +
>> +$(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale
>> +	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper)' '$(test-wrapper-env)' > $@; \
>> +	$(evaluate-test)
>
> This is missing run-built-tests conditionals so wrongly tries to run the 
> test unconditionally for cross-compilation.

Oh.  I will try to remember this aspect of tests-special.  I fixed it
with the patch below.  Committed.

Thanks,
Florian

locale/tst-locale-locpath: Run test only for $(run-built-tests) == yes

2019-04-24  Florian Weimer  <fweimer@redhat.com>

	* locale/Makefile (tests-special): Guard setting by
	$(run-built-tests) == yes, otherwise tst-locale-locpath attempts
	to run while cross-compiling.

diff --git a/locale/Makefile b/locale/Makefile
index 6822b795dd..0ad99ecabf 100644
--- a/locale/Makefile
+++ b/locale/Makefile
@@ -28,7 +28,6 @@ routines	= setlocale findlocale loadlocale loadarchive \
 		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
 		  newlocale duplocale freelocale uselocale
 tests		= tst-C-locale tst-locname tst-duplocale
-tests-special	= $(objpfx)tst-locale-locpath.out
 categories	= ctype messages monetary numeric time paper name \
 		  address telephone measurement identification collate
 aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
@@ -63,6 +62,10 @@ lib-modules		:= charmap-dir simple-hash xmalloc xstrdup \
 GPERF = gperf
 GPERFFLAGS = -acCgopt -k1,2,5,9,$$ -L ANSI-C
 
+ifeq ($(run-built-tests),yes)
+tests-special += $(objpfx)tst-locale-locpath.out
+endif
+
 include ../Rules
 
 CFLAGS-md5.c += -I../crypt
diff mbox series

Patch

diff --git a/locale/Makefile b/locale/Makefile
index 764e751c36..6822b795dd 100644
--- a/locale/Makefile
+++ b/locale/Makefile
@@ -28,6 +28,7 @@  routines	= setlocale findlocale loadlocale loadarchive \
 		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
 		  newlocale duplocale freelocale uselocale
 tests		= tst-C-locale tst-locname tst-duplocale
+tests-special	= $(objpfx)tst-locale-locpath.out
 categories	= ctype messages monetary numeric time paper name \
 		  address telephone measurement identification collate
 aux		= $(categories:%=lc-%) $(categories:%=C-%) SYS_libc C_name \
@@ -107,3 +108,7 @@  cpp-srcs-left := $(localedef-modules) $(localedef-aux) $(locale-modules) \
 		 $(lib-modules)
 lib := locale-programs
 include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left))
+
+$(objpfx)tst-locale-locpath.out : tst-locale-locpath.sh $(objpfx)locale
+	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper)' '$(test-wrapper-env)' > $@; \
+	$(evaluate-test)
diff --git a/locale/programs/locale.c b/locale/programs/locale.c
index 8af0d78a77..6eae3175bb 100644
--- a/locale/programs/locale.c
+++ b/locale/programs/locale.c
@@ -173,6 +173,9 @@  static int write_archive_locales (void **all_datap, char *linebuf);
 static void write_charmaps (void);
 static void show_locale_vars (void);
 static void show_info (const char *name);
+static void try_setlocale (int category, const char *category_name);
+static char *quote_string (const char *input);
+static void setlocale_diagnostics (void);
 
 
 int
@@ -186,10 +189,8 @@  main (int argc, char *argv[])
 
   /* Set locale.  Do not set LC_ALL because the other categories must
      not be affected (according to POSIX.2).  */
-  if (setlocale (LC_CTYPE, "") == NULL)
-    error (0, errno, gettext ("Cannot set LC_CTYPE to default locale"));
-  if (setlocale (LC_MESSAGES, "") == NULL)
-    error (0, errno, gettext ("Cannot set LC_MESSAGES to default locale"));
+  try_setlocale (LC_CTYPE, "LC_CTYPE");
+  try_setlocale (LC_MESSAGES, "LC_MESSAGES");
 
   /* Initialize the message catalog.  */
   textdomain (PACKAGE);
@@ -200,9 +201,8 @@  main (int argc, char *argv[])
   /* `-a' requests the names of all available locales.  */
   if (do_all != 0)
     {
-      if (setlocale (LC_COLLATE, "") == NULL)
-	error (0, errno,
-	       gettext ("Cannot set LC_COLLATE to default locale"));
+      setlocale_diagnostics ();
+      try_setlocale (LC_COLLATE, "LC_COLLATE");
       write_locales ();
       exit (EXIT_SUCCESS);
     }
@@ -211,14 +211,15 @@  main (int argc, char *argv[])
      used for the -f argument to localedef(1).  */
   if (do_charmaps != 0)
     {
+      setlocale_diagnostics ();
       write_charmaps ();
       exit (EXIT_SUCCESS);
     }
 
   /* Specific information about the current locale are requested.
      Change to this locale now.  */
-  if (setlocale (LC_ALL, "") == NULL)
-    error (0, errno, gettext ("Cannot set LC_ALL to default locale"));
+  try_setlocale (LC_ALL, "LC_ALL");
+  setlocale_diagnostics ();
 
   /* If no real argument is given we have to print the contents of the
      current locale definition variables.  These are LANG and the LC_*.  */
@@ -983,3 +984,121 @@  show_info (const char *name)
      For testing and perhaps advanced use allow some more symbols.  */
   locale_special (name, show_category_name, show_keyword_name);
 }
+
+/* Set to true by try_setlocale if setlocale fails.  Used by
+   setlocale_diagnostics.  */
+static bool setlocale_failed;
+
+/* Call setlocale, with non-fatal error reporting.  */
+static void
+try_setlocale (int category, const char *category_name)
+{
+  if (setlocale (category, "") == NULL)
+    {
+      error (0, errno, gettext ("Cannot set %s to default locale"),
+	     category_name);
+      setlocale_failed = true;
+    }
+}
+
+/* Return a quoted version of the passed string, or NULL on error.  */
+static char *
+quote_string (const char *input)
+{
+  char *buffer;
+  size_t length;
+  FILE *stream = open_memstream (&buffer, &length);
+  if (stream == NULL)
+    return NULL;
+
+  while (true)
+    {
+      unsigned char ch = *input++;
+      if (ch == '\0')
+	break;
+
+      /* Use C backslash escapes for those control characters for
+         which they are defined.  */
+      switch (ch)
+        {
+          case '\a':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('a', stream);
+            break;
+          case '\b':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('b', stream);
+            break;
+          case '\f':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('f', stream);
+            break;
+          case '\n':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('n', stream);
+            break;
+          case '\r':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('r', stream);
+            break;
+          case '\t':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('t', stream);
+            break;
+          case '\v':
+            putc_unlocked ('\\', stream);
+            putc_unlocked ('v', stream);
+            break;
+          case '\\':
+          case '\'':
+          case '\"':
+            putc_unlocked ('\\', stream);
+            putc_unlocked (ch, stream);
+            break;
+        default:
+          if (ch < ' ' || ch > '~')
+            /* Use octal sequences because they are fixed width,
+               unlike hexadecimal sequences.  */
+            fprintf (stream, "\\%03o", ch);
+          else
+            putc_unlocked (ch, stream);
+        }
+    }
+
+  if (ferror (stream))
+    {
+      fclose (stream);
+      free (buffer);
+      return NULL;
+    }
+  if (fclose (stream) != 0)
+    {
+      free (buffer);
+      return NULL;
+    }
+
+  return buffer;
+}
+
+/* Print additional information if there was a setlocale error (during
+   try_setlocale).  */
+static void
+setlocale_diagnostics (void)
+{
+  if (setlocale_failed)
+    {
+      const char *locpath = getenv ("LOCPATH");
+      if (locpath != NULL)
+	{
+	  char *quoted = quote_string (locpath);
+	  if (quoted != NULL)
+	    fprintf (stderr,
+		     gettext ("\
+warning: The LOCPATH variable is set to \"%s\"\n"),
+		     quoted);
+	  else
+	    fputs ("warning: The LOCPATH variable is set\n", stderr);
+	  free (quoted);
+	}
+    }
+}
diff --git a/locale/tst-locale-locpath.sh b/locale/tst-locale-locpath.sh
new file mode 100644
index 0000000000..b83de90a39
--- /dev/null
+++ b/locale/tst-locale-locpath.sh
@@ -0,0 +1,83 @@ 
+#!/bin/sh
+# Test that locale prints LOCPATH on failure.
+# Copyright (C) 2019 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+set -ex
+
+common_objpfx=$1
+test_wrapper_env=$2
+run_program_env=$3
+
+LIBPATH="$common_objpfx"
+
+testroot="${common_objpfx}locale/tst-locale-locpath-directory"
+cleanup () {
+    rm -rf "$testroot"
+}
+trap cleanup 0
+
+rm -rf "$testroot"
+mkdir -p $testroot
+
+unset LANG
+
+${test_wrapper_env} \
+${run_program_env} LC_ALL=invalid-locale LOCPATH=does-not-exist \
+${common_objpfx}elf/ld.so --library-path "$LIBPATH" \
+  "${common_objpfx}locale/locale" \
+  > "$testroot/stdout" 2> "$testroot/stderr"
+
+echo "* standard error"
+cat "$testroot/stderr"
+echo "* standard output"
+cat "$testroot/stdout"
+
+cat > "$testroot/stderr-expected" <<EOF
+${common_objpfx}locale/locale: Cannot set LC_CTYPE to default locale: No such file or directory
+${common_objpfx}locale/locale: Cannot set LC_MESSAGES to default locale: No such file or directory
+${common_objpfx}locale/locale: Cannot set LC_ALL to default locale: No such file or directory
+warning: The LOCPATH variable is set to "does-not-exist"
+EOF
+
+cat > "$testroot/stdout-expected" <<EOF
+LANG=
+LC_CTYPE="invalid-locale"
+LC_NUMERIC="invalid-locale"
+LC_TIME="invalid-locale"
+LC_COLLATE="invalid-locale"
+LC_MONETARY="invalid-locale"
+LC_MESSAGES="invalid-locale"
+LC_PAPER="invalid-locale"
+LC_NAME="invalid-locale"
+LC_ADDRESS="invalid-locale"
+LC_TELEPHONE="invalid-locale"
+LC_MEASUREMENT="invalid-locale"
+LC_IDENTIFICATION="invalid-locale"
+LC_ALL=invalid-locale
+EOF
+
+errors=0
+if ! cmp -s "$testroot/stderr-expected" "$testroot/stderr" ; then
+    echo "error: standard error not correct"
+    errors=1
+fi
+if ! cmp -s "$testroot/stdout-expected" "$testroot/stdout" ; then
+    echo "error: standard output not correct"
+    errors=1
+fi
+exit $errors