Example #1
0
File: live.py Project: jfkw/kiwi
    def __init__(self,
                 xml_state: XMLState,
                 target_dir: str,
                 root_dir: str,
                 custom_args: Dict = None):
        self.media_dir: str = ''
        self.live_container_dir: str = ''
        self.arch = Defaults.get_platform_name()
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.publisher = xml_state.build_type.get_publisher() or \
            Defaults.get_publisher()
        self.custom_args = custom_args

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(xml_state, target_dir, self.root_dir)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(),
            '-' + xml_state.get_image_version(), '.iso'
        ])
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()
Example #2
0
    def __init__(self, xml_state, root_dir):
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
        self.swap_mbytes = xml_state.get_oemconfig_swap_mbytes()
        self.configured_size = xml_state.get_build_type_size()
        self.disk_resize_requested = xml_state.get_oemconfig_oem_resize()
        self.filesystem = xml_state.build_type.get_filesystem()
        self.bootpart_requested = xml_state.build_type.get_bootpartition()
        self.bootpart_mbytes = xml_state.build_type.get_bootpartsize()
        self.spare_part_mbytes = xml_state.get_build_type_spare_part_size()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.luks = xml_state.build_type.get_luks()
        self.volume_manager = xml_state.get_volume_management()
        self.bootloader = xml_state.get_build_type_bootloader_name()
        self.oemconfig = xml_state.get_build_type_oemconfig_section()
        self.volumes = xml_state.get_volumes()

        self.firmware = FirmWare(
            xml_state
        )
        self.rootsize = SystemSize(
            root_dir
        )

        self.root_dir = root_dir
        self.xml_state = xml_state
Example #3
0
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.live_container_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }
        self.publisher = xml_state.build_type.get_publisher() or \
            Defaults.get_publisher()
        self.custom_args = custom_args

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(xml_state, target_dir, self.root_dir)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + platform.machine(),
            '-' + xml_state.get_image_version(), '.iso'
        ])
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()
Example #4
0
    def check_syslinux_installed_if_isolinux_is_used(self):
        """
        ISO images that are configured to use isolinux
        requires the host to provide a set of syslinux
        binaries.
        """
        message = dedent('''\n
            Required syslinux module(s) not found

            The ISO image build for this image setup uses isolinux
            and therefore requires the syslinux modules to be
            available on the build host. Please make sure your
            build host has the syslinux package installed.
        ''')
        firmware = FirmWare(self.xml_state)
        if Defaults.is_x86_arch(
                Defaults.get_platform_name()) and not firmware.efi_mode():
            image_builds_iso = False
            build_type = self.xml_state.get_build_type_name()
            if build_type == 'iso':
                image_builds_iso = True
            elif build_type == 'oem':
                install_iso = self.xml_state.build_type.get_installiso()
                install_stick = self.xml_state.build_type.get_installstick()
                if install_iso or install_stick:
                    image_builds_iso = True
            if image_builds_iso:
                syslinux_check_file = Path.which(
                    'isohdpfx.bin', Defaults.get_syslinux_search_paths())
                if not syslinux_check_file:
                    raise KiwiRuntimeError(message)
Example #5
0
File: grub2.py Project: pyzh/kiwi
    def post_init(self, custom_args):
        """
        grub2 post initialization method

        Setup class attributes
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if arch == 'x86_64':
            # grub2 support for bios and efi systems
            self.arch = arch
        elif arch.startswith('ppc64'):
            # grub2 support for ofw and opal systems
            self.arch = arch
        elif arch == 'i686' or arch == 'i586':
            # grub2 support for bios systems
            self.arch = 'ix86'
        elif arch == 'aarch64' or arch.startswith('arm'):
            # grub2 support for efi systems
            self.arch = arch
        else:
            raise KiwiBootLoaderGrubPlatformError(
                'host architecture %s not supported for grub2 setup' % arch
            )

        if self.custom_args and 'grub_directory_name' in self.custom_args:
            self.boot_directory_name = self.custom_args['grub_directory_name']
        else:
            self.boot_directory_name = 'grub'

        self.terminal = self.xml_state.build_type.get_bootloader_console() \
            or 'gfxterm'
        self.gfxmode = self.get_gfxmode('grub2')
        self.bootpath = self.get_boot_path()
        self.theme = self.get_boot_theme()
        self.timeout = self.get_boot_timeout_seconds()
        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.xen_guest = self.xml_state.is_xen_guest()
        self.firmware = FirmWare(
            self.xml_state
        )

        if self.xml_state.is_xen_server():
            self.hybrid_boot = False
            self.multiboot = True
        elif self.xen_guest:
            self.hybrid_boot = False
            self.multiboot = False
        else:
            self.hybrid_boot = True
            self.multiboot = False

        self.grub2 = BootLoaderTemplateGrub2()
        self.config = None
        self.efi_boot_path = None
        self.cmdline_failsafe = None
        self.cmdline = None
        self.iso_boot = False
        self.shim_fallback_setup = False
Example #6
0
    def __init__(self,
                 xml_state: XMLState,
                 root_dir: str,
                 target_dir: str,
                 boot_image_task: Optional[BootImageBase],
                 custom_args: Dict = None) -> None:
        self.arch = Defaults.get_platform_name()
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.setup = SystemSetup(self.xml_state, self.root_dir)
        self.iso_volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_install_volume_id()
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.iso'
        ])
        self.pxename = ''.join([
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version()
        ])
        self.pxetarball = ''.join(
            [target_dir, '/', self.pxename, '.install.tar'])
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()])
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw'])
        self.md5name = ''.join([xml_state.xml_data.get_name(), '.md5'])
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.custom_iso_args: Dict = {}

        if not boot_image_task:
            self.boot_image_task = BootImage.new(xml_state, target_dir,
                                                 root_dir)
            self.boot_image_task.prepare()
        else:
            self.boot_image_task = boot_image_task
Example #7
0
    def process(self):
        """
        reformats raw disk image and its format to a new disk
        geometry using the qemu tool chain
        """
        self.manual = Help()
        if self.command_args.get('help') is True:
            return self.manual.show('kiwi::image::resize')

        abs_target_dir_path = os.path.abspath(
            self.command_args['--target-dir'])

        if self.command_args['--root']:
            image_root = os.path.abspath(
                os.path.normpath(self.command_args['--root']))
        else:
            image_root = os.sep.join(
                [abs_target_dir_path, 'build', 'image-root'])

        self.load_xml_description(image_root)

        disk_format = self.xml_state.build_type.get_format()

        image_format = DiskFormat(disk_format or 'raw', self.xml_state,
                                  image_root, abs_target_dir_path)
        if not image_format.has_raw_disk():
            raise KiwiImageResizeError(
                'no raw disk image {0} found in build results'.format(
                    image_format.diskname))

        new_disk_size = StringToSize.to_bytes(self.command_args['--size'])

        # resize raw disk
        log.info('Resizing raw disk to {0} bytes'.format(new_disk_size))
        resize_result = image_format.resize_raw_disk(new_disk_size)

        # resize raw disk partition table
        firmware = FirmWare(self.xml_state)
        loop_provider = LoopDevice(image_format.diskname)
        loop_provider.create(overwrite=False)
        partitioner = Partitioner(firmware.get_partition_table_type(),
                                  loop_provider)
        partitioner.resize_table()
        del loop_provider

        # resize disk format from resized raw disk
        if disk_format and resize_result is True:
            log.info('Creating {0} disk format from resized raw disk'.format(
                disk_format))
            image_format.create_image_format()
        elif resize_result is False:
            log.info('Raw disk is already at {0} bytes'.format(new_disk_size))
Example #8
0
    def __init__(self,
                 xml_state,
                 root_dir,
                 target_dir,
                 boot_image_task,
                 custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.boot_image_task = boot_image_task
        self.xml_state = xml_state
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.setup = SystemSetup(self.xml_state, self.root_dir)
        self.iso_volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_install_volume_id()
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.iso'
        ])
        self.pxename = ''.join([
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version()
        ])
        self.pxetarball = ''.join(
            [target_dir, '/', self.pxename, '.install.tar'])
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()])
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw'])
        self.md5name = ''.join([xml_state.xml_data.get_name(), '.md5'])
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir = None
        self.pxe_dir = None
        self.squashed_contents = None
        self.custom_iso_args = None
Example #9
0
    def post_init(self, custom_args):
        """
        zipl post initialization method

        :param dict custom_args:
            Contains zipl config arguments

            .. code:: python

                {'targetbase': 'device_name'}
        """
        self.custom_args = custom_args
        arch = Defaults.get_platform_name()
        if 's390' in arch:
            self.arch = arch
        else:
            raise KiwiBootLoaderZiplPlatformError(
                'host architecture %s not supported for zipl setup' % arch
            )

        if not custom_args or 'targetbase' not in custom_args:
            raise KiwiBootLoaderZiplSetupError(
                'targetbase device name is required for zipl setup'
            )

        self.bootpath = '.'
        self.timeout = self.get_boot_timeout_seconds()
        self.cmdline = self.get_boot_cmdline()
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options()]
        )
        self.target_blocksize = \
            self.xml_state.build_type.get_target_blocksize()
        if not self.target_blocksize:
            self.target_blocksize = Defaults.get_s390_disk_block_size()

        self.target_type = \
            self.xml_state.get_build_type_bootloader_targettype()
        if not self.target_type:
            self.target_type = Defaults.get_s390_disk_type()

        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.target_device = custom_args['targetbase']
        self.firmware = FirmWare(self.xml_state)
        self.target_table_type = self.firmware.get_partition_table_type()

        self.zipl = BootLoaderTemplateZipl()
        self.config = None
Example #10
0
 def test_firmware_unsupported(self):
     xml_state = mock.Mock()
     xml_state.build_type.get_firmware = mock.Mock(
         return_value='bogus'
     )
     with raises(KiwiNotImplementedError):
         FirmWare(xml_state)
Example #11
0
 def append_unpartitioned_space(self):
     """
     Extends the raw disk if an unpartitioned area is specified
     """
     if self.unpartitioned_bytes:
         log.info('Expanding disk with %d bytes of unpartitioned space',
                  self.unpartitioned_bytes)
         disk_format = DiskFormat('raw', self.xml_state, self.root_dir,
                                  self.target_dir)
         disk_format.resize_raw_disk(self.unpartitioned_bytes, append=True)
         firmware = FirmWare(self.xml_state)
         loop_provider = LoopDevice(disk_format.diskname)
         loop_provider.create(overwrite=False)
         partitioner = Partitioner(firmware.get_partition_table_type(),
                                   loop_provider)
         partitioner.resize_table()
Example #12
0
    def __init__(
        self, xml_state, root_dir, target_dir, boot_image_task,
        custom_args=None
    ):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.boot_image_task = boot_image_task
        self.xml_state = xml_state
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.diskname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.raw'
            ]
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.install.iso'
            ]
        )
        self.pxename = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.install.tar.xz'
            ]
        )
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()]
        )
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw']
        )
        self.md5name = ''.join(
            [xml_state.xml_data.get_name(), '.md5']
        )
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir = None
        self.pxe_dir = None
        self.squashed_contents = None
        self.custom_iso_args = None
Example #13
0
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.types = Defaults.get_live_iso_types()
        self.hybrid = xml_state.build_type.get_hybrid()
        self.volume_id = xml_state.build_type.get_volid()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        boot_signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            boot_signing_keys = custom_args['signing_keys']

        self.boot_image_task = BootImage(xml_state,
                                         target_dir,
                                         signing_keys=boot_signing_keys,
                                         custom_args=custom_args)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + platform.machine(),
            '-' + xml_state.get_image_version(), '.iso'
        ])
        self.live_image_file = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '-read-only.', self.arch, '-',
            xml_state.get_image_version()
        ])
        self.result = Result(xml_state)
Example #14
0
    def setup(self, mock_platform):
        mock_platform.return_value = 'x86_64'
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock()
        xml_state.build_type.get_firmware.return_value = 'bios'
        self.firmware_bios = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = None
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = 'msdos'
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi_mbr = FirmWare(xml_state)

        xml_state.build_type.get_efipartsize.return_value = 42
        self.firmware_efi_custom_efi_part = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'ec2'
        self.firmware_ec2 = FirmWare(xml_state)

        mock_platform.return_value = 's390x'
        xml_state.build_type.get_firmware.return_value = None
        xml_state.build_type.get_zipl_targettype = mock.Mock()
        xml_state.build_type.get_zipl_targettype.return_value = 'LDL'
        self.firmware_s390_ldl = FirmWare(xml_state)

        xml_state.build_type.get_zipl_targettype.return_value = 'CDL'
        self.firmware_s390_cdl = FirmWare(xml_state)

        xml_state.build_type.get_zipl_targettype.return_value = 'SCSI'
        self.firmware_s390_scsi = FirmWare(xml_state)

        mock_platform.return_value = 'ppc64le'
        xml_state.build_type.get_firmware.return_value = 'ofw'
        self.firmware_ofw = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'opal'
        self.firmware_opal = FirmWare(xml_state)

        mock_platform.return_value = 'arm64'
Example #15
0
    def __init__(self,
                 xml_state,
                 root_dir,
                 target_dir,
                 boot_image_task,
                 custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.boot_image_task = boot_image_task
        self.xml_state = xml_state
        self.firmware = FirmWare(xml_state)
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.iso'
        ])
        self.pxename = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.tar.xz'
        ])
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw'])
        self.md5name = ''.join([xml_state.xml_data.get_name(), '.md5'])
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir = None
        self.pxe_dir = None
        self.squashed_contents = None
        self.custom_iso_args = None
Example #16
0
 def append_unpartitioned_space(self):
     """
     Extends the raw disk if an unpartitioned area is specified
     """
     if self.unpartitioned_bytes:
         log.info(
             'Expanding disk with %d bytes of unpartitioned space',
             self.unpartitioned_bytes
         )
         disk_format = DiskFormat(
             'raw', self.xml_state, self.root_dir, self.target_dir
         )
         disk_format.resize_raw_disk(self.unpartitioned_bytes, append=True)
         firmware = FirmWare(self.xml_state)
         loop_provider = LoopDevice(disk_format.diskname)
         loop_provider.create(overwrite=False)
         partitioner = Partitioner(
             firmware.get_partition_table_type(), loop_provider
         )
         partitioner.resize_table()
Example #17
0
    def check_partuuid_persistency_type_used_with_mbr(self) -> None:
        """
        The devicepersistency setting by-partuuid can only be
        used in combination with a partition table type that
        supports UUIDs. In any other case Linux creates artificial
        values for PTUUID and PARTUUID from the disk signature
        which can change without touching the actual partition
        table. We consider this unsafe and only allow the use
        of by-partuuid in combination with partition tables that
        actually supports it properly.
        """
        message = dedent('''\n
            devicepersistency={0!r} used with non UUID capable partition table

            PTUUID and PARTUUID exists in the GUID (GPT) partition table.
            According to the firmware setting: {1!r}, the selected partition
            table type is: {2!r}. This table type does not natively support
            UUIDs. In such a case Linux creates artificial values for PTUUID
            and PARTUUID from the disk signature which can change without
            touching the actual partition table. This is considered unsafe
            and KIWI only allows the use of by-partuuid in combination with
            partition tables that actually supports UUIDs properly.

            Please make sure to use one of the following firmware settings
            which leads to an image using an UUID capable partition table
            and therefore supporting consistent by-partuuid device names:

            <type ... firmware="efi|uefi">
        ''')
        persistency_type = self.xml_state.build_type.get_devicepersistency()
        if persistency_type and persistency_type == 'by-partuuid':
            supported_table_types = ['gpt']
            firmware = FirmWare(self.xml_state)
            table_type = firmware.get_partition_table_type()
            if table_type not in supported_table_types:
                raise KiwiRuntimeError(
                    message.format(
                        persistency_type, firmware.firmware, table_type
                    )
                )
Example #18
0
    def post_init(self, custom_args):
        """
        zipl post initialization method

        :param dict custom_args:
            Contains zipl config arguments

            .. code:: python

                {'targetbase': 'device_name'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if 's390' in arch:
            self.arch = arch
        else:
            raise KiwiBootLoaderZiplPlatformError(
                'host architecture %s not supported for zipl setup' % arch
            )

        if not custom_args or 'targetbase' not in custom_args:
            raise KiwiBootLoaderZiplSetupError(
                'targetbase device name is required for zipl setup'
            )

        self.bootpath = '.'
        self.timeout = self.get_boot_timeout_seconds()
        self.cmdline = self.get_boot_cmdline()
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options()]
        )
        self.target_blocksize = self.xml_state.build_type.get_target_blocksize()
        if not self.target_blocksize:
            self.target_blocksize = Defaults.get_s390_disk_block_size()

        self.target_type = self.xml_state.build_type.get_zipl_targettype()
        if not self.target_type:
            self.target_type = Defaults.get_s390_disk_type()

        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.target_device = custom_args['targetbase']
        self.firmware = FirmWare(self.xml_state)
        self.target_table_type = self.firmware.get_partition_table_type()

        self.zipl = BootLoaderTemplateZipl()
        self.config = None
Example #19
0
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.live_container_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }
        self.publisher = xml_state.build_type.get_publisher() or \
            Defaults.get_publisher()

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(
            xml_state, target_dir, self.root_dir
        )
        self.firmware = FirmWare(
            xml_state
        )
        self.system_setup = SystemSetup(
            xml_state=xml_state, root_dir=self.root_dir
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + platform.machine(),
                '-' + xml_state.get_image_version(),
                '.iso'
            ]
        )
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()
Example #20
0
    def __init__(self, xml_state, root_dir):
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
        self.configured_size = xml_state.get_build_type_size()
        self.build_type_name = xml_state.get_build_type_name()
        self.filesystem = xml_state.build_type.get_filesystem()
        self.bootpart_requested = xml_state.build_type.get_bootpartition()
        self.bootpart_mbytes = xml_state.build_type.get_bootpartsize()
        self.spare_part_mbytes = xml_state.get_build_type_spare_part_size()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.luks = xml_state.build_type.get_luks()
        self.volume_manager = xml_state.get_volume_management()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.oemconfig = xml_state.get_build_type_oemconfig_section()
        self.volumes = xml_state.get_volumes()

        self.firmware = FirmWare(
            xml_state
        )
        self.rootsize = SystemSize(
            root_dir
        )

        self.root_dir = root_dir
        self.xml_state = xml_state
Example #21
0
    def setup(self):
        Defaults.set_platform_name('x86_64')
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock()
        xml_state.build_type.get_firmware.return_value = 'bios'
        self.firmware_bios = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = None
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = 'msdos'
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi_mbr = FirmWare(xml_state)

        xml_state.build_type.get_efipartsize.return_value = 42
        self.firmware_efi_custom_efi_part = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'ec2'
        self.firmware_ec2 = FirmWare(xml_state)

        Defaults.set_platform_name('s390x')
        xml_state.build_type.get_firmware.return_value = None
        xml_state.get_build_type_bootloader_targettype = mock.Mock()

        xml_state.get_build_type_bootloader_targettype.return_value = 'CDL'
        self.firmware_s390_cdl = FirmWare(xml_state)

        xml_state.get_build_type_bootloader_targettype.return_value = 'SCSI'
        self.firmware_s390_scsi = FirmWare(xml_state)

        Defaults.set_platform_name('ppc64le')
        xml_state.build_type.get_firmware.return_value = 'ofw'
        self.firmware_ofw = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'opal'
        self.firmware_opal = FirmWare(xml_state)

        Defaults.set_platform_name('x86_64')
Example #22
0
class TestFirmWare(object):
    @patch('platform.machine')
    def setup(self, mock_platform):
        mock_platform.return_value = 'x86_64'
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock()
        xml_state.build_type.get_firmware.return_value = 'bios'
        self.firmware_bios = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = None
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = 'msdos'
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi_mbr = FirmWare(xml_state)

        xml_state.build_type.get_efipartsize.return_value = 42
        self.firmware_efi_custom_efi_part = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'ec2'
        self.firmware_ec2 = FirmWare(xml_state)

        mock_platform.return_value = 's390x'
        xml_state.build_type.get_firmware.return_value = None
        xml_state.build_type.get_zipl_targettype = mock.Mock()
        xml_state.build_type.get_zipl_targettype.return_value = 'LDL'
        self.firmware_s390_ldl = FirmWare(xml_state)

        xml_state.build_type.get_zipl_targettype.return_value = 'CDL'
        self.firmware_s390_cdl = FirmWare(xml_state)

        xml_state.build_type.get_zipl_targettype.return_value = 'SCSI'
        self.firmware_s390_scsi = FirmWare(xml_state)

        mock_platform.return_value = 'ppc64le'
        xml_state.build_type.get_firmware.return_value = 'ofw'
        self.firmware_ofw = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'opal'
        self.firmware_opal = FirmWare(xml_state)

        mock_platform.return_value = 'arm64'

    @raises(KiwiNotImplementedError)
    def test_firmware_unsupported(self):
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock(
            return_value='bogus'
        )
        FirmWare(xml_state)

    def test_get_partition_table_type(self):
        assert self.firmware_bios.get_partition_table_type() == 'msdos'
        assert self.firmware_efi.get_partition_table_type() == 'gpt'
        assert self.firmware_efi_mbr.get_partition_table_type() == 'msdos'
        assert self.firmware_s390_ldl.get_partition_table_type() == 'dasd'
        assert self.firmware_s390_cdl.get_partition_table_type() == 'dasd'
        assert self.firmware_s390_scsi.get_partition_table_type() == 'msdos'

    def test_get_partition_table_type_ppc_ofw_mode(self):
        assert self.firmware_ofw.get_partition_table_type() == 'msdos'

    def test_get_partition_table_type_ppc_opal_mode(self):
        assert self.firmware_opal.get_partition_table_type() == 'gpt'

    def test_legacy_bios_mode(self):
        assert self.firmware_bios.legacy_bios_mode() is False
        assert self.firmware_efi.legacy_bios_mode() is True

    def test_legacy_bios_mode_non_x86_platform(self):
        self.firmware_efi.arch = 'arm64'
        assert self.firmware_efi.legacy_bios_mode() is False

    def test_ec2_mode(self):
        assert self.firmware_ec2.ec2_mode() == 'ec2'
        assert self.firmware_bios.ec2_mode() is None

    def test_efi_mode(self):
        assert self.firmware_bios.efi_mode() is None
        assert self.firmware_efi.efi_mode() == 'efi'

    def test_bios_mode(self):
        assert self.firmware_bios.bios_mode() is True
        assert self.firmware_efi.bios_mode() is False

    def test_ofw_mode(self):
        assert self.firmware_ofw.ofw_mode() is True

    def test_opal_mode(self):
        assert self.firmware_opal.opal_mode() is True

    def test_get_legacy_bios_partition_size(self):
        assert self.firmware_bios.get_legacy_bios_partition_size() == 0
        assert self.firmware_efi.get_legacy_bios_partition_size() == 2

    def test_get_efi_partition_size(self):
        assert self.firmware_bios.get_efi_partition_size() == 0
        assert self.firmware_efi.get_efi_partition_size() == 20
        assert self.firmware_efi_custom_efi_part.get_efi_partition_size() == 42

    def test_get_prep_partition_size(self):
        assert self.firmware_ofw.get_prep_partition_size() == 8
Example #23
0
class DiskSetup(object):
    """
    Implement disk setup methods providing information required
    before building a disk image

    Attributes

    * :attr:`configured_size`
        Configured size setup

    * :attr:`build_type_name`
        Configured build type name

    * :attr:`filesystem`
        Configured filesystem name

    * :attr:`bootpart_requested`
        Configured request for a boot partition

    * :attr:`bootpart_mbytes`
        Configured boot partition size

    * :attr:`mdraid`
        Configured raid setup

    * :attr:`luks`
        Configured LUKS credentials

    * :attr:`volume_manager`
        Configured volume manager name

    * :attr:`bootloader`
        Configured bootloader

    * :attr:`oemconfig`
        Configured oemconfig section

    * :attr:`volumes`
        Configured volumes

    * :attr:`firmware`
        Instance of FirmWare

    * :attr:`rootsize`
        Instance of SystemSize

    * :attr:`root_dir`
        root directory path name

    * :attr:`xml_state`
        Instance of XMLState
    """
    def __init__(self, xml_state, root_dir):
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot(
        )
        self.configured_size = xml_state.get_build_type_size()
        self.build_type_name = xml_state.get_build_type_name()
        self.filesystem = xml_state.build_type.get_filesystem()
        self.bootpart_requested = xml_state.build_type.get_bootpartition()
        self.bootpart_mbytes = xml_state.build_type.get_bootpartsize()
        self.spare_part_mbytes = xml_state.get_build_type_spare_part_size()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.luks = xml_state.build_type.get_luks()
        self.volume_manager = xml_state.get_volume_management()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.oemconfig = xml_state.get_build_type_oemconfig_section()
        self.volumes = xml_state.get_volumes()

        self.firmware = FirmWare(xml_state)
        self.rootsize = SystemSize(root_dir)

        self.root_dir = root_dir
        self.xml_state = xml_state

    def get_disksize_mbytes(self):
        """
        Precalculate disk size requirements in mbytes

        :return: disk size mbytes
        :rtype: int
        """
        log.info('Precalculating required disk size')
        calculated_disk_mbytes = 0
        root_filesystem_mbytes = self.rootsize.customize(
            self.rootsize.accumulate_mbyte_file_sizes(), self.filesystem)
        calculated_disk_mbytes += root_filesystem_mbytes
        log.info('--> system data with filesystem overhead needs %s MB',
                 root_filesystem_mbytes)
        if self.volume_manager and self.volume_manager == 'lvm':
            if self.build_type_name == 'vmx':
                # only for vmx types we need to add the configured volume
                # sizes. oem disks are self expandable and will resize to
                # the configured sizes on first boot of the disk image
                volume_mbytes = self._accumulate_volume_size(
                    root_filesystem_mbytes)
                if volume_mbytes:
                    calculated_disk_mbytes += volume_mbytes
                    log.info('--> volume(s) size setup adding %s MB',
                             volume_mbytes)

        legacy_bios_mbytes = self.firmware.get_legacy_bios_partition_size()
        if legacy_bios_mbytes:
            calculated_disk_mbytes += legacy_bios_mbytes
            log.info('--> legacy bios boot partition adding %s MB',
                     legacy_bios_mbytes)

        boot_mbytes = self.boot_partition_size()
        if boot_mbytes:
            calculated_disk_mbytes += boot_mbytes
            log.info('--> boot partition adding %s MB', boot_mbytes)

        if self.spare_part_mbytes:
            calculated_disk_mbytes += self.spare_part_mbytes
            log.info('--> spare partition adding %s MB',
                     self.spare_part_mbytes)

        efi_mbytes = self.firmware.get_efi_partition_size()
        if efi_mbytes:
            calculated_disk_mbytes += efi_mbytes
            log.info('--> EFI partition adding %s MB', efi_mbytes)

        prep_mbytes = self.firmware.get_prep_partition_size()
        if prep_mbytes:
            calculated_disk_mbytes += prep_mbytes
            log.info('--> PReP partition adding %s MB', prep_mbytes)

        recovery_mbytes = self._inplace_recovery_partition_size()
        if recovery_mbytes:
            calculated_disk_mbytes += recovery_mbytes
            log.info('--> In-place recovery partition adding: %s MB',
                     recovery_mbytes)

        if not self.configured_size:
            log.info('Using calculated disk size: %d MB',
                     calculated_disk_mbytes)
            return calculated_disk_mbytes
        elif self.configured_size.additive:
            result_disk_mbytes = \
                self.configured_size.mbytes + calculated_disk_mbytes
            log.info(
                'Using configured disk size: %d MB + %d MB calculated = %d MB',
                self.configured_size.mbytes, calculated_disk_mbytes,
                result_disk_mbytes)
            return result_disk_mbytes
        else:
            log.info('Using configured disk size: %d MB',
                     self.configured_size.mbytes)
            if self.configured_size.mbytes < calculated_disk_mbytes:
                log.warning(
                    '--> Configured size smaller than calculated size: %d MB',
                    calculated_disk_mbytes)
            return self.configured_size.mbytes

    def need_boot_partition(self):
        """
        Decide if an extra boot partition is needed. This is done with
        the bootpartition attribute from the type, however if it is not
        set it depends on some other type configuration parameters if
        we need a boot partition or not

        :rtype: bool
        """
        if self.bootpart_requested is True:
            return True
        if self.bootpart_requested is False:
            return False
        if self.mdraid:
            return True
        if self.volume_manager:
            return True
        if self.filesystem == 'btrfs':
            return True
        if self.filesystem == 'xfs':
            return True
        if self.root_filesystem_is_overlay:
            return True
        if self.bootloader == 'grub2_s390x_emu':
            return True
        if self.luks:
            return True

    def get_boot_label(self):
        """
        Filesystem Label to use for the boot partition

        :return: label name
        :rtype: string
        """
        label = 'BOOT'
        if self.bootloader == 'grub2_s390x_emu':
            label = 'ZIPL'
        return label

    def get_root_label(self):
        """
        Filesystem Label to use for the root partition

        If not specified in the XML configuration the default
        root label is set to 'ROOT'

        :return: label name
        :rtype: string
        """
        root_label = self.xml_state.build_type.get_rootfs_label()
        if not root_label:
            root_label = 'ROOT'
        return root_label

    def get_efi_label(self):
        """
        Filesystem Label to use for the EFI partition

        :return: label name
        :rtype: string
        """
        return 'EFI'

    def boot_partition_size(self):
        """
        Size of the boot partition in mbytes

        :return: boot size mbytes
        :rtype: int
        """
        if self.need_boot_partition():
            if self.bootpart_mbytes:
                return self.bootpart_mbytes
            else:
                return Defaults.get_default_boot_mbytes()

    def _inplace_recovery_partition_size(self):
        """
        In inplace recovery mode the recovery archive is created at
        install time. This requires free space on the disk. The
        amount of free space is specified with the oem-recovery-part-size
        attribute. If specified we add the given size to the disk.
        If not specified an inplace setup at install time will be
        moved to the first boot of an oem image when the recovery
        partition has been created
        """
        if self.oemconfig and self.oemconfig.get_oem_inplace_recovery():
            recovery_mbytes = self.oemconfig.get_oem_recovery_part_size()
            if recovery_mbytes:
                return int(recovery_mbytes[0] * 1.7)

    def _accumulate_volume_size(self, root_mbytes):
        """
        Calculate number of mbytes to add to the disk to allow
        the creaton of the volumes with their configured size
        """
        disk_volume_mbytes = 0

        data_volume_mbytes = self._calculate_volume_mbytes()
        root_volume = self._get_root_volume_configuration()

        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/' and volume.size:
                [size_type, req_size] = volume.size.split(':')
                disk_add_mbytes = 0
                if size_type == 'freespace':
                    disk_add_mbytes += int(req_size)
                else:
                    disk_add_mbytes += int(req_size) - \
                        data_volume_mbytes.volume[volume.realpath]
                if disk_add_mbytes > 0:
                    disk_volume_mbytes += disk_add_mbytes + \
                        Defaults.get_min_volume_mbytes()
                else:
                    log.warning(
                        'volume size of %s MB for %s is too small, skipped',
                        int(req_size), volume.realpath)

        if root_volume:
            if root_volume.size_type == 'freespace':
                disk_add_mbytes = root_volume.req_size
            else:
                disk_add_mbytes = root_volume.req_size - \
                    root_mbytes + data_volume_mbytes.total

            if disk_add_mbytes > 0:
                disk_volume_mbytes += disk_add_mbytes + \
                    Defaults.get_min_volume_mbytes()
            else:
                log.warning('root volume size of %s MB is too small, skipped',
                            root_volume.req_size)

        return disk_volume_mbytes

    def _get_root_volume_configuration(self):
        """
        Provide LVRoot volume configuration if present and in
        use according to the selected volume management. So far
        this only affects the LVM volume manager
        """
        root_volume_type = namedtuple('root_volume_type',
                                      ['size_type', 'req_size'])
        for volume in self.volumes:
            if volume.name == 'LVRoot':
                if volume.size:
                    [size_type, req_size] = volume.size.split(':')
                    return root_volume_type(size_type=size_type,
                                            req_size=int(req_size))

    def _calculate_volume_mbytes(self):
        """
        Calculate the number of mbytes each volume path currently
        consumes and also provide a total number of these values
        """
        volume_mbytes_type = namedtuple('volume_mbytes_type',
                                        ['volume', 'total'])
        volume_mbytes = {}
        volume_total = 0
        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/':
                path_to_volume = self.root_dir + '/' + volume.realpath
                if os.path.exists(path_to_volume):
                    volume_size = SystemSize(path_to_volume)
                    volume_mbytes[volume.realpath] = volume_size.customize(
                        volume_size.accumulate_mbyte_file_sizes(),
                        self.filesystem)
                    volume_total += volume_mbytes[volume.realpath]

        return volume_mbytes_type(volume=volume_mbytes, total=volume_total)
