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

522 lines
18 KiB
Python

#
# Bootloader module.
#
# Copyright (C) 2018 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.
#
from pykickstart.errors import KickstartParseError
from pykickstart.constants import SECURE_BOOT_AUTO, SECURE_BOOT_ENABLED, SECURE_BOOT_DISABLED
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.modules.storage.bootloader import BootLoaderFactory
from pyanaconda.modules.storage.bootloader.efi import EFIBase
from pyanaconda.modules.storage.bootloader.grub2 import GRUB2
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.configuration.bootloader import BootloaderType
from pyanaconda.core.constants import BOOTLOADER_LOCATION_DEFAULT, BOOTLOADER_TIMEOUT_UNSET, \
BOOTLOADER_LOCATION_MBR, BOOTLOADER_LOCATION_PARTITION
from pyanaconda.core.i18n import _
from pyanaconda.core.dbus import DBus
from pyanaconda.core.signal import Signal
from pyanaconda.modules.common.constants.objects import BOOTLOADER
from pyanaconda.modules.common.structures.requirement import Requirement
from pyanaconda.modules.storage.bootloader.bootloader_interface import BootloaderInterface
from pyanaconda.modules.storage.bootloader.installation import ConfigureBootloaderTask, \
InstallBootloaderTask, FixZIPLBootloaderTask, FixBTRFSBootloaderTask, RecreateInitrdsTask, \
CreateRescueImagesTask, CreateBLSEntriesTask, CollectKernelArgumentsTask
from pyanaconda.modules.storage.constants import BootloaderMode, ZIPLSecureBoot
from pyanaconda.modules.storage.storage_subscriber import StorageSubscriberModule
log = get_module_logger(__name__)
class BootloaderModule(StorageSubscriberModule):
"""The bootloader module."""
def __init__(self):
"""Initialize the module."""
super().__init__()
self.bootloader_mode_changed = Signal()
self._bootloader_mode = BootloaderMode.ENABLED
self._default_type = BootloaderType.DEFAULT
self.set_default_type(conf.bootloader.type)
self.preferred_location_changed = Signal()
self._preferred_location = BOOTLOADER_LOCATION_DEFAULT
self.drive_changed = Signal()
self._drive = ""
self.drive_order_changed = Signal()
self._drive_order = []
self.keep_mbr_changed = Signal()
self._keep_mbr = False
self.keep_boot_order_changed = Signal()
self._keep_boot_order = False
self.extra_arguments_changed = Signal()
self._extra_arguments = []
self.timeout_changed = Signal()
self._timeout = BOOTLOADER_TIMEOUT_UNSET
self.zipl_secure_boot_changed = Signal()
self._zipl_secure_boot = None
self.password_is_set_changed = Signal()
self._password = ""
self._password_is_encrypted = False
def publish(self):
"""Publish the module."""
DBus.publish_object(BOOTLOADER.object_path, BootloaderInterface(self))
def process_kickstart(self, data):
"""Process the kickstart data."""
self._set_module_from_kickstart(data)
self._validate_grub2_configuration(data)
def _set_module_from_kickstart(self, data):
"""Set the module from the kickstart data."""
if not data.bootloader.seen:
self.set_bootloader_mode(BootloaderMode.ENABLED)
self.set_preferred_location(BOOTLOADER_LOCATION_DEFAULT)
elif data.bootloader.disabled:
self.set_bootloader_mode(BootloaderMode.DISABLED)
elif data.bootloader.location == "none":
self.set_bootloader_mode(BootloaderMode.SKIPPED)
elif data.bootloader.location == "mbr":
self.set_bootloader_mode(BootloaderMode.ENABLED)
self.set_preferred_location(BOOTLOADER_LOCATION_MBR)
elif data.bootloader.location == "partition":
self.set_bootloader_mode(BootloaderMode.ENABLED)
self.set_preferred_location(BOOTLOADER_LOCATION_PARTITION)
if data.bootloader.extlinux:
self.set_default_type(BootloaderType.EXTLINUX)
if data.bootloader.sdboot:
self.set_default_type(BootloaderType.SDBOOT)
if data.bootloader.bootDrive:
self.set_drive(data.bootloader.bootDrive)
if data.bootloader.driveorder:
self.set_drive_order(data.bootloader.driveorder)
if data.bootloader.nombr:
self.set_keep_mbr(True)
if data.bootloader.leavebootorder:
self.set_keep_boot_order(True)
if data.bootloader.appendLine:
args = data.bootloader.appendLine.split()
self.set_extra_arguments(args)
if data.bootloader.timeout is not None:
self.set_timeout(data.bootloader.timeout)
if data.bootloader.password:
self.set_password(data.bootloader.password, data.bootloader.isCrypted)
if data.zipl.secure_boot == SECURE_BOOT_ENABLED:
self.set_zipl_secure_boot(ZIPLSecureBoot.ENABLED)
elif data.zipl.secure_boot == SECURE_BOOT_DISABLED:
self.set_zipl_secure_boot(ZIPLSecureBoot.DISABLED)
elif data.zipl.secure_boot == SECURE_BOOT_AUTO:
self.set_zipl_secure_boot(ZIPLSecureBoot.AUTO)
def _validate_grub2_configuration(self, data):
"""Validate the GRUB2 configuration.
:raise: KickstartParseError if not valid
"""
# Skip other types of the boot loader.
if not issubclass(BootLoaderFactory.get_class(), GRUB2):
return
# Check the location support.
if self.preferred_location == BOOTLOADER_LOCATION_PARTITION:
raise KickstartParseError(_("GRUB2 does not support installation to a partition."),
lineno=data.bootloader.lineno)
# Check the password format.
if self.password_is_set \
and self.password_is_encrypted \
and not self.password.startswith("grub.pbkdf2."):
raise KickstartParseError(_("GRUB2 encrypted password must be in grub.pbkdf2 format."),
lineno=data.bootloader.lineno)
def setup_kickstart(self, data):
"""Setup the kickstart data."""
if self.get_default_type() == BootloaderType.EXTLINUX:
data.bootloader.extlinux = True
if self.get_default_type() == BootloaderType.SDBOOT:
data.bootloader.sdboot = True
if self.bootloader_mode == BootloaderMode.DISABLED:
data.bootloader.disabled = True
data.bootloader.location = "none"
elif self.bootloader_mode == BootloaderMode.SKIPPED:
data.bootloader.disabled = False
data.bootloader.location = "none"
elif self.preferred_location == BOOTLOADER_LOCATION_MBR:
data.bootloader.disabled = False
data.bootloader.location = "mbr"
elif self.preferred_location == BOOTLOADER_LOCATION_PARTITION:
data.bootloader.disabled = False
data.bootloader.location = "partition"
else:
data.bootloader.disabled = False
data.bootloader.location = None
data.bootloader.bootDrive = self.drive
data.bootloader.driveorder = self.drive_order
data.bootloader.nombr = self.keep_mbr
data.bootloader.leavebootorder = self.keep_boot_order
data.bootloader.appendLine = " ".join(self.extra_arguments)
if self.timeout == BOOTLOADER_TIMEOUT_UNSET:
data.bootloader.timeout = None
else:
data.bootloader.timeout = self.timeout
data.bootloader.password = self.password
data.bootloader.isCrypted = self.password_is_encrypted
if self._zipl_secure_boot == ZIPLSecureBoot.ENABLED:
data.zipl.secure_boot = SECURE_BOOT_ENABLED
elif self._zipl_secure_boot == ZIPLSecureBoot.DISABLED:
data.zipl.secure_boot = SECURE_BOOT_DISABLED
elif self._zipl_secure_boot == ZIPLSecureBoot.AUTO:
data.zipl.secure_boot = SECURE_BOOT_AUTO
return data
@property
def bootloader_mode(self):
"""The mode of the bootloader."""
return self._bootloader_mode
def set_bootloader_mode(self, mode):
"""Set the type of the bootloader.
:param mode: an instance of BootloaderMode
"""
self._bootloader_mode = mode
self.bootloader_mode_changed.emit()
log.debug("Bootloader mode is set to '%s'.", mode)
def get_default_type(self):
"""Get the default type of the boot loader.
FIXME: This is a temporary workaround for UI.
:return: an instance of BootloaderType
"""
return self._default_type
def set_default_type(self, default_type):
"""Set the default type of the boot loader.
:param default_type: an instance of BootloaderType
"""
# Set up the bootloader factory.
cls = BootLoaderFactory.get_class_by_name(default_type.value)
BootLoaderFactory.set_default_class(cls)
# Set up the property.
self._default_type = default_type
log.debug("The default type is set to '%s'.", default_type)
@property
def preferred_location(self):
"""Where the boot record is written."""
return self._preferred_location
def set_preferred_location(self, location):
"""Specify where the boot record is written.
Supported values: DEFAULT, MBR, PARTITION
:param location: a string with the location
"""
self._preferred_location = location
self.preferred_location_changed.emit()
log.debug("Preferred location is set to '%s'.", location)
@property
def drive(self):
"""The drive where the bootloader should be written."""
return self._drive
def set_drive(self, drive):
"""Set the drive where the bootloader should be written.
:param drive: a name of the drive
"""
self._drive = drive
self.drive_changed.emit()
log.debug("Drive is set to '%s'.", drive)
@property
def drive_order(self):
"""Potentially partial order for disks."""
return self._drive_order
def set_drive_order(self, drives):
"""Set the potentially partial order for disks.
:param drives: a list of names of drives
"""
self._drive_order = drives
self.drive_order_changed.emit()
log.debug("Drive order is set to '%s'.", drives)
@property
def keep_mbr(self):
"""Don't update the MBR."""
return self._keep_mbr
def set_keep_mbr(self, value):
"""Set if the MBR can be updated.
:param value: True if the MBR cannot be updated, otherwise False
"""
self._keep_mbr = value
self.keep_mbr_changed.emit()
log.debug("Keep MBR is set to '%s'.", value)
@property
def keep_boot_order(self):
"""Don't change the existing boot order."""
return self._keep_boot_order
def set_keep_boot_order(self, value):
"""Set if the the boot order can be changed.
:param value: True to use the existing order, otherwise False
:return:
"""
self._keep_boot_order = value
self.keep_boot_order_changed.emit()
log.debug("Keep boot order is set to '%s'.", value)
@property
def extra_arguments(self):
"""List of extra bootloader arguments."""
return self._extra_arguments
def set_extra_arguments(self, args):
"""Set the extra bootloader arguments.
:param args: a list of arguments
"""
self._extra_arguments = args
self.extra_arguments_changed.emit()
log.debug("Extra arguments are set to '%s'.", args)
@property
def timeout(self):
"""The bootloader timeout."""
return self._timeout
def set_timeout(self, timeout):
"""Set the bootloader timeout.
:param timeout: a number of seconds
"""
self._timeout = timeout
self.timeout_changed.emit()
log.debug("Timeout is set to '%s'.", timeout)
@property
def zipl_secure_boot(self):
"""The ZIPL Secure Boot for s390x.
:return: an instance of ZIPLSecureBoot
"""
if self._zipl_secure_boot is None:
return ZIPLSecureBoot.AUTO
return self._zipl_secure_boot
def set_zipl_secure_boot(self, value):
"""Set up the ZIPL Secure Boot for s390x.
:param value: an instance of ZIPLSecureBoot
"""
self._zipl_secure_boot = value
self.zipl_secure_boot_changed.emit()
log.debug("ZIPL Secure Boot is set to '%s'.", value)
@property
def password(self):
"""The GRUB boot loader password."""
return self._password
@property
def password_is_set(self):
"""Is the GRUB boot loader password set?"""
return self._password != ""
@property
def password_is_encrypted(self):
"""Is the GRUB boot loader password encrypted?"""
return self._password_is_encrypted
def set_password(self, password, encrypted):
"""Set the GRUB boot loader password.
:param password: a string with the password
:param encrypted: True if the password is encrypted, otherwise False
"""
self._password = password
self._password_is_encrypted = encrypted
self.password_is_set_changed.emit()
log.debug("Password is set.")
def is_efi(self):
"""Is the bootloader based on EFI?
:return: True or False
"""
return isinstance(self.storage.bootloader, EFIBase)
def get_arguments(self):
"""Get the bootloader arguments.
Get kernel parameters that are currently set up for the bootloader.
The list is complete and final after the bootloader installation.
FIXME: Collect the bootloader arguments on demand if possible.
:return: list of arguments
"""
return list(self.storage.bootloader.boot_args)
def detect_windows(self):
"""Are Windows OS installed on the system?
Guess by searching for bootable partitions of other operating
systems whether there are Windows OS installed on the system.
:return: True or False
"""
devices = filter(lambda d: d.format.name == "ntfs", self.storage.devices)
return self.storage.bootloader.has_windows(devices)
def collect_requirements(self):
"""Return installation requirements for this module.
:return: a list of requirements
"""
if conf.target.is_directory:
log.debug("The bootloader configuration is disabled for dir installations.")
return []
if self.bootloader_mode == BootloaderMode.DISABLED:
log.debug("The bootloader configuration is disabled.")
return []
requirements = []
for name in self.storage.bootloader.packages:
requirements.append(Requirement.for_package(
name, reason="Necessary for the bootloader configuration."
))
return requirements
def install_bootloader_with_tasks(self, payload_type, kernel_versions):
"""Install the bootloader with a list of tasks.
FIXME: This is just a temporary method.
:param payload_type: a string with the payload type
:param kernel_versions: a list of kernel versions
:return: a list of tasks
"""
return [
CreateRescueImagesTask(
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
),
ConfigureBootloaderTask(
storage=self.storage,
mode=self.bootloader_mode,
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
),
CollectKernelArgumentsTask(
storage=self.storage,
mode=self.bootloader_mode
),
InstallBootloaderTask(
storage=self.storage,
mode=self.bootloader_mode,
payload_type=payload_type,
sysroot=conf.target.system_root
),
CreateBLSEntriesTask(
storage=self.storage,
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
)
]
def generate_initramfs_with_tasks(self, payload_type, kernel_versions):
"""Generate initramfs with a list of tasks.
FIXME: This is just a temporary method.
:param payload_type: a string with the payload type
:param kernel_versions: a list of kernel versions
:return: a list of tasks
"""
return [
RecreateInitrdsTask(
storage=self.storage,
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
),
FixBTRFSBootloaderTask(
storage=self.storage,
mode=self.bootloader_mode,
payload_type=payload_type,
kernel_versions=kernel_versions,
sysroot=conf.target.system_root
),
]
def fix_zipl_bootloader_with_task(self):
"""Fix ZIPL bootloader with a task.
:return: an installation task
"""
return FixZIPLBootloaderTask(
mode=self.bootloader_mode
)