diff options
author | Jose M. Guisado <jguisado@soleta.eu> | 2023-08-16 14:10:45 +0200 |
---|---|---|
committer | Jose M. Guisado <jguisado@soleta.eu> | 2023-09-13 15:18:03 +0200 |
commit | 65f1cbc7eb8033d6dc9aca3c23feaf1cfc01ac04 (patch) | |
tree | d7ebe38404cda39805f9f014f868fe54825cd17c /src/utils/uefi.py | |
parent | a49988a22229e08eca600bf8ef06d04a9e7afa41 (diff) |
utils: add uefi.py
Add UEFI related utilities inside a new utility module: uefi.py
_check_efibootmgr_json
======================
Check if the system efibootmgr executable supports JSON output. This is
a private function used only by other functions from uefi.py.
is_uefi_supported
=================
Check if the system supports UEFI firmware.
run_efibootmgr_json
===================
Runs efibootmgr with json output support. Return the JSON output as a
Python dict.
efibootmgr_create_bootentry
===========================
Create nvram boot entry. This bootentry is usually later set to boot
next just once via "BootNext" nvram variable.
efibootmgr_delete_bootentry
===========================
Delete a nvram boot entry. Used to avoid duplicates when booting the
same disk and partition from a given client.
efibootmgr_bootnext
===================
Set nvram "BootNext" variable to a given boot entry so after client
reboot, PXE is not executed and the given boot entry takes precedence.
Add dependency with efibootmgr version >= 18, and efibootmgr JSON output
which is currently out of tree from util-linux repo.
Diffstat (limited to 'src/utils/uefi.py')
-rw-r--r-- | src/utils/uefi.py | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/src/utils/uefi.py b/src/utils/uefi.py new file mode 100644 index 0000000..184edba --- /dev/null +++ b/src/utils/uefi.py @@ -0,0 +1,97 @@ +# +# Copyright (C) 2023 Soleta Networks <info@soleta.eu> +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import json +import logging +import os +import shlex +import subprocess + +import fdisk + +def _find_bootentry(entries, label): + for entry in entries: + if entry['description'] == label: + return entry + else: + raise NameError('Boot entry {label} not found') + + +def _strip_boot_prefix(entry): + try: + num = entry['name'][4:] + except: + raise KeyError('Unable to strip "Boot" prefix from boot entry') + return num + + +def _check_efibootmgr_json(): + """ + Checks if efibootmgr --json option is supported. + Returns True if supported, False otherwise. + """ + supported = True + try: + subprocess.run(['efibootmgr', '--json'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + supported = False + return supported + + +def is_uefi_supported(): + return True if os.path.exists("/sys/firmware/efi") else False + + +def run_efibootmgr_json(): + if _check_efibootmgr_json() is False: + raise RuntimeError('efibootmgr does not support --json option') + + proc = subprocess.run(['efibootmgr', '--json'], capture_output=True, text=True) + dict_json = json.loads(proc.stdout) + + return dict_json + + +def efibootmgr_bootnext(description): + logging.info(f'Setting BootNext to value from boot entry with label {description}') + bootnext_cmd = 'efibootmgr -n {bootnum}' + boot_entries = run_efibootmgr_json().get('vars', []) + + entry = _find_bootentry(boot_entries, description) + num = _strip_boot_prefix(entry) # efibootmgr output uses BootXXXX for each entry, remove the "Boot" prefix. + bootnext_cmd = bootnext_cmd.format(bootnum=num) + subprocess.run(shlex.split(bootnext_cmd), check=True, + stdout=subprocess.DEVNULL) + + +def efibootmgr_delete_bootentry(label): + dict_json = run_efibootmgr_json() + efibootmgr_cmd = 'efibootmgr -q -b {bootnum} -B' + + for entry in dict_json.get('vars', []): + if entry['description'] == label: + num = entry['name'][4:] # Remove "Boot" prefix to extract num + efibootmgr_cmd = efibootmgr_cmd.format(bootnum=num) + subprocess.run(shlex.split(efibootmgr_cmd), check=True) + break + else: + logging.info(f'Cannot delete boot entry {label} because it was not found.') + + +def efibootmgr_create_bootentry(disk, part, loader, label, add_to_bootorder=True): + entries = run_efibootmgr_json().get('vars', []) + create_opt = '-c' if add_to_bootorder else '-C' + index_opt = f'-I {len(entries)}' # Requires efibootmgr version 18 + efibootmgr_cmd = f'efibootmgr {create_opt} {index_opt} -d {disk} -p {part} -L {label} -l {loader}' + logging.info(f'efibootmgr command creating boot entry: {efibootmgr_cmd}') + try: + proc = subprocess.run(shlex.split(efibootmgr_cmd), check=True, text=True) + except subprocess.CalledProcessError as e: + logging.error(f'Unexpected error adding boot entry to nvram. UEFI firmware might be buggy?.') + raise e |