Subject: [PATCH] gettext.c: only work around the vsnprintf bug on glibc < 2.17

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sat, 30 Nov 2013 08:51:31 +0700

Bug 6530 [1] causes "git show v0.99.6~1" to fail with error "your
vsnprintf is broken". The workaround avoids that, but it corrupts
system error messages in non-C locales.

The bug has been fixed since 2.17. If git is built with glibc, it can
know running libc version with gnu_get_libc_version() and avoid the
workaround on fixed versions. As a side effect, the workaround is
dropped for all non-glibc systems.

Tested on Gentoo Linux, glibc 2.17, amd64.

[1] http://sourceware.org/bugzilla/show_bug.cgi?id=6530

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 We could even dlopen and look for gnu_get_libc_version at runtime and
 drop USE_GLIBC define. But there may be other problems with dl* on
 other platforms..

 Somebody should test for the other two "USE_GLIBC = YesPlease" I
 added. I don't have Debian/FreeBSD nor any non-Linux GNU systems.

 Makefile         |  5 +++++
 config.mak.uname |  3 +++
 gettext.c        | 34 ++++++++++++++++++++++++++++------
 3 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/Makefile b/Makefile
index af847f8..8df6d6d 100644
--- a/Makefile
+++ b/Makefile
@@ -66,6 +66,8 @@ all::
 # Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
 # need -lintl when linking.
 #
+# Define USE_GLIBC if your libc version is glibc >= 2.1.
+#
 # Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
 # doesn't support GNU extensions like --check and --statistics
 #
@@ -1203,6 +1205,9 @@ ifndef NO_GETTEXT
 ifndef LIBC_CONTAINS_LIBINTL
 	EXTLIBS += -lintl
 endif
+ifdef USE_GLIBC
+	BASIC_CFLAGS += -DUSE_GLIBC
+endif
 endif
 ifdef NEEDS_SOCKET
 	EXTLIBS += -lsocket
diff --git a/config.mak.uname b/config.mak.uname
index 82d549e..ffb01e0 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -33,6 +33,7 @@ ifeq ($(uname_S),Linux)
 	HAVE_PATHS_H = YesPlease
 	LIBC_CONTAINS_LIBINTL = YesPlease
 	HAVE_DEV_TTY = YesPlease
+	USE_GLIBC = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
@@ -40,6 +41,7 @@ ifeq ($(uname_S),GNU/kFreeBSD)
 	HAVE_PATHS_H = YesPlease
 	DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
 	LIBC_CONTAINS_LIBINTL = YesPlease
+	USE_GLIBC = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -236,6 +238,7 @@ ifeq ($(uname_S),GNU)
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
 	LIBC_CONTAINS_LIBINTL = YesPlease
+	USE_GLIBC = YesPlease
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
diff --git a/gettext.c b/gettext.c
index 71e9545..91e679d 100644
--- a/gettext.c
+++ b/gettext.c
@@ -18,6 +18,13 @@
 #	endif
 #endif
 
+#ifdef USE_GLIBC
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <gnu/libc-version.h>
+#endif
+
 #ifdef GETTEXT_POISON
 int use_gettext_poison(void)
 {
@@ -30,6 +37,7 @@ int use_gettext_poison(void)
 
 #ifndef NO_GETTEXT
 static const char *charset;
+static int vsnprintf_workaround;
 static void init_gettext_charset(const char *domain)
 {
 	/*
@@ -99,9 +107,7 @@ static void init_gettext_charset(const char *domain)
 	   $ LANGUAGE= LANG=de_DE.utf8 ./test
 	   test: Kein passendes Ger?t gefunden
 
-	   In the long term we should probably see about getting that
-	   vsnprintf bug in glibc fixed, and audit our code so it won't
-	   fall apart under a non-C locale.
+	   The vsnprintf bug has been fixed since 2.17.
 
 	   Then we could simply set LC_CTYPE from the environment, which would
 	   make things like the external perror(3) messages work.
@@ -112,20 +118,36 @@ static void init_gettext_charset(const char *domain)
 	   1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
 	   2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
 	*/
-	setlocale(LC_CTYPE, "");
+	if (vsnprintf_workaround)
+		setlocale(LC_CTYPE, "");
 	charset = locale_charset();
 	bind_textdomain_codeset(domain, charset);
-	setlocale(LC_CTYPE, "C");
+	if (vsnprintf_workaround)
+		setlocale(LC_CTYPE, "C");
 }
 
 void git_setup_gettext(void)
 {
 	const char *podir = getenv("GIT_TEXTDOMAINDIR");
 
+#ifdef USE_GLIBC
+	int major, minor;
+	const char *version = gnu_get_libc_version();
+
+	if (version && sscanf(version, "%d.%d", &major, &minor) == 2 &&
+	    (major > 2 || (major == 2 && minor >= 17)))
+		vsnprintf_workaround = 0;
+	else
+		vsnprintf_workaround = 1;
+#endif
+
 	if (!podir)
 		podir = GIT_LOCALE_PATH;
 	bindtextdomain("git", podir);
-	setlocale(LC_MESSAGES, "");
+	if (vsnprintf_workaround)
+		setlocale(LC_MESSAGES, "");
+	else
+		setlocale(LC_ALL, "");
 	init_gettext_charset("git");
 	textdomain("git");
 }
-- 
1.8.2.83.gc99314b