#-*- 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 absolute_import, unicode_literals import locale import logging import os import textwrap import traceback import portage from portage.util import _unicode_decode, 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. """ try: from portage.util import libc except ImportError: 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(_unicode_decode(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: pass pid = os.fork() if pid == 0: try: if env is not None: try: locale.setlocale(locale.LC_CTYPE, portage._native_string(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"]