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