kiwi-el8/kiwi/bootloader/config/base.py
Marcus Schäfer 374be7c042 Completion for grub bootloader configuration
The configuration files /etc/sysconfig/bootloader and
/etc/default/grub needs to be created/updated with the
relevant values regarding the bootloader setup done by
kiwi. This Fixes #226
2017-01-23 17:24:04 +01:00

434 lines
14 KiB
Python

# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty 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 kiwi. If not, see <http://www.gnu.org/licenses/>
#
from collections import namedtuple
# project
from ...logger import log
from ...storage.setup import DiskSetup
from ...path import Path
from ...defaults import Defaults
from ...exceptions import (
KiwiBootLoaderTargetError
)
class BootLoaderConfigBase(object):
"""
Base class for bootloader configuration
Attributes
* :attr:`root_dir`
root directory path name
* :attr:`xml_state`
Instance of XMLState of the system image description
* :attr:`custom_args`
List of custom bootloader arguments
"""
def __init__(self, xml_state, root_dir, custom_args=None):
self.root_dir = root_dir
self.xml_state = xml_state
self.post_init(custom_args)
def post_init(self, custom_args):
"""
Post initialization method
Store custom arguments by default
:param list custom_args: custom bootloader arguments
"""
self.custom_args = custom_args
def write(self):
"""
Write config data to config file.
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_disk_image_config(
self, boot_uuid, root_uuid, hypervisor, kernel, initrd
):
"""
Create boot config file to boot from disk.
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_install_image_config(
self, mbrid, hypervisor, kernel, initrd
):
"""
Create boot config file to boot from install media in EFI mode.
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_live_image_config(
self, mbrid, hypervisor, kernel, initrd
):
"""
Create boot config file to boot live ISO image in EFI mode.
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_disk_boot_images(self, boot_uuid, lookup_path=None):
"""
Create bootloader images for disk boot
Some bootloaders requires to build a boot image the bootloader
can load from a specific offset address or from a standardized
path on a filesystem.
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_install_boot_images(self, mbrid, lookup_path=None):
"""
Create bootloader images for ISO boot an install media
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_live_boot_images(self, mbrid, lookup_path=None):
"""
Create bootloader images for ISO boot a live ISO image
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def setup_sysconfig_bootloader(self):
"""
Create or update etc/sysconfig/bootloader by parameters
required according to the bootloader setup
Implementation in specialized bootloader class required
"""
raise NotImplementedError
def create_efi_path(self, in_sub_dir='boot/efi'):
"""
Create standard EFI boot directory structure
:param string in_sub_dir: toplevel directory
:return: Full qualified EFI boot path
:rtype: string
"""
efi_boot_path = self.root_dir + '/' + in_sub_dir + '/EFI/BOOT'
Path.create(efi_boot_path)
return efi_boot_path
def get_boot_theme(self):
"""
Bootloader Theme name
:return: theme name
:rtype: string
"""
theme = None
for preferences in self.xml_state.get_preferences_sections():
section_content = preferences.get_bootloader_theme()
if section_content:
theme = section_content[0]
return theme
def get_boot_timeout_seconds(self):
"""
Bootloader timeout in seconds
If no timeout is specified the default timeout applies
:return: timeout seconds
:rtype: int
"""
timeout_seconds = self.xml_state.build_type.get_boottimeout()
if not timeout_seconds:
timeout_seconds = Defaults.get_default_boot_timeout_seconds()
return timeout_seconds
def failsafe_boot_entry_requested(self):
"""
Check if a failsafe boot entry is requested
:rtype: bool
"""
if self.xml_state.build_type.get_installprovidefailsafe() is False:
return False
return True
def get_hypervisor_domain(self):
"""
Hypervisor domain name
:return: domain name
:rtype: string
"""
machine = self.xml_state.get_build_type_machine_section()
if machine:
return machine.get_domain()
def get_boot_cmdline(self, uuid=None):
"""
Boot commandline arguments passed to the kernel
:return: kernel boot arguments
:rtype: string
"""
cmdline = ''
custom_cmdline = self.xml_state.build_type.get_kernelcmdline()
if custom_cmdline:
cmdline += ' ' + custom_cmdline
custom_root = self._get_root_cmdline_parameter(uuid)
if custom_root:
cmdline += ' ' + custom_root
return cmdline.strip()
def get_install_image_boot_default(self, loader=None):
"""
Provide the default boot menu entry identifier for install images
The install image can be configured to provide more than
one boot menu entry. Menu entries configured are:
* [0] Boot From Hard Disk
* [1] Install
* [2] Failsafe Install
The installboot attribute controlls which of these are used
by default. If not specified the boot from hard disk entry
will be the default. Depending on the specified loader type
either an entry number or name will be returned.
:return: menu name or id
:rtype: string
"""
menu_entry_title = self.get_menu_entry_title(plain=True)
menu_type = namedtuple(
'menu_type', ['name', 'menu_id']
)
menu_list = [
menu_type(
name='Boot_from_Hard_Disk', menu_id='0'
),
menu_type(
name='Install_' + menu_entry_title, menu_id='1'
),
menu_type(
name='Failsafe_--_Install_' + menu_entry_title, menu_id='2'
)
]
boot_id = 0
install_boot_name = self.xml_state.build_type.get_installboot()
if install_boot_name == 'failsafe-install':
boot_id = 2
elif install_boot_name == 'install':
boot_id = 1
if not self.failsafe_boot_entry_requested() and boot_id == 2:
log.warning(
'Failsafe install requested but failsafe menu entry is disabled'
)
log.warning('Switching to standard install')
boot_id = 1
if loader and loader == 'isolinux':
return menu_list[boot_id].name
else:
return menu_list[boot_id].menu_id
def get_boot_path(self, target='disk'):
"""
Bootloader lookup path on boot device
If the bootloader reads the data it needs to boot, it does
that from the configured boot device. Depending if that
device is an extra boot partition or the root partition or
or based on a non standard filesystem like a btrfs snapshot,
the path name varies
:return: path name
:rtype: string
"""
if target != 'disk' and target != 'iso':
raise KiwiBootLoaderTargetError(
'Invalid boot loader target %s' % target
)
bootpath = '/boot'
need_boot_partition = False
if target == 'disk':
disk_setup = DiskSetup(self.xml_state, self.root_dir)
need_boot_partition = disk_setup.need_boot_partition()
if need_boot_partition:
# if an extra boot partition is used we will find the
# data directly in the root of this partition and not
# below the boot/ directory
bootpath = '/'
if target == 'disk':
if not need_boot_partition:
filesystem = self.xml_state.build_type.get_filesystem()
volumes = self.xml_state.get_volumes()
if filesystem == 'btrfs' and volumes:
# boot or boot/grub2 on a subvolume prevents grub from
# finding its config file
for volume in volumes:
if volume.name == 'boot' or volume.name == 'boot/grub2':
raise KiwiBootLoaderTargetError(
'boot or boot/grub2 must not be a subvolume'
)
# if we directly boot a btrfs filesystem with a subvolume
# setup and no extra boot partition we have to care for
# the layout of the system which places all volumes below
# a toplevel volume. However if the root is placed into
# a snapshot we use the SUSE_BTRFS_SNAPSHOT_BOOTING
# extension which is configured in etc/default/grub and
# maps the correct snapshot to the bootpath
root_is_snapshot = \
self.xml_state.build_type.get_btrfs_root_is_snapshot()
if not root_is_snapshot:
bootpath = '/@' + bootpath
return bootpath
def quote_title(self, name):
"""
Quote special characters in the title name
Not all characters can be displayed correctly in the bootloader
environment. Therefore a quoting is required
:return: quoted text
:rtype: string
"""
name = name.replace(' ', '_')
name = name.replace('[', '(')
name = name.replace(']', ')')
return name
def get_menu_entry_title(self, plain=False):
"""
Prefixed menu entry title
If no displayname is specified in the image description,
the menu title is constructed from the image name and
build type
:return: title text
:rtype: string
"""
title = self.xml_state.xml_data.get_displayname()
if not title:
title = self.xml_state.xml_data.get_name()
type_name = self.xml_state.build_type.get_image()
if plain:
return title
return title + ' [ ' + type_name.upper() + ' ]'
def get_menu_entry_install_title(self):
"""
Prefixed menu entry title for install images
If no displayname is specified in the image description,
the menu title is constructed from the image name
:return: title text
:rtype: string
"""
title = self.xml_state.xml_data.get_displayname()
if not title:
title = self.xml_state.xml_data.get_name()
return title
def get_gfxmode(self, target):
"""
Graphics mode according to bootloader target
Bootloaders which support a graphics mode can be configured
to run graphics in a specific resolution and colors. There
is no standard for this setup which causes kiwi to create
a mapping from the kernel vesa mode number to the corresponding
bootloader graphics mode setup
:param string target: bootloader name
:return: boot graphics mode
:rtype: string
"""
gfxmode_map = Defaults.get_video_mode_map()
default_mode = Defaults.get_default_video_mode()
requested_gfxmode = self.xml_state.build_type.get_vga()
if requested_gfxmode in gfxmode_map:
gfxmode = requested_gfxmode
else:
gfxmode = default_mode
if target == 'grub2':
return gfxmode_map[gfxmode].grub2
elif target == 'isolinux':
return gfxmode_map[gfxmode].isolinux
else:
return gfxmode
def _get_root_cmdline_parameter(self, uuid):
firmware = self.xml_state.build_type.get_firmware()
initrd_system = self.xml_state.build_type.get_initrd_system()
cmdline = self.xml_state.build_type.get_kernelcmdline()
if cmdline and 'root=' in cmdline:
log.info(
'Kernel root device explicitly set via kernelcmdline'
)
return None
want_root_cmdline_parameter = False
if firmware and 'ec2' in firmware:
# EC2 requires to specifiy the root device in the bootloader
# configuration. This is because the used pvgrub or hvmloader
# reads this information and passes it to the guest configuration
# which has an impact on the devices attached to the guest.
want_root_cmdline_parameter = True
if initrd_system and 'dracut' in initrd_system:
# When using a dracut initrd we have to specify the location
# of the root device
want_root_cmdline_parameter = True
if want_root_cmdline_parameter:
if uuid:
return 'root=UUID=%s rw' % format(uuid)
else:
log.warning(
'root=UUID=<uuid> setup requested, but uuid is not provided'
)