From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <gentoo-portage-dev+bounces-5735-garchives=archives.gentoo.org@lists.gentoo.org>
Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80])
	by finch.gentoo.org (Postfix) with ESMTP id 19803138262
	for <garchives@archives.gentoo.org>; Thu, 19 May 2016 12:44:10 +0000 (UTC)
Received: from pigeon.gentoo.org (localhost [127.0.0.1])
	by pigeon.gentoo.org (Postfix) with SMTP id 78F12234005;
	Thu, 19 May 2016 12:44:09 +0000 (UTC)
Received: from virtual.dyc.edu (mail.virtual.dyc.edu [67.222.116.22])
	by pigeon.gentoo.org (Postfix) with ESMTP id 0C4CA234004
	for <gentoo-portage-dev@lists.gentoo.org>; Thu, 19 May 2016 12:44:08 +0000 (UTC)
Received: from opensource.dyc.edu (unknown [67.222.116.23])
	by virtual.dyc.edu (Postfix) with ESMTP id 9CD9C7E0018;
	Thu, 19 May 2016 08:44:08 -0400 (EDT)
Received: by opensource.dyc.edu (Postfix, from userid 1001)
	id 47E5D2B0010F; Thu, 19 May 2016 08:43:48 -0400 (EDT)
From: "Anthony G. Basile" <basile@opensource.dyc.edu>
To: gentoo-portage-dev@lists.gentoo.org
Cc: "Anthony G. Basile" <blueness@gentoo.org>
Subject: [gentoo-portage-dev] [PATCH 2/2] pym/portage/util/locale.py: add a C module to check locale
Date: Thu, 19 May 2016 08:43:38 -0400
Message-Id: <1463661818-6940-2-git-send-email-basile@opensource.dyc.edu>
X-Mailer: git-send-email 1.7.6.1
In-Reply-To: <1463661818-6940-1-git-send-email-basile@opensource.dyc.edu>
References: <1463661818-6940-1-git-send-email-basile@opensource.dyc.edu>
Precedence: bulk
List-Post: <mailto:gentoo-portage-dev@lists.gentoo.org>
List-Help: <mailto:gentoo-portage-dev+help@lists.gentoo.org>
List-Unsubscribe: <mailto:gentoo-portage-dev+unsubscribe@lists.gentoo.org>
List-Subscribe: <mailto:gentoo-portage-dev+subscribe@lists.gentoo.org>
List-Id: Gentoo Linux mail <gentoo-portage-dev.gentoo.org>
X-BeenThere: gentoo-portage-dev@lists.gentoo.org
Reply-to: gentoo-portage-dev@lists.gentoo.org
X-Archives-Salt: 0fb08d22-abd3-4e91-a2ab-6ae6b40e0fc6
X-Archives-Hash: 4b68e03bf24f89dd8bf150fc2b693edd

From: "Anthony G. Basile" <blueness@gentoo.org>

The current method to check for the system locale is to use python's
ctypes.util.find_library() to construct a full library path to the
system libc.so which is then passed to ctypes.CDLL().  However,
this gets bogged down in implementation dependant details and
fails with musl.

We work around this design flaw in ctypes with a small python module
written in C called 'portage_c_check_locale', and only fall back on
the current ctypes-based check when this module is not available.

This has been tested on glibc, uClibc and musl systems.

X-Gentoo-bug: 571444
X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=571444
Signed-off-by: Anthony G. Basile <blueness@gentoo.org>
---
 pym/portage/util/locale.py |  40 +++++++++----
 setup.py                   |   6 +-
 src/check_locale.c         | 144 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 178 insertions(+), 12 deletions(-)
 create mode 100644 src/check_locale.c

