252 lines
9.3 KiB
Python
252 lines
9.3 KiB
Python
|
#
|
||
|
# Copyright (C) 2018 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||
|
# Public License for more details. You should have received a copy of the
|
||
|
# GNU General Public License along with this program; if not, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# License and may only be used or replicated with the express permission of
|
||
|
# Red Hat, Inc.
|
||
|
#
|
||
|
import os
|
||
|
import shutil
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
from pyanaconda.core.constants import DEFAULT_VC_FONT
|
||
|
from pyanaconda.core.util import execWithCapture
|
||
|
from pyanaconda.core.path import join_paths
|
||
|
from pyanaconda.localization import get_locale_console_fonts, find_best_locale_match
|
||
|
from pyanaconda.modules.common.errors.installation import LanguageInstallationError, \
|
||
|
KeyboardInstallationError
|
||
|
from pyanaconda.modules.common.task import Task
|
||
|
from pyanaconda.modules.localization.utils import get_missing_keyboard_configuration
|
||
|
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
X_CONF_DIR = "/etc/X11/xorg.conf.d"
|
||
|
X_CONF_FILE_NAME = "00-keyboard.conf"
|
||
|
VC_CONF_FILE_PATH = "/etc/vconsole.conf"
|
||
|
|
||
|
|
||
|
class LanguageInstallationTask(Task):
|
||
|
"""Installation task for the language configuration."""
|
||
|
|
||
|
LOCALE_CONF_FILE_PATH = "/etc/locale.conf"
|
||
|
LOCALE_FALLBACK = "C.UTF-8"
|
||
|
|
||
|
def __init__(self, sysroot, lang):
|
||
|
"""Create a new task,
|
||
|
|
||
|
:param sysroot: a path to the root of the installed system
|
||
|
:param lang: a value for LANG locale variable
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._sysroot = sysroot
|
||
|
self._lang = lang
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Configure language"
|
||
|
|
||
|
def run(self):
|
||
|
"""Run the installation task."""
|
||
|
lang = self._get_supported_language()
|
||
|
self._write_language_configuration(lang)
|
||
|
|
||
|
def _get_supported_language(self):
|
||
|
"""Return a supported locale.
|
||
|
|
||
|
:return: a locale
|
||
|
"""
|
||
|
if self._is_language_support_installed(self._lang):
|
||
|
return self._lang
|
||
|
|
||
|
log.debug("The '%s' locale is unsupported.", self._lang)
|
||
|
log.debug("Using the '%s' locale as a fallback.", self.LOCALE_FALLBACK)
|
||
|
return self.LOCALE_FALLBACK
|
||
|
|
||
|
def _is_language_support_installed(self, lang):
|
||
|
"""Is the support for the specified language installed?
|
||
|
|
||
|
The language is considered to be supported if we are not
|
||
|
able to determine the supported locales due to missing tools.
|
||
|
|
||
|
:param lang: a value for the LANG locale variable
|
||
|
:return: False if the locale is known to be not supported, otherwise True
|
||
|
"""
|
||
|
try:
|
||
|
output = execWithCapture("locale", ["-a"], root=self._sysroot)
|
||
|
except OSError as e:
|
||
|
log.warning("Couldn't get supported locales: %s", e)
|
||
|
return True
|
||
|
|
||
|
match = find_best_locale_match(lang, output.splitlines())
|
||
|
|
||
|
log.debug("The '%s' locale matched '%s'.", lang, match)
|
||
|
return bool(match)
|
||
|
|
||
|
def _write_language_configuration(self, lang):
|
||
|
"""Write language configuration to the /etc/locale.conf file.
|
||
|
|
||
|
:param lang: a value for the LANG locale variable
|
||
|
"""
|
||
|
try:
|
||
|
fpath = join_paths(self._sysroot, self.LOCALE_CONF_FILE_PATH)
|
||
|
log.debug("Writing the '%s' locale to %s.", lang, fpath)
|
||
|
|
||
|
with open(fpath, "w") as fobj:
|
||
|
fobj.write('LANG="{}"\n'.format(lang))
|
||
|
|
||
|
except OSError as e:
|
||
|
msg = "Cannot write language configuration file: {}".format(e.strerror)
|
||
|
raise LanguageInstallationError(msg) from e
|
||
|
|
||
|
|
||
|
class KeyboardInstallationTask(Task):
|
||
|
"""Installation task for the keyboard configuration."""
|
||
|
|
||
|
def __init__(self, sysroot, localed_wrapper, x_layouts, switch_options, vc_keymap):
|
||
|
"""Create a new task,
|
||
|
|
||
|
:param localed_wrapper: instance of systemd-localed service wrapper
|
||
|
:type localed_wrapper: LocaledWrapper
|
||
|
:param sysroot: a path to the root of the installed system
|
||
|
:type sysroot: str
|
||
|
:param x_layouts: list of x layout specifications
|
||
|
:type x_layouts: list(str)
|
||
|
:param switch_options: list of options for layout switching
|
||
|
:type switch_options: list(str)
|
||
|
:param vc_keymap: virtual console keyboard mapping name
|
||
|
:type vc_keymap: str
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._sysroot = sysroot
|
||
|
self._localed_wrapper = localed_wrapper
|
||
|
self._x_layouts = x_layouts
|
||
|
self._switch_options = switch_options
|
||
|
self._vc_keymap = vc_keymap
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Configure keyboard"
|
||
|
|
||
|
def run(self):
|
||
|
x_layouts = self._x_layouts
|
||
|
vc_keymap = self._vc_keymap
|
||
|
|
||
|
if not self._x_layouts or not self._vc_keymap:
|
||
|
x_layouts, vc_keymap = get_missing_keyboard_configuration(
|
||
|
self._localed_wrapper,
|
||
|
self._x_layouts,
|
||
|
self._vc_keymap
|
||
|
)
|
||
|
|
||
|
if x_layouts:
|
||
|
write_x_configuration(
|
||
|
self._localed_wrapper,
|
||
|
x_layouts,
|
||
|
self._switch_options,
|
||
|
X_CONF_DIR,
|
||
|
self._sysroot
|
||
|
)
|
||
|
if vc_keymap:
|
||
|
write_vc_configuration(
|
||
|
vc_keymap,
|
||
|
self._sysroot
|
||
|
)
|
||
|
|
||
|
|
||
|
def write_x_configuration(localed_wrapper, x_layouts, switch_options, x_conf_dir_path, root):
|
||
|
"""Write X keyboard layout configuration to the configuration files.
|
||
|
|
||
|
:param localed_wrapper: instance of systemd-localed service wrapper
|
||
|
:type localed_wrapper: LocaledWrapper
|
||
|
:param x_layouts: list of X layout specifications
|
||
|
:type x_layouts: list(str)
|
||
|
:param switch_options: list of options for layout switching
|
||
|
:type switch_options: list(str)
|
||
|
:param x_conf_dir_path: path to directory holding X Layouts configuration
|
||
|
:type x_conf_dir_path: str
|
||
|
:param root: root path of the configured system
|
||
|
:type root: str
|
||
|
"""
|
||
|
|
||
|
errors = []
|
||
|
try:
|
||
|
if not os.path.isdir(x_conf_dir_path):
|
||
|
os.makedirs(x_conf_dir_path)
|
||
|
except OSError:
|
||
|
# Non-fatal, systemd-localed may create the directory on its own.
|
||
|
log.debug("Cannot create X Layouts configuration directory %s", x_conf_dir_path)
|
||
|
|
||
|
if root != "/":
|
||
|
# Writing to a different root, we need to save these values, so that
|
||
|
# we can restore them when we have the file written out.
|
||
|
saved_layouts_variants = localed_wrapper.layouts_variants
|
||
|
saved_options = localed_wrapper.options
|
||
|
|
||
|
# Set systemd-localed's layouts, variants and switch options, which
|
||
|
# also generates a new conf file.
|
||
|
localed_wrapper.set_layouts(x_layouts, switch_options)
|
||
|
|
||
|
if root != "/":
|
||
|
# Make sure the target directory exists under the given root.
|
||
|
rooted_xconf_dir = os.path.normpath(root + "/" + x_conf_dir_path)
|
||
|
try:
|
||
|
if not os.path.isdir(rooted_xconf_dir):
|
||
|
os.makedirs(rooted_xconf_dir)
|
||
|
except OSError:
|
||
|
errors.append("Cannot create directory {}".format(rooted_xconf_dir))
|
||
|
|
||
|
# Copy the file to the chroot.
|
||
|
xconf_file_path = os.path.normpath(x_conf_dir_path + "/" + X_CONF_FILE_NAME)
|
||
|
rooted_xconf_file_path = os.path.normpath(root + "/" + xconf_file_path)
|
||
|
try:
|
||
|
shutil.copy2(xconf_file_path, rooted_xconf_file_path)
|
||
|
except OSError as ioerr:
|
||
|
log.error("Cannot copy X layouts configuration file %s to target system: %s.",
|
||
|
xconf_file_path, ioerr.strerror)
|
||
|
|
||
|
# Restore the original values.
|
||
|
localed_wrapper.set_layouts(saved_layouts_variants, saved_options)
|
||
|
|
||
|
if errors:
|
||
|
raise KeyboardInstallationError("\n".join(errors))
|
||
|
|
||
|
|
||
|
def write_vc_configuration(vc_keymap, root):
|
||
|
"""Write virtual console keyboard mapping configuration.
|
||
|
|
||
|
:param vc_keymap: virtual console keyboard mapping name
|
||
|
:type vc_keymap: str
|
||
|
:param root: root path of the configured system
|
||
|
:type root: str
|
||
|
"""
|
||
|
try:
|
||
|
fpath = os.path.normpath(root + VC_CONF_FILE_PATH)
|
||
|
|
||
|
# "eurlatgr" may not be the best choice for all the languages,
|
||
|
# for example, Russian, Urdu, Serbian, Sindhi and a few more.
|
||
|
# refer: https://bugzilla.redhat.com/show_bug.cgi?id=1919486
|
||
|
# assuming that LANG is set by now.
|
||
|
vc_fonts = get_locale_console_fonts(os.environ["LANG"])
|
||
|
vc_font = vc_fonts[0] if vc_fonts else DEFAULT_VC_FONT
|
||
|
|
||
|
with open(fpath, "w") as fobj:
|
||
|
fobj.write('KEYMAP="%s"\n' % vc_keymap)
|
||
|
|
||
|
# systemd now defaults to a font that cannot display non-ascii
|
||
|
# characters, so we have to tell it to use a better one
|
||
|
fobj.write('FONT="%s"\n' % vc_font)
|
||
|
|
||
|
except OSError as e:
|
||
|
msg = "Cannot write vconsole configuration file: {}".format(e.strerror)
|
||
|
raise KeyboardInstallationError(msg) from e
|