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 |