aboutsummaryrefslogtreecommitdiff
blob: 58ecb2a43c001a7363471fb278ef57a194a28e36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#-*- coding:utf-8 -*-
# Copyright 2015 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
"""
Function to check whether the current used LC_CTYPE handles case
transformations of ASCII characters in a way compatible with the POSIX
locale.
"""
from __future__ import unicode_literals

import locale
import logging
import os
import textwrap
import traceback

from portage.util import writemsg_level
from portage.util._ctypes import find_library, LoadLibrary


locale_categories = (
	'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_MESSAGES',
	'LC_NUMERIC', 'LC_TIME',
	# GNU extensions
	'LC_ADDRESS', 'LC_IDENTIFICATION', 'LC_MEASUREMENT', 'LC_NAME',
	'LC_PAPER', 'LC_TELEPHONE',
)


_check_locale_cache = {}


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

	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]

	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 " +
			"the one specified by POSIX locale. This can break " +
			"ebuilds and cause issues in programs that rely on " +
			"the common character conversion scheme. " +
			"Please consider enabling another locale (such as " +
			"en_US.UTF-8) in /etc/locale.gen and setting it " +
			"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 uc != ruc:
			msg.extend([
				"  %s -> %s" % (chars(lc), chars(ruc)),
				"  %28s: %s" % ('expected', chars(uc))])
		if lc != rlc:
			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

	return True


def check_locale(silent=False, env=None):
	"""
	Check whether the locale is sane. Returns True if it is, prints
	warning and returns False if it is not. Returns None if the check
	can not be executed due to platform limitations.
	"""

	if env is not None:
		for v in ("LC_ALL", "LC_CTYPE", "LANG"):
			if v in env:
				mylocale = env[v]
				break
		else:
			mylocale = "C"

		try:
			return _check_locale_cache[mylocale]
		except KeyError:

	pid = os.fork()
	if pid == 0:
		try:
			if env is not None:
				try:
					locale.setlocale(locale.LC_CTYPE, mylocale)
				except locale.Error:
					os._exit(2)

			ret = _check_locale(silent)
			if ret is None:
				os._exit(2)
			else:
				os._exit(0 if ret else 1)
		except Exception:
			traceback.print_exc()
			os._exit(2)

	pid2, ret = os.waitpid(pid, 0)
	assert pid == pid2
	pyret = None
	if os.WIFEXITED(ret):
		ret = os.WEXITSTATUS(ret)
		if ret != 2:
			pyret = ret == 0

	if env is not None:
		_check_locale_cache[mylocale] = pyret
	return pyret


def split_LC_ALL(env):
	"""
	Replace LC_ALL with split-up LC_* variables if it is defined.
	Works on the passed environment (or settings instance).
	"""
	lc_all = env.get("LC_ALL")
	if lc_all is not None:
		for c in locale_categories:
			env[c] = lc_all
		del env["LC_ALL"]