Repository: OSInside/kiwi

Issue:
Fedora live images for ppc64le fail due to requiring non-existent `biosdisk` grub2 module
## Problem description
Fedora ppc64le live ISO image builds are failing because `grub2-mkimage` is requesting the `biosdisk` module (which is x86 only).

The kiwi code needs to be adjusted to not request this module for PowerPC architectures.

Build log snippet:
```
[ DEBUG   ]: 06:45:37 | EXEC: [chroot /builddir/result/image/build/image-root grub2-mkimage -O powerpc-ieee1275 -o /usr/lib/grub/powerpc-ieee1275/grub.elf -c /boot/grub2/earlyboot.cfg -p /boot/grub2 -d /usr/lib/grub/powerpc-ieee1275 ext2 iso9660 linux echo configfile search_label search_fs_file search search_fs_uuid ls normal gzio png fat gettext font minicmd gfxterm gfxmenu all_video xfs btrfs squash4 lvm luks gcry_rijndael gcry_sha256 gcry_sha512 crypto cryptodisk test true loadenv part_gpt part_msdos biosdisk vga vbe chain boot]
[ DEBUG   ]: 06:45:37 | EXEC: Failed with stderr: grub2-mkimage: error: cannot open `/usr/lib/grub/powerpc-ieee1275/biosdisk.mod': No such file or directory.
, stdout: (no output on stdout)
[ ERROR   ]: 06:45:37 | KiwiCommandError: chroot: stderr: grub2-mkimage: error: cannot open `/usr/lib/grub/powerpc-ieee1275/biosdisk.mod': No such file or directory.
```

I'm pretty sure this is the commit that broke it: d3d0cce94a2ae0585330fbdbd0a86d859c7a563b

## Expected behavior
GRUB doesn't fail during image build, and a ppc64le live image is made.

## Steps to reproduce the behavior
1. Get a ppc64le environment
2. `git clone https://pagure.io/fedora-kiwi-descriptions.git`
3.  `cd fedora-kiwi-descriptions`
4. `sudo ./kiwi-build --output-dir=$PWD/tmpoutput --image-type=iso --image-profile=KDE-Desktop-Live --image-release=0 --debug`

## OS and Software information

* KIWI version: 10.2.9
* Operating system host version: Fedora Linux 43
* Operating system target version: Fedora Linux 43
* Open Build Service version (N/A if not using OBS): N/A
* Koji version (N/A if not using Koji): 1.35.1

Discussion:

Solution patch:
diff --git a/kiwi/bootloader/config/grub2.py b/kiwi/bootloader/config/grub2.py
index c81c0461..621f665f 100644
--- a/kiwi/bootloader/config/grub2.py
+++ b/kiwi/bootloader/config/grub2.py
@@ -500,9 +500,9 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
 
         self._copy_theme_data_to_boot_directory(lookup_path, 'iso')
 
-        if self._supports_bios_modules():
+        if self._supports_platform_modules():
             self._copy_bios_modules_to_boot_directory(lookup_path)
-            self._setup_bios_image(mbrid=mbrid, lookup_path=lookup_path)
+            self._setup_platform_image(mbrid=mbrid, lookup_path=lookup_path)
 
         if self.firmware.efi_mode():
             self._setup_EFI_path(lookup_path)
@@ -549,7 +549,7 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
 
         self._copy_theme_data_to_boot_directory(lookup_path, 'disk')
 
-        if not self.xen_guest and self._supports_bios_modules():
+        if not self.xen_guest and self._supports_platform_modules():
             self._copy_bios_modules_to_boot_directory(lookup_path)
 
         if self.firmware.efi_mode() == 'efi':
@@ -614,7 +614,7 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
                 config_file, grub_config_file_for_efi_boot
             )
 
-    def _supports_bios_modules(self):
+    def _supports_platform_modules(self):
         if self.efi_csm and (
             self.arch == 'ix86' or self.arch == 'x86_64' or Defaults.is_ppc64_arch(self.arch)
         ):
@@ -983,18 +983,18 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
                 chrp_config.format(os_name=self.get_menu_entry_install_title())
             )
 
-    def _setup_bios_image(self, mbrid, lookup_path=None):
+    def _setup_platform_image(self, mbrid, 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)
+        grub_image = Defaults.get_grub_platform_core_loader(lookup_path)
         if grub_image:
             log.info(f'--> Using prebuilt bios image: {grub_image}')
         else:
             log.info('--> Creating bios image')
