284 lines
10 KiB
Python
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
|