From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <gentoo-portage-dev+bounces-5761-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 0AA41138262
	for <garchives@archives.gentoo.org>; Sun, 22 May 2016 17:05:16 +0000 (UTC)
Received: from pigeon.gentoo.org (localhost [127.0.0.1])
	by pigeon.gentoo.org (Postfix) with SMTP id 60D4B14250;
	Sun, 22 May 2016 17:05:07 +0000 (UTC)
Received: from virtual.dyc.edu (mail.virtual.dyc.edu [67.222.116.22])
	by pigeon.gentoo.org (Postfix) with ESMTP id 8B9481424F
	for <gentoo-portage-dev@lists.gentoo.org>; Sun, 22 May 2016 17:05:06 +0000 (UTC)
Received: from opensource.dyc.edu (unknown [67.222.116.23])
	by virtual.dyc.edu (Postfix) with ESMTP id EB4DC7E0050;
	Sun, 22 May 2016 13:05:05 -0400 (EDT)
Received: by opensource.dyc.edu (Postfix, from userid 1001)
	id 723572B00110; Sun, 22 May 2016 13:04:50 -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 help check locale
Date: Sun, 22 May 2016 13:04:40 -0400
Message-Id: <1463936680-16072-2-git-send-email-basile@opensource.dyc.edu>
X-Mailer: git-send-email 1.7.6.1
In-Reply-To: <1463936680-16072-1-git-send-email-basile@opensource.dyc.edu>
References: <1463936680-16072-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: 456f66f1-7626-4cbd-b7ab-8a81284a0ab5
X-Archives-Hash: cad89166a2a12181ee2f95d54d682e33

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

The current method to check for a sane system locale is to use python's
ctypes.util.find_library() to construct a full library path to the
system libc.so and pass that path to ctypes.CDLL() so we can call
toupper() and tolower() directly.  However, this gets bogged down in
implementation details and fails with musl.

We work around this design flaw in ctypes with a small python module
written in C which provides thin wrappers to toupper() and tolower(),
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   | 32 ++++++++++-----
 setup.py                     |  6 ++-
 src/portage_c_convert_case.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 121 insertions(+), 11 deletions(-)
 create mode 100644 src/portage_c_convert_case.c

diff --git a/pym/portage/util/locale.py b/pym/portage/util/locale.py
index 2a15ea1..85ddd2b 100644
--- a/pym/portage/util/locale.py
+++ b/pym/portage/util/locale.py
@@ -11,6 +11,7 @@ from __future__ import absolute_import, unicode_literals
 import locale
 import logging
 import os
+import sys
 import textwrap
 import traceback
 
@@ -34,18 +35,26 @@ def _check_locale(silent):
 	"""
 	The inner locale check function.
 	"""
-
-	libc_fn = find_library("c")
-	if libc_fn is None:
-		return None
-	libc = LoadLibrary(libc_fn)
-	if libc is None:
-		return None
+	try:
+		from portage_c_convert_case import _c_toupper, _c_tolower
+		libc_tolower = _c_tolower
+		libc_toupper = _c_toupper
+	except ImportError:
+		writemsg_level("!!! Unable to import portage_c_convert_case\n!!!\n",
+			level=logging.WARNING, noiselevel=-1)
+		libc_fn = find_library("c")
+		if libc_fn is None:
+			return None
+		libc = LoadLibrary(libc_fn)
+		if libc is None:
+			return None
+		libc_tolower = libc.tolower
+		libc_toupper = libc.toupper
 
 	lc = list(range(ord('a'), ord('z')+1))
 	uc = list(range(ord('A'), ord('Z')+1))
-	rlc = [libc.tolower(c) for c in uc]
-	ruc = [libc.toupper(c) for c in lc]
+	rlc = [libc_tolower(c) for c in uc]
+	ruc = [libc_toupper(c) for c in lc]
 
 	if lc != rlc or uc != ruc:
 		if silent:
@@ -62,7 +71,10 @@ def _check_locale(silent):
 			"as LC_CTYPE in make.conf.")
 		msg = [l for l in textwrap.wrap(msg, 70)]
 		msg.append("")
-		chars = lambda l: ''.join(chr(x) for x in l)
+		if sys.version_info.major >= 3:
+			chars = lambda l: ''.join(chr(x) for x in l)
+		else:
+			chars = lambda l: ''.join(chr(x).decode('utf-8', 'replace') for x in l)
 		if uc != ruc:
 			msg.extend([
 				"  %s -> %s" % (chars(lc), chars(ruc)),
diff --git a/setup.py b/setup.py
index 25429bc..8b6b408 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_convert_case' : [
+		'src/portage_c_convert_case.c',
+	],
+}
 
 class x_build(build):
 	""" Build command with extra build_man call. """
diff --git a/src/portage_c_convert_case.c b/src/portage_c_convert_case.c
new file mode 100644
index 0000000..f60b0c2
--- /dev/null
+++ b/src/portage_c_convert_case.c
@@ -0,0 +1,94 @@
+/* Copyright 2005-2016 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ */
+
+#include <Python.h>
+#include <ctype.h>
+
+static PyObject * portage_c_tolower(PyObject *, PyObject *);
+static PyObject * portage_c_toupper(PyObject *, PyObject *);
+
+static PyMethodDef ConvertCaseMethods[] = {
+	{"_c_tolower", portage_c_tolower, METH_VARARGS, "Convert to lower case using system locale."},
+	{"_c_toupper", portage_c_toupper, METH_VARARGS, "Convert to upper case using system locale."},
+	{NULL, NULL, 0, NULL}
+};
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+	PyModuleDef_HEAD_INIT,
+	"portage_c_convert_case",					/* m_name */
+	"Module for converting case using the system locale",		/* m_doc */
+	-1,								/* m_size */
+	ConvertCaseMethods,						/* m_methods */
+	NULL,								/* m_reload */
+	NULL,								/* m_traverse */
+	NULL,								/* m_clear */
+	NULL,								/* m_free */
+};
+#endif
+
+static PyObject *ConvertCaseError;
+
+PyMODINIT_FUNC
+#if PY_MAJOR_VERSION >= 3
+PyInit_portage_c_convert_case(void)
+#else
+initportage_c_convert_case(void)
+#endif
+{
+	PyObject *m;
+
+#if PY_MAJOR_VERSION >= 3
+	m = PyModule_Create(&moduledef);
+#else
+	m = Py_InitModule("portage_c_convert_case", ConvertCaseMethods);
+#endif
+
+	if (m == NULL)
+#if PY_MAJOR_VERSION >= 3
+		return NULL;
+#else
+		return;
+#endif
+
+	ConvertCaseError = PyErr_NewException("portage_c_convert_case.ConvertCaseError", NULL, NULL);
+	Py_INCREF(ConvertCaseError);
+	PyModule_AddObject(m, "ConvertCaseError", ConvertCaseError);
+
+#if PY_MAJOR_VERSION >= 3
+	return m;
+#else
+	return;
+#endif
+}
+
+
+static PyObject *
+portage_c_tolower(PyObject *self, PyObject *args)
+{
+	int c;
+
+	if (!PyArg_ParseTuple(args, "i", &c))
+	{
+		PyErr_SetString(ConvertCaseError, "_c_tolower: PyArg_ParseTuple failed");
+		return NULL;
+	}
+
+	return Py_BuildValue("i", tolower(c));
+}
+
+
+static PyObject *
+portage_c_toupper(PyObject *self, PyObject *args)
+{
+	int c;
+
+	if (!PyArg_ParseTuple(args, "i", &c))
+	{
+		PyErr_SetString(ConvertCaseError, "_c_toupper: PyArg_ParseTuple failed");
+		return NULL;
+	}
+
+	return Py_BuildValue("i", toupper(c));
+}
-- 
2.7.3