-            self._create_bios_image(
+            self._create_platform_image(
                 mbrid, lookup_path
             )
         if Defaults.is_ppc64_arch(Defaults.get_platform_name()):
@@ -1003,12 +1003,14 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
         bash_command = ' '.join(
             [
                 'cat',
-                self._get_bios_modules_path(lookup_path) + '/cdboot.img',
-                grub_image or self._get_bios_image_name(lookup_path),
+                self._get_grub_platform_modules_path(
+                    lookup_path
+                ) + '/cdboot.img',
+                grub_image or self._get_grub_platform_image_name(lookup_path),
                 '>',
                 os.sep.join(
                     [
-                        self._get_bios_modules_path(lookup_path),
+                        self._get_grub_platform_modules_path(lookup_path),
                         Defaults.get_iso_grub_loader()
                     ]
                 )
@@ -1113,7 +1115,7 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
                 efi_boot_config, mbrid
             )
 
-    def _create_bios_image(self, mbrid, lookup_path=None):
+    def _create_platform_image(self, mbrid, lookup_path=None):
         early_boot_script = os.path.normpath(
             os.sep.join([self._get_grub2_boot_path(), 'earlyboot.cfg'])
         )
@@ -1139,25 +1141,25 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
                 os.path.basename(
                     self._get_grub2_mkimage_tool()
                 ) or 'grub2-mkimage',
-                '-O', Defaults.get_bios_module_directory_name(),
-                '-o', self._get_bios_image_name(self.root_dir).replace(
+                '-O', Defaults.get_grub_platform_module_directory_name(),
+                '-o', self._get_grub_platform_image_name(self.root_dir).replace(
                     self.root_dir, ''
                 ),
                 '-c', early_boot_script.replace(self.boot_dir, ''),
                 '-p', os.sep.join(
                     [self.get_boot_path(), self.boot_directory_name]
                 ),
-                '-d', self._get_bios_modules_path(self.root_dir).replace(
-                    self.root_dir, ''
-                )
-            ] + Defaults.get_grub_bios_modules(multiboot=self.xen_guest)
+                '-d', self._get_grub_platform_modules_path(
+                    self.root_dir
+                ).replace(self.root_dir, '')
+            ] + Defaults.get_grub_platform_modules(multiboot=self.xen_guest)
         )
         log.debug(mkimage_call.output)
 
         # Copy generated EFI image to the media directory if this
         # is different from the system root directory, e.g the case
         # for live image builds or when using a custom kiwi initrd
-        bios_image_root_file = self._get_bios_image_name(self.root_dir)
+        bios_image_root_file = self._get_grub_platform_image_name(self.root_dir)
         bios_image_media_file = bios_image_root_file.replace(
             self.root_dir, lookup_path
         )
@@ -1260,11 +1262,11 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
             ]
         )
 
-    def _get_bios_image_name(self, lookup_path):
+    def _get_grub_platform_image_name(self, lookup_path):
         return os.sep.join(
             [
-                self._get_bios_modules_path(lookup_path),
-                Defaults.get_bios_image_name()
+                self._get_grub_platform_modules_path(lookup_path),
+                Defaults.get_grub_platform_image_name()
             ]
         )
 
@@ -1274,8 +1276,10 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
             lookup_path
         )
 
