anaconda/anaconda-40.22.3.13/pyanaconda/modules/storage/bootloader/efi.py
2024-11-14 21:39:56 -08:00

284 lines
10 KiB
Python

#
# Copyright (C) 2019 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 re
from pyanaconda.modules.storage.bootloader.base import BootLoaderError
from pyanaconda.modules.storage.bootloader.grub2 import GRUB2
from pyanaconda.modules.storage.bootloader.systemd import SystemdBoot
from pyanaconda.core import util
from pyanaconda.core.i18n import _
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.core.path import join_paths
from pyanaconda.core.product import get_product_name
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
__all__ = ["EFIBase", "EFIGRUB", "Aarch64EFIGRUB", "ArmEFIGRUB", "Aarch64EFISystemdBoot", "X64EFISystemdBoot"]
class EFIBase(object):
"""A base class for EFI-based boot loaders."""
@property
def efi_config_dir(self):
return "/boot/" + self._efi_config_dir
@property
def _efi_config_dir(self):
return "efi/EFI/{}".format(conf.bootloader.efi_dir)
def get_fw_platform_size(self):
try:
with open("/sys/firmware/efi/fw_platform_size", "r") as f:
value = f.readline().strip()
except OSError:
log.info("Reading /sys/firmware/efi/fw_platform_size failed, "
"defaulting to 64-bit install.")
value = '64'
return value
def efibootmgr(self, *args, **kwargs):
if not conf.target.is_hardware:
log.info("Skipping efibootmgr for image/directory install.")
return ""
if "noefi" in kernel_arguments:
log.info("Skipping efibootmgr for noefi")
return ""
if kwargs.pop("capture", False):
exec_func = util.execWithCapture
else:
exec_func = util.execWithRedirect
if "root" not in kwargs:
kwargs["root"] = conf.target.system_root
return exec_func("efibootmgr", list(args), **kwargs)
@property
def efi_dir_as_efifs_dir(self):
ret = self._efi_config_dir.replace('efi/', '')
return "\\" + ret.replace('/', '\\')
def _add_single_efi_boot_target(self, partition):
boot_disk = partition.disk
boot_part_num = str(partition.parted_partition.number)
create_method = "-C" if self.keep_boot_order else "-c" # pylint: disable=no-member
rc = self.efibootmgr(
create_method, "-w", "-L", get_product_name().split("-")[0], # pylint: disable=no-member
"-d", boot_disk.path, "-p", boot_part_num,
"-l", self.efi_dir_as_efifs_dir + self._efi_binary, # pylint: disable=no-member
root=conf.target.system_root
)
if rc:
raise BootLoaderError("Failed to set new efi boot target. This is most "
"likely a kernel or firmware bug.")
def add_efi_boot_target(self):
if self.stage1_device.type == "partition": # pylint: disable=no-member
self._add_single_efi_boot_target(self.stage1_device) # pylint: disable=no-member
elif self.stage1_device.type == "mdarray": # pylint: disable=no-member
for parent in self.stage1_device.parents: # pylint: disable=no-member
self._add_single_efi_boot_target(parent)
def remove_efi_boot_target(self):
buf = self.efibootmgr(capture=True)
for line in buf.splitlines():
try:
(slot, _product) = line.split(None, 1)
# keep only the name, if verbose output is default in this version
_product = _product.split("\t")[0]
except ValueError:
continue
if _product == get_product_name().split("-")[0]: # pylint: disable=no-member
slot_id = slot[4:8]
# slot_id is hex, we can't use .isint and use this regex:
if not re.match("^[0-9a-fA-F]+$", slot_id):
log.warning("failed to parse efi boot slot (%s)", slot)
continue
rc = self.efibootmgr("-b", slot_id, "-B")
if rc:
raise BootLoaderError("Failed to remove old efi boot entry. This is most "
"likely a kernel or firmware bug.")
def write(self):
""" Write the bootloader configuration and install the bootloader. """
if self.skip_bootloader: # pylint: disable=no-member
return
try:
os.sync()
self.stage2_device.format.sync(root=conf.target.physical_root) # pylint: disable=no-member
self.install()
finally:
self.write_config() # pylint: disable=no-member
def check(self):
return True
def install(self, args=None):
if not self.keep_boot_order: # pylint: disable=no-member
self.remove_efi_boot_target()
self.add_efi_boot_target()
class EFIGRUB(EFIBase, GRUB2):
"""EFI GRUBv2"""
_packages32 = [ "grub2-efi-ia32", "shim-ia32" ]
_packages_common = ["efibootmgr", "grub2-tools", "grub2-tools-extra", "grubby" ]
stage2_is_valid_stage1 = False
stage2_bootable = False
_is_32bit_firmware = False
def __init__(self):
super().__init__()
self._packages64 = [ "grub2-efi-x64", "shim-x64" ]
if self.get_fw_platform_size() == '32':
self._is_32bit_firmware = True
@property
def _efi_binary(self):
if self._is_32bit_firmware:
return "\\shimia32.efi"
return "\\shimx64.efi"
@property
def packages(self):
if self._is_32bit_firmware:
return self._packages32 + self._packages_common
return self._packages64 + self._packages_common
@property
def efi_config_file(self):
""" Full path to EFI configuration file. """
return "%s/%s" % (self.efi_config_dir, self._config_file)
def write_config(self):
config_path = "%s%s" % (conf.target.system_root, self.efi_config_file)
with open(config_path, "w") as fd:
grub_dir = self.config_dir
if self.stage2_device.format.type != "btrfs":
fs_uuid = self.stage2_device.format.uuid
else:
fs_uuid = self.stage2_device.format.vol_uuid
if fs_uuid is None:
raise BootLoaderError("Could not get stage2 filesystem UUID")
grub_dir = util.execWithCapture("grub2-mkrelpath", [grub_dir],
root=conf.target.system_root)
if not grub_dir:
raise BootLoaderError("Could not get GRUB directory path")
fd.write("search --no-floppy --fs-uuid --set=dev %s\n" % fs_uuid)
fd.write("set prefix=($dev)%s\n" % grub_dir)
fd.write("export $prefix\n")
fd.write("configfile $prefix/grub.cfg\n")
super().write_config()
class EFISystemdBoot(EFIBase, SystemdBoot):
"""EFI Systemd-boot"""
_packages_common = ["efibootmgr", "systemd-udev", "systemd-boot", "sdubby"]
_packages64 = []
def __init__(self):
super().__init__()
if self.get_fw_platform_size() == '32':
# not supported try a different bootloader
log.error("efi.py: systemd-boot is not supported on 32-bit platforms")
raise BootLoaderError(_("Systemd-boot is not supported on this platform"))
@property
def packages(self):
return self._packages64 + self._packages_common
@property
def efi_config_file(self):
""" Full path to EFI configuration file. """
return join_paths(self.efi_config_dir, self._config_file)
def check(self):
"""Verify the bootloader configuration."""
# Force the resolution order to run the systemd-boot check.
return SystemdBoot.check(self) and EFIBase.check(self)
def write_config(self):
""" Write the config settings to config file (ex: grub.cfg) not needed for systemd. """
config_path = join_paths(conf.target.system_root, self.efi_config_file)
log.info("efi.py: (systemd) write_config systemd : %s ", config_path)
super().write_config()
def install(self, args=None):
log.info("efi.py: (systemd) install")
# force the resolution order, we don't want to:
# efibootmgr remove old "fedora"
# or use efiboot mgr to install a new one
# lets just use `bootctl install` directly.
# which will fix the efi boot variables too.
SystemdBoot.install(self)
class Aarch64EFIGRUB(EFIGRUB):
_serial_consoles = ["ttyAMA", "ttyS"]
_efi_binary = "\\shimaa64.efi"
def __init__(self):
super().__init__()
self._packages64 = ["grub2-efi-aa64", "shim-aa64", "grub2-efi-aa64-cdboot"]
class Aarch64EFISystemdBoot(EFISystemdBoot):
_serial_consoles = ["ttyAMA", "ttyS"]
_efi_binary = "\\systemd-bootaa64.efi"
def __init__(self):
super().__init__()
self._packages64 = []
class X64EFISystemdBoot(EFISystemdBoot):
_efi_binary = "\\systemd-bootx64.efi"
def __init__(self):
super().__init__()
self._packages64 = []
class ArmEFIGRUB(EFIGRUB):
_serial_consoles = ["ttyAMA", "ttyS"]
_efi_binary = "\\grubarm.efi"
def __init__(self):
super().__init__()
self._packages32 = ["grub2-efi-arm"]
self._is_32bit_firmware = True