diff --git a/pym/portage/util/locale.py b/pym/portage/util/locale.py
index 2a15ea1..fc5d052 100644
--- a/pym/portage/util/locale.py
+++ b/pym/portage/util/locale.py
@@ -30,17 +30,17 @@ locale_categories = (
 _check_locale_cache = {}
 
 
-def _check_locale(silent):
+def _ctypes_check_locale():
 	"""
-	The inner locale check function.
+	Check for locale using ctypes.
 	"""
 
 	libc_fn = find_library("c")
 	if libc_fn is None:
-		return None
+		return (None, "")
 	libc = LoadLibrary(libc_fn)
 	if libc is None:
-		return None
+		return (None, "")
 
 	lc = list(range(ord('a'), ord('z')+1))
 	uc = list(range(ord('A'), ord('Z')+1))
@@ -48,9 +48,6 @@ def _check_locale(silent):
 	ruc = [libc.toupper(c) for c in lc]
 
 	if lc != rlc or uc != ruc:
-		if silent:
-			return False
-
 		msg = ("WARNING: The LC_CTYPE variable is set to a locale " +
 			"that specifies transformation between lowercase " +
 			"and uppercase ASCII characters that is different than " +
@@ -71,11 +68,32 @@ def _check_locale(silent):
 			msg.extend([
 				"  %s -> %s" % (chars(uc), chars(rlc)),
 				"  %28s: %s" % ('expected', chars(lc))])
-		writemsg_level("".join(["!!! %s\n" % l for l in msg]),
-			level=logging.ERROR, noiselevel=-1)
-		return False
+		msg = "".join(["!!! %s\n" % l for l in msg]),
+		return (False, msg)
+
+	return (True, "")
+
+
+def _check_locale(silent):
+	"""
+	The inner locale check function.
+	"""
+
+	try:
+		from portage_c_check_locale import _c_check_locale
+		(ret, msg) = _c_check_locale()
+	except ImportError:
+		writemsg_level("!!! Unable to import portage_c_check_locale\n",
+			level=logging.WARNING, noiselevel=-1)
+		(ret, msg) = _ctypes_check_locale()
+
+	if ret:
+		return True
+
+	if not silent:
+		writemsg_level(msg, level=logging.ERROR, noiselevel=-1)
 
-	return True
+	return False
 
 
 def check_locale(silent=False, env=None):
diff --git a/setup.py b/setup.py
index 25429bc..e44ac41 100755
--- a/setup.py
+++ b/setup.py
@@ -47,7 +47,11 @@ x_scripts = {
 # Dictionary custom modules written in C/C++ here.  The structure is
 #   key   = module name
 #   value = list of C/C++ source code, path relative to top source directory
-x_c_helpers = {}
+x_c_helpers = {
+	'portage_c_check_locale' : [
+		'src/check_locale.c',
+	],
+}
 
 class x_build(build):
 	""" Build command with extra build_man call. """
diff --git a/src/check_locale.c b/src/check_locale.c
new file mode 100644
index 0000000..9762ef2
--- /dev/null
+++ b/src/check_locale.c
@@ -0,0 +1,144 @@
+/* Copyright 2005-2015 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ */
+
+#include <Python.h>
+#include <ctype.h>
+#include <string.h>
+
+static PyObject * portage_c_check_locale(PyObject *, PyObject *);
+
+static PyMethodDef CheckLocaleMethods[] = {
+	{"_c_check_locale", portage_c_check_locale, METH_NOARGS, "Check the system locale."},
+	{NULL, NULL, 0, NULL}
+};
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+	PyModuleDef_HEAD_INIT,
+	"portage_c_check_locale",					/* m_name */
+	"Module for checking the system locale for portage",		/* m_doc */
+	-1,								/* m_size */
+	CheckLocaleMethods,						/* m_methods */
+	NULL,								/* m_reload */
+	NULL,								/* m_traverse */
+	NULL,								/* m_clear */
+	NULL,								/* m_free */
+};
+#endif
+
+static PyObject *LocaleError;
+
+PyMODINIT_FUNC
+#if PY_MAJOR_VERSION >= 3
+PyInit_portage_c_check_locale(void)
+#else
+initportage_c_check_locale(void)
+#endif
+{
+	PyObject *m;
+
+#if PY_MAJOR_VERSION >= 3
+	m = PyModule_Create(&moduledef);
+#else
+	m = Py_InitModule("portage_c_check_locale", CheckLocaleMethods);
+#endif
+
+	if (m == NULL)
+#if PY_MAJOR_VERSION >= 3
+		return NULL;
+#else
+		return;
+#endif
+
+	LocaleError = PyErr_NewException("locale.LocaleError", NULL, NULL);
+	Py_INCREF(LocaleError);
+	PyModule_AddObject(m, "LocaleError", LocaleError);
+
+#if PY_MAJOR_VERSION >= 3
+	return m;
+#else
+	return;
+#endif
+}
+
+
+static void
+error_msg(char msg[], int c[], int rc[], int ac[])
+{
+	int i, p;
+
+	strcat(msg, "  ");
+	p = strlen(msg);
+	for (i = 'a'; i <= 'z'; i++)
+		msg[p++] = c[i-'a'];
+
+	strcat(msg, " -> ");
+	p = strlen(msg);
+	for (i = 'a'; i <= 'z'; i++)
+		msg[p++] = rc[i-'a'];
+	strcat(msg, "\n");
+
+	strcat(msg, "                      expected: ");
+	p = strlen(msg);
+	for (i = 'a'; i <= 'z'; i++)
+		msg[p++] = ac[i-'a'];
+	strcat(msg, "\n\n");
+}
+
+
+static PyObject *
+portage_c_check_locale(PyObject *self, PyObject *args)
+{
+	int i, upper = 1, lower = 1;
+	int lc[26], uc[26], rlc[26], ruc[26];
+	char msg[1024];
+
+	memset(msg, 0, 1024);
+
+	for (i = 'a'; i <= 'z'; i++) {
+		lc[i-'a'] = i;
+		ruc[i-'a'] = toupper(i);
+	}
+
+	for (i = 'A'; i <= 'Z'; i++) {
+		uc[i-'A'] = i;
+		rlc[i-'A'] = tolower(i);
+	}
+
+	for (i = 'a'; i <= 'z'; i++)
+		if(lc[i-'a'] != rlc[i-'a']) {
+			lower = 0;
+			break;
+		}
+
+	for (i = 'A'; i <= 'Z'; i++)
+		if(uc[i-'A'] != ruc[i-'A']) {
+			upper = 0;
+			break;
+		}
+
+	if (lower == 0 || upper == 0) {
+		strcpy(msg,
+			"!!! WARNING: The LC_CTYPE variable is set to a locale that specifies\n"
+			"!!! transformation between lowercase and uppercase ASCII characters that\n"
+			"!!! is different than the one specified by POSIX locale. This can break\n"
+			"!!! ebuilds and cause issues in programs that rely on the common character\n"
+			"!!! conversion scheme.  Please consider enabling another locale (such as\n"
+			"!!! en_US.UTF-8) in /etc/locale.gen and setting it as LC_CTYPE in\n"
+			"!!! make.conf.\n\n"
+		);
+
+		if (lower == 0)
+			error_msg(msg, lc, ruc, uc);
+
+		if (upper == 0)
+			error_msg(msg, uc, rlc, lc);
+	}
+
+#if PY_MAJOR_VERSION >= 3
+	return Py_BuildValue("iy", lower && upper, msg);
+#else
+	return Py_BuildValue("is", lower && upper, msg);
+#endif
+}
-- 
2.7.3