-    def _get_bios_modules_path(self, lookup_path=None):
-        return self._get_module_path(Defaults.get_bios_module_directory_name(), lookup_path)
+    def _get_grub_platform_modules_path(self, lookup_path=None):
+        return self._get_module_path(
+            Defaults.get_grub_platform_module_directory_name(), lookup_path
+        )
 
     def _get_xen_modules_path(self, lookup_path=None):
         return self._get_module_path(
@@ -1419,7 +1423,7 @@ class BootLoaderConfigGrub2(BootLoaderConfigBase):
 
     def _copy_bios_modules_to_boot_directory(self, lookup_path):
         self._copy_modules_to_boot_directory_from(
-            self._get_bios_modules_path(lookup_path)
+            self._get_grub_platform_modules_path(lookup_path)
         )
 
     def _copy_xen_modules_to_boot_directory(self, lookup_path):
diff --git a/kiwi/bootloader/install/grub2.py b/kiwi/bootloader/install/grub2.py
index ac18c888..8a9e3027 100644
--- a/kiwi/bootloader/install/grub2.py
+++ b/kiwi/bootloader/install/grub2.py
@@ -155,7 +155,7 @@ class BootLoaderInstallGrub2(BootLoaderInstallBase):
             self.target = 'i386-pc'
             self.install_device = self.device
             self.modules = ' '.join(
-                Defaults.get_grub_bios_modules(multiboot=True)
+                Defaults.get_grub_platform_modules(multiboot=True)
             )
             self.install_arguments.append('--skip-fs-probe')
         elif self.arch.startswith('ppc64'):
diff --git a/kiwi/defaults.py b/kiwi/defaults.py
index 274c20db..71562257 100644
--- a/kiwi/defaults.py
+++ b/kiwi/defaults.py
@@ -729,9 +729,9 @@ class Defaults:
         return modules
 
     @staticmethod
-    def get_grub_bios_modules(multiboot=False):
+    def get_grub_platform_modules(multiboot=False):
         """
-        Provides list of grub bios modules
+        Provides list of platform specific grub modules
 
         :param bool multiboot: grub multiboot mode
 
@@ -739,15 +739,19 @@ class Defaults:
 
         :rtype: list
         """
-        modules = Defaults.get_grub_basic_modules(multiboot) + [
-            'part_gpt',
-            'part_msdos',
-            'biosdisk',
-            'vga',
-            'vbe',
-            'chain',
-            'boot'
-        ]
+        modules = Defaults.get_grub_basic_modules(multiboot)
+        if Defaults.is_ppc64_arch(Defaults.get_platform_name()):
+            return Defaults.get_grub_ofw_modules()
+        else:
+            modules += [
+                'part_gpt',
+                'part_msdos',
+                'biosdisk',
+                'vga',
+                'vbe',
+                'chain',
+                'boot'
+            ]
         return modules
 
     @staticmethod
@@ -972,7 +976,7 @@ class Defaults:
         return None
 
     @staticmethod
-    def get_grub_bios_core_loader(root_path):
+    def get_grub_platform_core_loader(root_path):
         """
         Provides grub bios image
 
@@ -987,10 +991,12 @@ class Defaults:
         """
         bios_grub_core_patterns = [
             '/usr/share/grub*/{0}/{1}'.format(
-                Defaults.get_bios_module_directory_name(), Defaults.get_bios_image_name()
+                Defaults.get_grub_platform_module_directory_name(),
+                Defaults.get_grub_platform_image_name()
             ),
             '/usr/lib/grub*/{0}/{1}'.format(
-                Defaults.get_bios_module_directory_name(), Defaults.get_bios_image_name()
+                Defaults.get_grub_platform_module_directory_name(),
+                Defaults.get_grub_platform_image_name()
             )
         ]
         for bios_grub_core_pattern in bios_grub_core_patterns:
@@ -1410,15 +1416,18 @@ class Defaults:
             return default_module_directory_names[arch]
 
     @staticmethod
-    def get_bios_module_directory_name():
+    def get_grub_platform_module_directory_name():
         """
-        Provides BIOS directory name which stores the pc binaries
+        Provides grub platform specific directory name which
+        stores the grub module binaries
 
         :return: directory name
 
         :rtype: str
         """
-        return 'powerpc-ieee1275' if Defaults.is_ppc64_arch(Defaults.get_platform_name()) else 'i386-pc'
+        return 'powerpc-ieee1275' if Defaults.is_ppc64_arch(
+            Defaults.get_platform_name()
+        ) else 'i386-pc'
 
     @staticmethod
     def get_efi_image_name(arch):
@@ -1446,15 +1455,17 @@ class Defaults:
             return default_efi_image_names[arch]
 
     @staticmethod
-    def get_bios_image_name():
+    def get_grub_platform_image_name():
         """
-        Provides bios core boot binary name
+        Provides platform specific core boot binary name
 
         :return: name
 
         :rtype: str
         """
-        return 'grub.elf' if Defaults.is_ppc64_arch(Defaults.get_platform_name()) else 'core.img'
+        return 'grub.elf' if Defaults.is_ppc64_arch(
+            Defaults.get_platform_name()
+        ) else 'core.img'
 
     @staticmethod
     def get_default_boot_timeout_seconds():


Test patch:
diff --git a/test/unit/bootloader/config/grub2_test.py b/test/unit/bootloader/config/grub2_test.py
index 486ab526..f605f359 100644
--- a/test/unit/bootloader/config/grub2_test.py
+++ b/test/unit/bootloader/config/grub2_test.py
@@ -1748,7 +1748,7 @@ class TestBootLoaderConfigGrub2:
 
     @patch('kiwi.bootloader.config.base.BootLoaderConfigBase.get_boot_path')
     @patch('kiwi.bootloader.config.grub2.Defaults.get_unsigned_grub_loader')
-    @patch('kiwi.bootloader.config.grub2.Defaults.get_grub_bios_core_loader')
+    @patch('kiwi.bootloader.config.grub2.Defaults.get_grub_platform_core_loader')
     @patch('kiwi.bootloader.config.grub2.Command.run')
     @patch('kiwi.bootloader.config.grub2.Path.which')
     @patch('kiwi.bootloader.config.grub2.Path.create')
@@ -1758,10 +1758,11 @@ class TestBootLoaderConfigGrub2:
     def test_setup_install_boot_images_ppc(
         self, mock_shutil_copy2, mock_exists, mock_sync,
         mock_Path_create, mock_Path_which, mock_command,
-        mock_get_grub_bios_core_loader, mock_get_unsigned_grub_loader,
+        mock_get_grub_platform_core_loader, mock_get_unsigned_grub_loader,
         mock_get_boot_path
     ):
         Defaults.set_platform_name('ppc64le')
+        mock_get_grub_platform_core_loader.return_value = None
         mock_Path_which.return_value = '/path/to/grub2-mkimage'
         mock_get_boot_path.return_value = '/boot'
         self.bootloader.arch = 'ppc64le'
@@ -1786,7 +1787,6 @@ class TestBootLoaderConfigGrub2:
 
         with open('../data/bootinfo.txt') as chrp:
             grub2_test_chrp_boot = chrp.read()
-
         with patch('builtins.open', create=True) as mock_open:
             mock_open.return_value = MagicMock(spec=io.IOBase)
             file_handle = mock_open.return_value.__enter__.return_value
@@ -1795,12 +1795,17 @@ class TestBootLoaderConfigGrub2:
             )
 
             assert mock_open.call_args_list == [
+                call('boot_dir/boot/grub2/earlyboot.cfg', 'w'),
                 call('boot_dir/boot/grub2/powerpc-ieee1275/grub.cfg', 'w'),
                 call('boot_dir/ppc/bootinfo.txt', 'w'),
                 call('boot_dir/boot/grub2/loopback.cfg', 'w')
             ]
 
             assert file_handle.write.call_args_list == [
+                call('set btrfs_relative_path="yes"\n'),
+                call('search --file --set=root /boot/0xffffffff\n'),
+                call('set prefix=($root)/boot/grub2\n'),
+                call('source ($root)/boot/grub2/grub.cfg\n'),
                 call('set btrfs_relative_path="yes"\n'),
                 call('search --file --set=root /boot/0xffffffff\n'),
                 call('set prefix=($root)/boot/grub2\n'),
@@ -1809,9 +1814,36 @@ class TestBootLoaderConfigGrub2:
                 call('source /boot/grub2/grub.cfg\n')
             ]
 