Example #24
0
class DiskSetup(object):
    """
    **Implements disk setup methods**

    Methods from this class provides information required
    before building a disk image

    :param object xml_state: Instance of XMLState
    :param string root_dir: root directory path name
    """
    def __init__(self, xml_state, root_dir):
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
        self.configured_size = xml_state.get_build_type_size()
        self.build_type_name = xml_state.get_build_type_name()
        self.filesystem = xml_state.build_type.get_filesystem()
        self.bootpart_requested = xml_state.build_type.get_bootpartition()
        self.bootpart_mbytes = xml_state.build_type.get_bootpartsize()
        self.spare_part_mbytes = xml_state.get_build_type_spare_part_size()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.luks = xml_state.build_type.get_luks()
        self.volume_manager = xml_state.get_volume_management()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.oemconfig = xml_state.get_build_type_oemconfig_section()
        self.volumes = xml_state.get_volumes()

        self.firmware = FirmWare(
            xml_state
        )
        self.rootsize = SystemSize(
            root_dir
        )

        self.root_dir = root_dir
        self.xml_state = xml_state

    def get_disksize_mbytes(self):
        """
        Precalculate disk size requirements in mbytes

        :return: disk size mbytes

        :rtype: int
        """
        log.info('Precalculating required disk size')
        calculated_disk_mbytes = 0
        root_filesystem_mbytes = self.rootsize.customize(
            self.rootsize.accumulate_mbyte_file_sizes(), self.filesystem
        )
        calculated_disk_mbytes += root_filesystem_mbytes
        log.info(
            '--> system data with filesystem overhead needs %s MB',
            root_filesystem_mbytes
        )
        if self.volume_manager and self.volume_manager == 'lvm':
            lvm_overhead_mbytes = Defaults.get_lvm_overhead_mbytes()
            log.info(
                '--> LVM overhead adding %s MB', lvm_overhead_mbytes
            )
            calculated_disk_mbytes += lvm_overhead_mbytes
            volume_mbytes = self._accumulate_volume_size(
                root_filesystem_mbytes
            )
            if volume_mbytes:
                calculated_disk_mbytes += volume_mbytes
                log.info(
                    '--> volume(s) size setup adding %s MB', volume_mbytes
                )

        legacy_bios_mbytes = self.firmware.get_legacy_bios_partition_size()
        if legacy_bios_mbytes:
            calculated_disk_mbytes += legacy_bios_mbytes
            log.info(
                '--> legacy bios boot partition adding %s MB',
                legacy_bios_mbytes
            )

        boot_mbytes = self.boot_partition_size()
        if boot_mbytes:
            calculated_disk_mbytes += boot_mbytes
            log.info(
                '--> boot partition adding %s MB', boot_mbytes
            )

        if self.spare_part_mbytes:
            calculated_disk_mbytes += self.spare_part_mbytes
            log.info(
                '--> spare partition adding %s MB', self.spare_part_mbytes
            )

        efi_mbytes = self.firmware.get_efi_partition_size()
        if efi_mbytes:
            calculated_disk_mbytes += efi_mbytes
            log.info(
                '--> EFI partition adding %s MB', efi_mbytes
            )

        prep_mbytes = self.firmware.get_prep_partition_size()
        if prep_mbytes:
            calculated_disk_mbytes += prep_mbytes
            log.info(
                '--> PReP partition adding %s MB', prep_mbytes
            )

        recovery_mbytes = self._inplace_recovery_partition_size()
        if recovery_mbytes:
            calculated_disk_mbytes += recovery_mbytes
            log.info(
                '--> In-place recovery partition adding: %s MB',
                recovery_mbytes
            )

        if not self.configured_size:
            log.info(
                'Using calculated disk size: %d MB',
                calculated_disk_mbytes
            )
            return calculated_disk_mbytes
        elif self.configured_size.additive:
            result_disk_mbytes = \
                self.configured_size.mbytes + calculated_disk_mbytes
            log.info(
                'Using configured disk size: %d MB + %d MB calculated = %d MB',
                self.configured_size.mbytes,
                calculated_disk_mbytes,
                result_disk_mbytes
            )
            return result_disk_mbytes
        else:
            log.info(
                'Using configured disk size: %d MB',
                self.configured_size.mbytes
            )
            if self.configured_size.mbytes < calculated_disk_mbytes:
                log.warning(
                    '--> Configured size smaller than calculated size: %d MB',
                    calculated_disk_mbytes
                )
            return self.configured_size.mbytes

    def need_boot_partition(self):
        """
        Decide if an extra boot partition is needed. This is done with
        the bootpartition attribute from the type, however if it is not
        set it depends on some other type configuration parameters if
        we need a boot partition or not

        :return: True or False

        :rtype: bool
        """
        if self.bootpart_requested is True:
            return True
        if self.bootpart_requested is False:
            return False
        if self.mdraid:
            return True
        if self.volume_manager:
            return True
        if self.filesystem == 'btrfs':
            return True
        if self.filesystem == 'xfs':
            return True
        if self.root_filesystem_is_overlay:
            return True
        if self.bootloader == 'grub2_s390x_emu':
            return True
        if self.luks:
            return True

    def get_boot_label(self):
        """
        Filesystem Label to use for the boot partition

        :return: label name

        :rtype: str
        """
        label = 'BOOT'
        if self.bootloader == 'grub2_s390x_emu':
            label = 'ZIPL'
        return label

    def get_root_label(self):
        """
        Filesystem Label to use for the root partition

        If not specified in the XML configuration the default
        root label is set to 'ROOT'

        :return: label name

        :rtype: str
        """
        root_label = self.xml_state.build_type.get_rootfs_label()
        if not root_label:
            root_label = 'ROOT'
        return root_label

    def get_efi_label(self):
        """
        Filesystem Label to use for the EFI partition

        :return: label name

        :rtype: str
        """
        return 'EFI'

    def boot_partition_size(self):
        """
        Size of the boot partition in mbytes

        :return: boot size mbytes

        :rtype: int
        """
        if self.need_boot_partition():
            if self.bootpart_mbytes:
                return self.bootpart_mbytes
            else:
                return Defaults.get_default_boot_mbytes()

    def _inplace_recovery_partition_size(self):
        """
        In inplace recovery mode the recovery archive is created at
        install time. This requires free space on the disk. The
        amount of free space is specified with the oem-recovery-part-size
        attribute. If specified we add the given size to the disk.
        If not specified an inplace setup at install time will be
        moved to the first boot of an oem image when the recovery
        partition has been created
        """
        if self.oemconfig and self.oemconfig.get_oem_inplace_recovery():
            recovery_mbytes = self.oemconfig.get_oem_recovery_part_size()
            if recovery_mbytes:
                return int(recovery_mbytes[0] * 1.7)

    def _accumulate_volume_size(self, root_mbytes):
        """
        Calculate number of mbytes to add to the disk to allow
        the creaton of the volumes with their configured size
        """
        disk_volume_mbytes = 0

        data_volume_mbytes = self._calculate_volume_mbytes()
        root_volume = self._get_root_volume_configuration()

        # For oem types we only add the default min volume size
        # because their target size request is handled on first boot
        # of the disk image in oemboot/repart
        if self.build_type_name == 'oem':
            for volume in self.volumes:
                disk_volume_mbytes += Defaults.get_min_volume_mbytes()
            return disk_volume_mbytes

        # For vmx types we need to add the configured volume
        # sizes because the image is used directly as it is without
        # being deployed and resized on a target disk
        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/' and volume.size:
                [size_type, req_size] = volume.size.split(':')
                disk_add_mbytes = 0
                if size_type == 'freespace':
                    disk_add_mbytes += int(req_size)
                else:
                    disk_add_mbytes += int(req_size) - \
                        data_volume_mbytes.volume[volume.realpath]
                if disk_add_mbytes > 0:
                    disk_volume_mbytes += disk_add_mbytes + \
                        Defaults.get_min_volume_mbytes()
                else:
                    log.warning(
                        'volume size of %s MB for %s is too small, skipped',
                        int(req_size), volume.realpath
                    )

        if root_volume:
            if root_volume.size_type == 'freespace':
                disk_add_mbytes = root_volume.req_size
            else:
                disk_add_mbytes = root_volume.req_size - \
                    root_mbytes + data_volume_mbytes.total

            if disk_add_mbytes > 0:
                disk_volume_mbytes += disk_add_mbytes + \
                    Defaults.get_min_volume_mbytes()
            else:
                log.warning(
                    'root volume size of %s MB is too small, skipped',
                    root_volume.req_size
                )

        return disk_volume_mbytes

    def _get_root_volume_configuration(self):
        """
        Provide LVRoot volume configuration if present and in
        use according to the selected volume management. So far
        this only affects the LVM volume manager
        """
        root_volume_type = namedtuple(
            'root_volume_type', ['size_type', 'req_size']
        )
        for volume in self.volumes:
            if volume.name == 'LVRoot':
                if volume.size:
                    [size_type, req_size] = volume.size.split(':')
                    return root_volume_type(
                        size_type=size_type,
                        req_size=int(req_size)
                    )

    def _calculate_volume_mbytes(self):
        """
        Calculate the number of mbytes each volume path currently
        consumes and also provide a total number of these values
        """
        volume_mbytes_type = namedtuple(
            'volume_mbytes_type', ['volume', 'total']
        )
        volume_mbytes = {}
        volume_total = 0
        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/':
                path_to_volume = self.root_dir + '/' + volume.realpath
                if os.path.exists(path_to_volume):
                    volume_size = SystemSize(path_to_volume)
                    volume_mbytes[volume.realpath] = volume_size.customize(
                        volume_size.accumulate_mbyte_file_sizes(),
                        self.filesystem
                    )
                    volume_total += volume_mbytes[volume.realpath]

        return volume_mbytes_type(
            volume=volume_mbytes,
            total=volume_total
        )
Example #25
0
 def test_firmware_unsupported(self):
     xml_state = mock.Mock()
     xml_state.build_type.get_firmware = mock.Mock(
         return_value='bogus'
     )
     FirmWare(xml_state)
Example #26
0
class TestFirmWare(object):
    @patch('platform.machine')
    def setup(self, mock_platform):
        mock_platform.return_value = 'x86_64'
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock()
        xml_state.build_type.get_firmware.return_value = 'bios'
        self.firmware_bios = FirmWare(xml_state)
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi = FirmWare(xml_state)
        xml_state.build_type.get_firmware.return_value = 'ec2'
        self.firmware_ec2 = FirmWare(xml_state)
        mock_platform.return_value = 's390x'
        xml_state.build_type.get_firmware.return_value = None
        xml_state.build_type.get_zipl_targettype = mock.Mock()
        xml_state.build_type.get_zipl_targettype.return_value = 'LDL'
        self.firmware_s390_ldl = FirmWare(xml_state)
        xml_state.build_type.get_zipl_targettype.return_value = 'CDL'
        self.firmware_s390_cdl = FirmWare(xml_state)
        xml_state.build_type.get_zipl_targettype.return_value = 'SCSI'
        self.firmware_s390_scsi = FirmWare(xml_state)

    @raises(KiwiNotImplementedError)
    def test_firmware_unsupported(self):
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock(
            return_value='bogus'
        )
        FirmWare(xml_state)

    def test_get_partition_table_type(self):
        assert self.firmware_bios.get_partition_table_type() == 'msdos'
        assert self.firmware_efi.get_partition_table_type() == 'gpt'
        assert self.firmware_s390_ldl.get_partition_table_type() == 'dasd'
        assert self.firmware_s390_cdl.get_partition_table_type() == 'dasd'
        assert self.firmware_s390_scsi.get_partition_table_type() == 'msdos'

    def test_legacy_bios_mode(self):
        assert self.firmware_bios.legacy_bios_mode() is False
        assert self.firmware_efi.legacy_bios_mode() is True

    def test_ec2_mode(self):
        assert self.firmware_ec2.ec2_mode() == 'ec2'
        assert self.firmware_bios.ec2_mode() is None

    def test_efi_mode(self):
        assert self.firmware_bios.efi_mode() is None
        assert self.firmware_efi.efi_mode() == 'efi'

    def test_bios_mode(self):
        assert self.firmware_bios.bios_mode() is True
        assert self.firmware_efi.bios_mode() is False

    def test_get_legacy_bios_partition_size(self):
        assert self.firmware_bios.get_legacy_bios_partition_size() == 0
        assert self.firmware_efi.get_legacy_bios_partition_size() == 2

    def test_get_efi_partition_size(self):
        assert self.firmware_bios.get_efi_partition_size() == 0
        assert self.firmware_efi.get_efi_partition_size() == 200
Example #27
0
File: live.py Project: agraf/kiwi
class LiveImageBuilder(object):
    """
    Live image builder

    Attributes

    * :attr:`xml_state`
        Instance of XMLState

    * :attr:`target_dir`
        target directory path name

    * :attr:`root_dir`
        root directory path name

    * :attr:`custom_args`
        Custom processing arguments defined as hash keys:
        * signing_keys: list of package signing keys
        * xz_options: string of XZ compression parameters
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.live_container_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.hybrid = xml_state.build_type.get_hybrid()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(
            xml_state, target_dir, self.root_dir
        )
        self.firmware = FirmWare(
            xml_state
        )
        self.system_setup = SystemSetup(
            xml_state=xml_state, root_dir=self.root_dir
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + platform.machine(),
                '-' + xml_state.get_image_version(),
                '.iso'
            ]
        )
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self):
        """
        Build a bootable hybrid live ISO image

        Image types which triggers this builder are:

        * image="iso"
        """
        # media dir to store CD contents
        self.media_dir = mkdtemp(
            prefix='live-media.', dir=self.target_dir
        )
        rootsize = SystemSize(self.media_dir)

        # custom iso metadata
        log.info('Using following live ISO metadata:')
        log.info('--> Application id: {0}'.format(self.mbrid.get_id()))
        log.info('--> Publisher: {0}'.format(Defaults.get_publisher()))
        log.info('--> Volume id: {0}'.format(self.volume_id))
        custom_iso_args = {
            'create_options': [
                '-A', self.mbrid.get_id(),
                '-p', Defaults.get_preparer(),
                '-publisher', Defaults.get_publisher(),
                '-V', self.volume_id
            ]
        }

        # pack system into live boot structure as expected by dracut
        log.info(
            'Packing system into dracut live ISO type: {0}'.format(
                self.live_type
            )
        )
        root_filesystem = Defaults.get_default_live_iso_root_filesystem()
        filesystem_custom_parameters = {
            'mount_options': self.xml_state.get_fs_mount_option_list()
        }
        filesystem_setup = FileSystemSetup(
            self.xml_state, self.root_dir
        )
        root_image = NamedTemporaryFile()
        loop_provider = LoopDevice(
            root_image.name,
            filesystem_setup.get_size_mbytes(root_filesystem),
            self.xml_state.build_type.get_target_blocksize()
        )
        loop_provider.create()
        live_filesystem = FileSystem(
            name=root_filesystem,
            device_provider=loop_provider,
            root_dir=self.root_dir + os.sep,
            custom_args=filesystem_custom_parameters
        )
        live_filesystem.create_on_device()
        log.info(
            '--> Syncing data to {0} root image'.format(root_filesystem)
        )
        live_filesystem.sync_data(
            Defaults.get_exclude_list_for_root_data_sync()
        )
        log.info('--> Creating squashfs container for root image')
        self.live_container_dir = mkdtemp(
            prefix='live-container.', dir=self.target_dir
        )
        Path.create(self.live_container_dir + '/LiveOS')
        shutil.copy(
            root_image.name, self.live_container_dir + '/LiveOS/rootfs.img'
        )
        live_container_image = FileSystem(
            name='squashfs',
            device_provider=None,
            root_dir=self.live_container_dir
        )
        container_image = NamedTemporaryFile()
        live_container_image.create_on_file(
            container_image.name
        )
        Path.create(self.media_dir + '/LiveOS')
        shutil.copy(
            container_image.name, self.media_dir + '/LiveOS/squashfs.img'
        )

        # setup bootloader config to boot the ISO via isolinux
        log.info('Setting up isolinux bootloader configuration')
        bootloader_config_isolinux = BootLoaderConfig(
            'isolinux', self.xml_state, self.media_dir
        )
        bootloader_config_isolinux.setup_live_boot_images(
            mbrid=None,
            lookup_path=self.boot_image.boot_root_directory
        )
        bootloader_config_isolinux.setup_live_image_config(
            mbrid=None
        )
        bootloader_config_isolinux.write()

        # setup bootloader config to boot the ISO via EFI
        if self.firmware.efi_mode():
            log.info('Setting up EFI grub bootloader configuration')
            bootloader_config_grub = BootLoaderConfig(
                'grub2', self.xml_state, self.media_dir, {
                    'grub_directory_name':
                        Defaults.get_grub_boot_directory_name(self.root_dir)
                }
            )
            bootloader_config_grub.setup_live_boot_images(
                mbrid=self.mbrid, lookup_path=self.root_dir
            )
            bootloader_config_grub.setup_live_image_config(
                mbrid=self.mbrid
            )
            bootloader_config_grub.write()

        # call custom editbootconfig script if present
        self.system_setup.call_edit_boot_config_script(
            filesystem='iso:{0}'.format(self.media_dir), boot_part_id=1,
            working_directory=self.root_dir
        )

        # create dracut initrd for live image
        log.info('Creating live ISO boot image')
        self._create_dracut_live_iso_config()
        self.boot_image.create_initrd(self.mbrid)

        # setup kernel file(s) and initrd in ISO boot layout
        log.info('Setting up kernel file(s) and boot image in ISO boot layout')
        self._setup_live_iso_kernel_and_initrd()

        # calculate size and decide if we need UDF
        if rootsize.accumulate_mbyte_file_sizes() > 4096:
            log.info('ISO exceeds 4G size, using UDF filesystem')
            custom_iso_args['create_options'].append('-iso-level')
            custom_iso_args['create_options'].append('3')
            custom_iso_args['create_options'].append('-udf')

        # create iso filesystem from media_dir
        log.info('Creating live ISO image')
        iso_image = FileSystemIsoFs(
            device_provider=None,
            root_dir=self.media_dir,
            custom_args=custom_iso_args
        )
        iso_header_offset = iso_image.create_on_file(self.isoname)

        # make it hybrid
        if self.hybrid:
            Iso.create_hybrid(
                iso_header_offset, self.mbrid, self.isoname,
                self.firmware.efi_mode()
            )

        # include metadata for checkmedia tool
        if self.xml_state.build_type.get_mediacheck() is True:
            Iso.set_media_tag(self.isoname)

        self.result.verify_image_size(
            self.runtime_config.get_max_size_constraint(),
            self.isoname
        )
        self.result.add(
            key='live_image',
            filename=self.isoname,
            use_for_bundle=True,
            compress=False,
            shasum=True
        )
        self.result.add(
            key='image_packages',
            filename=self.system_setup.export_package_list(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )
        self.result.add(
            key='image_verified',
            filename=self.system_setup.export_package_verification(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )
        return self.result

    def _create_dracut_live_iso_config(self):
        live_config_file = self.root_dir + '/etc/dracut.conf.d/02-livecd.conf'
        omit_modules = [
            'kiwi-dump', 'kiwi-overlay', 'kiwi-repart', 'kiwi-lib'
        ]
        live_config = [
            'add_dracutmodules+=" {0} pollcdrom "'.format(
                Defaults.get_live_dracut_module_from_flag(self.live_type)
            ),
            'omit_dracutmodules+=" {0} "'.format(' '.join(omit_modules)),
            'hostonly="no"',
            'dracut_rescue_image="no"'
        ]
        with open(live_config_file, 'w') as config:
            for entry in live_config:
                config.write(entry + os.linesep)

    def _setup_live_iso_kernel_and_initrd(self):
        """
        Copy kernel and initrd from the root tree into the iso boot structure
        """
        boot_path = ''.join(
            [self.media_dir, '/boot/', self.arch, '/loader']
        )
        Path.create(boot_path)

        # Move kernel files to iso filesystem structure
        kernel = Kernel(self.boot_image.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiLiveBootImageError(
                'No kernel in boot image tree {0} found'.format(
                    self.boot_image.boot_root_directory
                )
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiLiveBootImageError(
                    'No hypervisor in boot image tree {0} found'.format(
                        self.boot_image.boot_root_directory
                    )
                )

        # Move initrd to iso filesystem structure
        if os.path.exists(self.boot_image.initrd_filename):
            shutil.move(
                self.boot_image.initrd_filename, boot_path + '/initrd'
            )
        else:
            raise KiwiLiveBootImageError(
                'No boot image {0} in boot image tree {1} found'.format(
                    self.boot_image.initrd_filename,
                    self.boot_image.boot_root_directory
                )
            )

    def __del__(self):
        if self.media_dir or self.live_container_dir:
            log.info(
                'Cleaning up {0} instance'.format(type(self).__name__)
            )
            if self.media_dir:
                Path.wipe(self.media_dir)
            if self.live_container_dir:
                Path.wipe(self.live_container_dir)
Example #28
0
    def post_init(self, custom_args):
        """
        grub2 post initialization method

        :param dict custom_args:
            Contains grub config arguments

            .. code:: python

                {'grub_directory_name': 'grub|grub2'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if arch == 'x86_64':
            # grub2 support for bios and efi systems
            self.arch = arch
        elif arch.startswith('ppc64'):
            # grub2 support for ofw and opal systems
            self.arch = arch
        elif arch == 'i686' or arch == 'i586':
            # grub2 support for bios systems
            self.arch = 'ix86'
        elif arch == 'aarch64' or arch.startswith('arm'):
            # grub2 support for efi systems
            self.arch = arch
        else:
            raise KiwiBootLoaderGrubPlatformError(
                'host architecture %s not supported for grub2 setup' % arch
            )

        if self.custom_args and 'grub_directory_name' in self.custom_args:
            self.boot_directory_name = self.custom_args['grub_directory_name']
        else:
            self.boot_directory_name = 'grub'

        self.terminal = self.xml_state.build_type.get_bootloader_console() \
            or 'gfxterm'
        self.gfxmode = self.get_gfxmode('grub2')
        self.theme = self.get_boot_theme()
        self.timeout = self.get_boot_timeout_seconds()
        self.continue_on_timeout = self.get_continue_on_timeout()
        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.mediacheck_boot = self.xml_state.build_type.get_mediacheck()
        self.xen_guest = self.xml_state.is_xen_guest()
        self.firmware = FirmWare(
            self.xml_state
        )

        self.live_type = self.xml_state.build_type.get_flags()
        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.install_volid = self.xml_state.build_type.get_volid() or \
            Defaults.get_install_volume_id()

        self.live_boot_options = [
            'root=live:CDLABEL={0}'.format(self.volume_id),
            'rd.live.image'
        ]
        self.install_boot_options = [
            'loglevel=0'
        ]
        if self.xml_state.get_initrd_system() == 'dracut':
            self.install_boot_options.append(
                'root=install:CDLABEL={0}'.format(self.install_volid)
            )
        if self.xml_state.build_type.get_hybridpersistent():
            self.live_boot_options += \
                Defaults.get_live_iso_persistent_boot_options(
                    self.xml_state.build_type.get_hybridpersistent_filesystem()
                )

        if self.xml_state.is_xen_server():
            self.hybrid_boot = False
            self.multiboot = True
        elif self.xen_guest:
            self.hybrid_boot = False
            self.multiboot = False
        else:
            self.hybrid_boot = True
            self.multiboot = False

        self.grub2 = BootLoaderTemplateGrub2()
        self.config = None
        self.efi_boot_path = None
        self.cmdline_failsafe = None
        self.root_reference = None
        self.cmdline = None
        self.iso_boot = False
        self.shim_fallback_setup = False
        self.validate_use_of_linuxefi = False
Example #29
0
class InstallImageBuilder:
    """
    **Installation image builder**

    :param object xml_state: instance of :class:`XMLState`
    :param str root_dir: system image root directory
    :param str target_dir: target directory path name
    :param object boot_image_task: instance of :class:`BootImage`
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * xz_options: string of XZ compression parameters
    """
    def __init__(
        self, xml_state: XMLState, root_dir: str, target_dir: str,
        boot_image_task: Optional[BootImageBase],
        custom_args: Dict = None
    ) -> None:
        self.arch = Defaults.get_platform_name()
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.setup = SystemSetup(
            self.xml_state, self.root_dir
        )
        self.iso_volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_install_volume_id()
        self.diskname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.raw'
            ]
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.install.iso'
            ]
        )
        self.pxename = ''.join(
            [
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version()
            ]
        )
        self.pxetarball = ''.join(
            [
                target_dir, '/', self.pxename, '.install.tar'
            ]
        )
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()]
        )
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw']
        )
        self.md5name = ''.join(
            [xml_state.xml_data.get_name(), '.md5']
        )
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir: str = ''
        self.pxe_dir: str = ''
        self.squashed_contents: str = ''
        self.custom_iso_args: Dict = {}

        if not boot_image_task:
            self.boot_image_task = BootImage.new(
                xml_state, target_dir, root_dir
            )
            self.boot_image_task.prepare()
        else:
            self.boot_image_task = boot_image_task

    def create_install_iso(self) -> None:
        """
        Create an install ISO from the disk_image as hybrid ISO
        bootable via legacy BIOS, EFI and as disk from Stick

        Image types which triggers this builder are:

        * installiso="true|false"
        * installstick="true|false"
        """
        self.media_dir = mkdtemp(
            prefix='kiwi_install_media.', dir=self.target_dir
        )
        # unpack cdroot user files to media dir
        self.setup.import_cdroot_files(self.media_dir)

        # custom iso metadata
        self.custom_iso_args = {
            'meta_data': {
                'volume_id': self.iso_volume_id,
                'mbr_id': self.mbrid.get_id(),
                'efi_mode': self.firmware.efi_mode(),
                'ofw_mode': self.firmware.ofw_mode()
            }
        }

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        self.squashed_contents = mkdtemp(
            prefix='kiwi_install_squashfs.', dir=self.target_dir
        )
        checksum = Checksum(self.diskname)
        checksum.md5(self.squashed_contents + '/' + self.md5name)

        # the system image name is stored in a config file
        self._write_install_image_info_to_iso_image()
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the system image is stored as squashfs embedded file
        log.info('Creating squashfs embedded disk image')
        Command.run(
            [
                'cp', '-l', self.diskname,
                self.squashed_contents + '/' + self.squashed_diskname
            ]
        )
        squashed_image_file = ''.join(
            [
                self.target_dir, '/', self.squashed_diskname, '.squashfs'
            ]
        )
        squashed_image = FileSystemSquashFs(
            device_provider=DeviceProvider(),
            root_dir=self.squashed_contents,
            custom_args={
                'compression':
                    self.xml_state.build_type.get_squashfscompression()
            }
        )
        squashed_image.create_on_file(squashed_image_file)
        Command.run(
            ['mv', squashed_image_file, self.media_dir]
        )

        log.info(
            'Setting up install image bootloader configuration'
        )
        if self.firmware.efi_mode():
            # setup bootloader config to boot the ISO via EFI
            # This also embedds an MBR and the respective BIOS modules
            # for compat boot. The complete bootloader setup will be
            # based on grub
            bootloader_config = BootLoaderConfig.new(
                'grub2', self.xml_state, root_dir=self.root_dir,
                boot_dir=self.media_dir, custom_args={
                    'grub_directory_name':
                        Defaults.get_grub_boot_directory_name(self.root_dir)
                }
            )
            bootloader_config.setup_install_boot_images(
                mbrid=self.mbrid,
                lookup_path=self.boot_image_task.boot_root_directory
            )
        else:
            # setup bootloader config to boot the ISO via isolinux.
            # This allows for booting on x86 platforms in BIOS mode
            # only.
            bootloader_config = BootLoaderConfig.new(
                'isolinux', self.xml_state, root_dir=self.root_dir,
                boot_dir=self.media_dir
            )
        IsoToolsBase.setup_media_loader_directory(
            self.boot_image_task.boot_root_directory, self.media_dir,
            bootloader_config.get_boot_theme()
        )
        bootloader_config.write_meta_data()
        bootloader_config.setup_install_image_config(
            mbrid=self.mbrid
        )
        bootloader_config.write()

        # create initrd for install image
        log.info('Creating install image boot image')
        self._create_iso_install_kernel_and_initrd()

        # the system image initrd is stored to allow kexec
        self._copy_system_image_initrd_to_iso_image()

        # create iso filesystem from media_dir
        log.info('Creating ISO filesystem')
        iso_image = FileSystemIsoFs(
            device_provider=DeviceProvider(), root_dir=self.media_dir,
            custom_args=self.custom_iso_args
        )
        iso_image.create_on_file(self.isoname)
        self.boot_image_task.cleanup()

    def create_install_pxe_archive(self) -> None:
        """
        Create an oem install tar archive suitable for installing a
        disk image via the network using the PXE boot protocol.
        The archive contains:

        * The raw system image xz compressed
        * The raw system image checksum metadata file
        * The append file template for the boot server
        * The system image initrd for kexec
        * The install initrd
        * The kernel

        Image types which triggers this builder are:

        * installpxe="true|false"
        """
        self.pxe_dir = mkdtemp(
            prefix='kiwi_pxe_install_media.', dir=self.target_dir
        )
        # the system image is transfered as xz compressed variant
        log.info('xz compressing disk image')
        pxe_image_filename = ''.join(
            [
                self.pxe_dir, '/',
                self.pxename, '.xz'
            ]
        )
        compress = Compress(
            source_filename=self.diskname,
            keep_source_on_compress=True
        )
        compress.xz(self.xz_options)
        Command.run(
            ['mv', compress.compressed_filename, pxe_image_filename]
        )

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        pxe_md5_filename = ''.join(
            [
                self.pxe_dir, '/',
                self.pxename, '.md5'
            ]
        )
        checksum = Checksum(self.diskname)
        checksum.md5(pxe_md5_filename)

        # the install image name is stored in a config file
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the kexec required system image initrd is stored for dracut kiwi-dump
        if self.initrd_system == 'dracut':
            boot_names = self.boot_image_task.get_boot_names()
            system_image_initrd = os.sep.join(
                [self.root_dir, 'boot', boot_names.initrd_name]
            )
            target_initrd_name = '{0}/{1}.initrd'.format(
                self.pxe_dir, self.pxename
            )
            shutil.copy(
                system_image_initrd, target_initrd_name
            )
            os.chmod(target_initrd_name, 420)

        # create pxe config append information
        # this information helps to configure the boot server correctly
        append_filename = ''.join(
            [
                self.pxe_dir, '/', self.pxename, '.append'
            ]
        )
        if self.initrd_system == 'kiwi':
            cmdline = 'pxe=1'
        else:
            cmdline = ' '.join(
                [
                    'rd.kiwi.install.pxe',
                    'rd.kiwi.install.image=http://example.com/image.xz'
                ]
            )
        custom_cmdline = self.xml_state.build_type.get_kernelcmdline()
        if custom_cmdline:
            cmdline += ' ' + custom_cmdline
        with open(append_filename, 'w') as append:
            append.write('%s\n' % cmdline)

        # create initrd for pxe install
        log.info('Creating pxe install boot image')
        self._create_pxe_install_kernel_and_initrd()

        # create pxe image bound boot config file, contents can be
        # changed but presence is required.
        log.info('Creating pxe install boot options file')
        configname = '{0}.config.bootoptions'.format(self.pxename)
        shutil.copy(
            os.sep.join([self.root_dir, 'config.bootoptions']),
            os.sep.join([self.pxe_dir, configname])
        )

        # create pxe install tarball
        log.info('Creating pxe install archive')
        archive = ArchiveTar(self.pxetarball)

        archive.create(self.pxe_dir)
        self.boot_image_task.cleanup()

    def _create_pxe_install_kernel_and_initrd(self) -> None:
        kernelname = 'pxeboot.{0}.kernel'.format(self.pxename)
        initrdname = 'pxeboot.{0}.initrd.xz'.format(self.pxename)
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(self.pxe_dir, kernelname)
            os.symlink(
                kernelname, ''.join(
                    [
                        self.pxe_dir, '/',
                        self.pxename, '.kernel'
                    ]
                )
            )
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(
                    self.pxe_dir, '/pxeboot.{0}.xen.gz'.format(self.pxename)
                )
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory
                )
        if self.initrd_system == 'dracut':
            self.boot_image_task.include_module('kiwi-dump')
            self.boot_image_task.include_module('kiwi-dump-reboot')
            if self.root_filesystem_is_multipath is False:
                self.boot_image_task.omit_module('multipath')
            for mod in self.xml_state.get_installmedia_initrd_modules('add'):
                self.boot_image_task.include_module(mod)
            for mod in self.xml_state.get_installmedia_initrd_modules('omit'):
                self.boot_image_task.omit_module(mod)
            self.boot_image_task.set_static_modules(
                self.xml_state.get_installmedia_initrd_modules('set')
            )
        self.boot_image_task.create_initrd(
            self.mbrid, 'initrd_kiwi_install',
            install_initrd=True
        )
        Command.run(
            [
                'mv', self.boot_image_task.initrd_filename,
                self.pxe_dir + '/{0}'.format(initrdname)
            ]
        )
        os.chmod(self.pxe_dir + '/{0}'.format(initrdname), 420)

    def _create_iso_install_kernel_and_initrd(self) -> None:
        boot_path = self.media_dir + '/boot/' + self.arch + '/loader'
        Path.create(boot_path)
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory
                )
        if self.initrd_system == 'dracut':
            self.boot_image_task.include_module('kiwi-dump')
            self.boot_image_task.include_module('kiwi-dump-reboot')
            if self.root_filesystem_is_multipath is False:
                self.boot_image_task.omit_module('multipath')
            for mod in self.xml_state.get_installmedia_initrd_modules('add'):
                self.boot_image_task.include_module(mod)
            for mod in self.xml_state.get_installmedia_initrd_modules('omit'):
                self.boot_image_task.omit_module(mod)
            self.boot_image_task.set_static_modules(
                self.xml_state.get_installmedia_initrd_modules('set')
            )
            self._add_system_image_boot_options_to_boot_image()
        self.boot_image_task.create_initrd(
            self.mbrid, 'initrd_kiwi_install',
            install_initrd=True
        )
        Command.run(
            [
                'mv', self.boot_image_task.initrd_filename,
                boot_path + '/initrd'
            ]
        )

    def _add_system_image_boot_options_to_boot_image(self) -> None:
        filename = ''.join(
            [self.boot_image_task.boot_root_directory, '/config.bootoptions']
        )
        self.boot_image_task.include_file(os.sep + os.path.basename(filename))

    def _copy_system_image_initrd_to_iso_image(self) -> None:
        boot_names = self.boot_image_task.get_boot_names()
        system_image_initrd = os.sep.join(
            [self.root_dir, 'boot', boot_names.initrd_name]
        )
        shutil.copy(
            system_image_initrd, self.media_dir + '/initrd.system_image'
        )

    def _write_install_image_info_to_iso_image(self) -> None:
        iso_trigger = self.media_dir + '/config.isoclient'
        with open(iso_trigger, 'w') as iso_system:
            iso_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def _write_install_image_info_to_boot_image(self) -> None:
        initrd_trigger = \
            self.boot_image_task.boot_root_directory + '/config.vmxsystem'
        with open(initrd_trigger, 'w') as vmx_system:
            vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def __del__(self) -> None:
        log.info('Cleaning up %s instance', type(self).__name__)
        if self.media_dir:
            Path.wipe(self.media_dir)
        if self.pxe_dir:
            Path.wipe(self.pxe_dir)
        if self.squashed_contents:
            Path.wipe(self.squashed_contents)
Example #30
0
    def process(self):
        """
        reformats raw disk image and its format to a new disk
        geometry using the qemu tool chain
        """
        self.manual = Help()
        if self.command_args.get('help') is True:
            return self.manual.show('kiwi::image::resize')

        abs_target_dir_path = os.path.abspath(
            self.command_args['--target-dir']
        )

        if self.command_args['--root']:
            image_root = os.path.abspath(
                os.path.normpath(self.command_args['--root'])
            )
        else:
            image_root = os.sep.join(
                [abs_target_dir_path, 'build', 'image-root']
            )

        self.load_xml_description(
            image_root
        )

        disk_format = self.xml_state.build_type.get_format()

        image_format = DiskFormat(
            disk_format or 'raw', self.xml_state, image_root,
            abs_target_dir_path
        )
        if not image_format.has_raw_disk():
            raise KiwiImageResizeError(
                'no raw disk image {0} found in build results'.format(
                    image_format.diskname
                )
            )

        new_disk_size = StringToSize.to_bytes(self.command_args['--size'])

        # resize raw disk
        log.info(
            'Resizing raw disk to {0} bytes'.format(new_disk_size)
        )
        resize_result = image_format.resize_raw_disk(new_disk_size)

        # resize raw disk partition table
        firmware = FirmWare(self.xml_state)
        loop_provider = LoopDevice(image_format.diskname)
        loop_provider.create(overwrite=False)
        partitioner = Partitioner(
            firmware.get_partition_table_type(), loop_provider
        )
        partitioner.resize_table()
        del loop_provider

        # resize disk format from resized raw disk
        if disk_format and resize_result is True:
            log.info(
                'Creating {0} disk format from resized raw disk'.format(
                    disk_format
                )
            )
            image_format.create_image_format()
        elif resize_result is False:
            log.info(
                'Raw disk is already at {0} bytes'.format(new_disk_size)
            )
Example #31
0
class DiskBuilder:
    """
    **Disk image builder**

    :param object xml_state: Instance of :class:`XMLState`
    :param str target_dir: Target directory path name
    :param str root_dir: Root directory path name
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * signing_keys: list of package signing keys
        * xz_options: string of XZ compression parameters
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.spare_part_mbsize = xml_state.get_build_type_spare_part_size()
        self.spare_part_fs = xml_state.build_type.get_spare_part_fs()
        self.spare_part_is_last = xml_state.build_type.get_spare_part_is_last()
        self.spare_part_mountpoint = \
            xml_state.build_type.get_spare_part_mountpoint()
        self.persistency_type = xml_state.build_type.get_devicepersistency()
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot(
        )
        self.custom_root_mount_args = xml_state.get_fs_mount_option_list()
        self.custom_root_creation_args = xml_state.get_fs_create_option_list()
        self.build_type_name = xml_state.get_build_type_name()
        self.image_format = xml_state.build_type.get_format()
        self.install_iso = xml_state.build_type.get_installiso()
        self.install_stick = xml_state.build_type.get_installstick()
        self.install_pxe = xml_state.build_type.get_installpxe()
        self.blocksize = xml_state.build_type.get_target_blocksize()
        self.volume_manager_name = xml_state.get_volume_management()
        self.volumes = xml_state.get_volumes()
        self.volume_group_name = xml_state.get_volume_group_name()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr()
        self.force_mbr = xml_state.build_type.get_force_mbr()
        self.luks = xml_state.build_type.get_luks()
        self.luks_os = xml_state.build_type.get_luksOS()
        self.xen_server = xml_state.is_xen_server()
        self.requested_filesystem = xml_state.build_type.get_filesystem()
        self.requested_boot_filesystem = \
            xml_state.build_type.get_bootfilesystem()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.initrd_system = xml_state.get_initrd_system()
        self.target_removable = xml_state.build_type.get_target_removable()
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.disk_setup = DiskSetup(xml_state, root_dir)
        self.unpartitioned_bytes = \
            xml_state.get_build_type_unpartitioned_bytes()
        self.custom_args = custom_args

        self.signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            self.signing_keys = custom_args['signing_keys']

        self.boot_image = BootImage(xml_state,
                                    target_dir,
                                    root_dir,
                                    signing_keys=self.signing_keys)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.boot_is_crypto = True if self.luks and not \
            self.disk_setup.need_boot_partition() else False
        self.install_media = self._install_image_requested()
        self.generic_fstab_entries = []

        # an instance of a class with the sync_data capability
        # representing the entire image system except for the boot/ area
        # which could live on another part of the disk
        self.system = None

        # an instance of a class with the sync_data capability
        # representing the boot/ area of the disk if not part of
        # self.system
        self.system_boot = None

        # an instance of a class with the sync_data capability
        # representing the boot/efi area of the disk
        self.system_efi = None

        # an instance of a class with the sync_data capability
        # representing the spare_part_mountpoint area of the disk
        self.system_spare = None

        # result store
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self):
        """
        Build a bootable disk image and optional installation image
        The installation image is a bootable hybrid ISO image which
        embeds the disk image and an image installer

        Image types which triggers this builder are:

        * image="oem"
        * image="vmx"

        :return: result

        :rtype: instance of :class:`Result`
        """
        disk = DiskBuilder(self.xml_state, self.target_dir, self.root_dir,
                           self.custom_args)
        result = disk.create_disk()

        # cleanup disk resources taken prior to next steps
        del disk

        disk_installer = DiskBuilder(self.xml_state, self.target_dir,
                                     self.root_dir)
        result = disk_installer.create_install_media(result)

        disk_format = DiskBuilder(self.xml_state, self.target_dir,
                                  self.root_dir)

        disk_format.append_unpartitioned_space()
        result = disk_format.create_disk_format(result)

        return result

    def create_disk(self):  # noqa: C901
        """
        Build a bootable raw disk image

        :raises KiwiInstallMediaError:
            if install media is required and image type is not oem
        :raises KiwiVolumeManagerSetupError:
            root overlay at the same time volumes are defined is not supported

        :return: result

        :rtype: instance of :class:`Result`
        """
        if self.install_media and self.build_type_name != 'oem':
            raise KiwiInstallMediaError(
                'Install media requires oem type setup, got %s' %
                self.build_type_name)

        if self.root_filesystem_is_overlay and self.volume_manager_name:
            raise KiwiVolumeManagerSetupError(
                'Volume management together with root overlay is not supported'
            )

        # setup recovery archive, cleanup and create archive if requested
        self.system_setup.create_recovery_archive()

        # prepare boot(initrd) root system
        log.info('Preparing boot system')
        self.boot_image.prepare()

        # precalculate needed disk size
        disksize_mbytes = self.disk_setup.get_disksize_mbytes()

        # create the disk
        log.info('Creating raw disk image %s', self.diskname)
        loop_provider = LoopDevice(self.diskname, disksize_mbytes,
                                   self.blocksize)
        loop_provider.create()

        self.disk = Disk(self.firmware.get_partition_table_type(),
                         loop_provider, self.xml_state.get_disk_start_sector())

        # create the bootloader instance
        self.bootloader_config = BootLoaderConfig(
            self.bootloader,
            self.xml_state,
            root_dir=self.root_dir,
            boot_dir=self.root_dir,
            custom_args={
                'targetbase':
                loop_provider.get_device(),
                'grub_directory_name':
                Defaults.get_grub_boot_directory_name(self.root_dir),
                'boot_is_crypto':
                self.boot_is_crypto
            })

        # create disk partitions and instance device map
        device_map = self._build_and_map_disk_partitions(disksize_mbytes)

        # create raid on current root device if requested
        if self.mdraid:
            self.raid_root = RaidDevice(device_map['root'])
            self.raid_root.create_degraded_raid(raid_level=self.mdraid)
            device_map['root'] = self.raid_root.get_device()
            self.disk.public_partition_id_map['kiwi_RaidPart'] = \
                self.disk.public_partition_id_map['kiwi_RootPart']
            self.disk.public_partition_id_map['kiwi_RaidDev'] = \
                device_map['root'].get_device()

        # create luks on current root device if requested
        if self.luks:
            self.luks_root = LuksDevice(device_map['root'])
            self.luks_boot_keyname = '/.root.keyfile'
            self.luks_boot_keyfile = ''.join(
                [self.root_dir, self.luks_boot_keyname])
            self.luks_root.create_crypto_luks(passphrase=self.luks,
                                              os=self.luks_os,
                                              keyfile=self.luks_boot_keyfile
                                              if self.boot_is_crypto else None)
            if self.boot_is_crypto:
                self.luks_boot_keyfile_setup = ''.join(
                    [self.root_dir, '/etc/dracut.conf.d/99-luks-boot.conf'])
                self.boot_image.write_system_config_file(
                    config={'install_items': [self.luks_boot_keyname]},
                    config_file=self.luks_boot_keyfile_setup)
                self.boot_image.include_file(
                    os.sep + os.path.basename(self.luks_boot_keyfile))
            device_map['luks_root'] = device_map['root']
            device_map['root'] = self.luks_root.get_device()

        # create spare filesystem on spare partition if present
        self._build_spare_filesystem(device_map)

        # create filesystems on boot partition(s) if any
        self._build_boot_filesystems(device_map)

        # create volumes and filesystems for root system
        if self.volume_manager_name:
            volume_manager_custom_parameters = {
                'fs_mount_options':
                self.custom_root_mount_args,
                'fs_create_options':
                self.custom_root_creation_args,
                'root_label':
                self.disk_setup.get_root_label(),
                'root_is_snapshot':
                self.xml_state.build_type.get_btrfs_root_is_snapshot(),
                'root_is_readonly_snapshot':
                self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot(
                ),
                'quota_groups':
                self.xml_state.build_type.get_btrfs_quota_groups(),
                'image_type':
                self.xml_state.get_build_type_name()
            }
            volume_manager = VolumeManager(self.volume_manager_name,
                                           device_map['root'],
                                           self.root_dir + '/', self.volumes,
                                           volume_manager_custom_parameters)
            volume_manager.setup(self.volume_group_name)
            volume_manager.create_volumes(self.requested_filesystem)
            volume_manager.mount_volumes()
            self.generic_fstab_entries += volume_manager.get_fstab(
                self.persistency_type, self.requested_filesystem)
            self.system = volume_manager
            device_map['root'] = volume_manager.get_device()['root']
        else:
            log.info('Creating root(%s) filesystem on %s',
                     self.requested_filesystem,
                     device_map['root'].get_device())
            filesystem_custom_parameters = {
                'mount_options': self.custom_root_mount_args,
                'create_options': self.custom_root_creation_args
            }
            filesystem = FileSystem(self.requested_filesystem,
                                    device_map['root'], self.root_dir + '/',
                                    filesystem_custom_parameters)
            filesystem.create_on_device(label=self.disk_setup.get_root_label())
            self.system = filesystem

        # create a random image identifier
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        # create first stage metadata to boot image
        self._write_partition_id_config_to_boot_image()

        self._write_recovery_metadata_to_boot_image()

        self._write_raid_config_to_boot_image()

        self._write_generic_fstab_to_boot_image(device_map)

        self.system_setup.export_modprobe_setup(
            self.boot_image.boot_root_directory)

        # create first stage metadata to system image
        self._write_image_identifier_to_system_image()

        self._write_crypttab_to_system_image()

        self._write_generic_fstab_to_system_image(device_map)

        if self.initrd_system == 'dracut':
            if self.root_filesystem_is_multipath is False:
                self.boot_image.omit_module('multipath')
            if self.root_filesystem_is_overlay:
                self.boot_image.include_module('kiwi-overlay')
                self.boot_image.write_system_config_file(
                    config={'modules': ['kiwi-overlay']})
            if self.build_type_name == 'oem':
                self.boot_image.include_module('kiwi-repart')

        # create initrd cpio archive
        self.boot_image.create_initrd(self.mbrid)

        # create second stage metadata to system image
        self._copy_first_boot_files_to_system_image()

        self._write_bootloader_meta_data_to_system_image(device_map)

        self.mbrid.write_to_disk(self.disk.storage_provider)

        # set SELinux file security contexts if context exists
        self._setup_selinux_file_contexts()

        # syncing system data to disk image
        log.info('Syncing system to image')
        if self.system_spare:
            self.system_spare.sync_data()

        if self.system_efi:
            log.info('--> Syncing EFI boot data to EFI partition')
            self.system_efi.sync_data()

        if self.system_boot:
            log.info('--> Syncing boot data at extra partition')
            self.system_boot.sync_data(
                self._get_exclude_list_for_boot_data_sync())

        log.info('--> Syncing root filesystem data')
        if self.root_filesystem_is_overlay:
            squashed_root_file = NamedTemporaryFile()
            squashed_root = FileSystemSquashFs(device_provider=None,
                                               root_dir=self.root_dir)
            squashed_root.create_on_file(
                filename=squashed_root_file.name,
                exclude=self._get_exclude_list_for_root_data_sync(device_map))
            Command.run([
                'dd',
                'if=%s' % squashed_root_file.name,
                'of=%s' % device_map['readonly'].get_device()
            ])
        else:
            self.system.sync_data(
                self._get_exclude_list_for_root_data_sync(device_map))

        # install boot loader
        self._install_bootloader(device_map)

        # set root filesystem properties
        self._setup_property_root_is_readonly_snapshot()

        # prepare for install media if requested
        if self.install_media:
            log.info('Saving boot image instance to file')
            self.boot_image.dump(self.target_dir + '/boot_image.pickledump')

        self.result.verify_image_size(
            self.runtime_config.get_max_size_constraint(), self.diskname)
        # store image file name in result
        self.result.add(
            key='disk_image',
            filename=self.diskname,
            use_for_bundle=True if not self.image_format else False,
            compress=self.runtime_config.get_bundle_compression(default=True),
            shasum=True)

        # create image root metadata
        self.result.add(key='image_packages',
                        filename=self.system_setup.export_package_list(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        self.result.add(key='image_verified',
                        filename=self.system_setup.export_package_verification(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)

        return self.result

    def create_disk_format(self, result_instance):
        """
        Create a bootable disk format from a previously
        created raw disk image

        :param object result_instance: instance of :class:`Result`

        :return: updated result_instance

        :rtype: instance of :class:`Result`
        """
        if self.image_format:
            log.info('Creating %s Disk Format', self.image_format)
            disk_format = DiskFormat(self.image_format, self.xml_state,
                                     self.root_dir, self.target_dir)
            disk_format.create_image_format()
            disk_format.store_to_result(result_instance)

        return result_instance

    def append_unpartitioned_space(self):
        """
        Extends the raw disk if an unpartitioned area is specified
        """
        if self.unpartitioned_bytes:
            log.info('Expanding disk with %d bytes of unpartitioned space',
                     self.unpartitioned_bytes)
            disk_format = DiskFormat('raw', self.xml_state, self.root_dir,
                                     self.target_dir)
            disk_format.resize_raw_disk(self.unpartitioned_bytes, append=True)
            firmware = FirmWare(self.xml_state)
            loop_provider = LoopDevice(disk_format.diskname)
            loop_provider.create(overwrite=False)
            partitioner = Partitioner(firmware.get_partition_table_type(),
                                      loop_provider)
            partitioner.resize_table()

    def create_install_media(self, result_instance):
        """
        Build an installation image. The installation image is a
        bootable hybrid ISO image which embeds the raw disk image
        and an image installer

        :param object result_instance: instance of :class:`Result`

        :return: updated result_instance with installation media

        :rtype: instance of :class:`Result`
        """
        if self.install_media:
            install_image = InstallImageBuilder(
                self.xml_state, self.root_dir, self.target_dir,
                self._load_boot_image_instance(), self.custom_args)

            if self.install_iso or self.install_stick:
                log.info('Creating hybrid ISO installation image')
                install_image.create_install_iso()
                result_instance.add(key='installation_image',
                                    filename=install_image.isoname,
                                    use_for_bundle=True,
                                    compress=False,
                                    shasum=True)

            if self.install_pxe:
                log.info('Creating PXE installation archive')
                install_image.create_install_pxe_archive()
                result_instance.add(key='installation_pxe_archive',
                                    filename=install_image.pxename,
                                    use_for_bundle=True,
                                    compress=False,
                                    shasum=True)

        return result_instance

    def _load_boot_image_instance(self):
        boot_image_dump_file = self.target_dir + '/boot_image.pickledump'
        if not os.path.exists(boot_image_dump_file):
            raise KiwiInstallMediaError(
                'No boot image instance dump %s found' % boot_image_dump_file)
        try:
            with open(boot_image_dump_file, 'rb') as boot_image_dump:
                boot_image = pickle.load(boot_image_dump)
            boot_image.enable_cleanup()
            Path.wipe(boot_image_dump_file)
        except Exception as e:
            raise KiwiInstallMediaError('Failed to load boot image dump: %s' %
                                        type(e).__name__)
        return boot_image

    def _setup_selinux_file_contexts(self):
        security_context = '/etc/selinux/targeted/contexts/files/file_contexts'
        if os.path.exists(self.root_dir + security_context):
            self.system_setup.set_selinux_file_contexts(security_context)

    def _install_image_requested(self):
        if self.install_iso or self.install_stick or self.install_pxe:
            return True

    def _get_exclude_list_for_root_data_sync(self, device_map):
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        if 'spare' in device_map and self.spare_part_mountpoint:
            exclude_list.append('{0}/*'.format(
                self.spare_part_mountpoint.lstrip(os.sep)))
            exclude_list.append('{0}/.*'.format(
                self.spare_part_mountpoint.lstrip(os.sep)))
        if 'boot' in device_map and self.bootloader == 'grub2_s390x_emu':
            exclude_list.append('boot/zipl/*')
            exclude_list.append('boot/zipl/.*')
        elif 'boot' in device_map:
            exclude_list.append('boot/*')
            exclude_list.append('boot/.*')
        if 'efi' in device_map:
            exclude_list.append('boot/efi/*')
            exclude_list.append('boot/efi/.*')
        return exclude_list

    def _get_exclude_list_for_boot_data_sync(self):
        return ['efi/*']

    def _build_spare_filesystem(self, device_map):
        if 'spare' in device_map and self.spare_part_fs:
            spare_part_data_path = None
            if self.spare_part_mountpoint:
                spare_part_data_path = self.root_dir + '{0}/'.format(
                    self.spare_part_mountpoint)
            filesystem = FileSystem(self.spare_part_fs, device_map['spare'],
                                    spare_part_data_path)
            filesystem.create_on_device(label='SPARE')
            self.system_spare = filesystem

    def _build_boot_filesystems(self, device_map):
        if 'efi' in device_map:
            log.info('Creating EFI(fat16) filesystem on %s',
                     device_map['efi'].get_device())
            filesystem = FileSystem('fat16', device_map['efi'],
                                    self.root_dir + '/boot/efi/')
            filesystem.create_on_device(label=self.disk_setup.get_efi_label())
            self.system_efi = filesystem

        if 'boot' in device_map:
            boot_filesystem = self.requested_boot_filesystem
            if not boot_filesystem:
                boot_filesystem = self.requested_filesystem
            boot_directory = self.root_dir + '/boot/'
            if self.bootloader == 'grub2_s390x_emu':
                boot_directory = self.root_dir + '/boot/zipl/'
                boot_filesystem = 'ext2'
            log.info('Creating boot(%s) filesystem on %s', boot_filesystem,
                     device_map['boot'].get_device())
            filesystem = FileSystem(boot_filesystem, device_map['boot'],
                                    boot_directory)
            filesystem.create_on_device(label=self.disk_setup.get_boot_label())
            self.system_boot = filesystem

    def _build_and_map_disk_partitions(self, disksize_mbytes):  # noqa: C901
        self.disk.wipe()
        disksize_used_mbytes = 0
        if self.firmware.legacy_bios_mode():
            log.info('--> creating EFI CSM(legacy bios) partition')
            partition_mbsize = self.firmware.get_legacy_bios_partition_size()
            self.disk.create_efi_csm_partition(partition_mbsize)
            disksize_used_mbytes += partition_mbsize

        if self.firmware.efi_mode():
            log.info('--> creating EFI partition')
            partition_mbsize = self.firmware.get_efi_partition_size()
            self.disk.create_efi_partition(partition_mbsize)
            disksize_used_mbytes += partition_mbsize

        if self.firmware.ofw_mode():
            log.info('--> creating PReP partition')
            partition_mbsize = self.firmware.get_prep_partition_size()
            self.disk.create_prep_partition(partition_mbsize)
            disksize_used_mbytes += partition_mbsize

        if self.disk_setup.need_boot_partition():
            log.info('--> creating boot partition')
            partition_mbsize = self.disk_setup.boot_partition_size()
            self.disk.create_boot_partition(partition_mbsize)
            disksize_used_mbytes += partition_mbsize

        if self.spare_part_mbsize and not self.spare_part_is_last:
            log.info('--> creating spare partition')
            self.disk.create_spare_partition(self.spare_part_mbsize)

        if self.root_filesystem_is_overlay:
            log.info('--> creating readonly root partition')
            squashed_root_file = NamedTemporaryFile()
            squashed_root = FileSystemSquashFs(device_provider=None,
                                               root_dir=self.root_dir)
            squashed_root.create_on_file(
                filename=squashed_root_file.name,
                exclude=[Defaults.get_shared_cache_location()])
            squashed_rootfs_mbsize = int(
                os.path.getsize(squashed_root_file.name) /
                1048576) + Defaults.get_min_partition_mbytes()
            self.disk.create_root_readonly_partition(squashed_rootfs_mbsize)
            disksize_used_mbytes += squashed_rootfs_mbsize

        if self.spare_part_mbsize and self.spare_part_is_last:
            rootfs_mbsize = disksize_mbytes - disksize_used_mbytes - \
                self.spare_part_mbsize - Defaults.get_min_partition_mbytes()
        else:
            rootfs_mbsize = 'all_free'

        if self.volume_manager_name and self.volume_manager_name == 'lvm':
            log.info('--> creating LVM root partition')
            self.disk.create_root_lvm_partition(rootfs_mbsize)

        elif self.mdraid:
            log.info('--> creating mdraid root partition')
            self.disk.create_root_raid_partition(rootfs_mbsize)

        else:
            log.info('--> creating root partition')
            self.disk.create_root_partition(rootfs_mbsize)

        if self.spare_part_mbsize and self.spare_part_is_last:
            log.info('--> creating spare partition')
            self.disk.create_spare_partition('all_free')

        if self.firmware.bios_mode():
            log.info('--> setting active flag to primary boot partition')
            self.disk.activate_boot_partition()

        if self.firmware.ofw_mode():
            log.info('--> setting active flag to primary PReP partition')
            self.disk.activate_boot_partition()

        if self.firmware.efi_mode():
            if self.force_mbr:
                log.info('--> converting partition table to MBR')
                self.disk.create_mbr()
            elif self.hybrid_mbr:
                log.info('--> converting partition table to hybrid GPT/MBR')
                self.disk.create_hybrid_mbr()

        self.disk.map_partitions()

        return self.disk.get_device()

    def _write_partition_id_config_to_boot_image(self):
        log.info('Creating config.partids in boot system')
        filename = ''.join(
            [self.boot_image.boot_root_directory, '/config.partids'])
        partition_id_map = self.disk.get_public_partition_id_map()
        with open(filename, 'w') as partids:
            for id_name, id_value in list(partition_id_map.items()):
                partids.write('{0}="{1}"{2}'.format(id_name, id_value,
                                                    os.linesep))
        self.boot_image.include_file(os.sep + os.path.basename(filename))

    def _write_raid_config_to_boot_image(self):
        if self.mdraid:
            log.info('Creating etc/mdadm.conf in boot system')
            filename = ''.join(
                [self.boot_image.boot_root_directory, '/etc/mdadm.conf'])
            self.raid_root.create_raid_config(filename)
            self.boot_image.include_file(
                os.sep + os.sep.join(['etc', os.path.basename(filename)]))

    def _write_crypttab_to_system_image(self):
        if self.luks:
            log.info('Creating etc/crypttab')
            filename = ''.join([self.root_dir, '/etc/crypttab'])
            self.luks_root.create_crypttab(filename)
            self.boot_image.include_file(
                os.sep + os.sep.join(['etc', os.path.basename(filename)]))

    def _write_generic_fstab_to_system_image(self, device_map):
        log.info('Creating generic system etc/fstab')
        self._write_generic_fstab(device_map, self.system_setup)

    def _write_generic_fstab_to_boot_image(self, device_map):
        if self.initrd_system == 'kiwi':
            log.info('Creating generic boot image etc/fstab')
            self._write_generic_fstab(device_map, self.boot_image.setup)

    def _write_generic_fstab(self, device_map, setup):
        root_is_snapshot = \
            self.xml_state.build_type.get_btrfs_root_is_snapshot()
        root_is_readonly_snapshot = \
            self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot()

        fs_check_interval = '1 1'
        custom_root_mount_args = list(self.custom_root_mount_args)
        if root_is_snapshot and root_is_readonly_snapshot:
            custom_root_mount_args += ['ro']
            fs_check_interval = '0 0'

        self._add_generic_fstab_entry(device_map['root'].get_device(), '/',
                                      custom_root_mount_args,
                                      fs_check_interval)
        if 'spare' in device_map and \
           self.spare_part_fs and self.spare_part_mountpoint:
            self._add_generic_fstab_entry(device_map['spare'].get_device(),
                                          self.spare_part_mountpoint)
        if 'boot' in device_map:
            if self.bootloader == 'grub2_s390x_emu':
                boot_mount_point = '/boot/zipl'
            else:
                boot_mount_point = '/boot'
            self._add_generic_fstab_entry(device_map['boot'].get_device(),
                                          boot_mount_point)
        if 'efi' in device_map:
            self._add_generic_fstab_entry(device_map['efi'].get_device(),
                                          '/boot/efi')
        setup.create_fstab(self.generic_fstab_entries)

    def _add_generic_fstab_entry(self,
                                 device,
                                 mount_point,
                                 options=None,
                                 check='0 0'):
        if not options:
            options = ['defaults']
        block_operation = BlockID(device)
        blkid_type = 'LABEL' if self.persistency_type == 'by-label' else 'UUID'
        device_id = block_operation.get_blkid(blkid_type)
        fstab_entry = ' '.join([
            blkid_type + '=' + device_id, mount_point,
            block_operation.get_filesystem(), ','.join(options), check
        ])
        if fstab_entry not in self.generic_fstab_entries:
            self.generic_fstab_entries.append(fstab_entry)

    def _write_image_identifier_to_system_image(self):
        log.info('Creating image identifier: %s', self.mbrid.get_id())
        self.mbrid.write(self.root_dir + '/boot/mbrid')

    def _write_recovery_metadata_to_boot_image(self):
        if os.path.exists(self.root_dir + '/recovery.partition.size'):
            log.info('Copying recovery metadata to boot image')
            recovery_metadata = ''.join(
                [self.root_dir, '/recovery.partition.size'])
            Command.run(
                ['cp', recovery_metadata, self.boot_image.boot_root_directory])
            self.boot_image.include_file(os.sep +
                                         os.path.basename(recovery_metadata))

    def _write_bootloader_meta_data_to_system_image(self, device_map):
        if self.bootloader != 'custom':
            log.info('Creating %s bootloader configuration', self.bootloader)
            boot_options = []
            if self.mdraid:
                boot_options.append('rd.auto')
            root_device = device_map['root']
            boot_device = root_device
            if 'boot' in device_map:
                boot_device = device_map['boot']

            root_uuid = self.disk.get_uuid(device_map['root'].get_device())
            boot_uuid = self.disk.get_uuid(boot_device.get_device())
            boot_uuid_unmapped = self.disk.get_uuid(
                device_map['luks_root'].get_device(
                )) if self.luks else boot_uuid
            self.bootloader_config.setup_disk_boot_images(boot_uuid_unmapped)
            self.bootloader_config.write_meta_data(
                root_uuid=root_uuid, boot_options=' '.join(boot_options))

            log.info('Creating config.bootoptions')
            filename = ''.join(
                [self.boot_image.boot_root_directory, '/config.bootoptions'])
            kexec_boot_options = ' '.join(
                [self.bootloader_config.get_boot_cmdline(root_uuid)] +
                boot_options)
            with open(filename, 'w') as boot_options:
                boot_options.write('{0}{1}'.format(kexec_boot_options,
                                                   os.linesep))

        partition_id_map = self.disk.get_public_partition_id_map()
        boot_partition_id = partition_id_map['kiwi_RootPart']
        if 'kiwi_BootPart' in partition_id_map:
            boot_partition_id = partition_id_map['kiwi_BootPart']

        self.system_setup.call_edit_boot_config_script(
            self.requested_filesystem, boot_partition_id)

    def _install_bootloader(self, device_map):
        root_device = device_map['root']
        boot_device = root_device
        if 'boot' in device_map:
            boot_device = device_map['boot']

        if 'readonly' in device_map:
            root_device = device_map['readonly']

        custom_install_arguments = {
            'boot_device': boot_device.get_device(),
            'root_device': root_device.get_device(),
            'firmware': self.firmware,
            'target_removable': self.target_removable
        }

        if 'efi' in device_map:
            efi_device = device_map['efi']
            custom_install_arguments.update(
                {'efi_device': efi_device.get_device()})

        if 'prep' in device_map:
            prep_device = device_map['prep']
            custom_install_arguments.update(
                {'prep_device': prep_device.get_device()})

        if self.volume_manager_name:
            self.system.umount_volumes()
            custom_install_arguments.update(
                {'system_volumes': self.system.get_volumes()})

        # create bootloader config prior bootloader installation
        self.bootloader_config.setup_disk_image_config(
            boot_options=custom_install_arguments)

        # cleanup bootloader config resources taken prior to next steps
        del self.bootloader_config

        if self.bootloader != 'custom':
            log.debug("custom arguments for bootloader installation %s",
                      custom_install_arguments)
            bootloader = BootLoaderInstall(self.bootloader, self.root_dir,
                                           self.disk.storage_provider,
                                           custom_install_arguments)
            if bootloader.install_required():
                bootloader.install()

        self.system_setup.call_edit_boot_install_script(
            self.diskname, boot_device.get_device())

    def _setup_property_root_is_readonly_snapshot(self):
        if self.volume_manager_name:
            root_is_snapshot = \
                self.xml_state.build_type.get_btrfs_root_is_snapshot()
            root_is_readonly_snapshot = \
                self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot()
            if root_is_snapshot and root_is_readonly_snapshot:
                log.info('Setting root filesystem into read-only mode')
                self.system.mount_volumes()
                self.system.set_property_readonly_root()
                self.system.umount_volumes()

    def _copy_first_boot_files_to_system_image(self):
        boot_names = self.boot_image.get_boot_names()
        if self.initrd_system == 'kiwi':
            log.info('Copy boot files to system image')
            kernel = Kernel(self.boot_image.boot_root_directory)

            log.info('--> boot image kernel as %s', boot_names.kernel_name)
            kernel.copy_kernel(self.root_dir,
                               ''.join(['/boot/', boot_names.kernel_name]))

            if self.xen_server:
                if kernel.get_xen_hypervisor():
                    log.info('--> boot image Xen hypervisor as xen.gz')
                    kernel.copy_xen_hypervisor(self.root_dir, '/boot/xen.gz')
                else:
                    raise KiwiDiskBootImageError(
                        'No hypervisor in boot image tree %s found' %
                        self.boot_image.boot_root_directory)

        log.info('--> initrd archive as %s', boot_names.initrd_name)
        Command.run([
            'mv', self.boot_image.initrd_filename,
            self.root_dir + ''.join(['/boot/', boot_names.initrd_name])
        ])
Example #32
0
class DiskBuilder(object):
    """
    **Disk image builder**

    :param object xml_state: Instance of :class:`XMLState`
    :param str target_dir: Target directory path name
    :param str root_dir: Root directory path name
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * signing_keys: list of package signing keys
        * xz_options: string of XZ compression parameters
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.spare_part_mbsize = xml_state.get_build_type_spare_part_size()
        self.persistency_type = xml_state.build_type.get_devicepersistency()
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
        self.custom_root_mount_args = xml_state.get_fs_mount_option_list()
        self.build_type_name = xml_state.get_build_type_name()
        self.image_format = xml_state.build_type.get_format()
        self.install_iso = xml_state.build_type.get_installiso()
        self.install_stick = xml_state.build_type.get_installstick()
        self.install_pxe = xml_state.build_type.get_installpxe()
        self.blocksize = xml_state.build_type.get_target_blocksize()
        self.volume_manager_name = xml_state.get_volume_management()
        self.volumes = xml_state.get_volumes()
        self.volume_group_name = xml_state.get_volume_group_name()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr()
        self.force_mbr = xml_state.build_type.get_force_mbr()
        self.luks = xml_state.build_type.get_luks()
        self.luks_os = xml_state.build_type.get_luksOS()
        self.xen_server = xml_state.is_xen_server()
        self.requested_filesystem = xml_state.build_type.get_filesystem()
        self.requested_boot_filesystem = \
            xml_state.build_type.get_bootfilesystem()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.initrd_system = xml_state.get_initrd_system()
        self.target_removable = xml_state.build_type.get_target_removable()
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.disk_setup = DiskSetup(
            xml_state, root_dir
        )
        self.unpartitioned_bytes = \
            xml_state.get_build_type_unpartitioned_bytes()
        self.custom_args = custom_args

        self.signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            self.signing_keys = custom_args['signing_keys']

        self.boot_image = BootImage(
            xml_state, target_dir, root_dir,
            signing_keys=self.signing_keys, custom_args=self.custom_args
        )
        self.firmware = FirmWare(
            xml_state
        )
        self.system_setup = SystemSetup(
            xml_state=xml_state, root_dir=self.root_dir
        )
        self.diskname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.raw'
            ]
        )
        self.install_media = self._install_image_requested()
        self.generic_fstab_entries = []

        # an instance of a class with the sync_data capability
        # representing the entire image system except for the boot/ area
        # which could live on another part of the disk
        self.system = None

        # an instance of a class with the sync_data capability
        # representing the boot/ area of the disk if not part of
        # self.system
        self.system_boot = None

        # an instance of a class with the sync_data capability
        # representing the boot/efi area of the disk
        self.system_efi = None

        # result store
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self):
        """
        Build a bootable disk image and optional installation image
        The installation image is a bootable hybrid ISO image which
        embeds the disk image and an image installer

        Image types which triggers this builder are:

        * image="oem"
        * image="vmx"

        :return: result

        :rtype: instance of :class:`Result`
        """
        disk = DiskBuilder(
            self.xml_state, self.target_dir, self.root_dir, self.custom_args
        )
        result = disk.create_disk()

        # cleanup disk resources taken prior to next steps
        del disk

        disk_installer = DiskBuilder(
            self.xml_state, self.target_dir, self.root_dir
        )
        result = disk_installer.create_install_media(result)

        disk_format = DiskBuilder(
            self.xml_state, self.target_dir, self.root_dir
        )

        disk_format.append_unpartitioned_space()
        result = disk_format.create_disk_format(result)

        return result

    def create_disk(self):
        """
        Build a bootable raw disk image

        :raises KiwiInstallMediaError:
            if install media is required and image type is not oem
        :raises KiwiVolumeManagerSetupError:
            root overlay at the same time volumes are defined is not supported

        :return: result

        :rtype: instance of :class:`Result`
        """
        if self.install_media and self.build_type_name != 'oem':
            raise KiwiInstallMediaError(
                'Install media requires oem type setup, got %s' %
                self.build_type_name
            )

        if self.root_filesystem_is_overlay and self.volume_manager_name:
            raise KiwiVolumeManagerSetupError(
                'Volume management together with root overlay is not supported'
            )

        # setup recovery archive, cleanup and create archive if requested
        self.system_setup.create_recovery_archive()

        # prepare boot(initrd) root system
        log.info('Preparing boot system')
        self.boot_image.prepare()

        # precalculate needed disk size
        disksize_mbytes = self.disk_setup.get_disksize_mbytes()

        # create the disk
        log.info('Creating raw disk image %s', self.diskname)
        loop_provider = LoopDevice(
            self.diskname, disksize_mbytes, self.blocksize
        )
        loop_provider.create()

        self.disk = Disk(
            self.firmware.get_partition_table_type(), loop_provider,
            self.xml_state.get_disk_start_sector()
        )

        # create the bootloader instance
        self.bootloader_config = BootLoaderConfig(
            self.bootloader, self.xml_state, self.root_dir, {
                'targetbase':
                    loop_provider.get_device(),
                'grub_directory_name':
                    Defaults.get_grub_boot_directory_name(self.root_dir)
            }
        )

        # create disk partitions and instance device map
        device_map = self._build_and_map_disk_partitions()

        # create raid on current root device if requested
        if self.mdraid:
            self.raid_root = RaidDevice(device_map['root'])
            self.raid_root.create_degraded_raid(raid_level=self.mdraid)
            device_map['root'] = self.raid_root.get_device()
            self.disk.public_partition_id_map['kiwi_RaidPart'] = \
                self.disk.public_partition_id_map['kiwi_RootPart']
            self.disk.public_partition_id_map['kiwi_RaidDev'] = \
                device_map['root'].get_device()

        # create luks on current root device if requested
        if self.luks:
            self.luks_root = LuksDevice(device_map['root'])
            self.luks_root.create_crypto_luks(
                passphrase=self.luks, os=self.luks_os
            )
            device_map['root'] = self.luks_root.get_device()

        # create filesystems on boot partition(s) if any
        self._build_boot_filesystems(device_map)

        # create volumes and filesystems for root system
        if self.volume_manager_name:
            volume_manager_custom_parameters = {
                'fs_mount_options':
                    self.custom_root_mount_args,
                'root_label':
                    self.disk_setup.get_root_label(),
                'root_is_snapshot':
                    self.xml_state.build_type.get_btrfs_root_is_snapshot(),
                'root_is_readonly_snapshot':
                    self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot(),
                'image_type':
                    self.xml_state.get_build_type_name()
            }
            volume_manager = VolumeManager(
                self.volume_manager_name, device_map['root'],
                self.root_dir + '/',
                self.volumes,
                volume_manager_custom_parameters
            )
            volume_manager.setup(
                self.volume_group_name
            )
            volume_manager.create_volumes(
                self.requested_filesystem
            )
            volume_manager.mount_volumes()
            self.generic_fstab_entries += volume_manager.get_fstab(
                self.persistency_type, self.requested_filesystem
            )
            self.system = volume_manager
            device_map['root'] = volume_manager.get_device()['root']
        else:
            log.info(
                'Creating root(%s) filesystem on %s',
                self.requested_filesystem, device_map['root'].get_device()
            )
            filesystem_custom_parameters = {
                'mount_options': self.custom_root_mount_args
            }
            filesystem = FileSystem(
                self.requested_filesystem, device_map['root'],
                self.root_dir + '/',
                filesystem_custom_parameters
            )
            filesystem.create_on_device(
                label=self.disk_setup.get_root_label()
            )
            self.system = filesystem

        # create a random image identifier
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        # create first stage metadata to boot image
        self._write_partition_id_config_to_boot_image()

        self._write_recovery_metadata_to_boot_image()

        self._write_raid_config_to_boot_image()

        self._write_generic_fstab_to_boot_image(device_map)

        self.system_setup.export_modprobe_setup(
            self.boot_image.boot_root_directory
        )

        # create first stage metadata to system image
        self._write_image_identifier_to_system_image()

        self._write_crypttab_to_system_image()

        self._write_generic_fstab_to_system_image(device_map)

        if self.initrd_system == 'dracut':
            self._create_dracut_config()

        # create initrd cpio archive
        self.boot_image.create_initrd(self.mbrid)

        # create dracut config omitting one time kiwi dracut modules
        if self.initrd_system == 'dracut':
            self._create_system_dracut_config()

        # create second stage metadata to system image
        self._copy_first_boot_files_to_system_image()

        self._write_bootloader_config_to_system_image(device_map)

        self.mbrid.write_to_disk(
            self.disk.storage_provider
        )

        # set SELinux file security contexts if context exists
        self._setup_selinux_file_contexts()

        # syncing system data to disk image
        log.info('Syncing system to image')
        if self.system_efi:
            log.info('--> Syncing EFI boot data to EFI partition')
            self.system_efi.sync_data()

        if self.system_boot:
            log.info('--> Syncing boot data at extra partition')
            self.system_boot.sync_data(
                self._get_exclude_list_for_boot_data_sync()
            )

        log.info('--> Syncing root filesystem data')
        if self.root_filesystem_is_overlay:
            squashed_root_file = NamedTemporaryFile()
            squashed_root = FileSystemSquashFs(
                device_provider=None, root_dir=self.root_dir
            )
            squashed_root.create_on_file(
                filename=squashed_root_file.name,
                exclude=self._get_exclude_list_for_root_data_sync(device_map)
            )
            Command.run(
                [
                    'dd',
                    'if=%s' % squashed_root_file.name,
                    'of=%s' % device_map['readonly'].get_device()
                ]
            )
        else:
            self.system.sync_data(
                self._get_exclude_list_for_root_data_sync(device_map)
            )

        # install boot loader
        self._install_bootloader(device_map)

        # set root filesystem properties
        self._setup_property_root_is_readonly_snapshot()

        # prepare for install media if requested
        if self.install_media:
            log.info('Saving boot image instance to file')
            self.boot_image.dump(
                self.target_dir + '/boot_image.pickledump'
            )

        self.result.verify_image_size(
            self.runtime_config.get_max_size_constraint(),
            self.diskname
        )
        # store image file name in result
        self.result.add(
            key='disk_image',
            filename=self.diskname,
            use_for_bundle=True if not self.image_format else False,
            compress=True,
            shasum=True
        )

        # create image root metadata
        self.result.add(
            key='image_packages',
            filename=self.system_setup.export_package_list(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )
        self.result.add(
            key='image_verified',
            filename=self.system_setup.export_package_verification(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )

        return self.result

    def create_disk_format(self, result_instance):
        """
        Create a bootable disk format from a previously
        created raw disk image

        :param object result_instance: instance of :class:`Result`

        :return: updated result_instance

        :rtype: instance of :class:`Result`
        """
        if self.image_format:
            log.info('Creating %s Disk Format', self.image_format)
            disk_format = DiskFormat(
                self.image_format, self.xml_state,
                self.root_dir, self.target_dir
            )
            disk_format.create_image_format()
            disk_format.store_to_result(result_instance)

        return result_instance

    def append_unpartitioned_space(self):
        """
        Extends the raw disk if an unpartitioned area is specified
        """
        if self.unpartitioned_bytes:
            log.info(
                'Expanding disk with %d bytes of unpartitioned space',
                self.unpartitioned_bytes
            )
            disk_format = DiskFormat(
                'raw', self.xml_state, self.root_dir, self.target_dir
            )
            disk_format.resize_raw_disk(self.unpartitioned_bytes, append=True)
            firmware = FirmWare(self.xml_state)
            loop_provider = LoopDevice(disk_format.diskname)
            loop_provider.create(overwrite=False)
            partitioner = Partitioner(
                firmware.get_partition_table_type(), loop_provider
            )
            partitioner.resize_table()

    def create_install_media(self, result_instance):
        """
        Build an installation image. The installation image is a
        bootable hybrid ISO image which embeds the raw disk image
        and an image installer

        :param object result_instance: instance of :class:`Result`

        :return: updated result_instance with installation media

        :rtype: instance of :class:`Result`
        """
        if self.install_media:
            install_image = InstallImageBuilder(
                self.xml_state, self.root_dir, self.target_dir,
                self._load_boot_image_instance(), self.custom_args
            )

            if self.install_iso or self.install_stick:
                log.info('Creating hybrid ISO installation image')
                install_image.create_install_iso()
                result_instance.add(
                    key='installation_image',
                    filename=install_image.isoname,
                    use_for_bundle=True,
                    compress=False,
                    shasum=True
                )

            if self.install_pxe:
                log.info('Creating PXE installation archive')
                install_image.create_install_pxe_archive()
                result_instance.add(
                    key='installation_pxe_archive',
                    filename=install_image.pxename,
                    use_for_bundle=True,
                    compress=False,
                    shasum=True
                )

        return result_instance

    def _load_boot_image_instance(self):
        boot_image_dump_file = self.target_dir + '/boot_image.pickledump'
        if not os.path.exists(boot_image_dump_file):
            raise KiwiInstallMediaError(
                'No boot image instance dump %s found' % boot_image_dump_file
            )
        try:
            with open(boot_image_dump_file, 'rb') as boot_image_dump:
                boot_image = pickle.load(boot_image_dump)
            boot_image.enable_cleanup()
            Path.wipe(boot_image_dump_file)
        except Exception as e:
            raise KiwiInstallMediaError(
                'Failed to load boot image dump: %s' % type(e).__name__
            )
        return boot_image

    def _setup_selinux_file_contexts(self):
        security_context = '/etc/selinux/targeted/contexts/files/file_contexts'
        if os.path.exists(self.root_dir + security_context):
            self.system_setup.set_selinux_file_contexts(
                security_context
            )

    def _install_image_requested(self):
        if self.install_iso or self.install_stick or self.install_pxe:
            return True

    def _get_exclude_list_for_root_data_sync(self, device_map):
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        if 'boot' in device_map and self.bootloader == 'grub2_s390x_emu':
            exclude_list.append('boot/zipl/*')
            exclude_list.append('boot/zipl/.*')
        elif 'boot' in device_map:
            exclude_list.append('boot/*')
            exclude_list.append('boot/.*')
        if 'efi' in device_map:
            exclude_list.append('boot/efi/*')
            exclude_list.append('boot/efi/.*')
        return exclude_list

    def _get_exclude_list_for_boot_data_sync(self):
        return ['efi/*']

    def _build_boot_filesystems(self, device_map):
        if 'efi' in device_map:
            log.info(
                'Creating EFI(fat16) filesystem on %s',
                device_map['efi'].get_device()
            )
            filesystem = FileSystem(
                'fat16', device_map['efi'], self.root_dir + '/boot/efi/'
            )
            filesystem.create_on_device(
                label=self.disk_setup.get_efi_label()
            )
            self.system_efi = filesystem

        if 'boot' in device_map:
            boot_filesystem = self.requested_boot_filesystem
            if not boot_filesystem:
                boot_filesystem = self.requested_filesystem
            boot_directory = self.root_dir + '/boot/'
            if self.bootloader == 'grub2_s390x_emu':
                boot_directory = self.root_dir + '/boot/zipl/'
                boot_filesystem = 'ext2'
            log.info(
                'Creating boot(%s) filesystem on %s',
                boot_filesystem, device_map['boot'].get_device()
            )
            filesystem = FileSystem(
                boot_filesystem, device_map['boot'], boot_directory
            )
            filesystem.create_on_device(
                label=self.disk_setup.get_boot_label()
            )
            self.system_boot = filesystem

    def _build_and_map_disk_partitions(self):               # noqa: C901
        self.disk.wipe()
        if self.firmware.legacy_bios_mode():
            log.info('--> creating EFI CSM(legacy bios) partition')
            self.disk.create_efi_csm_partition(
                self.firmware.get_legacy_bios_partition_size()
            )

        if self.firmware.efi_mode():
            log.info('--> creating EFI partition')
            self.disk.create_efi_partition(
                self.firmware.get_efi_partition_size()
            )

        if self.firmware.ofw_mode():
            log.info('--> creating PReP partition')
            self.disk.create_prep_partition(
                self.firmware.get_prep_partition_size()
            )

        if self.disk_setup.need_boot_partition():
            log.info('--> creating boot partition')
            self.disk.create_boot_partition(
                self.disk_setup.boot_partition_size()
            )

        if self.spare_part_mbsize:
            log.info('--> creating spare partition')
            self.disk.create_spare_partition(
                self.spare_part_mbsize
            )

        if self.root_filesystem_is_overlay:
            log.info('--> creating readonly root partition')
            squashed_root_file = NamedTemporaryFile()
            squashed_root = FileSystemSquashFs(
                device_provider=None, root_dir=self.root_dir
            )
            squashed_root.create_on_file(
                filename=squashed_root_file.name,
                exclude=[Defaults.get_shared_cache_location()]
            )
            squashed_rootfs_mbsize = os.path.getsize(
                squashed_root_file.name
            ) / 1048576
            self.disk.create_root_readonly_partition(
                int(squashed_rootfs_mbsize + 50)
            )

        if self.volume_manager_name and self.volume_manager_name == 'lvm':
            log.info('--> creating LVM root partition')
            self.disk.create_root_lvm_partition('all_free')

        elif self.mdraid:
            log.info('--> creating mdraid root partition')
            self.disk.create_root_raid_partition('all_free')

        else:
            log.info('--> creating root partition')
            self.disk.create_root_partition('all_free')

        if self.firmware.bios_mode():
            log.info('--> setting active flag to primary boot partition')
            self.disk.activate_boot_partition()

        if self.firmware.ofw_mode():
            log.info('--> setting active flag to primary PReP partition')
            self.disk.activate_boot_partition()

        if self.firmware.efi_mode():
            if self.force_mbr:
                log.info('--> converting partition table to MBR')
                self.disk.create_mbr()
            elif self.hybrid_mbr:
                log.info('--> converting partition table to hybrid GPT/MBR')
                self.disk.create_hybrid_mbr()

        self.disk.map_partitions()

        return self.disk.get_device()

    def _create_dracut_config(self):
        dracut_config = [
            'hostonly="no"',
            'dracut_rescue_image="no"'
        ]
        dracut_modules = []
        dracut_modules_omit = ['kiwi-live', 'kiwi-dump']
        if self.root_filesystem_is_multipath is False:
            dracut_modules_omit.append('multipath')
        if self.root_filesystem_is_overlay:
            dracut_modules.append('kiwi-overlay')
        else:
            dracut_modules_omit.append('kiwi-overlay')
        if self.build_type_name == 'oem':
            dracut_modules.append('kiwi-lib')
            dracut_modules.append('kiwi-repart')
        self._write_dracut_config(
            config=dracut_config,
            modules=dracut_modules,
            omit_modules=dracut_modules_omit
        )

    def _create_system_dracut_config(self):
        dracut_modules = []
        dracut_modules_omit = ['kiwi-live', 'kiwi-dump', 'kiwi-repart']
        if self.root_filesystem_is_overlay:
            dracut_modules.append('kiwi-overlay')
        else:
            dracut_modules_omit.append('kiwi-overlay')
        self._write_dracut_config(
            config=[], modules=dracut_modules, omit_modules=dracut_modules_omit
        )

    def _write_dracut_config(self, config, modules, omit_modules):
        dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()]
        )
        if modules:
            config.append(
                'add_dracutmodules+=" {0} "'.format(' '.join(modules))
            )
        if omit_modules:
            config.append(
                'omit_dracutmodules+=" {0} "'.format(' '.join(omit_modules))
            )
        with open(dracut_config_file, 'w') as dracut_config:
            for entry in config:
                dracut_config.write(entry + os.linesep)

    def _write_partition_id_config_to_boot_image(self):
        log.info('Creating config.partids in boot system')
        filename = ''.join(
            [self.boot_image.boot_root_directory, '/config.partids']
        )
        partition_id_map = self.disk.get_public_partition_id_map()
        with open(filename, 'w') as partids:
            for id_name, id_value in list(partition_id_map.items()):
                partids.write('{0}="{1}"{2}'.format(
                    id_name, id_value, os.linesep)
                )
        self.boot_image.include_file(
            os.sep + os.path.basename(filename)
        )

    def _write_raid_config_to_boot_image(self):
        if self.mdraid:
            log.info('Creating etc/mdadm.conf in boot system')
            filename = ''.join(
                [self.boot_image.boot_root_directory, '/etc/mdadm.conf']
            )
            self.raid_root.create_raid_config(filename)
            self.boot_image.include_file(
                os.sep + os.sep.join(['etc', os.path.basename(filename)])
            )

    def _write_crypttab_to_system_image(self):
        if self.luks:
            log.info('Creating etc/crypttab')
            filename = ''.join(
                [self.root_dir, '/etc/crypttab']
            )
            self.luks_root.create_crypttab(filename)
            self.boot_image.include_file(
                os.sep + os.sep.join(['etc', os.path.basename(filename)])
            )

    def _write_generic_fstab_to_system_image(self, device_map):
        log.info('Creating generic system etc/fstab')
        self._write_generic_fstab(device_map, self.system_setup)

    def _write_generic_fstab_to_boot_image(self, device_map):
        if self.initrd_system == 'kiwi':
            log.info('Creating generic boot image etc/fstab')
            self._write_generic_fstab(device_map, self.boot_image.setup)

    def _write_generic_fstab(self, device_map, setup):
        root_is_snapshot = \
            self.xml_state.build_type.get_btrfs_root_is_snapshot()
        root_is_readonly_snapshot = \
            self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot()

        fs_check_interval = '1 1'
        custom_root_mount_args = list(self.custom_root_mount_args)
        if root_is_snapshot and root_is_readonly_snapshot:
            custom_root_mount_args += ['ro']
            fs_check_interval = '0 0'

        self._add_generic_fstab_entry(
            device_map['root'].get_device(), '/',
            custom_root_mount_args, fs_check_interval
        )
        if 'boot' in device_map:
            if self.bootloader == 'grub2_s390x_emu':
                boot_mount_point = '/boot/zipl'
            else:
                boot_mount_point = '/boot'
            self._add_generic_fstab_entry(
                device_map['boot'].get_device(), boot_mount_point
            )
        if 'efi' in device_map:
            self._add_generic_fstab_entry(
                device_map['efi'].get_device(), '/boot/efi'
            )
        setup.create_fstab(
            self.generic_fstab_entries
        )

    def _add_generic_fstab_entry(
        self, device, mount_point, options=None, check='0 0'
    ):
        if not options:
            options = ['defaults']
        block_operation = BlockID(device)
        blkid_type = 'LABEL' if self.persistency_type == 'by-label' else 'UUID'
        device_id = block_operation.get_blkid(blkid_type)
        fstab_entry = ' '.join(
            [
                blkid_type + '=' + device_id, mount_point,
                block_operation.get_filesystem(), ','.join(options), check
            ]
        )
        if fstab_entry not in self.generic_fstab_entries:
            self.generic_fstab_entries.append(
                fstab_entry
            )

    def _write_image_identifier_to_system_image(self):
        log.info('Creating image identifier: %s', self.mbrid.get_id())
        self.mbrid.write(
            self.root_dir + '/boot/mbrid'
        )

    def _write_recovery_metadata_to_boot_image(self):
        if os.path.exists(self.root_dir + '/recovery.partition.size'):
            log.info('Copying recovery metadata to boot image')
            recovery_metadata = ''.join(
                [self.root_dir, '/recovery.partition.size']
            )
            Command.run(
                ['cp', recovery_metadata, self.boot_image.boot_root_directory]
            )
            self.boot_image.include_file(
                os.sep + os.path.basename(recovery_metadata)
            )

    def _write_bootloader_config_to_system_image(self, device_map):
        if self.bootloader is not 'custom':
            log.info('Creating %s bootloader configuration', self.bootloader)
            boot_options = []
            if self.mdraid:
                boot_options.append('rd.auto')
            boot_names = self.boot_image.get_boot_names()
            boot_device = device_map['root']
            if 'boot' in device_map:
                boot_device = device_map['boot']

            root_uuid = self.disk.get_uuid(
                device_map['root'].get_device()
            )
            boot_uuid = self.disk.get_uuid(
                boot_device.get_device()
            )
            self.bootloader_config.setup_disk_boot_images(boot_uuid)
            self.bootloader_config.setup_disk_image_config(
                boot_uuid=boot_uuid,
                root_uuid=root_uuid,
                kernel=boot_names.kernel_name,
                initrd=boot_names.initrd_name,
                boot_options=' '.join(boot_options)
            )
            self.bootloader_config.write()

            log.info('Creating config.bootoptions')
            filename = ''.join(
                [self.boot_image.boot_root_directory, '/config.bootoptions']
            )
            kexec_boot_options = ' '.join(
                [
                    self.bootloader_config.get_boot_cmdline(root_uuid)
                ] + boot_options
            )
            with open(filename, 'w') as boot_options:
                boot_options.write(
                    '{0}{1}'.format(kexec_boot_options, os.linesep)
                )

        partition_id_map = self.disk.get_public_partition_id_map()
        boot_partition_id = partition_id_map['kiwi_RootPart']
        if 'kiwi_BootPart' in partition_id_map:
            boot_partition_id = partition_id_map['kiwi_BootPart']

        self.system_setup.call_edit_boot_config_script(
            self.requested_filesystem, boot_partition_id
        )

    def _install_bootloader(self, device_map):
        root_device = device_map['root']
        boot_device = root_device
        if 'boot' in device_map:
            boot_device = device_map['boot']

        if 'readonly' in device_map:
            root_device = device_map['readonly']

        custom_install_arguments = {
            'boot_device': boot_device.get_device(),
            'root_device': root_device.get_device(),
            'firmware': self.firmware,
            'target_removable': self.target_removable
        }

        if 'efi' in device_map:
            efi_device = device_map['efi']
            custom_install_arguments.update(
                {'efi_device': efi_device.get_device()}
            )

        if 'prep' in device_map:
            prep_device = device_map['prep']
            custom_install_arguments.update(
                {'prep_device': prep_device.get_device()}
            )

        if self.volume_manager_name:
            self.system.umount_volumes()
            custom_install_arguments.update(
                {'system_volumes': self.system.get_volumes()}
            )

        if self.bootloader is not 'custom':
            log.debug(
                "custom arguments for bootloader installation %s",
                custom_install_arguments
            )
            bootloader = BootLoaderInstall(
                self.bootloader, self.root_dir, self.disk.storage_provider,
                custom_install_arguments
            )
            if bootloader.install_required():
                bootloader.install()

        self.system_setup.call_edit_boot_install_script(
            self.diskname, boot_device.get_device()
        )

    def _setup_property_root_is_readonly_snapshot(self):
        if self.volume_manager_name:
            root_is_snapshot = \
                self.xml_state.build_type.get_btrfs_root_is_snapshot()
            root_is_readonly_snapshot = \
                self.xml_state.build_type.get_btrfs_root_is_readonly_snapshot()
            if root_is_snapshot and root_is_readonly_snapshot:
                log.info(
                    'Setting root filesystem into read-only mode'
                )
                self.system.mount_volumes()
                self.system.set_property_readonly_root()
                self.system.umount_volumes()

    def _copy_first_boot_files_to_system_image(self):
        boot_names = self.boot_image.get_boot_names()
        if self.initrd_system == 'kiwi':
            log.info('Copy boot files to system image')
            kernel = Kernel(self.boot_image.boot_root_directory)

            log.info('--> boot image kernel as %s', boot_names.kernel_name)
            kernel.copy_kernel(
                self.root_dir, ''.join(['/boot/', boot_names.kernel_name])
            )

            if self.xen_server:
                if kernel.get_xen_hypervisor():
                    log.info('--> boot image Xen hypervisor as xen.gz')
                    kernel.copy_xen_hypervisor(
                        self.root_dir, '/boot/xen.gz'
                    )
                else:
                    raise KiwiDiskBootImageError(
                        'No hypervisor in boot image tree %s found' %
                        self.boot_image.boot_root_directory
                    )

        log.info('--> initrd archive as %s', boot_names.initrd_name)
        Command.run(
            [
                'mv', self.boot_image.initrd_filename,
                self.root_dir + ''.join(['/boot/', boot_names.initrd_name])
            ]
        )
Example #33
0
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.spare_part_mbsize = xml_state.get_build_type_spare_part_size()
        self.persistency_type = xml_state.build_type.get_devicepersistency()
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
        self.custom_root_mount_args = xml_state.get_fs_mount_option_list()
        self.build_type_name = xml_state.get_build_type_name()
        self.image_format = xml_state.build_type.get_format()
        self.install_iso = xml_state.build_type.get_installiso()
        self.install_stick = xml_state.build_type.get_installstick()
        self.install_pxe = xml_state.build_type.get_installpxe()
        self.blocksize = xml_state.build_type.get_target_blocksize()
        self.volume_manager_name = xml_state.get_volume_management()
        self.volumes = xml_state.get_volumes()
        self.volume_group_name = xml_state.get_volume_group_name()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr()
        self.force_mbr = xml_state.build_type.get_force_mbr()
        self.luks = xml_state.build_type.get_luks()
        self.luks_os = xml_state.build_type.get_luksOS()
        self.xen_server = xml_state.is_xen_server()
        self.requested_filesystem = xml_state.build_type.get_filesystem()
        self.requested_boot_filesystem = \
            xml_state.build_type.get_bootfilesystem()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.initrd_system = xml_state.get_initrd_system()
        self.target_removable = xml_state.build_type.get_target_removable()
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.disk_setup = DiskSetup(
            xml_state, root_dir
        )
        self.unpartitioned_bytes = \
            xml_state.get_build_type_unpartitioned_bytes()
        self.custom_args = custom_args

        self.signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            self.signing_keys = custom_args['signing_keys']

        self.boot_image = BootImage(
            xml_state, target_dir, root_dir,
            signing_keys=self.signing_keys, custom_args=self.custom_args
        )
        self.firmware = FirmWare(
            xml_state
        )
        self.system_setup = SystemSetup(
            xml_state=xml_state, root_dir=self.root_dir
        )
        self.diskname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.raw'
            ]
        )
        self.install_media = self._install_image_requested()
        self.generic_fstab_entries = []

        # an instance of a class with the sync_data capability
        # representing the entire image system except for the boot/ area
        # which could live on another part of the disk
        self.system = None

        # an instance of a class with the sync_data capability
        # representing the boot/ area of the disk if not part of
        # self.system
        self.system_boot = None

        # an instance of a class with the sync_data capability
        # representing the boot/efi area of the disk
        self.system_efi = None

        # result store
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()
Example #34
0
class LiveImageBuilder(object):
    """
    **Live image builder**

    :param object xml_state: instance of :class:`XMLState`
    :param str target_dir: target directory path name
    :param str root_dir: root directory path name
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * xz_options: string of XZ compression parameters
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.live_container_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }
        self.publisher = xml_state.build_type.get_publisher() or \
            Defaults.get_publisher()

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(
            xml_state, target_dir, self.root_dir
        )
        self.firmware = FirmWare(
            xml_state
        )
        self.system_setup = SystemSetup(
            xml_state=xml_state, root_dir=self.root_dir
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + platform.machine(),
                '-' + xml_state.get_image_version(),
                '.iso'
            ]
        )
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self):
        """
        Build a bootable hybrid live ISO image

        Image types which triggers this builder are:

        * image="iso"

        :raises KiwiLiveBootImageError: if no kernel or hipervisor is found
            in boot image tree
        :return: result

        :rtype: instance of :class:`Result`
        """
        # media dir to store CD contents
        self.media_dir = mkdtemp(
            prefix='live-media.', dir=self.target_dir
        )

        # unpack cdroot user files to media dir
        self.system_setup.import_cdroot_files(self.media_dir)

        rootsize = SystemSize(self.media_dir)

        # custom iso metadata
        log.info('Using following live ISO metadata:')
        log.info('--> Application id: {0}'.format(self.mbrid.get_id()))
        log.info('--> Publisher: {0}'.format(Defaults.get_publisher()))
        log.info('--> Volume id: {0}'.format(self.volume_id))
        custom_iso_args = {
            'meta_data': {
                'publisher': self.publisher,
                'preparer': Defaults.get_preparer(),
                'volume_id': self.volume_id,
                'mbr_id': self.mbrid.get_id(),
                'efi_mode': self.firmware.efi_mode()
            }
        }

        # pack system into live boot structure as expected by dracut
        log.info(
            'Packing system into dracut live ISO type: {0}'.format(
                self.live_type
            )
        )
        root_filesystem = Defaults.get_default_live_iso_root_filesystem()
        filesystem_custom_parameters = {
            'mount_options': self.xml_state.get_fs_mount_option_list()
        }
        filesystem_setup = FileSystemSetup(
            self.xml_state, self.root_dir
        )
        root_image = NamedTemporaryFile()
        loop_provider = LoopDevice(
            root_image.name,
            filesystem_setup.get_size_mbytes(root_filesystem),
            self.xml_state.build_type.get_target_blocksize()
        )
        loop_provider.create()
        live_filesystem = FileSystem(
            name=root_filesystem,
            device_provider=loop_provider,
            root_dir=self.root_dir + os.sep,
            custom_args=filesystem_custom_parameters
        )
        live_filesystem.create_on_device()
        log.info(
            '--> Syncing data to {0} root image'.format(root_filesystem)
        )
        live_filesystem.sync_data(
            Defaults.get_exclude_list_for_root_data_sync()
        )
        log.info('--> Creating squashfs container for root image')
        self.live_container_dir = mkdtemp(
            prefix='live-container.', dir=self.target_dir
        )
        Path.create(self.live_container_dir + '/LiveOS')
        shutil.copy(
            root_image.name, self.live_container_dir + '/LiveOS/rootfs.img'
        )
        live_container_image = FileSystem(
            name='squashfs',
            device_provider=None,
            root_dir=self.live_container_dir
        )
        container_image = NamedTemporaryFile()
        live_container_image.create_on_file(
            container_image.name
        )
        Path.create(self.media_dir + '/LiveOS')
        shutil.copy(
            container_image.name, self.media_dir + '/LiveOS/squashfs.img'
        )

        # setup bootloader config to boot the ISO via isolinux
        log.info('Setting up isolinux bootloader configuration')
        bootloader_config_isolinux = BootLoaderConfig(
            'isolinux', self.xml_state, self.media_dir
        )
        bootloader_config_isolinux.setup_live_boot_images(
            mbrid=None,
            lookup_path=self.boot_image.boot_root_directory
        )
        bootloader_config_isolinux.setup_live_image_config(
            mbrid=None
        )
        bootloader_config_isolinux.write()

        # setup bootloader config to boot the ISO via EFI
        if self.firmware.efi_mode():
            log.info('Setting up EFI grub bootloader configuration')
            bootloader_config_grub = BootLoaderConfig(
                'grub2', self.xml_state, self.media_dir, {
                    'grub_directory_name':
                        Defaults.get_grub_boot_directory_name(self.root_dir)
                }
            )
            bootloader_config_grub.setup_live_boot_images(
                mbrid=self.mbrid, lookup_path=self.root_dir
            )
            bootloader_config_grub.setup_live_image_config(
                mbrid=self.mbrid
            )
            bootloader_config_grub.write()

        # call custom editbootconfig script if present
        self.system_setup.call_edit_boot_config_script(
            filesystem='iso:{0}'.format(self.media_dir), boot_part_id=1,
            working_directory=self.root_dir
        )

        # prepare dracut initrd call
        self.boot_image.prepare()

        # create dracut initrd for live image
        log.info('Creating live ISO boot image')
        self._create_dracut_live_iso_config()
        self.boot_image.create_initrd(self.mbrid)

        # setup kernel file(s) and initrd in ISO boot layout
        log.info('Setting up kernel file(s) and boot image in ISO boot layout')
        self._setup_live_iso_kernel_and_initrd()

        # calculate size and decide if we need UDF
        if rootsize.accumulate_mbyte_file_sizes() > 4096:
            log.info('ISO exceeds 4G size, using UDF filesystem')
            custom_iso_args['meta_data']['udf'] = True

        # create iso filesystem from media_dir
        log.info('Creating live ISO image')
        iso_image = FileSystemIsoFs(
            device_provider=None, root_dir=self.media_dir,
            custom_args=custom_iso_args
        )
        iso_image.create_on_file(self.isoname)

        # include metadata for checkmedia tool
        if self.xml_state.build_type.get_mediacheck() is True:
            Iso.set_media_tag(self.isoname)

        self.result.verify_image_size(
            self.runtime_config.get_max_size_constraint(),
            self.isoname
        )
        self.result.add(
            key='live_image',
            filename=self.isoname,
            use_for_bundle=True,
            compress=False,
            shasum=True
        )
        self.result.add(
            key='image_packages',
            filename=self.system_setup.export_package_list(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )
        self.result.add(
            key='image_verified',
            filename=self.system_setup.export_package_verification(
                self.target_dir
            ),
            use_for_bundle=True,
            compress=False,
            shasum=False
        )
        return self.result

    def _create_dracut_live_iso_config(self):
        live_config_file = self.root_dir + '/etc/dracut.conf.d/02-livecd.conf'
        omit_modules = [
            'kiwi-dump', 'kiwi-overlay', 'kiwi-repart', 'kiwi-lib', 'multipath'
        ]
        live_config = [
            'add_dracutmodules+=" {0} pollcdrom "'.format(
                Defaults.get_live_dracut_module_from_flag(self.live_type)
            ),
            'omit_dracutmodules+=" {0} "'.format(' '.join(omit_modules)),
            'hostonly="no"',
            'dracut_rescue_image="no"'
        ]
        with open(live_config_file, 'w') as config:
            for entry in live_config:
                config.write(entry + os.linesep)

    def _setup_live_iso_kernel_and_initrd(self):
        """
        Copy kernel and initrd from the root tree into the iso boot structure
        """
        boot_path = ''.join(
            [self.media_dir, '/boot/', self.arch, '/loader']
        )
        Path.create(boot_path)

        # Move kernel files to iso filesystem structure
        kernel = Kernel(self.boot_image.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiLiveBootImageError(
                'No kernel in boot image tree {0} found'.format(
                    self.boot_image.boot_root_directory
                )
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiLiveBootImageError(
                    'No hypervisor in boot image tree {0} found'.format(
                        self.boot_image.boot_root_directory
                    )
                )

        # Move initrd to iso filesystem structure
        if os.path.exists(self.boot_image.initrd_filename):
            shutil.move(
                self.boot_image.initrd_filename, boot_path + '/initrd'
            )
        else:
            raise KiwiLiveBootImageError(
                'No boot image {0} in boot image tree {1} found'.format(
                    self.boot_image.initrd_filename,
                    self.boot_image.boot_root_directory
                )
            )

    def __del__(self):
        if self.media_dir or self.live_container_dir:
            log.info(
                'Cleaning up {0} instance'.format(type(self).__name__)
            )
            if self.media_dir:
                Path.wipe(self.media_dir)
            if self.live_container_dir:
                Path.wipe(self.live_container_dir)
Example #35
0
class BootLoaderConfigGrub2(BootLoaderConfigBase):
    """
    **grub2 bootloader configuration.**
    """
    def post_init(self, custom_args):
        """
        grub2 post initialization method

        :param dict custom_args:
            Contains grub config arguments

            .. code:: python

                {'grub_directory_name': 'grub|grub2'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if arch == 'x86_64':
            # grub2 support for bios and efi systems
            self.arch = arch
        elif arch.startswith('ppc64'):
            # grub2 support for ofw and opal systems
            self.arch = arch
        elif arch == 'i686' or arch == 'i586':
            # grub2 support for bios systems
            self.arch = 'ix86'
        elif arch == 'aarch64' or arch.startswith('arm'):
            # grub2 support for efi systems
            self.arch = arch
        else:
            raise KiwiBootLoaderGrubPlatformError(
                'host architecture %s not supported for grub2 setup' % arch
            )

        if self.custom_args and 'grub_directory_name' in self.custom_args:
            self.boot_directory_name = self.custom_args['grub_directory_name']
        else:
            self.boot_directory_name = 'grub'

        self.terminal = self.xml_state.build_type.get_bootloader_console() \
            or 'gfxterm'
        self.gfxmode = self.get_gfxmode('grub2')
        self.theme = self.get_boot_theme()
        self.timeout = self.get_boot_timeout_seconds()
        self.continue_on_timeout = self.get_continue_on_timeout()
        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.mediacheck_boot = self.xml_state.build_type.get_mediacheck()
        self.xen_guest = self.xml_state.is_xen_guest()
        self.firmware = FirmWare(
            self.xml_state
        )

        self.live_type = self.xml_state.build_type.get_flags()
        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.install_volid = self.xml_state.build_type.get_volid() or \
            Defaults.get_install_volume_id()

        self.live_boot_options = [
            'root=live:CDLABEL={0}'.format(self.volume_id),
            'rd.live.image'
        ]
        self.install_boot_options = [
            'loglevel=0'
        ]
        if self.xml_state.get_initrd_system() == 'dracut':
            self.install_boot_options.append(
                'root=install:CDLABEL={0}'.format(self.install_volid)
            )
        if self.xml_state.build_type.get_hybridpersistent():
            self.live_boot_options += \
                Defaults.get_live_iso_persistent_boot_options(
                    self.xml_state.build_type.get_hybridpersistent_filesystem()
                )

        if self.xml_state.is_xen_server():
            self.hybrid_boot = False
            self.multiboot = True
        elif self.xen_guest:
            self.hybrid_boot = False
            self.multiboot = False
        else:
            self.hybrid_boot = True
            self.multiboot = False

        self.grub2 = BootLoaderTemplateGrub2()
        self.config = None
        self.efi_boot_path = None
        self.cmdline_failsafe = None
        self.root_reference = None
        self.cmdline = None
        self.iso_boot = False
        self.shim_fallback_setup = False
        self.validate_use_of_linuxefi = False

    def write(self):
        """
        Write bootloader configuration

        * writes grub.cfg template by KIWI if template system is used
        * copies grub config file to alternative boot path for EFI systems
          in fallback mode
        * creates an embedded fat efi image for EFI ISO boot
        """
        if self.config:
            log.info('Writing KIWI template grub.cfg file')
            config_dir = self._get_grub2_boot_path()
            config_file = config_dir + '/grub.cfg'
            Path.create(config_dir)
            with open(config_file, 'w') as config:
                config.write(self.config)

            if self.firmware.efi_mode():
                self._copy_grub_config_to_efi_path(
                    self.boot_dir, config_file
                )
                if self.iso_boot:
                    self._create_embedded_fat_efi_image()

    def write_meta_data(self, root_uuid=None, boot_options=''):
        """
        Write bootloader setup meta data files

        * cmdline arguments initialization
        * etc/default/grub setup file
        * etc/sysconfig/bootloader

        :param string root_uuid: root device UUID
        :param string boot_options: kernel options as string
        :param bool iso_boot: indicate target is an ISO
        """
        self.cmdline = ' '.join(
            [self.get_boot_cmdline(root_uuid), boot_options]
        )
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options(), boot_options]
        )
        self.root_reference = self._get_root_cmdline_parameter(root_uuid)

        self._setup_default_grub()
        self._setup_sysconfig_bootloader()

    def setup_disk_image_config(
        self, boot_uuid=None, root_uuid=None, hypervisor=None,
        kernel=None, initrd=None, boot_options={}
    ):
        """
        Create grub2 config file to boot from disk using grub2-mkconfig

        :param string boot_uuid: unused
        :param string root_uuid: unused
        :param string hypervisor: unused
        :param string kernel: unused
        :param string initrd: unused
        :param dict boot_options:

        options dictionary that has to contain the root and boot
        device and optional volume configuration. KIWI has to
        mount the system prior to run grub2-mkconfig.

        .. code:: python

            {
                'root_device': string,
                'boot_device': string,
                'efi_device': string,
                'system_volumes': volume_manager_instance.get_volumes()
            }
        """
        self._mount_system(
            boot_options.get('root_device'),
            boot_options.get('boot_device'),
            boot_options.get('efi_device'),
            boot_options.get('system_volumes')
        )
        config_file = os.sep.join(
            [
                self.root_mount.mountpoint, 'boot',
                self.boot_directory_name, 'grub.cfg'
            ]
        )
        Command.run(
            [
                'chroot', self.root_mount.mountpoint,
                os.path.basename(self._get_grub2_mkconfig_tool()), '-o',
                config_file.replace(self.root_mount.mountpoint, '')
            ]
        )

        if self.validate_use_of_linuxefi:
            # On systems that uses GRUB_USE_LINUXEFI with grub2 version
            # less than 2.04 there is no support for dynamic EFI
            # environment checking. In this condition we change the
            # grub config to add this support as follows:
            #
            # * Apply only on grub < 2.04
            #    1. Modify grub.cfg to set linux/initrd as variables
            #    2. Prepend hybrid setup to select linux vs. linuxefi on demand
            #
            # Please note this is a one time modification done by kiwi
            # Any subsequent call of the grub config tool will overwrite
            # the setup and disables dynamic EFI environment checking
            # at boot time
            if not CommandCapabilities.check_version(
                self._get_grub2_mkconfig_tool(),
                version_waterline=(2, 4), raise_on_error=False
            ):
                with open(config_file) as grub_config_file:
                    grub_config = grub_config_file.read()
                    grub_config = re.sub(
                        r'([ \t]+)linux[efi]*([ \t]+)', r'\1$linux\2',
                        grub_config
                    )
                    grub_config = re.sub(
                        r'([ \t]+)initrd[efi]*([ \t]+)', r'\1$initrd\2',
                        grub_config
                    )
                with open(config_file, 'w') as grub_config_file:
                    grub_config_file.write(
                        Template(self.grub2.header_hybrid).substitute()
                    )
                    grub_config_file.write(grub_config)

        if self.root_reference:
            if self.root_filesystem_is_overlay or \
               Defaults.is_buildservice_worker():
                # grub2-mkconfig has no idea how the correct root= setup is
                # for disk images created with overlayroot enabled or in a
                # buildservice worker environment. Because of that the mkconfig
                # tool just finds the raw partition loop device and includes it
                # which is wrong. In this particular case we have to patch the
                # written config file and replace the wrong root= reference with
                # the correct value.
                with open(config_file) as grub_config_file:
                    grub_config = grub_config_file.read()
                    grub_config = grub_config.replace(
                        'root={0}'.format(boot_options.get('root_device')),
                        self.root_reference
                    )
                with open(config_file, 'w') as grub_config_file:
                    grub_config_file.write(grub_config)

        if self.firmware.efi_mode():
            self._copy_grub_config_to_efi_path(
                self.efi_mount.mountpoint, config_file
            )

    def setup_install_image_config(
        self, mbrid, hypervisor='xen.gz', kernel='linux', initrd='initrd'
    ):
        """
        Create grub2 config file to boot from an ISO install image

        :param string mbrid: mbrid file name on boot device
        :param string hypervisor: hypervisor name
        :param string kernel: kernel name
        :param string initrd: initrd name
        """
        log.info('Creating grub2 install config file from template')
        self.iso_boot = True
        parameters = {
            'search_params': '--file --set=root /boot/' + mbrid.get_id(),
            'default_boot': self.get_install_image_boot_default(),
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': ' '.join(
                [self.cmdline] + self.install_boot_options
            ),
            'failsafe_boot_options': ' '.join(
                [self.cmdline_failsafe] + self.install_boot_options
            ),
            'gfxmode': self.gfxmode,
            'theme': self.theme,
            'boot_timeout': self.timeout,
            'title': self.get_menu_entry_install_title(),
            'bootpath': self.get_boot_path('iso'),
            'boot_directory_name': self.boot_directory_name,
            'efi_image_name': Defaults.get_efi_image_name(self.arch),
            'terminal_setup': self.terminal
        }
        if self.multiboot:
            log.info('--> Using multiboot install template')
            parameters['hypervisor'] = hypervisor
            template = self.grub2.get_multiboot_install_template(
                self.failsafe_boot, self.terminal,
                self.continue_on_timeout
            )
        else:
            log.info('--> Using standard boot install template')
            hybrid_boot = True
            template = self.grub2.get_install_template(
                self.failsafe_boot, hybrid_boot, self.terminal,
                self.continue_on_timeout
            )
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_live_image_config(
        self, mbrid, hypervisor='xen.gz', kernel='linux', initrd='initrd'
    ):
        """
        Create grub2 config file to boot a live media ISO image

        :param string mbrid: mbrid file name on boot device
        :param string hypervisor: hypervisor name
        :param string kernel: kernel name
        :param string initrd: initrd name
        """
        log.info('Creating grub2 live ISO config file from template')
        self.iso_boot = True
        parameters = {
            'search_params': '--file --set=root /boot/' + mbrid.get_id(),
            'default_boot': '0',
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': ' '.join(
                [self.cmdline] + self.live_boot_options
            ),
            'failsafe_boot_options': ' '.join(
                [self.cmdline_failsafe] + self.live_boot_options
            ),
            'gfxmode': self.gfxmode,
            'theme': self.theme,
            'boot_timeout': self.timeout,
            'title': self.get_menu_entry_title(plain=True),
            'bootpath': self.get_boot_path('iso'),
            'boot_directory_name': self.boot_directory_name,
            'efi_image_name': Defaults.get_efi_image_name(self.arch),
            'terminal_setup': self.terminal
        }
        if self.multiboot:
            log.info('--> Using multiboot template')
            parameters['hypervisor'] = hypervisor
            template = self.grub2.get_multiboot_iso_template(
                self.failsafe_boot, self.terminal, self.mediacheck_boot
            )
        else:
            log.info('--> Using standard boot template')
            hybrid_boot = True
            template = self.grub2.get_iso_template(
                self.failsafe_boot, hybrid_boot,
                self.terminal, self.mediacheck_boot
            )
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_install_boot_images(self, mbrid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        In order to boot from the ISO grub2 modules, images and theme
        data needs to be created and provided at the correct place on
        the iso filesystem

        :param string mbrid: mbrid file name on boot device
        :param string lookup_path: custom module lookup path
        """
        log.info('Creating grub2 bootloader images')
        self.efi_boot_path = self.create_efi_path(in_sub_dir='')

        log.info('--> Creating identifier file %s', mbrid.get_id())
        Path.create(
            self._get_grub2_boot_path()
        )
        mbrid.write(
            self.boot_dir + '/boot/' + mbrid.get_id()
        )
        mbrid.write(
            self.boot_dir + '/boot/mbrid'
        )

        self._copy_theme_data_to_boot_directory(lookup_path, 'iso')

        if self._supports_bios_modules():
            self._copy_bios_modules_to_boot_directory(lookup_path)
            self._setup_bios_image(mbrid=mbrid, lookup_path=lookup_path)

        if self.firmware.efi_mode():
            self._setup_EFI_path(lookup_path)

        if self.firmware.efi_mode() == 'efi':
            self._setup_efi_image(mbrid=mbrid, lookup_path=lookup_path)
            self._copy_efi_modules_to_boot_directory(lookup_path)
        elif self.firmware.efi_mode() == 'uefi':
            self._copy_efi_modules_to_boot_directory(lookup_path)
            self._setup_secure_boot_efi_image(lookup_path)

    def setup_live_boot_images(self, mbrid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        Calls setup_install_boot_images because no different action required
        """
        self.setup_install_boot_images(mbrid, lookup_path)

    def setup_disk_boot_images(self, boot_uuid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        In order to boot from the disk grub2 modules, images and theme
        data needs to be created and provided at the correct place in
        the filesystem

        :param string boot_uuid: boot device UUID
        :param string lookup_path: custom module lookup path
        """
        log.info('Creating grub2 bootloader images')

        if self.firmware.efi_mode():
            self.efi_boot_path = self.create_efi_path()

        self._copy_theme_data_to_boot_directory(lookup_path, 'disk')

        if not self.xen_guest and self._supports_bios_modules():
            self._copy_bios_modules_to_boot_directory(lookup_path)

        if self.firmware.efi_mode() == 'efi':
            self._setup_efi_image(uuid=boot_uuid, lookup_path=lookup_path)
            self._copy_efi_modules_to_boot_directory(lookup_path)
        elif self.firmware.efi_mode() == 'uefi':
            self._copy_efi_modules_to_boot_directory(lookup_path)
            if not self._get_shim_install():
                self.shim_fallback_setup = True
                self._setup_secure_boot_efi_image(lookup_path)

        if self.xen_guest:
            self._copy_xen_modules_to_boot_directory(lookup_path)

    def _copy_grub_config_to_efi_path(self, root_path, config_file):
        if self.iso_boot or self.shim_fallback_setup:
            efi_boot_path = Defaults.get_shim_vendor_directory(
                root_path
            )
            if not efi_boot_path:
                efi_boot_path = os.path.normpath(
                    os.sep.join([root_path, 'EFI/BOOT'])
                )
            Path.create(efi_boot_path)
            grub_config_file_for_efi_boot = os.sep.join(
                [efi_boot_path, 'grub.cfg']
            )
            log.info(
                'Copying {0} -> {1} to be found by EFI'.format(
                    config_file, grub_config_file_for_efi_boot
                )
            )
            shutil.copy(
                config_file, grub_config_file_for_efi_boot
            )

    def _supports_bios_modules(self):
        if self.arch == 'ix86' or self.arch == 'x86_64':
            return True
        return False

    def _setup_sysconfig_bootloader(self):
        """
        Create or update etc/sysconfig/bootloader by the following
        parameters required according to the grub2 bootloader setup

        * LOADER_TYPE
        * LOADER_LOCATION
        * DEFAULT_APPEND
        * FAILSAFE_APPEND
        * SECURE_BOOT
        """
        sysconfig_bootloader_entries = {
            'LOADER_TYPE':
                'grub2-efi' if self.firmware.efi_mode() else 'grub2',
            'LOADER_LOCATION':
                'none' if self.firmware.efi_mode() else 'mbr'
        }
        if self.firmware.efi_mode() == 'uefi':
            sysconfig_bootloader_entries['SECURE_BOOT'] = 'yes'
        if self.cmdline:
            sysconfig_bootloader_entries['DEFAULT_APPEND'] = '"{0}"'.format(
                self.cmdline
            )
        if self.cmdline_failsafe:
            sysconfig_bootloader_entries['FAILSAFE_APPEND'] = '"{0}"'.format(
                self.cmdline_failsafe
            )

        sysconfig_bootloader_location = ''.join(
            [self.root_dir, '/etc/sysconfig/']
        )
        if os.path.exists(sysconfig_bootloader_location):
            log.info('Writing sysconfig bootloader file')
            sysconfig_bootloader_file = ''.join(
                [sysconfig_bootloader_location, 'bootloader']
            )
            sysconfig_bootloader = SysConfig(
                sysconfig_bootloader_file
            )
            sysconfig_bootloader_entries_sorted = OrderedDict(
                sorted(sysconfig_bootloader_entries.items())
            )
            for key, value in list(sysconfig_bootloader_entries_sorted.items()):
                log.info('--> {0}:{1}'.format(key, value))
                sysconfig_bootloader[key] = value
            sysconfig_bootloader.write()

    def _setup_default_grub(self):  # noqa: C901
        """
        Create or update etc/default/grub by parameters required
        according to the root filesystem setup

        * GRUB_TIMEOUT
        * SUSE_BTRFS_SNAPSHOT_BOOTING
        * GRUB_BACKGROUND
        * GRUB_THEME
        * GRUB_USE_LINUXEFI
        * GRUB_USE_INITRDEFI
        * GRUB_SERIAL_COMMAND
        * GRUB_CMDLINE_LINUX_DEFAULT
        * GRUB_GFXMODE
        * GRUB_TERMINAL
        * GRUB_DISTRIBUTOR
        """
        grub_default_entries = {
            'GRUB_TIMEOUT': self.timeout,
            'GRUB_GFXMODE': self.gfxmode,
            'GRUB_TERMINAL': '"{0}"'.format(self.terminal),
            'GRUB_DISTRIBUTOR': '"{0}"'.format(self.get_menu_entry_title())
        }
        if self.cmdline:
            grub_default_entries['GRUB_CMDLINE_LINUX_DEFAULT'] = '"{0}"'.format(
                re.sub(r'root=.* |root=.*$', '', self.cmdline).strip()
            )
        if self.terminal and 'serial' in self.terminal:
            serial_format = '"serial {0} {1} {2} {3} {4}"'
            grub_default_entries['GRUB_SERIAL_COMMAND'] = serial_format.format(
                '--speed=38400',
                '--unit=0',
                '--word=8',
                '--parity=no',
                '--stop=1'
            )
        if self.theme:
            theme_setup = '{0}/{1}/theme.txt'
            grub_default_entries['GRUB_THEME'] = theme_setup.format(
                ''.join(['/boot/', self.boot_directory_name, '/themes']),
                self.theme
            )
            theme_background = '{0}/{1}/background.png'.format(
                ''.join(['/boot/', self.boot_directory_name, '/themes']),
                self.theme
            )
            if os.path.exists(os.sep.join([self.root_dir, theme_background])):
                grub_default_entries['GRUB_BACKGROUND'] = theme_background
        if self.firmware.efi_mode():
            # linuxefi/initrdefi only exist on x86, others always use efi
            if self.arch == 'ix86' or self.arch == 'x86_64':
                use_linuxefi_implemented = Command.run(
                    [
                        'grep', '-q', 'GRUB_USE_LINUXEFI',
                        self._get_grub2_mkconfig_tool()
                    ], raise_on_error=False
                )
                if use_linuxefi_implemented.returncode == 0:
                    grub_default_entries['GRUB_USE_LINUXEFI'] = 'true'
                    grub_default_entries['GRUB_USE_INITRDEFI'] = 'true'
                    self.validate_use_of_linuxefi = True
        if self.xml_state.build_type.get_btrfs_root_is_snapshot():
            grub_default_entries['SUSE_BTRFS_SNAPSHOT_BOOTING'] = 'true'
        if self.custom_args.get('boot_is_crypto'):
            grub_default_entries['GRUB_ENABLE_CRYPTODISK'] = 'y'

        enable_blscfg_implemented = Command.run(
            [
                'grep', '-q', 'GRUB_ENABLE_BLSCFG',
                self._get_grub2_mkconfig_tool()
            ], raise_on_error=False
        )
        if enable_blscfg_implemented.returncode == 0:
            grub_default_entries['GRUB_ENABLE_BLSCFG'] = 'true'

        if grub_default_entries:
            log.info('Writing grub2 defaults file')
            grub_default_location = ''.join([self.root_dir, '/etc/default/'])
            if os.path.exists(grub_default_location):
                grub_default_file = ''.join([grub_default_location, 'grub'])
                grub_default = SysConfig(grub_default_file)
                grub_default_entries_sorted = OrderedDict(
                    sorted(grub_default_entries.items())
                )
                for key, value in list(grub_default_entries_sorted.items()):
                    log.info('--> {0}:{1}'.format(key, value))
                    grub_default[key] = value
                grub_default.write()

    def _setup_secure_boot_efi_image(self, lookup_path):
        """
        Provide the shim loader and the shim signed grub2 loader
        in the required boot path. Normally this task is done by
        the shim-install tool. However, shim-install does not exist
        on all distributions and the script does not operate well
        in e.g CD environments from which we generate live and/or
        install media. Thus shim-install is used if possible at
        install time of the bootloader because it requires access
        to the target block device. In any other case this setup
        code should act as the fallback solution
        """
        log.warning(
            '--> Running fallback setup for shim secure boot efi image'
        )
        if not lookup_path:
            lookup_path = self.boot_dir
        grub_image = Defaults.get_signed_grub_loader(lookup_path)
        if not grub_image:
            raise KiwiBootLoaderGrubSecureBootError(
                'Signed grub2 efi loader not found'
            )
        shim_image = Defaults.get_shim_loader(lookup_path)
        if shim_image:
            # The shim concept is based on a two step system including a
            # grub image(shim) that got signed by Microsoft followed by
            # a grub image that got signed by the shim. The shim image
            # is the one that gets loaded by the firmware which itself
            # loads the second stage grub image
            Command.run(
                ['cp', shim_image, self._get_efi_image_name()]
            )
            Command.run(
                ['cp', grub_image, self.efi_boot_path]
            )
        else:
            # Without shim a self signed grub image is used that
            # gets loaded by the firmware
            Command.run(
                ['cp', grub_image, self._get_efi_image_name()]
            )

    def _setup_efi_image(self, uuid=None, mbrid=None, lookup_path=None):
        """
        Provide the unsigned grub2 efi image in the required boot path
        If a prebuilt efi image as provided by the distribution could
        be found, this image will be used. If no such image could be
        found we create a custom image with a pre defined set of
        grub modules
        """
        if not lookup_path:
            lookup_path = self.boot_dir
        grub_image = Defaults.get_unsigned_grub_loader(lookup_path)
        if grub_image:
            log.info('--> Using prebuilt unsigned efi image')
            Command.run(
                ['cp', grub_image, self._get_efi_image_name()]
            )
            self._create_efi_config_search(uuid, mbrid)
        else:
            log.info('--> Creating unsigned efi image')
            self._create_efi_image(
                uuid, mbrid, lookup_path
            )

    def _setup_bios_image(self, mbrid=None, lookup_path=None):
        """
        Provide bios grub image
        """
        if not lookup_path:
            lookup_path = self.boot_dir
        grub_image = Defaults.get_grub_bios_core_loader(lookup_path)
        if grub_image:
            log.info('--> Using prebuilt bios image')
        else:
            log.info('--> Creating bios image')
            self._create_bios_image(
                mbrid, lookup_path
            )
        bash_command = ' '.join(
            [
                'cat',
                self._get_bios_modules_path(lookup_path) + '/cdboot.img',
                grub_image or self._get_bios_image_name(lookup_path),
                '>',
                os.sep.join(
                    [
                        self._get_bios_modules_path(lookup_path),
                        Defaults.get_isolinux_bios_grub_loader()
                    ]
                )
            ]
        )
        Command.run(
            ['bash', '-c', bash_command]
        )

    def _create_embedded_fat_efi_image(self):
        Path.create(self.boot_dir + '/boot/' + self.arch)
        efi_fat_image = ''.join(
            [self.boot_dir + '/boot/', self.arch, '/efi']
        )
        Command.run(
            ['qemu-img', 'create', efi_fat_image, '15M']
        )
        Command.run(
            ['mkdosfs', '-n', 'BOOT', efi_fat_image]
        )
        Command.run(
            [
                'mcopy', '-Do', '-s', '-i', efi_fat_image,
                self.boot_dir + '/EFI', '::'
            ]
        )

    def _create_efi_image(self, uuid=None, mbrid=None, lookup_path=None):
        early_boot_script = os.path.normpath(
            os.sep.join([self.efi_boot_path, 'earlyboot.cfg'])
        )
        if uuid:
            self._create_early_boot_script_for_uuid_search(
                early_boot_script, uuid
            )
        else:
            self._create_early_boot_script_for_mbrid_search(
                early_boot_script, mbrid
            )
        module_list = Defaults.get_grub_efi_modules(multiboot=self.xen_guest)
        module_path = self._get_efi_modules_path(lookup_path)
        if os.path.exists(module_path + '/linuxefi.mod'):
            module_list.append('linuxefi')
        Command.run(
            [
                self._get_grub2_mkimage_tool() or 'grub2-mkimage',
                '-O', Defaults.get_efi_module_directory_name(self.arch),
                '-o', self._get_efi_image_name(),
                '-c', early_boot_script,
                '-p', self.get_boot_path() + '/' + self.boot_directory_name,
                '-d', module_path
            ] + module_list
        )

    def _create_efi_config_search(self, uuid=None, mbrid=None):
        efi_boot_config = self.efi_boot_path + '/grub.cfg'
        if uuid:
            self._create_early_boot_script_for_uuid_search(
                efi_boot_config, uuid
            )
        else:
            self._create_early_boot_script_for_mbrid_search(
                efi_boot_config, mbrid
            )
        with open(efi_boot_config, 'a') as config:
            config.write('normal{0}'.format(os.linesep))

    def _create_bios_image(self, mbrid=None, lookup_path=None):
        early_boot_script = os.path.normpath(
            os.sep.join([self._get_grub2_boot_path(), 'earlyboot.cfg'])
        )
        self._create_early_boot_script_for_mbrid_search(
            early_boot_script, mbrid
        )
        Command.run(
            [
                self._get_grub2_mkimage_tool() or 'grub2-mkimage',
                '-O', Defaults.get_bios_module_directory_name(),
                '-o', self._get_bios_image_name(lookup_path),
                '-c', early_boot_script,
                '-p', self.get_boot_path() + '/' + self.boot_directory_name,
                '-d', self._get_bios_modules_path(lookup_path)
            ] + Defaults.get_grub_bios_modules(multiboot=self.xen_guest)
        )

    def _create_early_boot_script_for_uuid_search(self, filename, uuid):
        with open(filename, 'w') as early_boot:
            early_boot.write(
                'set btrfs_relative_path="yes"{0}'.format(os.linesep)
            )
            if self.custom_args.get('boot_is_crypto'):
                early_boot.write(
                    'insmod cryptodisk{0}'.format(os.linesep)
                )
                early_boot.write(
                    'insmod luks{0}'.format(os.linesep)
                )
                early_boot.write(
                    'cryptomount -u {0}{1}'.format(
                        uuid.replace('-', ''), os.linesep
                    )
                )
                early_boot.write(
                    'set root="cryptouuid/{0}"{1}'.format(
                        uuid.replace('-', ''), os.linesep
                    )
                )
            early_boot.write(
                'search --fs-uuid --set=root {0}{1}'.format(uuid, os.linesep)
            )
            early_boot.write(
                'set prefix=($root){0}/{1}{2}'.format(
                    self.get_boot_path(), self.boot_directory_name, os.linesep
                )
            )

    def _create_early_boot_script_for_mbrid_search(self, filename, mbrid):
        with open(filename, 'w') as early_boot:
            early_boot.write(
                'set btrfs_relative_path="yes"{0}'.format(os.linesep)
            )
            early_boot.write(
                'search --file --set=root /boot/{0}{1}'.format(
                    mbrid.get_id(), os.linesep
                )
            )
            early_boot.write(
                'set prefix=($root)/boot/{0}{1}'.format(
                    self.boot_directory_name, os.linesep
                )
            )

    def _get_grub2_mkimage_tool(self):
        for grub_mkimage_tool in ['grub2-mkimage', 'grub-mkimage']:
            if Path.which(grub_mkimage_tool):
                return grub_mkimage_tool

    def _get_grub2_mkconfig_tool(self):
        for grub_mkconfig_tool in ['grub2-mkconfig', 'grub-mkconfig']:
            grub_mkconfig_file_path = Path.which(
                grub_mkconfig_tool, root_dir=self.root_dir
            )
            if grub_mkconfig_file_path:
                return grub_mkconfig_file_path

    def _get_grub2_boot_path(self):
        return self.boot_dir + '/boot/' + self.boot_directory_name

    def _get_efi_image_name(self):
        return os.sep.join(
            [
                self.efi_boot_path,
                Defaults.get_efi_image_name(self.arch)
            ]
        )

    def _get_bios_image_name(self, lookup_path):
        return os.sep.join(
            [
                self._get_bios_modules_path(lookup_path),
                Defaults.get_bios_image_name()
            ]
        )

    def _get_efi_modules_path(self, lookup_path=None):
        return self._get_module_path(
            Defaults.get_efi_module_directory_name(self.arch),
            lookup_path
        )

    def _get_bios_modules_path(self, lookup_path=None):
        return self._get_module_path('i386-pc', lookup_path)

    def _get_xen_modules_path(self, lookup_path=None):
        return self._get_module_path(
            Defaults.get_efi_module_directory_name('x86_64_xen'),
            lookup_path
        )

    def _get_module_path(self, format_name, lookup_path=None):
        if not lookup_path:
            lookup_path = self.boot_dir
        return Defaults.get_grub_path(lookup_path, format_name)

    def _find_theme_background_file(self, lookup_path):
        background_pattern = os.sep.join(
            [
                lookup_path, 'boot', self.boot_directory_name, 'themes',
                '*', 'background.png'
            ]
        )
        for background_file in glob.iglob(background_pattern):
            return background_file

    def _copy_theme_data_to_boot_directory(self, lookup_path, target):
        if not lookup_path:
            lookup_path = self.boot_dir
        font_name = 'unicode.pf2'
        efi_font_dir = Defaults.get_grub_efi_font_directory(
            lookup_path
        )
        boot_fonts_dir = os.path.normpath(
            os.sep.join(
                [
                    self.boot_dir,
                    self.get_boot_path(target),
                    self.boot_directory_name,
                    'fonts'
                ]
            )
        )
        try:
            unicode_font = Defaults.get_grub_path(
                lookup_path, font_name
            )
            if not os.path.exists(os.sep.join([boot_fonts_dir, font_name])):
                Path.create(boot_fonts_dir)
                Command.run(
                    ['cp', unicode_font, boot_fonts_dir]
                )
            if efi_font_dir:
                Command.run(
                    ['cp', unicode_font, efi_font_dir]
                )
        except Exception as issue:
            raise KiwiBootLoaderGrubFontError(
                'Setting up unicode font failed with {0}'.format(issue)
            )

        boot_theme_dir = os.sep.join(
            [self.boot_dir, 'boot', self.boot_directory_name, 'themes']
        )
        Path.create(boot_theme_dir)

        if self.theme:
            theme_dir = Defaults.get_grub_path(
                lookup_path, 'themes/' + self.theme, raise_on_error=False
            )
            boot_theme_background_file = self._find_theme_background_file(
                lookup_path
            )
            if theme_dir and os.path.exists(theme_dir):
                if boot_theme_background_file:
                    # A background file was found. Preserve a copy of the
                    # file which was created at install time of the theme
                    # package by the activate-theme script
                    boot_theme_background_backup_file = os.sep.join(
                        [self.boot_dir, 'background.png']
                    )
                    Command.run(
                        [
                            'cp', boot_theme_background_file,
                            boot_theme_background_backup_file
                        ]
                    )
                # sync theme data from install path to boot path
                data = DataSync(
                    theme_dir, boot_theme_dir
                )
                data.sync_data(
                    options=['-a']
                )
                if boot_theme_background_file:
                    # Install preserved background file to the theme
                    Command.run(
                        [
                            'mv', boot_theme_background_backup_file,
                            os.sep.join([boot_theme_dir, self.theme])
                        ]
                    )
            elif boot_theme_background_file:
                # assume all theme data is in the directory of the
                # background file and just sync that directory to the
                # boot path
                data = DataSync(
                    os.path.dirname(boot_theme_background_file), boot_theme_dir
                )
                data.sync_data(
                    options=['-a']
                )

        self._check_boot_theme_exists()

    def _check_boot_theme_exists(self):
        if self.theme:
            theme_dir = os.sep.join(
                [
                    self.boot_dir, 'boot', self.boot_directory_name,
                    'themes', self.theme
                ]
            )
            if not os.path.exists(theme_dir):
                log.warning('Theme %s not found', theme_dir)
                log.warning('Set bootloader terminal to console mode')
                self.terminal = 'console'

    def _setup_EFI_path(self, lookup_path):
        """
        Copy efi boot data from lookup_path to the root directory
        """
        if not lookup_path:
            lookup_path = self.boot_dir
        efi_path = lookup_path + '/boot/efi/'
        if os.path.exists(efi_path):
            efi_data = DataSync(efi_path, self.boot_dir)
            efi_data.sync_data(options=['-a'])

    def _copy_efi_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_efi_modules_path(lookup_path)
        )

    def _copy_bios_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_bios_modules_path(lookup_path)
        )

    def _copy_xen_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_xen_modules_path(lookup_path)
        )

    def _copy_modules_to_boot_directory_from(self, module_path):
        boot_module_path = \
            self._get_grub2_boot_path() + '/' + os.path.basename(module_path)
        try:
            data = DataSync(
                module_path + '/', boot_module_path
            )
            data.sync_data(
                options=['-a'], exclude=['*.module']
            )
        except Exception as e:
            raise KiwiBootLoaderGrubModulesError(
                'Module synchronisation failed with: %s' % format(e)
            )

    def _get_shim_install(self):
        return Path.which(
            filename='shim-install', root_dir=self.boot_dir
        )
Example #36
0
class TestFirmWare:
    def setup(self):
        Defaults.set_platform_name('x86_64')
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock()
        xml_state.build_type.get_firmware.return_value = 'bios'
        self.firmware_bios = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = None
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi = FirmWare(xml_state)

        xml_state.build_type.get_efiparttable.return_value = 'msdos'
        xml_state.build_type.get_efipartsize.return_value = None
        xml_state.build_type.get_firmware.return_value = 'efi'
        self.firmware_efi_mbr = FirmWare(xml_state)

        xml_state.build_type.get_efipartsize.return_value = 42
        self.firmware_efi_custom_efi_part = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'ec2'
        self.firmware_ec2 = FirmWare(xml_state)

        Defaults.set_platform_name('s390x')
        xml_state.build_type.get_firmware.return_value = None
        xml_state.get_build_type_bootloader_targettype = mock.Mock()

        xml_state.get_build_type_bootloader_targettype.return_value = 'CDL'
        self.firmware_s390_cdl = FirmWare(xml_state)

        xml_state.get_build_type_bootloader_targettype.return_value = 'SCSI'
        self.firmware_s390_scsi = FirmWare(xml_state)

        Defaults.set_platform_name('ppc64le')
        xml_state.build_type.get_firmware.return_value = 'ofw'
        self.firmware_ofw = FirmWare(xml_state)

        xml_state.build_type.get_firmware.return_value = 'opal'
        self.firmware_opal = FirmWare(xml_state)

        Defaults.set_platform_name('x86_64')

    def setup_method(self, cls):
        self.setup()

    def test_firmware_unsupported(self):
        xml_state = mock.Mock()
        xml_state.build_type.get_firmware = mock.Mock(return_value='bogus')
        with raises(KiwiNotImplementedError):
            FirmWare(xml_state)

    def test_get_partition_table_type(self):
        assert self.firmware_bios.get_partition_table_type() == 'msdos'
        assert self.firmware_efi.get_partition_table_type() == 'gpt'
        assert self.firmware_efi_mbr.get_partition_table_type() == 'msdos'
        assert self.firmware_s390_cdl.get_partition_table_type() == 'dasd'
        assert self.firmware_s390_scsi.get_partition_table_type() == 'msdos'

    def test_get_partition_table_type_ppc_ofw_mode(self):
        assert self.firmware_ofw.get_partition_table_type() == 'gpt'

    def test_get_partition_table_type_ppc_opal_mode(self):
        assert self.firmware_opal.get_partition_table_type() == 'gpt'

    def test_legacy_bios_mode(self):
        assert self.firmware_bios.legacy_bios_mode() is False
        assert self.firmware_efi.legacy_bios_mode() is True

    def test_legacy_bios_mode_non_x86_platform(self):
        self.firmware_efi.arch = 'arm64'
        assert self.firmware_efi.legacy_bios_mode() is False

    def test_ec2_mode(self):
        assert self.firmware_ec2.ec2_mode() == 'ec2'
        assert self.firmware_bios.ec2_mode() is None

    def test_efi_mode(self):
        assert self.firmware_bios.efi_mode() is None
        assert self.firmware_efi.efi_mode() == 'efi'

    def test_bios_mode(self):
        assert self.firmware_bios.bios_mode() is True
        assert self.firmware_efi.bios_mode() is False

    def test_ofw_mode(self):
        assert self.firmware_ofw.ofw_mode() is True
        assert self.firmware_bios.ofw_mode() is False

    def test_opal_mode(self):
        assert self.firmware_opal.opal_mode() is True
        assert self.firmware_bios.opal_mode() is False

    def test_get_legacy_bios_partition_size(self):
        assert self.firmware_bios.get_legacy_bios_partition_size() == 0
        assert self.firmware_efi.get_legacy_bios_partition_size() == 2

    def test_get_efi_partition_size(self):
        assert self.firmware_bios.get_efi_partition_size() == 0
        assert self.firmware_efi.get_efi_partition_size() == 20
        assert self.firmware_efi_custom_efi_part.get_efi_partition_size() == 42

    def test_get_prep_partition_size(self):
        assert self.firmware_ofw.get_prep_partition_size() == 8
Example #37
0
class BootLoaderConfigZipl(BootLoaderConfigBase):
    """
    **zipl bootloader configuration.**
    """
    def post_init(self, custom_args):
        """
        zipl post initialization method

        :param dict custom_args:
            Contains zipl config arguments

            .. code:: python

                {'targetbase': 'device_name'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if 's390' in arch:
            self.arch = arch
        else:
            raise KiwiBootLoaderZiplPlatformError(
                'host architecture %s not supported for zipl setup' % arch
            )

        if not custom_args or 'targetbase' not in custom_args:
            raise KiwiBootLoaderZiplSetupError(
                'targetbase device name is required for zipl setup'
            )

        self.bootpath = '.'
        self.timeout = self.get_boot_timeout_seconds()
        self.cmdline = self.get_boot_cmdline()
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options()]
        )
        self.target_blocksize = self.xml_state.build_type.get_target_blocksize()
        if not self.target_blocksize:
            self.target_blocksize = Defaults.get_s390_disk_block_size()

        self.target_type = self.xml_state.build_type.get_zipl_targettype()
        if not self.target_type:
            self.target_type = Defaults.get_s390_disk_type()

        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.target_device = custom_args['targetbase']
        self.firmware = FirmWare(self.xml_state)
        self.target_table_type = self.firmware.get_partition_table_type()

        self.zipl = BootLoaderTemplateZipl()
        self.config = None

    def write(self):
        """
        Write zipl config file
        """
        log.info('Writing zipl config file')
        config_dir = self._get_zipl_boot_path()
        config_file = config_dir + '/config'
        if self.config:
            Path.create(config_dir)
            with open(config_file, 'w') as config:
                config.write(self.config)

            log.info('Moving initrd/kernel to zipl boot directory')
            Command.run(
                [
                    'mv',
                    self.root_dir + '/boot/initrd.vmx',
                    self.root_dir + '/boot/linux.vmx',
                    self._get_zipl_boot_path()
                ]
            )

    def setup_disk_image_config(
        self, boot_uuid=None, root_uuid=None, hypervisor=None,
        kernel='linux.vmx', initrd='initrd.vmx', boot_options=''
    ):
        """
        Create the zipl config in memory from a template suitable to
        boot from a disk image.

        :param string boot_uuid: unused
        :param string root_uuid: unused
        :param string hypervisor: unused
        :param string kernel: kernel name
        :param string initrd: initrd name
        :param string boot_options: kernel options as string
        """
        log.info('Creating zipl config file from template')
        parameters = {
            'device': self.target_device,
            'target_type': self.target_type,
            'blocksize': self.target_blocksize,
            'offset': self._get_target_offset(),
            'geometry': self._get_target_geometry(),
            'default_boot': '1',
            'bootpath': self.bootpath,
            'boot_timeout': self.timeout,
            'title': self.quote_title(self.get_menu_entry_title()),
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': ' '.join([self.cmdline, boot_options]),
            'failsafe_boot_options': ' '.join(
                [self.cmdline_failsafe, boot_options]
            )
        }
        log.info('--> Using standard disk boot template')
        template = self.zipl.get_template(self.failsafe_boot)
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_disk_boot_images(self, boot_uuid, lookup_path=None):
        """
        On s390 no bootloader images needs to be created

        Thus this method does nothing

        :param string boot_uuid: boot device UUID
        :param string lookup_path: custom module lookup path
        """
        pass

    def _get_zipl_boot_path(self):
        return self.root_dir + '/boot/zipl'

    def _get_target_geometry(self):
        if self.target_table_type == 'dasd':
            return '%d,%d,%d' % (
                self._read_dasd_disk_geometry('cylinders'),
                self._read_dasd_disk_geometry('tracks per cylinder'),
                self._read_dasd_disk_geometry('blocks per track')
            )
        else:
            return '%d,%d,%d' % (
                self._read_msdos_disk_geometry('cylinders'),
                self._read_msdos_disk_geometry('tracks per cylinder'),
                self._read_msdos_disk_geometry('blocks per track')
            )

    def _get_target_offset(self):
        if self.target_table_type == 'dasd':
            blocks = self._read_dasd_disk_geometry('blocks per track')
            bash_command = [
                'fdasd', '-f', '-s', '-p', self.target_device,
                '|', 'head', '-n', '1', '|', 'tr', '-s', '" "'
            ]
            fdasd_call = Command.run(
                ['bash', '-c', ' '.join(bash_command)]
            )
            fdasd_output = fdasd_call.output
            try:
                start_track = int(fdasd_output.split(' ')[2].lstrip())
            except Exception:
                raise KiwiDiskGeometryError(
                    'unknown partition format: %s' % fdasd_output
                )
            return start_track * blocks
        else:
            blocks = self._read_msdos_disk_geometry('blocks per track')
            parted_call = Command.run(
                ['parted', '-m', self.target_device, 'unit', 's', 'print']
            )
            parted_output = parted_call.output.lstrip()
            first_partition_format = re.search('1:(.*?)s', parted_output)
            if not first_partition_format:
                raise KiwiDiskGeometryError(
                    'unknown partition format: %s' % parted_output
                )
            start_track = int(first_partition_format.group(1))
            return start_track * blocks

    def _read_msdos_disk_geometry(self, value):
        sfdisk_call = Command.run(
            ['sfdisk', '-g', self.target_device]
        )
        sfdisk_output = sfdisk_call.output.lstrip()
        geometry_format = re.search(
            '/dev.*: (.*) cylinders, (.*) heads, (.*) sectors/track',
            sfdisk_output
        )
        if not geometry_format:
            raise KiwiDiskGeometryError(
                'unknown format for geometry: %s' % sfdisk_output
            )
        result = {
            'cylinders': geometry_format.group(1),
            'tracks per cylinder': geometry_format.group(2),
            'blocks per track': geometry_format.group(3)
        }
        if value in result:
            return int(result[value])

    def _read_dasd_disk_geometry(self, value):
        fdasd = ['fdasd', '-f', '-p', self.target_device]
        bash_command = fdasd + ['|', 'grep', '"' + value + '"']
        fdasd_call = Command.run(
            ['bash', '-c', ' '.join(bash_command)]
        )
        fdasd_output = fdasd_call.output
        try:
            return int(fdasd_output.split(':')[1].lstrip())
        except Exception:
            raise KiwiDiskGeometryError(
                'unknown format for disk geometry: %s' % fdasd_output
            )
Example #38
0
class LiveImageBuilder(object):
    """
    Live image builder

    Attributes

    * :attr:`xml_state`
        Instance of XMLState

    * :attr:`target_dir`
        target directory path name

    * :attr:`root_dir`
        root directory path name

    * :attr:`custom_args`
        Custom processing arguments defined as hash keys:
        * signing_keys: list of package signing keys
        * xz_options: string of XZ compression parameters
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.types = Defaults.get_live_iso_types()
        self.hybrid = xml_state.build_type.get_hybrid()
        self.volume_id = xml_state.build_type.get_volid()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.filesystem_custom_parameters = {
            'mount_options': xml_state.get_fs_mount_option_list()
        }

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        boot_signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            boot_signing_keys = custom_args['signing_keys']

        self.boot_image_task = BootImage(xml_state,
                                         target_dir,
                                         signing_keys=boot_signing_keys,
                                         custom_args=custom_args)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + platform.machine(),
            '-' + xml_state.get_image_version(), '.iso'
        ])
        self.live_image_file = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '-read-only.', self.arch, '-',
            xml_state.get_image_version()
        ])
        self.result = Result(xml_state)

    def create(self):
        """
        Build a bootable hybrid live ISO image

        Image types which triggers this builder are:

        * image="iso"
        """
        # media dir to store CD contents
        self.media_dir = mkdtemp(prefix='live-media.', dir=self.target_dir)
        rootsize = SystemSize(self.media_dir)

        # custom iso metadata
        log.info('Using following live ISO metadata:')
        log.info('--> Application id: %s', self.mbrid.get_id())
        log.info('--> Publisher: %s', Defaults.get_publisher())
        custom_iso_args = {
            'create_options': [
                '-A',
                self.mbrid.get_id(), '-p', '"' + Defaults.get_preparer() + '"',
                '-publisher', '"' + Defaults.get_publisher() + '"'
            ]
        }
        if self.volume_id:
            log.info('--> Volume id: %s', self.volume_id)
            custom_iso_args['create_options'].append('-V')
            custom_iso_args['create_options'].append('"' + self.volume_id +
                                                     '"')

        # prepare boot(initrd) root system
        log.info('Preparing live ISO boot system')
        self.boot_image_task.prepare()

        # export modprobe configuration to boot image
        self.system_setup.export_modprobe_setup(
            self.boot_image_task.boot_root_directory)

        # pack system into live boot structure
        log.info('Packing system into live ISO type: %s', self.live_type)
        if self.live_type in self.types:
            live_type_image = FileSystem(
                name=self.types[self.live_type],
                device_provider=None,
                root_dir=self.root_dir,
                custom_args=self.filesystem_custom_parameters)
            live_type_image.create_on_file(
                self.live_image_file,
                exclude=Defaults.get_exclude_list_for_root_data_sync())
            Command.run(['mv', self.live_image_file, self.media_dir])
            self._create_live_iso_client_config(self.live_type)
        else:
            raise KiwiLiveBootImageError('live ISO type "%s" not supported' %
                                         self.live_type)

        # setup bootloader config to boot the ISO via isolinux
        log.info('Setting up isolinux bootloader configuration')
        bootloader_config_isolinux = BootLoaderConfig('isolinux',
                                                      self.xml_state,
                                                      self.media_dir)
        bootloader_config_isolinux.setup_live_boot_images(
            mbrid=None, lookup_path=self.boot_image_task.boot_root_directory)
        bootloader_config_isolinux.setup_live_image_config(mbrid=None)
        bootloader_config_isolinux.write()
        self.system_setup.call_edit_boot_config_script(
            filesystem=self.types[self.live_type],
            boot_part_id=1,
            working_directory=self.media_dir)

        # setup bootloader config to boot the ISO via EFI
        if self.firmware.efi_mode():
            log.info('Setting up EFI grub bootloader configuration')
            bootloader_config_grub = BootLoaderConfig(
                'grub2', self.xml_state, self.media_dir, {
                    'grub_directory_name':
                    Defaults.get_grub_boot_directory_name(self.root_dir)
                })
            bootloader_config_grub.setup_live_boot_images(
                mbrid=self.mbrid, lookup_path=self.root_dir)
            bootloader_config_grub.setup_live_image_config(mbrid=self.mbrid)
            bootloader_config_grub.write()

        # create initrd for live image
        log.info('Creating live ISO boot image')
        self._create_live_iso_kernel_and_initrd()

        # calculate size and decide if we need UDF
        if rootsize.accumulate_mbyte_file_sizes() > 4096:
            log.info('ISO exceeds 4G size, using UDF filesystem')
            custom_iso_args['create_options'].append('-iso-level')
            custom_iso_args['create_options'].append('3')
            custom_iso_args['create_options'].append('-udf')

        # create iso filesystem from media_dir
        log.info('Creating live ISO image')
        iso_image = FileSystemIsoFs(device_provider=None,
                                    root_dir=self.media_dir,
                                    custom_args=custom_iso_args)
        iso_header_offset = iso_image.create_on_file(self.isoname)

        # make it hybrid
        if self.hybrid:
            Iso.create_hybrid(iso_header_offset, self.mbrid, self.isoname)

        # include metadata for checkmedia tool
        if self.xml_state.build_type.get_mediacheck() is True:
            Iso.set_media_tag(self.isoname)

        self.result.add(key='live_image',
                        filename=self.isoname,
                        use_for_bundle=True,
                        compress=False,
                        shasum=True)
        self.result.add(key='image_packages',
                        filename=self.system_setup.export_package_list(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        self.result.add(key='image_verified',
                        filename=self.system_setup.export_package_verification(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        return self.result

    def _create_live_iso_kernel_and_initrd(self):
        boot_path = self.media_dir + '/boot/' + self.arch + '/loader'
        Path.create(boot_path)
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiLiveBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory)
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiLiveBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory)
        self.boot_image_task.create_initrd(self.mbrid)
        Command.run([
            'mv', self.boot_image_task.initrd_filename, boot_path + '/initrd'
        ])

    def _create_live_iso_client_config(self, iso_type):
        """
            Setup IMAGE and UNIONFS_CONFIG variables as they are used in
            the kiwi isoboot code. Variable contents:

            + IMAGE=target_device;live_iso_name_definition
            + UNIONFS_CONFIG=rw_device,ro_device,union_type

            If no real block device is used or can be predefined the
            word 'loop' is set as a placeholder or indicator to use a loop
            device. For more details please refer to the kiwi shell boot
            code
        """
        iso_client_config_file = self.media_dir + '/config.isoclient'
        iso_client_params = Defaults.get_live_iso_client_parameters()
        (system_device, union_device, union_type) = iso_client_params[iso_type]

        with open(iso_client_config_file, 'w') as config:
            config.write('IMAGE="%s;%s.%s;%s"\n' %
                         (system_device, self.xml_state.xml_data.get_name(),
                          self.arch, self.xml_state.get_image_version()))
            config.write('UNIONFS_CONFIG="%s,loop,%s"\n' %
                         (union_device, union_type))

    def __del__(self):
        if self.media_dir:
            log.info('Cleaning up %s instance', type(self).__name__)
            Path.wipe(self.media_dir)
Example #39
0
class InstallImageBuilder(object):
    """
    **Installation image builder**

    :param XMLState xml_state: instance of :class:`XMLState`
    :param str root_dir: system image root directory
    :param str target_dir: target directory path name
    :param BootImage boot_image_task: instance of :class:`BootImage`
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * xz_options: string of XZ compression parameters
    """
    def __init__(self,
                 xml_state,
                 root_dir,
                 target_dir,
                 boot_image_task,
                 custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.boot_image_task = boot_image_task
        self.xml_state = xml_state
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.setup = SystemSetup(self.xml_state, self.root_dir)
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.iso'
        ])
        self.pxename = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.install.tar.xz'
        ])
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()])
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw'])
        self.md5name = ''.join([xml_state.xml_data.get_name(), '.md5'])
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir = None
        self.pxe_dir = None
        self.squashed_contents = None
        self.custom_iso_args = None

    def create_install_iso(self):
        """
        Create an install ISO from the disk_image as hybrid ISO
        bootable via legacy BIOS, EFI and as disk from Stick

        Image types which triggers this builder are:

        * installiso="true|false"
        * installstick="true|false"
        """
        self.media_dir = mkdtemp(prefix='kiwi_install_media.',
                                 dir=self.target_dir)
        # unpack cdroot user files to media dir
        self.setup.import_cdroot_files(self.media_dir)

        # custom iso metadata
        self.custom_iso_args = {
            'meta_data': {
                'volume_id': Defaults.get_install_volume_id(),
                'mbr_id': self.mbrid.get_id(),
                'efi_mode': self.firmware.efi_mode()
            }
        }

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        self.squashed_contents = mkdtemp(prefix='kiwi_install_squashfs.',
                                         dir=self.target_dir)
        checksum = Checksum(self.diskname)
        checksum.md5(self.squashed_contents + '/' + self.md5name)

        # the system image name is stored in a config file
        self._write_install_image_info_to_iso_image()
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the system image is stored as squashfs embedded file
        log.info('Creating squashfs embedded disk image')
        Command.run([
            'cp', '-l', self.diskname,
            self.squashed_contents + '/' + self.squashed_diskname
        ])
        squashed_image_file = ''.join(
            [self.target_dir, '/', self.squashed_diskname, '.squashfs'])
        squashed_image = FileSystemSquashFs(device_provider=None,
                                            root_dir=self.squashed_contents)
        squashed_image.create_on_file(squashed_image_file)
        Command.run(['mv', squashed_image_file, self.media_dir])

        # setup bootloader config to boot the ISO via isolinux
        log.info('Setting up install image bootloader configuration')
        bootloader_config_isolinux = BootLoaderConfig('isolinux',
                                                      self.xml_state,
                                                      self.media_dir)
        bootloader_config_isolinux.setup_install_boot_images(
            mbrid=None, lookup_path=self.boot_image_task.boot_root_directory)
        bootloader_config_isolinux.setup_install_image_config(mbrid=None)
        bootloader_config_isolinux.write()

        # setup bootloader config to boot the ISO via EFI
        bootloader_config_grub = BootLoaderConfig(
            'grub2', self.xml_state, self.media_dir, {
                'grub_directory_name':
                Defaults.get_grub_boot_directory_name(self.root_dir)
            })
        bootloader_config_grub.setup_install_boot_images(
            mbrid=self.mbrid, lookup_path=self.root_dir)
        bootloader_config_grub.setup_install_image_config(mbrid=self.mbrid)
        bootloader_config_grub.write()

        # create initrd for install image
        log.info('Creating install image boot image')
        self._create_iso_install_kernel_and_initrd()

        # the system image initrd is stored to allow kexec
        self._copy_system_image_initrd_to_iso_image()

        # create iso filesystem from media_dir
        log.info('Creating ISO filesystem')
        iso_image = FileSystemIsoFs(device_provider=None,
                                    root_dir=self.media_dir,
                                    custom_args=self.custom_iso_args)
        iso_image.create_on_file(self.isoname)

    def create_install_pxe_archive(self):
        """
        Create an oem install tar archive suitable for installing a
        disk image via the network using the PXE boot protocol.
        The archive contains:

        * The raw system image xz compressed
        * The raw system image checksum metadata file
        * The append file template for the boot server
        * The system image initrd for kexec
        * The install initrd
        * The kernel

        Image types which triggers this builder are:

        * installpxe="true|false"
        """
        self.pxe_dir = mkdtemp(prefix='kiwi_pxe_install_media.',
                               dir=self.target_dir)
        # the system image is transfered as xz compressed variant
        log.info('xz compressing disk image')
        pxe_image_filename = ''.join(
            [self.pxe_dir, '/',
             self.xml_state.xml_data.get_name(), '.xz'])
        compress = Compress(source_filename=self.diskname,
                            keep_source_on_compress=True)
        compress.xz(self.xz_options)
        Command.run(['mv', compress.compressed_filename, pxe_image_filename])

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        pxe_md5_filename = ''.join(
            [self.pxe_dir, '/',
             self.xml_state.xml_data.get_name(), '.md5'])
        checksum = Checksum(self.diskname)
        checksum.md5(pxe_md5_filename)

        # the install image name is stored in a config file
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the kexec required system image initrd is stored for dracut kiwi-dump
        if self.initrd_system == 'dracut':
            boot_names = self.boot_image_task.get_boot_names()
            system_image_initrd = os.sep.join(
                [self.root_dir, 'boot', boot_names.initrd_name])
            target_initrd_name = '{0}/{1}.initrd'.format(
                self.pxe_dir, self.xml_state.xml_data.get_name())
            shutil.copy(system_image_initrd, target_initrd_name)
            os.chmod(target_initrd_name, 420)

        # create pxe config append information
        # this information helps to configure the boot server correctly
        append_filename = ''.join(
            [self.pxe_dir, '/',
             self.xml_state.xml_data.get_name(), '.append'])
        if self.initrd_system == 'kiwi':
            cmdline = 'pxe=1'
        else:
            cmdline = ' '.join([
                'rd.kiwi.install.pxe',
                'rd.kiwi.install.image=http://example.com/image.xz'
            ])
        custom_cmdline = self.xml_state.build_type.get_kernelcmdline()
        if custom_cmdline:
            cmdline += ' ' + custom_cmdline
        with open(append_filename, 'w') as append:
            append.write('%s\n' % cmdline)

        # create initrd for pxe install
        log.info('Creating pxe install boot image')
        self._create_pxe_install_kernel_and_initrd()

        # create pxe install tarball
        log.info('Creating pxe install archive')
        archive = ArchiveTar(self.pxename.replace('.xz', ''))
        archive.create_xz_compressed(self.pxe_dir, xz_options=self.xz_options)

    def _create_pxe_install_kernel_and_initrd(self):
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(self.pxe_dir, '/pxeboot.kernel')
            os.symlink(
                'pxeboot.kernel', ''.join([
                    self.pxe_dir, '/',
                    self.xml_state.xml_data.get_name(), '.kernel'
                ]))
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory)
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(self.pxe_dir, '/pxeboot.xen.gz')
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory)
        if self.initrd_system == 'dracut':
            self._create_dracut_install_config()
            self._add_system_image_boot_options_to_boot_image()
        self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install')
        Command.run([
            'mv', self.boot_image_task.initrd_filename,
            self.pxe_dir + '/pxeboot.initrd.xz'
        ])
        os.chmod(self.pxe_dir + '/pxeboot.initrd.xz', 420)

    def _create_iso_install_kernel_and_initrd(self):
        boot_path = self.media_dir + '/boot/' + self.arch + '/loader'
        Path.create(boot_path)
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory)
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory)
        if self.initrd_system == 'dracut':
            self._create_dracut_install_config()
            self._add_system_image_boot_options_to_boot_image()
        self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install')
        Command.run([
            'mv', self.boot_image_task.initrd_filename, boot_path + '/initrd'
        ])

    def _add_system_image_boot_options_to_boot_image(self):
        filename = ''.join(
            [self.boot_image_task.boot_root_directory, '/config.bootoptions'])
        self.boot_image_task.include_file(os.sep + os.path.basename(filename))

    def _copy_system_image_initrd_to_iso_image(self):
        boot_names = self.boot_image_task.get_boot_names()
        system_image_initrd = os.sep.join(
            [self.root_dir, 'boot', boot_names.initrd_name])
        shutil.copy(system_image_initrd,
                    self.media_dir + '/initrd.system_image')

    def _write_install_image_info_to_iso_image(self):
        iso_trigger = self.media_dir + '/config.isoclient'
        with open(iso_trigger, 'w') as iso_system:
            iso_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def _write_install_image_info_to_boot_image(self):
        initrd_trigger = \
            self.boot_image_task.boot_root_directory + '/config.vmxsystem'
        with open(initrd_trigger, 'w') as vmx_system:
            vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def _create_dracut_install_config(self):
        dracut_config = ['hostonly="no"', 'dracut_rescue_image="no"']
        dracut_modules = ['kiwi-lib', 'kiwi-dump']
        dracut_modules_omit = ['kiwi-overlay', 'kiwi-live', 'kiwi-repart']
        if self.root_filesystem_is_multipath is False:
            dracut_modules_omit.append('multipath')
        dracut_config.append('add_dracutmodules+=" {0} "'.format(
            ' '.join(dracut_modules)))
        dracut_config.append('omit_dracutmodules+=" {0} "'.format(
            ' '.join(dracut_modules_omit)))
        with open(self.dracut_config_file, 'w') as config:
            for entry in dracut_config:
                config.write(entry + os.linesep)

    def _delete_dracut_install_config(self):
        if os.path.exists(self.dracut_config_file):
            os.remove(self.dracut_config_file)

    def __del__(self):
        log.info('Cleaning up %s instance', type(self).__name__)
        if self.initrd_system == 'dracut':
            self._delete_dracut_install_config()
        if self.media_dir:
            Path.wipe(self.media_dir)
        if self.pxe_dir:
            Path.wipe(self.pxe_dir)
        if self.squashed_contents:
            Path.wipe(self.squashed_contents)
Example #40
0
class DiskSetup:
    """
    **Implements disk setup methods**

    Methods from this class provides information required
    before building a disk image

    :param object xml_state: Instance of XMLState
    :param string root_dir: root directory path name
    """
    def __init__(self, xml_state: XMLState, root_dir: str):
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot(
        )
        self.swap_mbytes = xml_state.get_oemconfig_swap_mbytes()
        self.configured_size = xml_state.get_build_type_size()
        self.disk_resize_requested = xml_state.get_oemconfig_oem_resize()
        self.filesystem = xml_state.build_type.get_filesystem()
        self.bootpart_requested = xml_state.build_type.get_bootpartition()
        self.bootpart_mbytes = xml_state.build_type.get_bootpartsize()
        self.spare_part_mbytes = xml_state.get_build_type_spare_part_size()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.luks = xml_state.build_type.get_luks()
        self.volume_manager = xml_state.get_volume_management()
        self.bootloader = xml_state.get_build_type_bootloader_name()
        self.oemconfig = xml_state.get_build_type_oemconfig_section()
        self.volumes = xml_state.get_volumes()
        self.custom_partitions = xml_state.get_partitions()

        self.firmware = FirmWare(xml_state)
        self.rootsize = SystemSize(root_dir)

        self.root_dir = root_dir
        self.xml_state = xml_state

    def get_disksize_mbytes(self) -> int:
        """
        Precalculate disk size requirements in mbytes

        :return: disk size mbytes

        :rtype: int
        """
        log.info('Precalculating required disk size')
        calculated_disk_mbytes = 0
        root_filesystem_mbytes = self.rootsize.customize(
            self.rootsize.accumulate_mbyte_file_sizes(), self.filesystem)
        calculated_disk_mbytes += root_filesystem_mbytes
        log.info('--> system data with filesystem overhead needs %s MB',
                 root_filesystem_mbytes)
        if self.custom_partitions:
            partition_mbytes = self._accumulate_partitions_size()
            if partition_mbytes:
                calculated_disk_mbytes += partition_mbytes
                log.info('--> partition(s) size setup adding %s MB',
                         partition_mbytes)
        if self.volume_manager and self.volume_manager == 'lvm':
            lvm_overhead_mbytes = Defaults.get_lvm_overhead_mbytes()
            log.info('--> LVM overhead adding %s MB', lvm_overhead_mbytes)
            calculated_disk_mbytes += lvm_overhead_mbytes
            volume_mbytes = self._accumulate_volume_size(
                root_filesystem_mbytes)
            if volume_mbytes:
                calculated_disk_mbytes += volume_mbytes
                log.info('--> volume(s) size setup adding %s MB',
                         volume_mbytes)
        elif self.swap_mbytes:
            calculated_disk_mbytes += self.swap_mbytes
            log.info('--> swap partition adding %s MB', self.swap_mbytes)

        legacy_bios_mbytes = self.firmware.get_legacy_bios_partition_size()
        if legacy_bios_mbytes:
            calculated_disk_mbytes += legacy_bios_mbytes
            log.info('--> legacy bios boot partition adding %s MB',
                     legacy_bios_mbytes)

        boot_mbytes = self.boot_partition_size()
        if boot_mbytes:
            calculated_disk_mbytes += boot_mbytes
            log.info('--> boot partition adding %s MB', boot_mbytes)

        if self.spare_part_mbytes:
            calculated_disk_mbytes += self.spare_part_mbytes
            log.info('--> spare partition adding %s MB',
                     self.spare_part_mbytes)

        efi_mbytes = self.firmware.get_efi_partition_size()
        if efi_mbytes:
            calculated_disk_mbytes += efi_mbytes
            log.info('--> EFI partition adding %s MB', efi_mbytes)

        prep_mbytes = self.firmware.get_prep_partition_size()
        if prep_mbytes:
            calculated_disk_mbytes += prep_mbytes
            log.info('--> PReP partition adding %s MB', prep_mbytes)

        recovery_mbytes = self._inplace_recovery_partition_size()
        if recovery_mbytes:
            calculated_disk_mbytes += recovery_mbytes
            log.info('--> In-place recovery partition adding: %s MB',
                     recovery_mbytes)

        if not self.configured_size:
            log.info('Using calculated disk size: %d MB',
                     calculated_disk_mbytes)
            return calculated_disk_mbytes
        elif self.configured_size.additive:
            result_disk_mbytes = \
                self.configured_size.mbytes + calculated_disk_mbytes
            log.info(
                'Using configured disk size: %d MB + %d MB calculated = %d MB',
                self.configured_size.mbytes, calculated_disk_mbytes,
                result_disk_mbytes)
            return result_disk_mbytes
        else:
            log.info('Using configured disk size: %d MB',
                     self.configured_size.mbytes)
            if self.configured_size.mbytes < calculated_disk_mbytes:
                log.warning(
                    '--> Configured size smaller than calculated size: %d MB',
                    calculated_disk_mbytes)
            return self.configured_size.mbytes

    def need_boot_partition(self):
        """
        Decide if an extra boot partition is needed. This is done with
        the bootpartition attribute from the type, however if it is not
        set it depends on some other type configuration parameters if
        we need a boot partition or not

        :return: True or False

        :rtype: bool
        """
        if self.bootpart_requested is True:
            return True
        if self.bootpart_requested is False:
            return False
        if self.mdraid:
            return True
        if self.volume_manager == 'lvm':
            return True
        if self.volume_manager == 'btrfs':
            return False
        if self.root_filesystem_is_overlay:
            return True
        return False

    @staticmethod
    def get_boot_label() -> str:
        """
        Filesystem Label to use for the boot partition

        :return: label name

        :rtype: str
        """
        return 'BOOT'

    def get_root_label(self) -> str:
        """
        Filesystem Label to use for the root partition

        If not specified in the XML configuration the default
        root label is set to 'ROOT'

        :return: label name

        :rtype: str
        """
        root_label = self.xml_state.build_type.get_rootfs_label()
        if not root_label:
            root_label = 'ROOT'
        return root_label

    @staticmethod
    def get_efi_label() -> str:
        """
        Filesystem Label to use for the EFI partition

        :return: label name

        :rtype: str
        """
        return 'EFI'

    def boot_partition_size(self) -> int:
        """
        Size of the boot partition in mbytes

        :return: boot size mbytes

        :rtype: int
        """
        if self.need_boot_partition():
            if self.bootpart_mbytes:
                return self.bootpart_mbytes
            return Defaults.get_default_boot_mbytes()
        return 0

    def _inplace_recovery_partition_size(self):
        """
        In inplace recovery mode the recovery archive is created at
        install time. This requires free space on the disk. The
        amount of free space is specified with the oem-recovery-part-size
        attribute. If specified we add the given size to the disk.
        If not specified an inplace setup at install time will be
        moved to the first boot of an oem image when the recovery
        partition has been created
        """
        if self.oemconfig and self.oemconfig.get_oem_inplace_recovery():
            recovery_mbytes = self.oemconfig.get_oem_recovery_part_size()
            if recovery_mbytes:
                return int(recovery_mbytes[0] * 1.7)

    def _accumulate_partitions_size(self):
        """
        Calculate number of mbytes to add to the disk to allow
        the creaton of the partitions with their configured size
        """
        disk_partition_mbytes = 0
        data_partition_mbytes = self._calculate_partition_mbytes()
        for map_name in sorted(self.custom_partitions.keys()):
            partition_mount_path = self.custom_partitions[map_name].mountpoint
            partition_mbsize = self.custom_partitions[map_name].mbsize
            disk_add_mbytes = int(partition_mbsize) - \
                data_partition_mbytes.partition[partition_mount_path]
            if disk_add_mbytes > 0:
                disk_partition_mbytes += disk_add_mbytes
            else:
                message = dedent('''\n
                    Requested partition size {0}MB for {1!r} is too small

                    The minimum byte value to store the data below
                    the {1!r} path was calculated to be {2}MB
                ''')
                raise KiwiPartitionTooSmallError(
                    message.format(
                        partition_mbsize, partition_mount_path,
                        data_partition_mbytes.partition[partition_mount_path]))
        return disk_partition_mbytes

    def _accumulate_volume_size(self, root_mbytes):
        """
        Calculate number of mbytes to add to the disk to allow
        the creaton of the volumes with their configured size
        """
        disk_volume_mbytes = 0

        data_volume_mbytes = self._calculate_volume_mbytes()
        root_volume = self._get_root_volume_configuration()

        # If disk resize is requested we only add the default min
        # volume size because their target size request is handled
        # on first boot of the disk image in oemboot/repart
        if self.disk_resize_requested:
            for volume in self.volumes:
                disk_volume_mbytes += Defaults.get_min_volume_mbytes()
            return disk_volume_mbytes

        # For static disk(no resize requested) we need to add the
        # configured volume sizes because the image is used directly
        # as it is without being deployed and resized on a target disk
        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/' and volume.size:
                [size_type, req_size] = volume.size.split(':')
                disk_add_mbytes = 0
                if size_type == 'freespace':
                    disk_add_mbytes += int(req_size)
                else:
                    disk_add_mbytes += int(req_size) - \
                        data_volume_mbytes.volume[volume.realpath]
                if disk_add_mbytes > 0:
                    disk_volume_mbytes += disk_add_mbytes + \
                        Defaults.get_min_volume_mbytes()
                else:
                    message = dedent('''\n
                        Requested volume size {0}MB for {1!r} is too small

                        The minimum byte value to store the data below
                        the {1!r} path was calculated to be {2}MB
                    ''')
                    raise KiwiVolumeTooSmallError(
                        message.format(
                            req_size, volume.realpath,
                            data_volume_mbytes.volume[volume.realpath]))

        if root_volume:
            if root_volume.size_type == 'freespace':
                disk_add_mbytes = root_volume.req_size
            else:
                disk_add_mbytes = root_volume.req_size - \
                    root_mbytes + data_volume_mbytes.total

            if disk_add_mbytes > 0:
                disk_volume_mbytes += disk_add_mbytes + \
                    Defaults.get_min_volume_mbytes()
            else:
                log.warning('root volume size of %s MB is too small, skipped',
                            root_volume.req_size)

        return disk_volume_mbytes

    def _get_root_volume_configuration(self):
        """
        Provide root volume configuration if present and in
        use according to the selected volume management. So far
        this only affects the LVM volume manager
        """
        root_volume_type = namedtuple('root_volume_type',
                                      ['size_type', 'req_size'])
        for volume in self.volumes:
            if volume.is_root_volume:
                if volume.size:
                    [size_type, req_size] = volume.size.split(':')
                    return root_volume_type(size_type=size_type,
                                            req_size=int(req_size))

    def _calculate_volume_mbytes(self):
        """
        Calculate the number of mbytes each volume path currently
        consumes and also provide a total number of these values.
        """
        volume_mbytes_type = namedtuple('volume_mbytes_type',
                                        ['volume', 'total'])
        volume_mbytes = {}
        volume_total = 0
        for volume in self.volumes:
            if volume.realpath and not volume.realpath == '/':
                path_to_volume = self.root_dir + '/' + volume.realpath
                if os.path.exists(path_to_volume):
                    volume_size = SystemSize(path_to_volume)
                    volume_mbytes[volume.realpath] = volume_size.customize(
                        volume_size.accumulate_mbyte_file_sizes(),
                        self.filesystem)
                else:
                    volume_mbytes[volume.realpath] = 0
                volume_total += volume_mbytes[volume.realpath]

        return volume_mbytes_type(volume=volume_mbytes, total=volume_total)

    def _calculate_partition_mbytes(self):
        """
        Calculate the number of mbytes each partition path consumes
        """
        partition_mbytes_type = namedtuple('partition_mbytes_type',
                                           ['partition'])
        partition_mbytes = {}
        for map_name in sorted(self.custom_partitions.keys()):
            partition_mount_path = self.custom_partitions[map_name].mountpoint
            partition_filesystem = self.custom_partitions[map_name].filesystem
            path_to_partition = os.path.normpath(
                os.sep.join([self.root_dir, partition_mount_path]))
            if os.path.exists(path_to_partition):
                partition_size = SystemSize(path_to_partition)
                partition_mbytes[
                    partition_mount_path] = partition_size.customize(
                        partition_size.accumulate_mbyte_file_sizes(),
                        partition_filesystem)
            else:
                partition_mbytes[partition_mount_path] = 0
        return partition_mbytes_type(partition=partition_mbytes)
Example #41
0
 def setup(self, mock_platform):
     mock_platform.return_value = 'x86_64'
     xml_state = mock.Mock()
     xml_state.build_type.get_firmware = mock.Mock()
     xml_state.build_type.get_firmware.return_value = 'bios'
     self.firmware_bios = FirmWare(xml_state)
     xml_state.build_type.get_firmware.return_value = 'efi'
     self.firmware_efi = FirmWare(xml_state)
     xml_state.build_type.get_firmware.return_value = 'ec2'
     self.firmware_ec2 = FirmWare(xml_state)
     mock_platform.return_value = 's390x'
     xml_state.build_type.get_firmware.return_value = None
     xml_state.build_type.get_zipl_targettype = mock.Mock()
     xml_state.build_type.get_zipl_targettype.return_value = 'LDL'
     self.firmware_s390_ldl = FirmWare(xml_state)
     xml_state.build_type.get_zipl_targettype.return_value = 'CDL'
     self.firmware_s390_cdl = FirmWare(xml_state)
     xml_state.build_type.get_zipl_targettype.return_value = 'SCSI'
     self.firmware_s390_scsi = FirmWare(xml_state)
Example #42
0
File: live.py Project: isbm/kiwi
class LiveImageBuilder:
    """
    **Live image builder**

    :param object xml_state: instance of :class:`XMLState`
    :param str target_dir: target directory path name
    :param str root_dir: root directory path name
    :param dict custom_args: Custom processing arguments
    """
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.media_dir = None
        self.live_container_dir = None
        self.arch = Defaults.get_platform_name()
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.live_type = xml_state.build_type.get_flags()
        self.volume_id = xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()
        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()
        self.publisher = xml_state.build_type.get_publisher() or \
            Defaults.get_publisher()
        self.custom_args = custom_args

        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.boot_image = BootImageDracut(xml_state, target_dir, self.root_dir)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.isoname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(),
            '-' + xml_state.get_image_version(), '.iso'
        ])
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self):
        """
        Build a bootable hybrid live ISO image

        Image types which triggers this builder are:

        * image="iso"

        :raises KiwiLiveBootImageError: if no kernel or hipervisor is found
            in boot image tree
        :return: result

        :rtype: instance of :class:`Result`
        """
        # media dir to store CD contents
        self.media_dir = mkdtemp(prefix='live-media.', dir=self.target_dir)

        # unpack cdroot user files to media dir
        self.system_setup.import_cdroot_files(self.media_dir)

        rootsize = SystemSize(self.media_dir)

        # custom iso metadata
        log.info('Using following live ISO metadata:')
        log.info('--> Application id: {0}'.format(self.mbrid.get_id()))
        log.info('--> Publisher: {0}'.format(Defaults.get_publisher()))
        log.info('--> Volume id: {0}'.format(self.volume_id))
        custom_iso_args = {
            'meta_data': {
                'publisher': self.publisher,
                'preparer': Defaults.get_preparer(),
                'volume_id': self.volume_id,
                'mbr_id': self.mbrid.get_id(),
                'efi_mode': self.firmware.efi_mode()
            }
        }

        log.info('Setting up live image bootloader configuration')
        if self.firmware.efi_mode():
            # setup bootloader config to boot the ISO via EFI
            # This also embedds an MBR and the respective BIOS modules
            # for compat boot. The complete bootloader setup will be
            # based on grub
            bootloader_config = BootLoaderConfig(
                'grub2',
                self.xml_state,
                root_dir=self.root_dir,
                boot_dir=self.media_dir,
                custom_args={
                    'grub_directory_name':
                    Defaults.get_grub_boot_directory_name(self.root_dir)
                })
            bootloader_config.setup_live_boot_images(mbrid=self.mbrid,
                                                     lookup_path=self.root_dir)
        else:
            # setup bootloader config to boot the ISO via isolinux.
            # This allows for booting on x86 platforms in BIOS mode
            # only.
            bootloader_config = BootLoaderConfig('isolinux',
                                                 self.xml_state,
                                                 root_dir=self.root_dir,
                                                 boot_dir=self.media_dir)
        IsoToolsBase.setup_media_loader_directory(
            self.boot_image.boot_root_directory, self.media_dir,
            bootloader_config.get_boot_theme())
        bootloader_config.write_meta_data()
        bootloader_config.setup_live_image_config(mbrid=self.mbrid)
        bootloader_config.write()

        # call custom editbootconfig script if present
        self.system_setup.call_edit_boot_config_script(
            filesystem='iso:{0}'.format(self.media_dir),
            boot_part_id=1,
            working_directory=self.root_dir)

        # prepare dracut initrd call
        self.boot_image.prepare()

        # create dracut initrd for live image
        log.info('Creating live ISO boot image')
        live_dracut_modules = Defaults.get_live_dracut_modules_from_flag(
            self.live_type)
        live_dracut_modules.append('pollcdrom')
        for dracut_module in live_dracut_modules:
            self.boot_image.include_module(dracut_module)
        self.boot_image.omit_module('multipath')
        self.boot_image.write_system_config_file(
            config={
                'modules': live_dracut_modules,
                'omit_modules': ['multipath']
            },
            config_file=self.root_dir + '/etc/dracut.conf.d/02-livecd.conf')
        self.boot_image.create_initrd(self.mbrid)

        # setup kernel file(s) and initrd in ISO boot layout
        log.info('Setting up kernel file(s) and boot image in ISO boot layout')
        self._setup_live_iso_kernel_and_initrd()

        # calculate size and decide if we need UDF
        if rootsize.accumulate_mbyte_file_sizes() > 4096:
            log.info('ISO exceeds 4G size, using UDF filesystem')
            custom_iso_args['meta_data']['udf'] = True

        # pack system into live boot structure as expected by dracut
        log.info('Packing system into dracut live ISO type: {0}'.format(
            self.live_type))
        root_filesystem = Defaults.get_default_live_iso_root_filesystem()
        filesystem_custom_parameters = {
            'mount_options': self.xml_state.get_fs_mount_option_list(),
            'create_options': self.xml_state.get_fs_create_option_list()
        }
        filesystem_setup = FileSystemSetup(self.xml_state, self.root_dir)
        root_image = NamedTemporaryFile()
        loop_provider = LoopDevice(
            root_image.name, filesystem_setup.get_size_mbytes(root_filesystem),
            self.xml_state.build_type.get_target_blocksize())
        loop_provider.create()
        live_filesystem = FileSystem(name=root_filesystem,
                                     device_provider=loop_provider,
                                     root_dir=self.root_dir + os.sep,
                                     custom_args=filesystem_custom_parameters)
        live_filesystem.create_on_device()
        log.info('--> Syncing data to {0} root image'.format(root_filesystem))
        live_filesystem.sync_data(
            Defaults.get_exclude_list_for_root_data_sync())
        log.info('--> Creating squashfs container for root image')
        self.live_container_dir = mkdtemp(prefix='live-container.',
                                          dir=self.target_dir)
        Path.create(self.live_container_dir + '/LiveOS')
        shutil.copy(root_image.name,
                    self.live_container_dir + '/LiveOS/rootfs.img')
        live_container_image = FileSystem(
            name='squashfs',
            device_provider=None,
            root_dir=self.live_container_dir,
            custom_args={
                'compression':
                self.xml_state.build_type.get_squashfscompression()
            })
        container_image = NamedTemporaryFile()
        live_container_image.create_on_file(container_image.name)
        Path.create(self.media_dir + '/LiveOS')
        shutil.copy(container_image.name,
                    self.media_dir + '/LiveOS/squashfs.img')

        # create iso filesystem from media_dir
        log.info('Creating live ISO image')
        iso_image = FileSystemIsoFs(device_provider=None,
                                    root_dir=self.media_dir,
                                    custom_args=custom_iso_args)
        iso_image.create_on_file(self.isoname)

        # include metadata for checkmedia tool
        if self.xml_state.build_type.get_mediacheck() is True:
            Iso.set_media_tag(self.isoname)

        self.result.verify_image_size(
            self.runtime_config.get_max_size_constraint(), self.isoname)
        self.result.add(key='live_image',
                        filename=self.isoname,
                        use_for_bundle=True,
                        compress=False,
                        shasum=True)
        self.result.add(key='image_packages',
                        filename=self.system_setup.export_package_list(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        self.result.add(key='image_verified',
                        filename=self.system_setup.export_package_verification(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        return self.result

    def _setup_live_iso_kernel_and_initrd(self):
        """
        Copy kernel and initrd from the root tree into the iso boot structure
        """
        boot_path = ''.join([self.media_dir, '/boot/', self.arch, '/loader'])
        Path.create(boot_path)

        # Move kernel files to iso filesystem structure
        kernel = Kernel(self.boot_image.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiLiveBootImageError(
                'No kernel in boot image tree {0} found'.format(
                    self.boot_image.boot_root_directory))
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiLiveBootImageError(
                    'No hypervisor in boot image tree {0} found'.format(
                        self.boot_image.boot_root_directory))

        # Move initrd to iso filesystem structure
        if os.path.exists(self.boot_image.initrd_filename):
            shutil.move(self.boot_image.initrd_filename, boot_path + '/initrd')
        else:
            raise KiwiLiveBootImageError(
                'No boot image {0} in boot image tree {1} found'.format(
                    self.boot_image.initrd_filename,
                    self.boot_image.boot_root_directory))

    def __del__(self):
        if self.media_dir or self.live_container_dir:
            log.info('Cleaning up {0} instance'.format(type(self).__name__))
            if self.media_dir:
                Path.wipe(self.media_dir)
            if self.live_container_dir:
                Path.wipe(self.live_container_dir)
Example #43
0
class BootLoaderConfigGrub2(BootLoaderConfigBase):
    """
    **grub2 bootloader configuration.**
    """
    def post_init(self, custom_args):
        """
        grub2 post initialization method

        :param dict custom_args:
            Contains grub config arguments

            .. code:: python

                {'grub_directory_name': 'grub|grub2'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if arch == 'x86_64':
            # grub2 support for bios and efi systems
            self.arch = arch
        elif arch.startswith('ppc64'):
            # grub2 support for ofw and opal systems
            self.arch = arch
        elif arch == 'i686' or arch == 'i586':
            # grub2 support for bios systems
            self.arch = 'ix86'
        elif arch == 'aarch64' or arch.startswith('arm'):
            # grub2 support for efi systems
            self.arch = arch
        else:
            raise KiwiBootLoaderGrubPlatformError(
                'host architecture %s not supported for grub2 setup' % arch
            )

        if self.custom_args and 'grub_directory_name' in self.custom_args:
            self.boot_directory_name = self.custom_args['grub_directory_name']
        else:
            self.boot_directory_name = 'grub'

        self.terminal = self.xml_state.build_type.get_bootloader_console() \
            or 'gfxterm'
        self.gfxmode = self.get_gfxmode('grub2')
        self.bootpath = self.get_boot_path()
        self.theme = self.get_boot_theme()
        self.timeout = self.get_boot_timeout_seconds()
        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.mediacheck_boot = self.xml_state.build_type.get_mediacheck()
        self.xen_guest = self.xml_state.is_xen_guest()
        self.firmware = FirmWare(
            self.xml_state
        )

        self.live_type = self.xml_state.build_type.get_flags()
        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()

        self.live_boot_options = [
            'root=live:CDLABEL={0}'.format(self.volume_id),
            'rd.live.image'
        ]
        self.install_boot_options = [
            'loglevel=0'
        ]
        if self.xml_state.get_initrd_system() == 'dracut':
            self.install_boot_options.append(
                'root=install:CDLABEL={0}'.format(
                    Defaults.get_install_volume_id()
                )
            )
        if self.xml_state.build_type.get_hybridpersistent():
            self.live_boot_options += \
                Defaults.get_live_iso_persistent_boot_options(
                    self.xml_state.build_type.get_hybridpersistent_filesystem()
                )

        if self.xml_state.is_xen_server():
            self.hybrid_boot = False
            self.multiboot = True
        elif self.xen_guest:
            self.hybrid_boot = False
            self.multiboot = False
        else:
            self.hybrid_boot = True
            self.multiboot = False

        self.grub2 = BootLoaderTemplateGrub2()
        self.config = None
        self.efi_boot_path = None
        self.cmdline_failsafe = None
        self.cmdline = None
        self.iso_boot = False
        self.shim_fallback_setup = False

    def write(self):
        """
        Write grub.cfg and etc/default/grub file
        """
        config_dir = self._get_grub2_boot_path()
        config_file = config_dir + '/grub.cfg'
        if self.config:
            log.info('Writing grub.cfg file')
            Path.create(config_dir)
            with open(config_file, 'w') as config:
                config.write(self.config)

            if self.firmware.efi_mode():
                if self.iso_boot or self.shim_fallback_setup:
                    efi_vendor_boot_path = Defaults.get_shim_vendor_directory(
                        self.root_dir
                    )
                    if efi_vendor_boot_path:
                        grub_config_file_for_efi_boot = os.sep.join(
                            [efi_vendor_boot_path, 'grub.cfg']
                        )
                    else:
                        grub_config_file_for_efi_boot = os.path.normpath(
                            os.sep.join([self.efi_boot_path, 'grub.cfg'])
                        )
                    log.info(
                        'Writing {0} file to be found by EFI firmware'.format(
                            grub_config_file_for_efi_boot
                        )
                    )
                    with open(grub_config_file_for_efi_boot, 'w') as config:
                        config.write(self.config)

                if self.iso_boot:
                    self._create_embedded_fat_efi_image()

            self._setup_default_grub()
            self.setup_sysconfig_bootloader()

    def setup_sysconfig_bootloader(self):
        """
        Create or update etc/sysconfig/bootloader by the following
        parameters required according to the grub2 bootloader setup

        * LOADER_TYPE
        * LOADER_LOCATION
        * DEFAULT_APPEND
        * FAILSAFE_APPEND
        """
        sysconfig_bootloader_entries = {
            'LOADER_TYPE':
                'grub2-efi' if self.firmware.efi_mode() else 'grub2',
            'LOADER_LOCATION':
                'none' if self.firmware.efi_mode() else 'mbr'
        }
        if self.cmdline:
            sysconfig_bootloader_entries['DEFAULT_APPEND'] = '"{0}"'.format(
                self.cmdline
            )
        if self.cmdline_failsafe:
            sysconfig_bootloader_entries['FAILSAFE_APPEND'] = '"{0}"'.format(
                self.cmdline_failsafe
            )

        log.info('Writing sysconfig bootloader file')
        sysconfig_bootloader_location = ''.join(
            [self.root_dir, '/etc/sysconfig/']
        )
        if os.path.exists(sysconfig_bootloader_location):
            sysconfig_bootloader_file = ''.join(
                [sysconfig_bootloader_location, 'bootloader']
            )
            sysconfig_bootloader = SysConfig(
                sysconfig_bootloader_file
            )
            sysconfig_bootloader_entries_sorted = OrderedDict(
                sorted(sysconfig_bootloader_entries.items())
            )
            for key, value in list(sysconfig_bootloader_entries_sorted.items()):
                log.info('--> {0}:{1}'.format(key, value))
                sysconfig_bootloader[key] = value
            sysconfig_bootloader.write()

    def setup_disk_image_config(
        self, boot_uuid, root_uuid, hypervisor='xen.gz', kernel='linux.vmx',
        initrd='initrd.vmx', boot_options=''
    ):
        """
        Create the grub.cfg in memory from a template suitable to boot
        from a disk image

        :param string boot_uuid: boot device UUID
        :param string root_uuid: root device UUID
        :param string hypervisor: hypervisor name
        :param string kernel: kernel name
        :param string initrd: initrd name
        :param string boot_options: kernel options as string
        """
        log.info('Creating grub2 config file from template')
        self.cmdline = ' '.join(
            [self.get_boot_cmdline(root_uuid), boot_options]
        )
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options(), boot_options]
        )
        parameters = {
            'search_params': ' '.join(['--fs-uuid', '--set=root', boot_uuid]),
            'default_boot': '0',
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': self.cmdline,
            'failsafe_boot_options': self.cmdline_failsafe,
            'gfxmode': self.gfxmode,
            'theme': self.theme,
            'boot_timeout': self.timeout,
            'title': self.get_menu_entry_title(),
            'bootpath': self.bootpath,
            'boot_directory_name': self.boot_directory_name
        }
        if self.multiboot:
            log.info('--> Using multiboot disk template')
            parameters['hypervisor'] = hypervisor
            template = self.grub2.get_multiboot_disk_template(
                self.failsafe_boot, self.terminal
            )
        else:
            log.info('--> Using hybrid boot disk template')
            template = self.grub2.get_disk_template(
                self.failsafe_boot, self.hybrid_boot, self.terminal
            )
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_install_image_config(
        self, mbrid, hypervisor='xen.gz', kernel='linux', initrd='initrd'
    ):
        """
        Create grub2 config file to boot from an ISO install image

        :param string mbrid: mbrid file name on boot device
        :param string hypervisor: hypervisor name
        :param string kernel: kernel name
        :param string initrd: initrd name
        """
        log.info('Creating grub2 install config file from template')
        self.iso_boot = True
        self.cmdline = self.get_boot_cmdline()
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options()]
        )
        parameters = {
            'search_params': '--file --set=root /boot/' + mbrid.get_id(),
            'default_boot': self.get_install_image_boot_default(),
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': ' '.join(
                [self.cmdline] + self.install_boot_options
            ),
            'failsafe_boot_options': ' '.join(
                [self.cmdline_failsafe] + self.install_boot_options
            ),
            'gfxmode': self.gfxmode,
            'theme': self.theme,
            'boot_timeout': self.timeout,
            'title': self.get_menu_entry_install_title(),
            'bootpath': '/boot/' + self.arch + '/loader',
            'boot_directory_name': self.boot_directory_name
        }
        if self.multiboot:
            log.info('--> Using multiboot install template')
            parameters['hypervisor'] = hypervisor
            template = self.grub2.get_multiboot_install_template(
                self.failsafe_boot, self.terminal
            )
        else:
            log.info('--> Using standard boot install template')
            hybrid_boot = True
            template = self.grub2.get_install_template(
                self.failsafe_boot, hybrid_boot, self.terminal
            )
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_live_image_config(
        self, mbrid, hypervisor='xen.gz', kernel='linux', initrd='initrd'
    ):
        """
        Create grub2 config file to boot a live media ISO image

        :param string mbrid: mbrid file name on boot device
        :param string hypervisor: hypervisor name
        :param string kernel: kernel name
        :param string initrd: initrd name
        """
        log.info('Creating grub2 live ISO config file from template')
        self.iso_boot = True
        self.cmdline = self.get_boot_cmdline()
        self.cmdline_failsafe = ' '.join(
            [self.cmdline, Defaults.get_failsafe_kernel_options()]
        )
        parameters = {
            'search_params': '--file --set=root /boot/' + mbrid.get_id(),
            'default_boot': '0',
            'kernel_file': kernel,
            'initrd_file': initrd,
            'boot_options': ' '.join(
                [self.cmdline] + self.live_boot_options
            ),
            'failsafe_boot_options': ' '.join(
                [self.cmdline_failsafe] + self.live_boot_options
            ),
            'gfxmode': self.gfxmode,
            'theme': self.theme,
            'boot_timeout': self.timeout,
            'title': self.get_menu_entry_title(plain=True),
            'bootpath': '/boot/' + self.arch + '/loader',
            'boot_directory_name': self.boot_directory_name
        }
        if self.multiboot:
            log.info('--> Using multiboot template')
            parameters['hypervisor'] = hypervisor
            template = self.grub2.get_multiboot_iso_template(
                self.failsafe_boot, self.terminal, self.mediacheck_boot
            )
        else:
            log.info('--> Using standard boot template')
            hybrid_boot = True
            template = self.grub2.get_iso_template(
                self.failsafe_boot, hybrid_boot,
                self.terminal, self.mediacheck_boot
            )
        try:
            self.config = template.substitute(parameters)
        except Exception as e:
            raise KiwiTemplateError(
                '%s: %s' % (type(e).__name__, format(e))
            )

    def setup_install_boot_images(self, mbrid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        In order to boot from the ISO grub2 modules, images and theme
        data needs to be created and provided at the correct place on
        the iso filesystem

        :param string mbrid: mbrid file name on boot device
        :param string lookup_path: custom module lookup path
        """
        log.info('Creating grub2 bootloader images')
        self.efi_boot_path = self.create_efi_path(in_sub_dir='')

        log.info('--> Creating identifier file %s', mbrid.get_id())
        Path.create(
            self._get_grub2_boot_path()
        )
        mbrid.write(
            self.root_dir + '/boot/' + mbrid.get_id()
        )
        mbrid.write(
            self.root_dir + '/boot/mbrid'
        )

        self._copy_theme_data_to_boot_directory(lookup_path)

        if self._supports_bios_modules():
            self._copy_bios_modules_to_boot_directory(lookup_path)

        if self.firmware.efi_mode():
            self._setup_EFI_path(lookup_path)

        if self.firmware.efi_mode() == 'efi':
            self._setup_efi_image(mbrid=mbrid, lookup_path=lookup_path)
            self._copy_efi_modules_to_boot_directory(lookup_path)
        elif self.firmware.efi_mode() == 'uefi':
            self._copy_efi_modules_to_boot_directory(lookup_path)
            self._setup_secure_boot_efi_image(lookup_path)

    def setup_live_boot_images(self, mbrid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        Calls setup_install_boot_images because no different action required
        """
        self.setup_install_boot_images(mbrid, lookup_path)

    def setup_disk_boot_images(self, boot_uuid, lookup_path=None):
        """
        Create/Provide grub2 boot images and metadata

        In order to boot from the disk grub2 modules, images and theme
        data needs to be created and provided at the correct place in
        the filesystem

        :param string boot_uuid: boot device UUID
        :param string lookup_path: custom module lookup path
        """
        log.info('Creating grub2 bootloader images')

        if self.firmware.efi_mode():
            self.efi_boot_path = self.create_efi_path()

        self._copy_theme_data_to_boot_directory(lookup_path)

        if not self.xen_guest and self._supports_bios_modules():
            self._copy_bios_modules_to_boot_directory(lookup_path)

        if self.firmware.efi_mode() == 'efi':
            self._setup_efi_image(uuid=boot_uuid, lookup_path=lookup_path)
            self._copy_efi_modules_to_boot_directory(lookup_path)
        elif self.firmware.efi_mode() == 'uefi':
            self._copy_efi_modules_to_boot_directory(lookup_path)
            if not self._get_shim_install():
                self.shim_fallback_setup = True
                self._setup_secure_boot_efi_image(lookup_path)

        if self.xen_guest:
            self._copy_xen_modules_to_boot_directory(lookup_path)

    def _supports_bios_modules(self):
        if self.arch == 'ix86' or self.arch == 'x86_64':
            return True
        return False

    def _setup_default_grub(self):
        """
        Create or update etc/default/grub by parameters required
        according to the root filesystem setup

        * GRUB_TIMEOUT
        * SUSE_BTRFS_SNAPSHOT_BOOTING
        * GRUB_BACKGROUND
        * GRUB_THEME
        * GRUB_USE_LINUXEFI
        * GRUB_USE_INITRDEFI
        * GRUB_SERIAL_COMMAND
        * GRUB_CMDLINE_LINUX_DEFAULT
        """
        grub_default_entries = {
            'GRUB_TIMEOUT': self.timeout
        }
        if self.cmdline:
            grub_default_entries['GRUB_CMDLINE_LINUX_DEFAULT'] = '"{0}"'.format(
                self.cmdline
            )
        if self.terminal and self.terminal == 'serial':
            serial_format = '"serial {0} {1} {2} {3} {4}"'
            grub_default_entries['GRUB_SERIAL_COMMAND'] = serial_format.format(
                '--speed=38400',
                '--unit=0',
                '--word=8',
                '--parity=no',
                '--stop=1'
            )
        if self.theme:
            theme_setup = '{0}/{1}/theme.txt'
            grub_default_entries['GRUB_THEME'] = theme_setup.format(
                ''.join(['/boot/', self.boot_directory_name, '/themes']),
                self.theme
            )
            theme_background = '{0}/{1}/background.png'.format(
                ''.join(['/boot/', self.boot_directory_name, '/themes']),
                self.theme
            )
            if os.path.exists(os.sep.join([self.root_dir, theme_background])):
                grub_default_entries['GRUB_BACKGROUND'] = theme_background
        if self.firmware.efi_mode():
            # linuxefi/initrdefi only exist on x86, others always use efi
            if self.arch == 'ix86' or self.arch == 'x86_64':
                grub_default_entries['GRUB_USE_LINUXEFI'] = 'true'
                grub_default_entries['GRUB_USE_INITRDEFI'] = 'true'
        if self.xml_state.build_type.get_btrfs_root_is_snapshot():
            grub_default_entries['SUSE_BTRFS_SNAPSHOT_BOOTING'] = 'true'

        if grub_default_entries:
            log.info('Writing grub2 defaults file')
            grub_default_location = ''.join([self.root_dir, '/etc/default/'])
            if os.path.exists(grub_default_location):
                grub_default_file = ''.join([grub_default_location, 'grub'])
                grub_default = SysConfig(grub_default_file)
                grub_default_entries_sorted = OrderedDict(
                    sorted(grub_default_entries.items())
                )
                for key, value in list(grub_default_entries_sorted.items()):
                    log.info('--> {0}:{1}'.format(key, value))
                    grub_default[key] = value
                grub_default.write()

    def _setup_secure_boot_efi_image(self, lookup_path):
        """
        Provide the shim loader and the shim signed grub2 loader
        in the required boot path. Normally this task is done by
        the shim-install tool. However, shim-install does not exist
        on all distributions and the script does not operate well
        in e.g CD environments from which we generate live and/or
        install media. Thus shim-install is used if possible at
        install time of the bootloader because it requires access
        to the target block device. In any other case this setup
        code should act as the fallback solution
        """
        log.warning(
            '--> Running fallback setup for shim secure boot efi image'
        )
        if not lookup_path:
            lookup_path = self.root_dir
        shim_image = Defaults.get_shim_loader(lookup_path)
        if not shim_image:
            raise KiwiBootLoaderGrubSecureBootError(
                'Microsoft signed shim loader not found'
            )
        grub_image = Defaults.get_signed_grub_loader(lookup_path)
        if not grub_image:
            raise KiwiBootLoaderGrubSecureBootError(
                'Shim signed grub2 efi loader not found'
            )
        Command.run(
            ['cp', shim_image, self._get_efi_image_name()]
        )
        Command.run(
            ['cp', grub_image, self.efi_boot_path]
        )

    def _setup_efi_image(self, uuid=None, mbrid=None, lookup_path=None):
        """
        Provide the unsigned grub2 efi image in the required boot path
        If a prebuilt efi image as provided by the distribution could
        be found, this image will be used. If no such image could be
        found we create a custom image with a pre defined set of
        grub modules
        """
        if not lookup_path:
            lookup_path = self.root_dir
        grub_image = Defaults.get_unsigned_grub_loader(lookup_path)
        if grub_image:
            log.info('--> Using prebuilt unsigned efi image')
            Command.run(
                ['cp', grub_image, self._get_efi_image_name()]
            )
            self._create_efi_config_search(uuid, mbrid)
        else:
            log.info('--> Creating unsigned efi image')
            self._create_efi_image(
                uuid, mbrid, lookup_path
            )

    def _create_embedded_fat_efi_image(self):
        Path.create(self.root_dir + '/boot/' + self.arch)
        efi_fat_image = ''.join(
            [self.root_dir + '/boot/', self.arch, '/efi']
        )
        Command.run(
            ['qemu-img', 'create', efi_fat_image, '15M']
        )
        Command.run(
            ['mkdosfs', '-n', 'BOOT', efi_fat_image]
        )
        Command.run(
            [
                'mcopy', '-Do', '-s', '-i', efi_fat_image,
                self.root_dir + '/EFI', '::'
            ]
        )

    def _create_efi_image(self, uuid=None, mbrid=None, lookup_path=None):
        early_boot_script = self.efi_boot_path + '/earlyboot.cfg'
        if uuid:
            self._create_early_boot_script_for_uuid_search(
                early_boot_script, uuid
            )
        else:
            self._create_early_boot_script_for_mbrid_search(
                early_boot_script, mbrid
            )
        for grub_mkimage_tool in ['grub2-mkimage', 'grub-mkimage']:
            if Path.which(grub_mkimage_tool):
                break
        Command.run(
            [
                grub_mkimage_tool,
                '-O', Defaults.get_efi_module_directory_name(self.arch),
                '-o', self._get_efi_image_name(),
                '-c', early_boot_script,
                '-p', self.get_boot_path() + '/' + self.boot_directory_name,
                '-d', self._get_efi_modules_path(lookup_path)
            ] + Defaults.get_grub_efi_modules(multiboot=self.xen_guest)
        )

    def _create_efi_config_search(self, uuid=None, mbrid=None):
        efi_boot_config = self.efi_boot_path + '/grub.cfg'
        if uuid:
            self._create_early_boot_script_for_uuid_search(
                efi_boot_config, uuid
            )
        else:
            self._create_early_boot_script_for_mbrid_search(
                efi_boot_config, mbrid
            )
        with open(efi_boot_config, 'a') as config:
            config.write('normal{0}'.format(os.linesep))

    def _create_early_boot_script_for_uuid_search(self, filename, uuid):
        with open(filename, 'w') as early_boot:
            early_boot.write(
                'set btrfs_relative_path="yes"{0}'.format(os.linesep)
            )
            early_boot.write(
                'search --fs-uuid --set=root {0}{1}'.format(uuid, os.linesep)
            )
            early_boot.write(
                'set prefix=($root){0}/{1}{2}'.format(
                    self.get_boot_path(), self.boot_directory_name, os.linesep
                )
            )

    def _create_early_boot_script_for_mbrid_search(self, filename, mbrid):
        with open(filename, 'w') as early_boot:
            early_boot.write(
                'set btrfs_relative_path="yes"{0}'.format(os.linesep)
            )
            early_boot.write(
                'search --file --set=root /boot/{0}{1}'.format(
                    mbrid.get_id(), os.linesep
                )
            )
            early_boot.write(
                'set prefix=($root)/boot/{0}{1}'.format(
                    self.boot_directory_name, os.linesep
                )
            )

    def _get_grub2_boot_path(self):
        return self.root_dir + '/boot/' + self.boot_directory_name

    def _get_efi_image_name(self):
        return self.efi_boot_path + '/' + Defaults.get_efi_image_name(self.arch)

    def _get_efi_modules_path(self, lookup_path=None):
        return self._get_module_path(
            Defaults.get_efi_module_directory_name(self.arch),
            lookup_path
        )

    def _get_bios_modules_path(self, lookup_path=None):
        return self._get_module_path('i386-pc', lookup_path)

    def _get_xen_modules_path(self, lookup_path=None):
        return self._get_module_path(
            Defaults.get_efi_module_directory_name('x86_64_xen'),
            lookup_path
        )

    def _get_module_path(self, format_name, lookup_path=None):
        if not lookup_path:
            lookup_path = self.root_dir
        return ''.join(
            [
                self._find_grub_data(lookup_path + '/usr/lib'),
                '/', format_name
            ]
        )

    def _find_theme_background_file(self, lookup_path):
        background_pattern = os.sep.join(
            [
                lookup_path, 'boot', self.boot_directory_name, 'themes',
                '*', 'background.png'
            ]
        )
        for background_file in glob.iglob(background_pattern):
            return background_file

    def _copy_theme_data_to_boot_directory(self, lookup_path):
        if not lookup_path:
            lookup_path = self.root_dir
        boot_unicode_font = self.root_dir + '/boot/unicode.pf2'
        if not os.path.exists(boot_unicode_font):
            unicode_font = self._find_grub_data(lookup_path + '/usr/share') + \
                '/unicode.pf2'
            try:
                Command.run(
                    ['cp', unicode_font, boot_unicode_font]
                )
            except Exception:
                raise KiwiBootLoaderGrubFontError(
                    'Unicode font %s not found' % unicode_font
                )

        boot_theme_dir = os.sep.join(
            [self.root_dir, 'boot', self.boot_directory_name, 'themes']
        )
        Path.create(boot_theme_dir)

        if self.theme:
            theme_dir = self._find_grub_data(lookup_path + '/usr/share') + \
                '/themes/' + self.theme
            boot_theme_background_file = self._find_theme_background_file(
                lookup_path
            )
            if os.path.exists(theme_dir):
                if boot_theme_background_file:
                    # A background file was found. Preserve a copy of the
                    # file which was created at install time of the theme
                    # package by the activate-theme script
                    boot_theme_background_backup_file = os.sep.join(
                        [self.root_dir, 'background.png']
                    )
                    Command.run(
                        [
                            'cp', boot_theme_background_file,
                            boot_theme_background_backup_file
                        ]
                    )
                # sync theme data from install path to boot path
                data = DataSync(
                    theme_dir, boot_theme_dir
                )
                data.sync_data(
                    options=['-z', '-a']
                )
                if boot_theme_background_file:
                    # Install preserved background file to the theme
                    Command.run(
                        [
                            'mv', boot_theme_background_backup_file,
                            os.sep.join([boot_theme_dir, self.theme])
                        ]
                    )
            elif boot_theme_background_file:
                # assume all theme data is in the directory of the
                # background file and just sync that directory to the
                # boot path
                data = DataSync(
                    os.path.dirname(boot_theme_background_file), boot_theme_dir
                )
                data.sync_data(
                    options=['-z', '-a']
                )

        self._check_boot_theme_exists()

    def _check_boot_theme_exists(self):
        if self.theme:
            theme_dir = os.sep.join(
                [
                    self.root_dir, 'boot', self.boot_directory_name,
                    'themes', self.theme
                ]
            )
            if not os.path.exists(theme_dir):
                log.warning('Theme %s not found', theme_dir)
                log.warning('Set bootloader terminal to console mode')
                self.terminal = 'console'

    def _setup_EFI_path(self, lookup_path):
        """
        Copy efi boot data from lookup_path to the root directory
        """
        if not lookup_path:
            lookup_path = self.root_dir
        efi_path = lookup_path + '/boot/efi/'
        if os.path.exists(efi_path):
            efi_data = DataSync(efi_path, self.root_dir)
            efi_data.sync_data(options=['-a'])

    def _copy_efi_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_efi_modules_path(lookup_path)
        )

    def _copy_bios_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_bios_modules_path(lookup_path)
        )

    def _copy_xen_modules_to_boot_directory(self, lookup_path):
        self._copy_modules_to_boot_directory_from(
            self._get_xen_modules_path(lookup_path)
        )

    def _copy_modules_to_boot_directory_from(self, module_path):
        boot_module_path = \
            self._get_grub2_boot_path() + '/' + os.path.basename(module_path)
        try:
            data = DataSync(
                module_path + '/', boot_module_path
            )
            data.sync_data(
                options=['-z', '-a'], exclude=['*.module']
            )
        except Exception as e:
            raise KiwiBootLoaderGrubModulesError(
                'Module synchronisation failed with: %s' % format(e)
            )

    def _find_grub_data(self, lookup_path):
        grub_path = Defaults.get_grub_path(lookup_path)
        if grub_path:
            return grub_path

        raise KiwiBootLoaderGrubDataError(
            'No grub2 installation found in %s' % lookup_path
        )

    def _get_shim_install(self):
        chroot_env = {
            'PATH': os.sep.join([self.root_dir, 'usr', 'sbin'])
        }
        return Path.which(
            filename='shim-install', custom_env=chroot_env
        )
Example #44
0
class InstallImageBuilder(object):
    """
    Installation image builder

    Attributes

    * :attr:`xml_state`
        Instance of XMLState

    * :attr:`root_dir`
        systemi image root directory

    * :attr:`target_dir`
        target directory path name

    * :attr:`boot_image_task`
        Instance of BootImage

    * :attr:`custom_args`
        Custom processing arguments defined as hash keys:
        * xz_options: string of XZ compression parameters
    """
    def __init__(
        self, xml_state, root_dir, target_dir, boot_image_task,
        custom_args=None
    ):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.boot_image_task = boot_image_task
        self.xml_state = xml_state
        self.initrd_system = xml_state.get_initrd_system()
        self.firmware = FirmWare(xml_state)
        self.diskname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.raw'
            ]
        )
        self.isoname = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.install.iso'
            ]
        )
        self.pxename = ''.join(
            [
                target_dir, '/',
                xml_state.xml_data.get_name(),
                '.' + self.arch,
                '-' + xml_state.get_image_version(),
                '.install.tar.xz'
            ]
        )
        self.dracut_config_file = ''.join(
            [self.root_dir, Defaults.get_dracut_conf_name()]
        )
        self.squashed_diskname = ''.join(
            [xml_state.xml_data.get_name(), '.raw']
        )
        self.md5name = ''.join(
            [xml_state.xml_data.get_name(), '.md5']
        )
        self.xz_options = custom_args['xz_options'] if custom_args \
            and 'xz_options' in custom_args else None

        self.mbrid = SystemIdentifier()
        self.mbrid.calculate_id()

        self.media_dir = None
        self.pxe_dir = None
        self.squashed_contents = None
        self.custom_iso_args = None

    def create_install_iso(self):
        """
        Create an install ISO from the disk_image as hybrid ISO
        bootable via legacy BIOS, EFI and as disk from Stick

        Image types which triggers this builder are:

        * installiso="true|false"
        * installstick="true|false"
        """
        self.media_dir = mkdtemp(
            prefix='kiwi_install_media.', dir=self.target_dir
        )
        # custom iso metadata
        self.custom_iso_args = {
            'create_options': [
                '-V', Defaults.get_install_volume_id(),
                '-A', self.mbrid.get_id()
            ]
        }

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        self.squashed_contents = mkdtemp(
            prefix='kiwi_install_squashfs.', dir=self.target_dir
        )
        checksum = Checksum(self.diskname)
        checksum.md5(self.squashed_contents + '/' + self.md5name)

        # the system image name is stored in a config file
        self._write_install_image_info_to_iso_image()
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the system image is stored as squashfs embedded file
        log.info('Creating squashfs embedded disk image')
        Command.run(
            [
                'cp', '-l', self.diskname,
                self.squashed_contents + '/' + self.squashed_diskname
            ]
        )
        squashed_image_file = ''.join(
            [
                self.target_dir, '/', self.squashed_diskname, '.squashfs'
            ]
        )
        squashed_image = FileSystemSquashFs(
            device_provider=None, root_dir=self.squashed_contents
        )
        squashed_image.create_on_file(squashed_image_file)
        Command.run(
            ['mv', squashed_image_file, self.media_dir]
        )

        # setup bootloader config to boot the ISO via isolinux
        log.info('Setting up install image bootloader configuration')
        bootloader_config_isolinux = BootLoaderConfig(
            'isolinux', self.xml_state, self.media_dir
        )
        bootloader_config_isolinux.setup_install_boot_images(
            mbrid=None,
            lookup_path=self.boot_image_task.boot_root_directory
        )
        bootloader_config_isolinux.setup_install_image_config(
            mbrid=None
        )
        bootloader_config_isolinux.write()

        # setup bootloader config to boot the ISO via EFI
        bootloader_config_grub = BootLoaderConfig(
            'grub2', self.xml_state, self.media_dir, {
                'grub_directory_name':
                    Defaults.get_grub_boot_directory_name(self.root_dir)
            }
        )
        bootloader_config_grub.setup_install_boot_images(
            mbrid=self.mbrid, lookup_path=self.root_dir
        )
        bootloader_config_grub.setup_install_image_config(
            mbrid=self.mbrid
        )
        bootloader_config_grub.write()

        # create initrd for install image
        log.info('Creating install image boot image')
        self._create_iso_install_kernel_and_initrd()

        # the system image initrd is stored to allow kexec
        self._copy_system_image_initrd_to_iso_image()

        # create iso filesystem from media_dir
        log.info('Creating ISO filesystem')
        iso_image = FileSystemIsoFs(
            device_provider=None,
            root_dir=self.media_dir,
            custom_args=self.custom_iso_args
        )
        iso_header_offset = iso_image.create_on_file(self.isoname)

        # make it hybrid
        Iso.create_hybrid(
            iso_header_offset, self.mbrid, self.isoname,
            self.firmware.efi_mode()
        )

    def create_install_pxe_archive(self):
        """
        Create an oem install tar archive suitable for installing a
        disk image via the network using the PXE boot protocol.
        The archive contains:

        * The raw system image xz compressed
        * The raw system image checksum metadata file
        * The append file template for the boot server
        * The system image initrd for kexec
        * The install initrd
        * The kernel

        Image types which triggers this builder are:

        * installpxe="true|false"
        """
        self.pxe_dir = mkdtemp(
            prefix='kiwi_pxe_install_media.', dir=self.target_dir
        )
        # the system image is transfered as xz compressed variant
        log.info('xz compressing disk image')
        pxe_image_filename = ''.join(
            [
                self.pxe_dir, '/',
                self.xml_state.xml_data.get_name(), '.xz'
            ]
        )
        compress = Compress(
            source_filename=self.diskname,
            keep_source_on_compress=True
        )
        compress.xz(self.xz_options)
        Command.run(
            ['mv', compress.compressed_filename, pxe_image_filename]
        )

        # the system image transfer is checked against a checksum
        log.info('Creating disk image checksum')
        pxe_md5_filename = ''.join(
            [
                self.pxe_dir, '/',
                self.xml_state.xml_data.get_name(), '.md5'
            ]
        )
        checksum = Checksum(self.diskname)
        checksum.md5(pxe_md5_filename)

        # the install image name is stored in a config file
        if self.initrd_system == 'kiwi':
            self._write_install_image_info_to_boot_image()

        # the kexec required system image initrd is stored for dracut kiwi-dump
        if self.initrd_system == 'dracut':
            boot_names = self.boot_image_task.get_boot_names()
            system_image_initrd = os.sep.join(
                [self.root_dir, 'boot', boot_names.initrd_name]
            )
            target_initrd_name = '{0}/{1}.initrd'.format(
                self.pxe_dir, self.xml_state.xml_data.get_name()
            )
            shutil.copy(
                system_image_initrd, target_initrd_name
            )
            os.chmod(target_initrd_name, 420)

        # create pxe config append information
        # this information helps to configure the boot server correctly
        append_filename = ''.join(
            [
                self.pxe_dir, '/',
                self.xml_state.xml_data.get_name(), '.append'
            ]
        )
        if self.initrd_system == 'kiwi':
            cmdline = 'pxe=1'
        else:
            cmdline = ' '.join(
                [
                    'rd.kiwi.install.pxe',
                    'rd.kiwi.install.image=http://example.com/image.xz'
                ]
            )
        custom_cmdline = self.xml_state.build_type.get_kernelcmdline()
        if custom_cmdline:
            cmdline += ' ' + custom_cmdline
        with open(append_filename, 'w') as append:
            append.write('%s\n' % cmdline)

        # create initrd for pxe install
        log.info('Creating pxe install boot image')
        self._create_pxe_install_kernel_and_initrd()

        # create pxe install tarball
        log.info('Creating pxe install archive')
        archive = ArchiveTar(
            self.pxename.replace('.xz', '')
        )
        archive.create_xz_compressed(
            self.pxe_dir, xz_options=self.xz_options
        )

    def _create_pxe_install_kernel_and_initrd(self):
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(self.pxe_dir, '/pxeboot.kernel')
            os.symlink(
                'pxeboot.kernel', ''.join(
                    [
                        self.pxe_dir, '/',
                        self.xml_state.xml_data.get_name(), '.kernel'
                    ]
                )
            )
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(self.pxe_dir, '/pxeboot.xen.gz')
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory
                )
        if self.initrd_system == 'dracut':
            self._create_dracut_install_config()
            self._add_system_image_boot_options_to_boot_image()
        self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install')
        Command.run(
            [
                'mv', self.boot_image_task.initrd_filename,
                self.pxe_dir + '/pxeboot.initrd.xz'
            ]
        )
        os.chmod(self.pxe_dir + '/pxeboot.initrd.xz', 420)

    def _create_iso_install_kernel_and_initrd(self):
        boot_path = self.media_dir + '/boot/' + self.arch + '/loader'
        Path.create(boot_path)
        kernel = Kernel(self.boot_image_task.boot_root_directory)
        if kernel.get_kernel():
            kernel.copy_kernel(boot_path, '/linux')
        else:
            raise KiwiInstallBootImageError(
                'No kernel in boot image tree %s found' %
                self.boot_image_task.boot_root_directory
            )
        if self.xml_state.is_xen_server():
            if kernel.get_xen_hypervisor():
                kernel.copy_xen_hypervisor(boot_path, '/xen.gz')
            else:
                raise KiwiInstallBootImageError(
                    'No hypervisor in boot image tree %s found' %
                    self.boot_image_task.boot_root_directory
                )
        if self.initrd_system == 'dracut':
            self._create_dracut_install_config()
            self._add_system_image_boot_options_to_boot_image()
        self.boot_image_task.create_initrd(self.mbrid, 'initrd_kiwi_install')
        Command.run(
            [
                'mv', self.boot_image_task.initrd_filename,
                boot_path + '/initrd'
            ]
        )

    def _add_system_image_boot_options_to_boot_image(self):
        filename = ''.join(
            [self.boot_image_task.boot_root_directory, '/config.bootoptions']
        )
        self.boot_image_task.include_file(
            os.sep + os.path.basename(filename)
        )

    def _copy_system_image_initrd_to_iso_image(self):
        boot_names = self.boot_image_task.get_boot_names()
        system_image_initrd = os.sep.join(
            [self.root_dir, 'boot', boot_names.initrd_name]
        )
        shutil.copy(
            system_image_initrd, self.media_dir + '/initrd.system_image'
        )

    def _write_install_image_info_to_iso_image(self):
        iso_trigger = self.media_dir + '/config.isoclient'
        with open(iso_trigger, 'w') as iso_system:
            iso_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def _write_install_image_info_to_boot_image(self):
        initrd_trigger = \
            self.boot_image_task.boot_root_directory + '/config.vmxsystem'
        with open(initrd_trigger, 'w') as vmx_system:
            vmx_system.write('IMAGE="%s"\n' % self.squashed_diskname)

    def _create_dracut_install_config(self):
        dracut_config = [
            'hostonly="no"',
            'dracut_rescue_image="no"'
        ]
        dracut_modules = ['kiwi-lib', 'kiwi-dump']
        dracut_modules_omit = ['kiwi-overlay', 'kiwi-live', 'kiwi-repart']
        dracut_config.append(
            'add_dracutmodules+=" {0} "'.format(' '.join(dracut_modules))
        )
        dracut_config.append(
            'omit_dracutmodules+=" {0} "'.format(' '.join(dracut_modules_omit))
        )
        with open(self.dracut_config_file, 'w') as config:
            for entry in dracut_config:
                config.write(entry + os.linesep)

    def _delete_dracut_install_config(self):
        if os.path.exists(self.dracut_config_file):
            os.remove(self.dracut_config_file)

    def __del__(self):
        log.info('Cleaning up %s instance', type(self).__name__)
        if self.initrd_system == 'dracut':
            self._delete_dracut_install_config()
        if self.media_dir:
            Path.wipe(self.media_dir)
        if self.pxe_dir:
            Path.wipe(self.pxe_dir)
        if self.squashed_contents:
            Path.wipe(self.squashed_contents)
Example #45
0
    def __init__(self, xml_state, target_dir, root_dir, custom_args=None):
        self.arch = platform.machine()
        if self.arch == 'i686' or self.arch == 'i586':
            self.arch = 'ix86'
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.xml_state = xml_state
        self.spare_part_mbsize = xml_state.get_build_type_spare_part_size()
        self.spare_part_fs = xml_state.build_type.get_spare_part_fs()
        self.spare_part_is_last = xml_state.build_type.get_spare_part_is_last()
        self.spare_part_mountpoint = \
            xml_state.build_type.get_spare_part_mountpoint()
        self.persistency_type = xml_state.build_type.get_devicepersistency()
        self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot(
        )
        self.custom_root_mount_args = xml_state.get_fs_mount_option_list()
        self.custom_root_creation_args = xml_state.get_fs_create_option_list()
        self.build_type_name = xml_state.get_build_type_name()
        self.image_format = xml_state.build_type.get_format()
        self.install_iso = xml_state.build_type.get_installiso()
        self.install_stick = xml_state.build_type.get_installstick()
        self.install_pxe = xml_state.build_type.get_installpxe()
        self.blocksize = xml_state.build_type.get_target_blocksize()
        self.volume_manager_name = xml_state.get_volume_management()
        self.volumes = xml_state.get_volumes()
        self.volume_group_name = xml_state.get_volume_group_name()
        self.mdraid = xml_state.build_type.get_mdraid()
        self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr()
        self.force_mbr = xml_state.build_type.get_force_mbr()
        self.luks = xml_state.build_type.get_luks()
        self.luks_os = xml_state.build_type.get_luksOS()
        self.xen_server = xml_state.is_xen_server()
        self.requested_filesystem = xml_state.build_type.get_filesystem()
        self.requested_boot_filesystem = \
            xml_state.build_type.get_bootfilesystem()
        self.bootloader = xml_state.build_type.get_bootloader()
        self.initrd_system = xml_state.get_initrd_system()
        self.target_removable = xml_state.build_type.get_target_removable()
        self.root_filesystem_is_multipath = \
            xml_state.get_oemconfig_oem_multipath_scan()
        self.disk_setup = DiskSetup(xml_state, root_dir)
        self.unpartitioned_bytes = \
            xml_state.get_build_type_unpartitioned_bytes()
        self.custom_args = custom_args

        self.signing_keys = None
        if custom_args and 'signing_keys' in custom_args:
            self.signing_keys = custom_args['signing_keys']

        self.boot_image = BootImage(xml_state,
                                    target_dir,
                                    root_dir,
                                    signing_keys=self.signing_keys)
        self.firmware = FirmWare(xml_state)
        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.diskname = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + self.arch,
            '-' + xml_state.get_image_version(), '.raw'
        ])
        self.boot_is_crypto = True if self.luks and not \
            self.disk_setup.need_boot_partition() else False
        self.install_media = self._install_image_requested()
        self.generic_fstab_entries = []

        # an instance of a class with the sync_data capability
        # representing the entire image system except for the boot/ area
        # which could live on another part of the disk
        self.system = None

        # an instance of a class with the sync_data capability
        # representing the boot/ area of the disk if not part of
        # self.system
        self.system_boot = None

        # an instance of a class with the sync_data capability
        # representing the boot/efi area of the disk
        self.system_efi = None

        # an instance of a class with the sync_data capability
        # representing the spare_part_mountpoint area of the disk
        self.system_spare = None

        # result store
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()
Example #46
0
    def post_init(self, custom_args):
        """
        grub2 post initialization method

        :param dict custom_args:
            Contains grub config arguments

            .. code:: python

                {'grub_directory_name': 'grub|grub2'}
        """
        self.custom_args = custom_args
        arch = platform.machine()
        if arch == 'x86_64':
            # grub2 support for bios and efi systems
            self.arch = arch
        elif arch.startswith('ppc64'):
            # grub2 support for ofw and opal systems
            self.arch = arch
        elif arch == 'i686' or arch == 'i586':
            # grub2 support for bios systems
            self.arch = 'ix86'
        elif arch == 'aarch64' or arch.startswith('arm'):
            # grub2 support for efi systems
            self.arch = arch
        else:
            raise KiwiBootLoaderGrubPlatformError(
                'host architecture %s not supported for grub2 setup' % arch
            )

        if self.custom_args and 'grub_directory_name' in self.custom_args:
            self.boot_directory_name = self.custom_args['grub_directory_name']
        else:
            self.boot_directory_name = 'grub'

        self.terminal = self.xml_state.build_type.get_bootloader_console() \
            or 'gfxterm'
        self.gfxmode = self.get_gfxmode('grub2')
        self.bootpath = self.get_boot_path()
        self.theme = self.get_boot_theme()
        self.timeout = self.get_boot_timeout_seconds()
        self.failsafe_boot = self.failsafe_boot_entry_requested()
        self.mediacheck_boot = self.xml_state.build_type.get_mediacheck()
        self.xen_guest = self.xml_state.is_xen_guest()
        self.firmware = FirmWare(
            self.xml_state
        )

        self.live_type = self.xml_state.build_type.get_flags()
        if not self.live_type:
            self.live_type = Defaults.get_default_live_iso_type()

        self.volume_id = self.xml_state.build_type.get_volid() or \
            Defaults.get_volume_id()

        self.live_boot_options = [
            'root=live:CDLABEL={0}'.format(self.volume_id),
            'rd.live.image'
        ]
        self.install_boot_options = [
            'loglevel=0'
        ]
        if self.xml_state.get_initrd_system() == 'dracut':
            self.install_boot_options.append(
                'root=install:CDLABEL={0}'.format(
                    Defaults.get_install_volume_id()
                )
            )
        if self.xml_state.build_type.get_hybridpersistent():
            self.live_boot_options += \
                Defaults.get_live_iso_persistent_boot_options(
                    self.xml_state.build_type.get_hybridpersistent_filesystem()
                )

        if self.xml_state.is_xen_server():
            self.hybrid_boot = False
            self.multiboot = True
        elif self.xen_guest:
            self.hybrid_boot = False
            self.multiboot = False
        else:
            self.hybrid_boot = True
            self.multiboot = False

        self.grub2 = BootLoaderTemplateGrub2()
        self.config = None
        self.efi_boot_path = None
        self.cmdline_failsafe = None
        self.cmdline = None
        self.iso_boot = False
        self.shim_fallback_setup = False