From 76b1ba6257a0a87c3580ac500a225a41692dc362 Mon Sep 17 00:00:00 2001 From: codefiles <11915375+codefiles@users.noreply.github.com> Date: Tue, 16 Aug 2022 19:58:24 -0400 Subject: [PATCH] Rework system locale --- archinstall/lib/installer.py | 123 ++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 0889628960..7ef7d6f936 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -12,7 +12,7 @@ from .disk import get_partitions_in_use, Partition from .general import SysCommand, generate_password from .hardware import has_uefi, is_vm, cpu_vendor -from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout +from .locale_helpers import list_locales, verify_keyboard_layout, verify_x11_keyboard_layout from .disk.helpers import findmnt from .mirrors import use_mirrors from .plugins import plugins @@ -443,29 +443,120 @@ def set_locale(self, locale :str, encoding :str = 'UTF-8', *args :str, **kwargs if not len(locale): return True - modifier = '' + # Get locale entries from /etc/locale.gen + entries = list_locales() + + entry = '' + + # Validate locale with entries from /etc/locale.gen + for row in entries: + entry_locale_name, entry_encoding = row.split() + if '.' in entry_locale_name: + entry_locale = entry_locale_name.split('.', 1)[0] + else: + entry_locale = entry_locale_name + if locale == entry_locale and encoding == entry_encoding: + entry = row + locale_name = entry_locale_name + locale = entry_locale + break + else: + self.log(f"Locale language '{locale}' and encoding '{encoding}' not found in /etc/locale.gen.", fg="red", level=logging.ERROR) + return False + + # A user could modify /etc/locale.gen in the install environment and add an invalid entry before launching the installer. + # Verify the locale did not validate against an invalid entry due to a user modified /etc/locale.gen. + if not os.path.exists(f'{self.target}/usr/share/i18n/locales/{locale}'): + self.log(f'Invalid locale: {locale}.', fg="red", level=logging.ERROR) + return False - # This is a temporary patch to fix #1200 - if '.' in locale: - locale, potential_encoding = locale.split('.', 1) + # Check if the locale is already installed using the output of localedef. + # Encodings are formatted differently in the output of localedef (no dashes and lowercase). + formatted_encoding = encoding.replace('-','').lower() + locale_formatted_encoding = f'{locale}.{formatted_encoding}' - # Override encoding if encoding is set to the default parameter - # and the "found" encoding differs. - if encoding == 'UTF-8' and encoding != potential_encoding: - encoding = potential_encoding + installed = [] + + # Get installed locales from the output of localedef. + for line in SysCommand(['localedef', '--list-archive']).decode().split(): + installed.append(line) + + # Install the locale if it is not already installed. + if locale_formatted_encoding not in installed: + # Before installing the locale check if the locale archive already exists and remove it if it does. + try: + os.remove('f{self.target}/usr/lib/locale/locale-archive') + except FileNotFoundError: + pass + + # Use localdef rather than local-gen since local-gen is a wrapper of localdef for user convenience + # and all necessary parameters for localedef are avaliable. + command = f'localedef -i {locale} -c -f {encoding} -A /usr/share/locale/locale.alias {locale_name}' + + if not subprocess.run(command).returncode == 0: + self.log(f"Failed to install locale language '{locale}' with '{encoding}'.", fg="red", level=logging.ERROR) + return False + + modifier = '' # Make sure we extract the modifier, that way we can put it in if needed. if '@' in locale: - locale, modifier = locale.split('@', 1) + modifier = locale.split('@', 1)[1] modifier = f"@{modifier}" - # - End patch - with open(f'{self.target}/etc/locale.gen', 'a') as fh: - fh.write(f'{locale}.{encoding}{modifier} {encoding}\n') - with open(f'{self.target}/etc/locale.conf', 'w') as fh: - fh.write(f'LANG={locale}.{encoding}{modifier}\n') + # A format of locale to check and set the system locale with. + formatted_locale = f'{locale}.{encoding}{modifier}' + + locale_conf = f'{self.target}/etc/locale.conf' - return True if SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen').exit_code == 0 else False + found = '' + + # Check if the system locale is already set correctly. + try: + with open(locale_conf, 'r') as fh: + regex = re.compile(rf'^LANG="?({locale_name}|{formatted_locale})"?$') + + for line in fh: + found = regex.search(line) + if found: + found = found.group(1) + break + except FileNotFoundError: + pass + + # Change the system locale if it is not set or not set correctly. + if not found: + with open(locale_conf, 'w') as fh: + fh.write(f'LANG={formatted_locale}\n') + + locale_gen = f'{self.target}/etc/locale.gen' + + # Update /etc/locale.gen if necessary so that locale-gen will function properly. + # Check if the locale is the only uncommented entry. + with open(locale_gen, 'r') as fh: + uncommented = [] + for line in fh: + if line[0] != '#': + uncommented.append(line.strip()) + + if len(uncommented) == 1 and entry in uncommented: + return True + + # Uncomment the entry for the locale and comment all other uncommented entries. + with open(locale_gen, 'r') as fh: + contents = fh.readlines() + + index = 0 + for index, line in enumerate(contents): + uncommented_line = line.replace('#', '') + if uncommented_line.rstrip() == entry: + contents[index] = uncommented_line + continue + if line[0] != '#': + contents[index] = f'#{contents[index]}' + + with open(locale_gen, 'w') as fh: + return True if fh.writelines(contents) else False def set_timezone(self, zone :str, *args :str, **kwargs :str) -> bool: if not zone: