Gentoo Archives: gentoo-portage-dev

From: "Anthony G. Basile" <basile@××××××××××××××.edu>
To: gentoo-portage-dev@l.g.o
Cc: "Anthony G. Basile" <blueness@g.o>
Subject: [gentoo-portage-dev] [PATCH] pym/portage/util/locale.py: add a C module to check locale
Date: Tue, 17 May 2016 12:47:40
Message-Id: 1463489233-1902-1-git-send-email-basile@opensource.dyc.edu
1 From: "Anthony G. Basile" <blueness@g.o>
2
3 The current method to check for the system locale is to use python's
4 ctype.util.find_library() to construct a full library path to the
5 system libc.so which is then passed to ctypes.CDLL(). However,
6 this gets bogged down in implementation dependant details and
7 fails with musl.
8
9 We work around this design flaw in ctypes with a small python module
10 written in C called 'portage_c_check_locale', and only fall back on
11 the current ctype-based check when this module is not available.
12
13 Since this is the first python module written in C included in portage,
14 as a side effect, we introduce the machinary for future modules in
15 setup.py.
16
17 X-Gentoo-bug: 571444
18 X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=571444
19 ---
20 pym/portage/util/locale.py | 36 +++++++++---
21 setup.py | 9 ++-
22 src/check_locale.c | 139 +++++++++++++++++++++++++++++++++++++++++++++
23 3 files changed, 174 insertions(+), 10 deletions(-)
24 create mode 100644 src/check_locale.c
25
26 diff --git a/pym/portage/util/locale.py b/pym/portage/util/locale.py
27 index 2a15ea1..ed97f61 100644
28 --- a/pym/portage/util/locale.py
29 +++ b/pym/portage/util/locale.py
30 @@ -30,17 +30,17 @@ locale_categories = (
31 _check_locale_cache = {}
32
33
34 -def _check_locale(silent):
35 +def _ctypes_check_locale():
36 """
37 - The inner locale check function.
38 + Check for locale using ctypes.
39 """
40
41 libc_fn = find_library("c")
42 if libc_fn is None:
43 - return None
44 + return (None, "")
45 libc = LoadLibrary(libc_fn)
46 if libc is None:
47 - return None
48 + return (None, "")
49
50 lc = list(range(ord('a'), ord('z')+1))
51 uc = list(range(ord('A'), ord('Z')+1))
52 @@ -48,9 +48,6 @@ def _check_locale(silent):
53 ruc = [libc.toupper(c) for c in lc]
54
55 if lc != rlc or uc != ruc:
56 - if silent:
57 - return False
58 -
59 msg = ("WARNING: The LC_CTYPE variable is set to a locale " +
60 "that specifies transformation between lowercase " +
61 "and uppercase ASCII characters that is different than " +
62 @@ -71,11 +68,32 @@ def _check_locale(silent):
63 msg.extend([
64 " %s -> %s" % (chars(uc), chars(rlc)),
65 " %28s: %s" % ('expected', chars(lc))])
66 + return (False, msg)
67 +
68 + return (True, "")
69 +
70 +
71 +def _check_locale(silent):
72 + """
73 + The inner locale check function.
74 + """
75 +
76 + try:
77 + from portage_c_check_locale import _c_check_locale
78 + (ret, msg) = _c_check_locale()
79 + except ImportError:
80 + writemsg_level("!!! Unable to import portage_c_check_locale\n",
81 + level=logging.WARNING, noiselevel=-1)
82 + (ret, msg) = _ctypes_check_locale()
83 +
84 + if ret:
85 + return True
86 +
87 + if not silent:
88 writemsg_level("".join(["!!! %s\n" % l for l in msg]),
89 level=logging.ERROR, noiselevel=-1)
90 - return False
91
92 - return True
93 + return False
94
95
96 def check_locale(silent=False, env=None):
97 diff --git a/setup.py b/setup.py
98 index b066fae..ed353a3 100755
99 --- a/setup.py
100 +++ b/setup.py
101 @@ -4,7 +4,7 @@
102
103 from __future__ import print_function
104
105 -from distutils.core import setup, Command
106 +from distutils.core import setup, Command, Extension
107 from distutils.command.build import build
108 from distutils.command.build_scripts import build_scripts
109 from distutils.command.clean import clean
110 @@ -41,6 +41,11 @@ x_scripts = {
111 ],
112 }
113
114 +x_c_helpers = {
115 + 'portage_c_check_locale' : [
116 + 'src/check_locale.c'
117 + ],
118 +}
119
120 class x_build(build):
121 """ Build command with extra build_man call. """
122 @@ -636,6 +641,8 @@ setup(
123 ['$sysconfdir/portage/repo.postsync.d', ['cnf/repo.postsync.d/example']],
124 ],
125
126 + ext_modules = [Extension(name=n, sources=m) for n, m in x_c_helpers.items()],
127 +
128 cmdclass = {
129 'build': x_build,
130 'build_man': build_man,
131 diff --git a/src/check_locale.c b/src/check_locale.c
132 new file mode 100644
133 index 0000000..a995028
134 --- /dev/null
135 +++ b/src/check_locale.c
136 @@ -0,0 +1,139 @@
137 +/* Copyright 2005-2015 Gentoo Foundation
138 + * Distributed under the terms of the GNU General Public License v2
139 + */
140 +
141 +#include <Python.h>
142 +#include <ctype.h>
143 +#include <string.h>
144 +
145 +static PyObject * portage_c_check_locale(PyObject *, PyObject *);
146 +
147 +static PyMethodDef CheckLocaleMethods[] = {
148 + {"_c_check_locale", portage_c_check_locale, METH_NOARGS, "Check the system locale."},
149 + {NULL, NULL, 0, NULL}
150 +};
151 +
152 +#if PY_MAJOR_VERSION >= 3
153 +static struct PyModuleDef moduledef = {
154 + PyModuleDef_HEAD_INIT,
155 + "portage_c_check_locale", /* m_name */
156 + "Module for checking the system locale for portage", /* m_doc */
157 + -1, /* m_size */
158 + CheckLocaleMethods, /* m_methods */
159 + NULL, /* m_reload */
160 + NULL, /* m_traverse */
161 + NULL, /* m_clear */
162 + NULL, /* m_free */
163 +};
164 +#endif
165 +
166 +static PyObject *LocaleError;
167 +
168 +PyMODINIT_FUNC
169 +#if PY_MAJOR_VERSION >= 3
170 +PyInit_portage_c_check_locale(void)
171 +#else
172 +initportage_c_check_locale(void)
173 +#endif
174 +{
175 + PyObject *m;
176 +
177 +#if PY_MAJOR_VERSION >= 3
178 + m = PyModule_Create(&moduledef);
179 +#else
180 + m = Py_InitModule("portage_c_check_locale", CheckLocaleMethods);
181 +#endif
182 +
183 + if (m == NULL)
184 +#if PY_MAJOR_VERSION >= 3
185 + return NULL;
186 +#else
187 + return;
188 +#endif
189 +
190 + LocaleError = PyErr_NewException("locale.LocaleError", NULL, NULL);
191 + Py_INCREF(LocaleError);
192 + PyModule_AddObject(m, "LocaleError", LocaleError);
193 +
194 +#if PY_MAJOR_VERSION >= 3
195 + return m;
196 +#else
197 + return;
198 +#endif
199 +}
200 +
201 +
202 +static void
203 +error_msg(char msg[], int c[], int rc[], int ac[])
204 +{
205 + int i, p;
206 +
207 + strcat(msg, " ");
208 + p = strlen(msg);
209 + for (i = 'a'; i <= 'z'; i++)
210 + msg[p++] = c[i-'a'];
211 +
212 + strcat(msg, " -> ");
213 + p = strlen(msg);
214 + for (i = 'a'; i <= 'z'; i++)
215 + msg[p++] = rc[i-'a'];
216 + strcat(msg, "\n");
217 +
218 + strcat(msg, " expected: ");
219 + p = strlen(msg);
220 + for (i = 'a'; i <= 'z'; i++)
221 + msg[p++] = ac[i-'a'];
222 + strcat(msg, "\n\n");
223 +}
224 +
225 +
226 +static PyObject *
227 +portage_c_check_locale(PyObject *self, PyObject *args)
228 +{
229 + int i, upper = 1, lower = 1;
230 + int lc[26], uc[26], rlc[26], ruc[26];
231 + char msg[1024];
232 +
233 + memset(msg, 0, 1024);
234 +
235 + for (i = 'a'; i <= 'z'; i++) {
236 + lc[i-'a'] = i;
237 + ruc[i-'a'] = toupper(i);
238 + }
239 +
240 + for (i = 'A'; i <= 'Z'; i++) {
241 + uc[i-'A'] = i;
242 + rlc[i-'A'] = tolower(i);
243 + }
244 +
245 + for (i = 'a'; i <= 'z'; i++)
246 + if(lc[i-'a'] != rlc[i-'a']) {
247 + lower = 0;
248 + break;
249 + }
250 +
251 + for (i = 'A'; i <= 'Z'; i++)
252 + if(uc[i-'A'] != ruc[i-'A']) {
253 + upper = 0;
254 + break;
255 + }
256 +
257 + if (lower == 0 || upper == 0) {
258 + strcpy(msg,
259 + "WARNING: The LC_CTYPE variable is set to a locale that specifies\n"
260 + "transformation between lowercase and uppercase ASCII characters that\n"
261 + "is different than the one specified by POSIX locale. This can break\n"
262 + "ebuilds and cause issues in programs that rely on the common character\n"
263 + "conversion scheme. Please consider enabling another locale (such as\n"
264 + "en_US.UTF-8) in /etc/locale.gen and setting it as LC_CTYPE in make.conf.\n\n"
265 + );
266 +
267 + if (lower == 0)
268 + error_msg(msg, lc, ruc, uc);
269 +
270 + if (upper == 0)
271 + error_msg(msg, uc, rlc, lc);
272 + }
273 +
274 + return Py_BuildValue("is", lower && upper, msg);
275 +}
276 --
277 2.7.3

Replies