+        assert mock_command.call_args_list == [
+            call(
+                [
+                    'cp', 'lookup_dir/usr/share/grub2/unicode.pf2',
+                    'boot_dir/boot/grub2/fonts'
+                ]
+            ),
+            call(
+                [
+                    'chroot', 'root_dir', 'grub2-mkimage',
+                    '-O', 'powerpc-ieee1275',
+                    '-o', '/usr/share/grub2/powerpc-ieee1275/grub.elf',
+                    '-c', '/boot/grub2/earlyboot.cfg',
+                    '-p', '/boot/grub2',
+                    '-d', '/usr/share/grub2/powerpc-ieee1275',
+                    'ext2', 'iso9660', 'linux', 'echo', 'configfile',
+                    'search_label', 'search_fs_file', 'search',
+                    'search_fs_uuid', 'ls', 'normal', 'gzio', 'png', 'fat',
+                    'gettext', 'font', 'minicmd', 'gfxterm', 'gfxmenu',
+                    'all_video', 'xfs', 'btrfs', 'squash4', 'lvm', 'luks',
+                    'gcry_rijndael', 'gcry_sha256', 'gcry_sha512',
+                    'crypto', 'cryptodisk', 'test', 'true', 'loadenv',
+                    'part_gpt', 'part_msdos', 'boot'
+                ]
+            )
+        ]
+
     @patch('kiwi.bootloader.config.base.BootLoaderConfigBase.get_boot_path')
     @patch('kiwi.bootloader.config.grub2.Defaults.get_unsigned_grub_loader')
-    @patch('kiwi.bootloader.config.grub2.Defaults.get_grub_bios_core_loader')
+    @patch('kiwi.bootloader.config.grub2.Defaults.get_grub_platform_core_loader')
     @patch('kiwi.bootloader.config.grub2.Command.run')
     @patch('kiwi.bootloader.config.grub2.Path.which')
     @patch('kiwi.bootloader.config.grub2.Path.create')
@@ -1821,14 +1853,14 @@ class TestBootLoaderConfigGrub2:
     def test_setup_install_boot_images_efi(
         self, mock_shutil_copy2, mock_exists, mock_sync,
         mock_Path_create, mock_Path_which, mock_command,
-        mock_get_grub_bios_core_loader, mock_get_unsigned_grub_loader,
+        mock_get_grub_platform_core_loader, mock_get_unsigned_grub_loader,
         mock_get_boot_path
     ):
         Defaults.set_platform_name('x86_64')
         mock_Path_which.return_value = '/path/to/grub2-mkimage'
         mock_get_boot_path.return_value = '/boot'
         mock_get_unsigned_grub_loader.return_value = None
-        mock_get_grub_bios_core_loader.return_value = None
+        mock_get_grub_platform_core_loader.return_value = None
         data = Mock()
         mock_sync.return_value = data
         self.firmware.efi_mode = Mock(
@@ -1978,7 +2010,7 @@ class TestBootLoaderConfigGrub2:
             )
         ]
         mock_get_unsigned_grub_loader.return_value = 'custom_grub_image'
-        mock_get_grub_bios_core_loader.return_value = 'custom_bios_grub_image'
+        mock_get_grub_platform_core_loader.return_value = 'custom_bios_grub_image'
         mock_command.reset_mock()
 
         with patch('builtins.open', create=True) as mock_open:
@@ -2032,7 +2064,7 @@ class TestBootLoaderConfigGrub2:
                 call('source ($root)/boot/grub2/grub.cfg\n'),
             ]
 
-    @patch.object(BootLoaderConfigGrub2, '_supports_bios_modules')
+    @patch.object(BootLoaderConfigGrub2, '_supports_platform_modules')
     @patch('kiwi.bootloader.config.grub2.Command.run')
     @patch('os.path.exists')
     @patch('glob.iglob')
@@ -2041,10 +2073,10 @@ class TestBootLoaderConfigGrub2:
     @patch('os.path.isfile')
     def test_setup_install_boot_images_efi_secure_boot(
         self, mock_isfile, mock_stat, mock_chmod, mock_glob,
-        mock_exists, mock_command, mock_supports_bios_modules
+        mock_exists, mock_command, mock_supports_platform_modules
     ):
         Defaults.set_platform_name('x86_64')
-        mock_supports_bios_modules.return_value = False
+        mock_supports_platform_modules.return_value = False
         mock_isfile.return_value = False
         self.os_exists['root_dir'] = True
         self.firmware.efi_mode = Mock(
@@ -2113,19 +2145,19 @@ class TestBootLoaderConfigGrub2:
                 ]
 
     @patch('kiwi.defaults.Defaults.get_grub_efi_font_directory')
-    @patch.object(BootLoaderConfigGrub2, '_supports_bios_modules')
+    @patch.object(BootLoaderConfigGrub2, '_supports_platform_modules')
     @patch('kiwi.bootloader.config.grub2.Command.run')
     @patch('kiwi.bootloader.config.grub2.DataSync')
     @patch('os.path.exists')
     @patch('glob.iglob')
     def test_setup_install_boot_images_with_theme_from_usr_share(
         self, mock_glob, mock_exists,
-        mock_sync, mock_command, mock_supports_bios_modules,
+        mock_sync, mock_command, mock_supports_platform_modules,
         mock_get_grub_efi_font_directory
     ):
         Defaults.set_platform_name('x86_64')
         mock_get_grub_efi_font_directory.return_value = None
-        mock_supports_bios_modules.return_value = False
+        mock_supports_platform_modules.return_value = False
         mock_glob.return_value = [
             'root_dir/boot/grub2/themes/some-theme/background.png'
         ]
diff --git a/test/unit/bootloader/install/grub2_test.py b/test/unit/bootloader/install/grub2_test.py
index 73f17d23..cff754d0 100644
--- a/test/unit/bootloader/install/grub2_test.py
+++ b/test/unit/bootloader/install/grub2_test.py
@@ -203,7 +203,7 @@ class TestBootLoaderInstallGrub2:
                 '--boot-directory', '/boot',
                 '--target', 'i386-pc',
                 '--modules', ' '.join(
-                    Defaults.get_grub_bios_modules(multiboot=True)
+                    Defaults.get_grub_platform_modules(multiboot=True)
                 ),
                 '/dev/some-device'
             ]
@@ -392,7 +392,7 @@ class TestBootLoaderInstallGrub2:
                 '--boot-directory', '/boot',
                 '--target', 'i386-pc',
                 '--modules', ' '.join(
-                    Defaults.get_grub_bios_modules(multiboot=True)
+                    Defaults.get_grub_platform_modules(multiboot=True)
                 ),
                 '/dev/some-device'
             ]