[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
- [bug-gettext] [PATCH] intl: Add API to get/set language precedence list,
Daiki Ueno <=