From 0cf188d74f145cdd859bb1495a2b02f16fca6298 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Jan 2024 21:01:00 -0600 Subject: image-tools: T5980: add support for configurable kernel boot options (cherry picked from commit 1b1569d5b88a20994fc65fd529f8103db371bf3f) --- python/vyos/system/compat.py | 8 ++++-- python/vyos/system/grub.py | 61 ++++++++++++++++++++++++++++++++++++++--- python/vyos/system/grub_util.py | 30 +++++++++++++++++++- python/vyos/system/image.py | 13 ++++++++- 4 files changed, 104 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py index 436da14e8..626ce0067 100644 --- a/python/vyos/system/compat.py +++ b/python/vyos/system/compat.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -246,13 +246,17 @@ def update_version_list(root_dir: str = '') -> list[dict]: menu_entries = list(filter(lambda x: x.get('version') != ver, menu_entries)) + # reset boot_opts in case of config update + for entry in menu_entries: + entry['boot_opts'] = grub.get_boot_opts(entry['version']) + add = list(set(current_versions) - set(menu_versions)) for ver in add: last = menu_entries[0].get('version') new = deepcopy(list(filter(lambda x: x.get('version') == last, menu_entries))) for e in new: - boot_opts = e.get('boot_opts').replace(last, ver) + boot_opts = grub.get_boot_opts(ver) e.update({'version': ver, 'boot_opts': boot_opts}) menu_entries = new + menu_entries diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py index 3f5f89103..4e133c5fc 100644 --- a/python/vyos/system/grub.py +++ b/python/vyos/system/grub.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -43,10 +43,14 @@ TMPL_GRUB_MODULES: str = 'grub/grub_modules.j2' TMPL_GRUB_OPTS: str = 'grub/grub_options.j2' TMPL_GRUB_COMMON: str = 'grub/grub_common.j2' +# default boot options +BOOT_OPTS_STEM: str = 'boot=live rootdelay=5 noautologin net.ifnames=0 biosdevname=0 vyos-union=/boot/' + # prepare regexes REGEX_GRUB_VARS: str = r'^set (?P.+)=[\'"]?(?P.*)(?.+)$' REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?Pboot|live)/((?P.+)/)?vmlinuz.*$' +REGEX_GRUB_BOOT_OPTS: str = r'^\s*set boot_opts="(?P[^$]+)"$' def install(drive_path: str, boot_dir: str, efi_dir: str, id: str = 'VyOS') -> None: @@ -87,7 +91,8 @@ def gen_version_uuid(version_name: str) -> str: def version_add(version_name: str, root_dir: str = '', - boot_opts: str = '') -> None: + boot_opts: str = '', + boot_opts_config = None) -> None: """Add a new VyOS version to GRUB loader configuration Args: @@ -104,7 +109,9 @@ def version_add(version_name: str, version_config, TMPL_VYOS_VERSION, { 'version_name': version_name, 'version_uuid': gen_version_uuid(version_name), - 'boot_opts': boot_opts + 'boot_opts_default': BOOT_OPTS_STEM + version_name, + 'boot_opts': boot_opts, + 'boot_opts_config': boot_opts_config }) @@ -286,12 +293,43 @@ def vars_write(grub_cfg: str, grub_vars: dict[str, str]) -> None: """ render(grub_cfg, TMPL_GRUB_VARS, {'vars': grub_vars}) +def get_boot_opts(version_name: str, root_dir: str = '') -> str: + """Read boot_opts setting from version file; return default setting on + any failure. + + Args: + version_name (str): version name + root_dir (str, optional): an optional path to the root directory. + Defaults to empty. + """ + if not root_dir: + root_dir = disk.find_persistence() + + boot_opts_default: str = BOOT_OPTS_STEM + version_name + boot_opts: str = '' + regex_filter = re_compile(REGEX_GRUB_BOOT_OPTS) + version_config: str = f'{root_dir}/{GRUB_DIR_VYOS_VERS}/{version_name}.cfg' + try: + config_text: list[str] = Path(version_config).read_text().splitlines() + except FileNotFoundError: + return boot_opts_default + for line in config_text: + search_result = regex_filter.fullmatch(line) + if search_result: + search_dict = search_result.groupdict() + boot_opts = search_dict.get('boot_opts', '') + break + + if not boot_opts: + boot_opts = boot_opts_default + + return boot_opts def set_default(version_name: str, root_dir: str = '') -> None: """Set version as default boot entry Args: - version_name (str): versio name + version_name (str): version name root_dir (str, optional): an optional path to the root directory. Defaults to empty. """ @@ -361,3 +399,18 @@ def set_console_speed(console_speed: str, root_dir: str = '') -> None: vars_current: dict[str, str] = vars_read(vars_file) vars_current['console_speed'] = str(console_speed) vars_write(vars_file, vars_current) + +def set_kernel_cmdline_options(cmdline_options: str, version_name: str, + root_dir: str = '') -> None: + """Write additional cmdline options to GRUB configuration + + Args: + cmdline_options (str): cmdline options to add to default boot line + version_name (str): image version name + root_dir (str, optional): an optional path to the root directory. + """ + if not root_dir: + root_dir = disk.find_persistence() + + version_add(version_name=version_name, root_dir=root_dir, + boot_opts_config=cmdline_options) diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py index 9e79d41d4..4a3d8795e 100644 --- a/python/vyos/system/grub_util.py +++ b/python/vyos/system/grub_util.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -from vyos.system import disk, grub, compat +from vyos.system import disk, grub, image, compat @compat.grub_cfg_update def set_console_speed(console_speed: str, root_dir: str = '') -> None: @@ -29,6 +29,7 @@ def set_console_speed(console_speed: str, root_dir: str = '') -> None: grub.set_console_speed(console_speed, root_dir) +@image.if_not_live_boot def update_console_speed(console_speed: str, root_dir: str = '') -> None: """Update console_speed if different from current value""" @@ -40,3 +41,30 @@ def update_console_speed(console_speed: str, root_dir: str = '') -> None: console_speed_current = vars_current.get('console_speed', None) if console_speed != console_speed_current: set_console_speed(console_speed, root_dir) + +@compat.grub_cfg_update +def set_kernel_cmdline_options(cmdline_options: str, version: str = '', + root_dir: str = '') -> None: + """Write Kernel CLI cmdline options to GRUB configuration""" + if not root_dir: + root_dir = disk.find_persistence() + + if not version: + version = image.get_running_image() + + grub.set_kernel_cmdline_options(cmdline_options, version, root_dir) + +@image.if_not_live_boot +def update_kernel_cmdline_options(cmdline_options: str, + root_dir: str = '') -> None: + """Update Kernel custom cmdline options""" + if not root_dir: + root_dir = disk.find_persistence() + + version = image.get_running_image() + + boot_opts_current = grub.get_boot_opts(version, root_dir) + boot_opts_proposed = grub.BOOT_OPTS_STEM + f'{version} {cmdline_options}' + + if boot_opts_proposed != boot_opts_current: + set_kernel_cmdline_options(cmdline_options, version, root_dir) diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py index 514275654..5460e6a36 100644 --- a/python/vyos/system/image.py +++ b/python/vyos/system/image.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors +# Copyright 2023-2024 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,6 +15,7 @@ from pathlib import Path from re import compile as re_compile +from functools import wraps from tempfile import TemporaryDirectory from typing import TypedDict @@ -262,6 +263,16 @@ def is_live_boot() -> bool: return True return False +def if_not_live_boot(func): + """Decorator to call function only if not live boot""" + @wraps(func) + def wrapper(*args, **kwargs): + if not is_live_boot(): + ret = func(*args, **kwargs) + return ret + return None + return wrapper + def is_running_as_container() -> bool: if Path('/.dockerenv').exists(): return True -- cgit v1.2.3