bug-gettext
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[bug-gettext] [PATCH] intl: Add API to get/set language precedence list


From: Daiki Ueno
Subject: [bug-gettext] [PATCH] intl: Add API to get/set language precedence list
Date: Thu, 12 May 2016 18:36:53 +0900

Due to a potential getenv() call for LANGUAGE envvar, multi-threaded
programs may crash in gettext(), when there is another thread calling
setenv().  To mitigate this, add a new API set_i18n_language() to allow
setting language precedence list programatically.
Suggested by Bruno Haible in:
https://lists.gnu.org/archive/html/bug-gettext/2016-05/msg00009.html
* gettext-runtime/intl/dcigettext.c (_nl_default_i18n_language)
(_nl_current_i18n_language): New variable.
(GET_I18N_LANGUAGE, SET_I18N_LANGUAGE): New functions.
(guess_category_value): Check LANGUAGE envvar only when
_nl_current_i18n_language is not set.
* gettext-runtime/intl/libgnuintl.in.h (get_i18n_language)
(set_i18n_language): New function declaration.
* gettext-tools/tests/Makefile.am (TESTS): Add test for
set_i18n_language().
* gettext-tools/tests/gettext-9: New test.
* gettext-tools/tests/gettext-9-prg.c: New test program.
* gettext-tools/tests/.gitignore: Ignore gettext-9-prg.
---
 gettext-runtime/intl/dcigettext.c    | 54 ++++++++++++++++++++++--
 gettext-runtime/intl/libgnuintl.in.h | 34 +++++++++++++++
 gettext-tools/tests/.gitignore       |  1 +
 gettext-tools/tests/Makefile.am      |  4 +-
 gettext-tools/tests/gettext-9        | 56 ++++++++++++++++++++++++
 gettext-tools/tests/gettext-9-prg.c  | 82 ++++++++++++++++++++++++++++++++++++
 6 files changed, 226 insertions(+), 5 deletions(-)
 create mode 100755 gettext-tools/tests/gettext-9
 create mode 100644 gettext-tools/tests/gettext-9-prg.c

diff --git a/gettext-runtime/intl/dcigettext.c 
b/gettext-runtime/intl/dcigettext.c
index 83bd775..f474786 100644
--- a/gettext-runtime/intl/dcigettext.c
+++ b/gettext-runtime/intl/dcigettext.c
@@ -342,6 +342,9 @@ libc_hidden_data_def (_nl_default_dirname)
 struct binding *_nl_domain_bindings;
 #endif
 
+static char _nl_default_i18n_language[] = "";
+static char *_nl_current_i18n_language = _nl_default_i18n_language;
+
 /* Prototypes for local functions.  */
 static char *plural_lookup (struct loaded_l10nfile *domain,
                            unsigned long int n,
@@ -430,8 +433,12 @@ typedef unsigned char transmem_block_t;
    prefix.  So we have to make a difference here.  */
 #ifdef _LIBC
 # define DCIGETTEXT __dcigettext
+# define GET_I18N_LANGUAGE __get_i18n_language
+# define SET_I18N_LANGUAGE __set_i18n_language
 #else
 # define DCIGETTEXT libintl_dcigettext
+# define GET_I18N_LANGUAGE libintl_get_i18n_language
+# define SET_I18N_LANGUAGE libintl_set_i18n_language
 #endif
 
 /* Lock variable to protect the global data in the gettext implementation.  */
@@ -875,6 +882,43 @@ DCIGETTEXT (const char *domainname, const char *msgid1, 
const char *msgid2,
 }
 
 
+const char *
+GET_I18N_LANGUAGE (void)
+{
+  return _nl_current_i18n_language;
+}
+
+
+const char *
+SET_I18N_LANGUAGE (const char *language)
+{
+  gl_rwlock_wrlock (_nl_state_lock);
+
+  if (language == NULL)
+    language = getenv ("LANGUAGE");
+
+  if (language != NULL && language[0] != '\0')
+    {
+      char *new_language;
+
+      new_language = strdup (language);
+      if (__builtin_expect (new_language == NULL, 0))
+        {
+          gl_rwlock_unlock (_nl_state_lock);
+          return NULL;
+        }
+
+      if (_nl_current_i18n_language != _nl_default_i18n_language)
+        free (_nl_current_i18n_language);
+      _nl_current_i18n_language = new_language;
+    }
+
+  gl_rwlock_unlock (_nl_state_lock);
+
+  return _nl_current_i18n_language;
+}
+
+
 /* Look up the translation of msgid within DOMAIN_FILE and DOMAINBINDING.
    Return it if found.  Return NULL if not found or in case of a conversion
    failure (problem in the particular message catalog).  Return (char *) -1
@@ -1582,9 +1626,13 @@ guess_category_value (int category, const char 
*categoryname)
   if (strcmp (locale, "C") == 0)
     return locale;
 
-  /* The highest priority value is the value of the 'LANGUAGE' environment
-     variable.  */
-  language = getenv ("LANGUAGE");
+  /* The highest priority value is the language precedence list
+     supplied either by calling set_i18n_language() or the value of
+     the 'LANGUAGE' environment variable.  If the former is used, the
+     value is stored in _nl_current_i18n_language.  */
+  language = _nl_current_i18n_language;
+  if (language == _nl_default_i18n_language)
+    language = getenv ("LANGUAGE");
   if (language != NULL && language[0] != '\0')
     return language;
 #if !defined IN_LIBGLOCALE && !defined _LIBC
diff --git a/gettext-runtime/intl/libgnuintl.in.h 
b/gettext-runtime/intl/libgnuintl.in.h
index 4fb1514..3526555 100644
--- a/gettext-runtime/intl/libgnuintl.in.h
+++ b/gettext-runtime/intl/libgnuintl.in.h
@@ -302,6 +302,40 @@ extern char *bind_textdomain_codeset (const char 
*__domainname,
 
 #endif /* IN_LIBGLOCALE */
 
+#ifndef IN_LIBGLOCALE
+
+/* Returns the language precedence list for the program.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern const char *libintl_get_i18n_language (void);
+static inline const char *get_i18n_language (void)
+{
+  return libintl_get_i18n_language ();
+}
+#else
+#ifdef _INTL_REDIRECT_MACROS
+# define get_i18n_language libintl_get_i18n_language
+#endif
+extern const char *get_i18n_language (void)
+       _INTL_ASM (libintl_get_i18n_language);
+#endif
+
+/* Sets the language precedence list for the program.
+   NULL means to use the one inferred from the environment variable.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern const char *libintl_set_i18n_language (const char *__language);
+static inline const char *set_i18n_language (const char *__language)
+{
+  return libintl_set_i18n_language (__language);
+}
+#else
+#ifdef _INTL_REDIRECT_MACROS
+# define set_i18n_language libintl_set_i18n_language
+#endif
+extern const char *set_i18n_language (const char *__language)
+       _INTL_ASM (libintl_set_i18n_language);
+#endif
+
+#endif /* IN_LIBGLOCALE */
 
 /* Support for format strings with positions in *printf(), following the
    POSIX/XSI specification.
diff --git a/gettext-tools/tests/.gitignore b/gettext-tools/tests/.gitignore
index 9e9b3b6..8513d2e 100644
--- a/gettext-tools/tests/.gitignore
+++ b/gettext-tools/tests/.gitignore
@@ -16,6 +16,7 @@
 /gettext-6-prg
 /gettext-7-prg
 /gettext-8-prg
+/gettext-9-prg
 /gettextpo-1-prg
 /sentence
 /testlocale
diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am
index 20ad11c..260483e 100644
--- a/gettext-tools/tests/Makefile.am
+++ b/gettext-tools/tests/Makefile.am
@@ -21,7 +21,7 @@ EXTRA_DIST =
 MOSTLYCLEANFILES = core *.stackdump
 
 TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
-       gettext-8 \
+       gettext-8 gettext-9 \
        msgattrib-1 msgattrib-2 msgattrib-3 msgattrib-4 msgattrib-5 \
        msgattrib-6 msgattrib-7 msgattrib-8 msgattrib-9 msgattrib-10 \
        msgattrib-11 msgattrib-12 msgattrib-13 msgattrib-14 msgattrib-15 \
@@ -216,7 +216,7 @@ DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
 LDADD = $(address@hidden@) @INTL_MACOSX_LIBS@
 LDADD_yes = ../intl/libintl.la @LTLIBTHREAD@
 LDADD_no = ../intl/libgnuintl.la @LTLIBTHREAD@ @LTLIBINTL@
-check_PROGRAMS = tstgettext tstngettext testlocale gettext-3-prg gettext-4-prg 
gettext-5-prg gettext-6-prg gettext-7-prg gettext-8-prg cake fc3 fc4 fc5 
gettextpo-1-prg sentence
+check_PROGRAMS = tstgettext tstngettext testlocale gettext-3-prg gettext-4-prg 
gettext-5-prg gettext-6-prg gettext-7-prg gettext-8-prg gettext-9-prg cake fc3 
fc4 fc5 gettextpo-1-prg sentence
 tstgettext_SOURCES = tstgettext.c setlocale.c
 tstgettext_CFLAGS = -DINSTALLDIR=\".\"
 tstgettext_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD)
diff --git a/gettext-tools/tests/gettext-9 b/gettext-tools/tests/gettext-9
new file mode 100755
index 0000000..754383a
--- /dev/null
+++ b/gettext-tools/tests/gettext-9
@@ -0,0 +1,56 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test that on glibc systems, gettext() works right even with intermediate
+# setlocale() calls.
+
+# This test works only on glibc systems.
+: ${GLIBC2=no}
+test "$GLIBC2" = yes || {
+  echo "Skipping test: not a glibc system"
+  exit 77
+}
+
+# This test works only on systems that have a de_DE and fr_FR locale installed.
+LC_ALL=de_DE ../testlocale || {
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: locale de_DE not installed"
+  else
+    echo "Skipping test: locale de_DE not supported"
+  fi
+  exit 77
+}
+LC_ALL=fr_FR ../testlocale || {
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: locale fr_FR not installed"
+  else
+    echo "Skipping test: locale fr_FR not supported"
+  fi
+  exit 77
+}
+
+test -d gt-9 || mkdir gt-9
+test -d gt-9/de_DE || mkdir gt-9/de_DE
+test -d gt-9/de_DE/LC_MESSAGES || mkdir gt-9/de_DE/LC_MESSAGES
+test -d gt-9/fr_FR || mkdir gt-9/fr_FR
+test -d gt-9/fr_FR/LC_MESSAGES || mkdir gt-9/fr_FR/LC_MESSAGES
+
+: ${MSGFMT=msgfmt}
+${MSGFMT} -o gt-9/de_DE/LC_MESSAGES/tstlang.mo "$abs_srcdir"/gettext-3-1.po
+${MSGFMT} -o gt-9/fr_FR/LC_MESSAGES/tstlang.mo "$abs_srcdir"/gettext-3-2.po
+
+cat <<EOF > gt-9.ok
+String1 - Lang2: 1st string
+String2 - Lang2: 2nd string
+String1 - Lang2: 1st string
+String2 - Lang2: 2nd string
+String1 - First string for testing.
+String2 - Another string for testing.
+EOF
+
+../gettext-9-prg > gt-9.out || exit 1
+
+: ${DIFF=diff}
+${DIFF} gt-9.ok gt-9.out || exit 1
+
+exit 0
diff --git a/gettext-tools/tests/gettext-9-prg.c 
b/gettext-tools/tests/gettext-9-prg.c
new file mode 100644
index 0000000..31ee078
--- /dev/null
+++ b/gettext-tools/tests/gettext-9-prg.c
@@ -0,0 +1,82 @@
+/* Test that gettext() honors language precedence list.
+   Copyright (C) 2007, 2015-2016 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <address@hidden>, 2007.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* Make sure we use the included libintl, not the system's one. */
+#undef _LIBINTL_H
+#include "libgnuintl.h"
+
+#define N_(string) string
+
+struct data_t
+{
+  const char *selection;
+  const char *description;
+};
+
+struct data_t strings[] =
+{
+  { "String1", N_("First string for testing.") },
+  { "String2", N_("Another string for testing.") }
+};
+const int data_cnt = sizeof (strings) / sizeof (strings[0]);
+
+const char *lang[] = { "de_DE", "fr_FR", "ll_CC" };
+const int lang_cnt = sizeof (lang) / sizeof (lang[0]);
+
+int
+main ()
+{
+  int i;
+
+  /* Clean up environment.  */
+  unsetenv ("LANGUAGE");
+  unsetenv ("LC_ALL");
+  unsetenv ("LC_MESSAGES");
+  unsetenv ("LC_CTYPE");
+  unsetenv ("LANG");
+  unsetenv ("OUTPUT_CHARSET");
+
+  textdomain ("tstlang");
+
+  set_i18n_language ("fr:fr_FR:de:de_DE");
+
+  for (i = 0; i < lang_cnt; ++i)
+    {
+      int j;
+
+      if (setlocale (LC_ALL, lang[i]) == NULL)
+        setlocale (LC_ALL, "C");
+
+      bindtextdomain ("tstlang", "gt-9");
+
+      for (j = 0; j < data_cnt; ++j)
+        printf ("%s - %s\n", strings[j].selection,
+                gettext (strings[j].description));
+    }
+
+  return 0;
+}
-- 
2.5.5




reply via email to

[Prev in Thread] Current Thread [Next in Thread]