xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/wic/plugins/source/isoimage-isohybrid.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4# DESCRIPTION
5# This implements the 'isoimage-isohybrid' source plugin class for 'wic'
6#
7# AUTHORS
8# Mihaly Varga <mihaly.varga (at] ni.com>
9
10import glob
11import logging
12import os
13import re
14import shutil
15
16from wic import WicError
17from wic.engine import get_custom_config
18from wic.pluginbase import SourcePlugin
19from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
20
21logger = logging.getLogger('wic')
22
23class IsoImagePlugin(SourcePlugin):
24    """
25    Create a bootable ISO image
26
27    This plugin creates a hybrid, legacy and EFI bootable ISO image. The
28    generated image can be used on optical media as well as USB media.
29
30    Legacy boot uses syslinux and EFI boot uses grub or gummiboot (not
31    implemented yet) as bootloader. The plugin creates the directories required
32    by bootloaders and populates them by creating and configuring the
33    bootloader files.
34
35    Example kickstart file:
36    part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi, \\
37    image_name= IsoImage" --ondisk cd --label LIVECD
38    bootloader  --timeout=10  --append=" "
39
40    In --sourceparams "loader" specifies the bootloader used for booting in EFI
41    mode, while "image_name" specifies the name of the generated image. In the
42    example above, wic creates an ISO image named IsoImage-cd.direct (default
43    extension added by direct imeger plugin) and a file named IsoImage-cd.iso
44    """
45
46    name = 'isoimage-isohybrid'
47
48    @classmethod
49    def do_configure_syslinux(cls, creator, cr_workdir):
50        """
51        Create loader-specific (syslinux) config
52        """
53        splash = os.path.join(cr_workdir, "ISO/boot/splash.jpg")
54        if os.path.exists(splash):
55            splashline = "menu background splash.jpg"
56        else:
57            splashline = ""
58
59        bootloader = creator.ks.bootloader
60
61        syslinux_conf = ""
62        syslinux_conf += "PROMPT 0\n"
63        syslinux_conf += "TIMEOUT %s \n" % (bootloader.timeout or 10)
64        syslinux_conf += "\n"
65        syslinux_conf += "ALLOWOPTIONS 1\n"
66        syslinux_conf += "SERIAL 0 115200\n"
67        syslinux_conf += "\n"
68        if splashline:
69            syslinux_conf += "%s\n" % splashline
70        syslinux_conf += "DEFAULT boot\n"
71        syslinux_conf += "LABEL boot\n"
72
73        kernel = get_bitbake_var("KERNEL_IMAGETYPE")
74        if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
75            if get_bitbake_var("INITRAMFS_IMAGE"):
76                kernel = "%s-%s.bin" % \
77                    (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
78
79        syslinux_conf += "KERNEL /" + kernel + "\n"
80        syslinux_conf += "APPEND initrd=/initrd LABEL=boot %s\n" \
81                             % bootloader.append
82
83        logger.debug("Writing syslinux config %s/ISO/isolinux/isolinux.cfg",
84                     cr_workdir)
85
86        with open("%s/ISO/isolinux/isolinux.cfg" % cr_workdir, "w") as cfg:
87            cfg.write(syslinux_conf)
88
89    @classmethod
90    def do_configure_grubefi(cls, part, creator, target_dir):
91        """
92        Create loader-specific (grub-efi) config
93        """
94        configfile = creator.ks.bootloader.configfile
95        if configfile:
96            grubefi_conf = get_custom_config(configfile)
97            if grubefi_conf:
98                logger.debug("Using custom configuration file %s for grub.cfg",
99                             configfile)
100            else:
101                raise WicError("configfile is specified "
102                               "but failed to get it from %s", configfile)
103        else:
104            splash = os.path.join(target_dir, "splash.jpg")
105            if os.path.exists(splash):
106                splashline = "menu background splash.jpg"
107            else:
108                splashline = ""
109
110            bootloader = creator.ks.bootloader
111
112            grubefi_conf = ""
113            grubefi_conf += "serial --unit=0 --speed=115200 --word=8 "
114            grubefi_conf += "--parity=no --stop=1\n"
115            grubefi_conf += "default=boot\n"
116            grubefi_conf += "timeout=%s\n" % (bootloader.timeout or 10)
117            grubefi_conf += "\n"
118            grubefi_conf += "search --set=root --label %s " % part.label
119            grubefi_conf += "\n"
120            grubefi_conf += "menuentry 'boot'{\n"
121
122            kernel = get_bitbake_var("KERNEL_IMAGETYPE")
123            if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
124                if get_bitbake_var("INITRAMFS_IMAGE"):
125                    kernel = "%s-%s.bin" % \
126                        (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
127
128            grubefi_conf += "linux /%s rootwait %s\n" \
129                            % (kernel, bootloader.append)
130            grubefi_conf += "initrd /initrd \n"
131            grubefi_conf += "}\n"
132
133            if splashline:
134                grubefi_conf += "%s\n" % splashline
135
136        cfg_path = os.path.join(target_dir, "grub.cfg")
137        logger.debug("Writing grubefi config %s", cfg_path)
138
139        with open(cfg_path, "w") as cfg:
140            cfg.write(grubefi_conf)
141
142    @staticmethod
143    def _build_initramfs_path(rootfs_dir, cr_workdir):
144        """
145        Create path for initramfs image
146        """
147
148        initrd = get_bitbake_var("INITRD_LIVE") or get_bitbake_var("INITRD")
149        if not initrd:
150            initrd_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
151            if not initrd_dir:
152                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting.")
153
154            image_name = get_bitbake_var("IMAGE_BASENAME")
155            if not image_name:
156                raise WicError("Couldn't find IMAGE_BASENAME, exiting.")
157
158            image_type = get_bitbake_var("INITRAMFS_FSTYPES")
159            if not image_type:
160                raise WicError("Couldn't find INITRAMFS_FSTYPES, exiting.")
161
162            machine = os.path.basename(initrd_dir)
163
164            pattern = '%s/%s*%s.%s' % (initrd_dir, image_name, machine, image_type)
165            files = glob.glob(pattern)
166            if files:
167                initrd = files[0]
168
169        if not initrd or not os.path.exists(initrd):
170            # Create initrd from rootfs directory
171            initrd = "%s/initrd.cpio.gz" % cr_workdir
172            initrd_dir = "%s/INITRD" % cr_workdir
173            shutil.copytree("%s" % rootfs_dir, \
174                            "%s" % initrd_dir, symlinks=True)
175
176            if os.path.isfile("%s/init" % rootfs_dir):
177                shutil.copy2("%s/init" % rootfs_dir, "%s/init" % initrd_dir)
178            elif os.path.lexists("%s/init" % rootfs_dir):
179                os.symlink(os.readlink("%s/init" % rootfs_dir), \
180                            "%s/init" % initrd_dir)
181            elif os.path.isfile("%s/sbin/init" % rootfs_dir):
182                shutil.copy2("%s/sbin/init" % rootfs_dir, \
183                            "%s" % initrd_dir)
184            elif os.path.lexists("%s/sbin/init" % rootfs_dir):
185                os.symlink(os.readlink("%s/sbin/init" % rootfs_dir), \
186                            "%s/init" % initrd_dir)
187            else:
188                raise WicError("Couldn't find or build initrd, exiting.")
189
190            exec_cmd("cd %s && find . | cpio -o -H newc -R root:root >%s/initrd.cpio " \
191                     % (initrd_dir, cr_workdir), as_shell=True)
192            exec_cmd("gzip -f -9 %s/initrd.cpio" % cr_workdir, as_shell=True)
193            shutil.rmtree(initrd_dir)
194
195        return initrd
196
197    @classmethod
198    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
199                               oe_builddir, bootimg_dir, kernel_dir,
200                               native_sysroot):
201        """
202        Called before do_prepare_partition(), creates loader-specific config
203        """
204        isodir = "%s/ISO/" % cr_workdir
205
206        if os.path.exists(isodir):
207            shutil.rmtree(isodir)
208
209        install_cmd = "install -d %s " % isodir
210        exec_cmd(install_cmd)
211
212        # Overwrite the name of the created image
213        logger.debug(source_params)
214        if 'image_name' in source_params and \
215                    source_params['image_name'].strip():
216            creator.name = source_params['image_name'].strip()
217            logger.debug("The name of the image is: %s", creator.name)
218
219    @staticmethod
220    def _install_payload(source_params, iso_dir):
221        """
222        Copies contents of payload directory (as specified in 'payload_dir' param) into iso_dir
223        """
224
225        if source_params.get('payload_dir'):
226            payload_dir = source_params['payload_dir']
227
228            logger.debug("Payload directory: %s", payload_dir)
229            shutil.copytree(payload_dir, iso_dir, symlinks=True, dirs_exist_ok=True)
230
231    @classmethod
232    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
233                             oe_builddir, bootimg_dir, kernel_dir,
234                             rootfs_dir, native_sysroot):
235        """
236        Called to do the actual content population for a partition i.e. it
237        'prepares' the partition to be incorporated into the image.
238        In this case, prepare content for a bootable ISO image.
239        """
240
241        isodir = "%s/ISO" % cr_workdir
242
243        cls._install_payload(source_params, isodir)
244
245        if part.rootfs_dir is None:
246            if not 'ROOTFS_DIR' in rootfs_dir:
247                raise WicError("Couldn't find --rootfs-dir, exiting.")
248            rootfs_dir = rootfs_dir['ROOTFS_DIR']
249        else:
250            if part.rootfs_dir in rootfs_dir:
251                rootfs_dir = rootfs_dir[part.rootfs_dir]
252            elif part.rootfs_dir:
253                rootfs_dir = part.rootfs_dir
254            else:
255                raise WicError("Couldn't find --rootfs-dir=%s connection "
256                               "or it is not a valid path, exiting." %
257                               part.rootfs_dir)
258
259        if not os.path.isdir(rootfs_dir):
260            rootfs_dir = get_bitbake_var("IMAGE_ROOTFS")
261        if not os.path.isdir(rootfs_dir):
262            raise WicError("Couldn't find IMAGE_ROOTFS, exiting.")
263
264        part.rootfs_dir = rootfs_dir
265        deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
266        img_iso_dir = get_bitbake_var("ISODIR")
267
268        # Remove the temporary file created by part.prepare_rootfs()
269        if os.path.isfile(part.source_file):
270            os.remove(part.source_file)
271
272        # Support using a different initrd other than default
273        if source_params.get('initrd'):
274            initrd = source_params['initrd']
275            if not deploy_dir:
276                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
277            cp_cmd = "cp %s/%s %s" % (deploy_dir, initrd, cr_workdir)
278            exec_cmd(cp_cmd)
279        else:
280            # Prepare initial ramdisk
281            initrd = "%s/initrd" % deploy_dir
282            if not os.path.isfile(initrd):
283                initrd = "%s/initrd" % img_iso_dir
284            if not os.path.isfile(initrd):
285                initrd = cls._build_initramfs_path(rootfs_dir, cr_workdir)
286
287        install_cmd = "install -m 0644 %s %s/initrd" % (initrd, isodir)
288        exec_cmd(install_cmd)
289
290        # Remove the temporary file created by _build_initramfs_path function
291        if os.path.isfile("%s/initrd.cpio.gz" % cr_workdir):
292            os.remove("%s/initrd.cpio.gz" % cr_workdir)
293
294        kernel = get_bitbake_var("KERNEL_IMAGETYPE")
295        if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
296            if get_bitbake_var("INITRAMFS_IMAGE"):
297                kernel = "%s-%s.bin" % \
298                    (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
299
300        install_cmd = "install -m 0644 %s/%s %s/%s" % \
301                      (kernel_dir, kernel, isodir, kernel)
302        exec_cmd(install_cmd)
303
304        #Create bootloader for efi boot
305        try:
306            target_dir = "%s/EFI/BOOT" % isodir
307            if os.path.exists(target_dir):
308                shutil.rmtree(target_dir)
309
310            os.makedirs(target_dir)
311
312            if source_params['loader'] == 'grub-efi':
313                # Builds bootx64.efi/bootia32.efi if ISODIR didn't exist or
314                # didn't contains it
315                target_arch = get_bitbake_var("TARGET_SYS")
316                if not target_arch:
317                    raise WicError("Coludn't find target architecture")
318
319                if re.match("x86_64", target_arch):
320                    grub_src_image = "grub-efi-bootx64.efi"
321                    grub_dest_image = "bootx64.efi"
322                elif re.match('i.86', target_arch):
323                    grub_src_image = "grub-efi-bootia32.efi"
324                    grub_dest_image = "bootia32.efi"
325                else:
326                    raise WicError("grub-efi is incompatible with target %s" %
327                                   target_arch)
328
329                grub_target = os.path.join(target_dir, grub_dest_image)
330                if not os.path.isfile(grub_target):
331                    grub_src = os.path.join(deploy_dir, grub_src_image)
332                    if not os.path.exists(grub_src):
333                        raise WicError("Grub loader %s is not found in %s. "
334                                       "Please build grub-efi first" % (grub_src_image, deploy_dir))
335                    shutil.copy(grub_src, grub_target)
336
337                if not os.path.isfile(os.path.join(target_dir, "boot.cfg")):
338                    cls.do_configure_grubefi(part, creator, target_dir)
339
340            else:
341                raise WicError("unrecognized bootimg-efi loader: %s" %
342                               source_params['loader'])
343        except KeyError:
344            raise WicError("bootimg-efi requires a loader, none specified")
345
346        # Create efi.img that contains bootloader files for EFI booting
347        # if ISODIR didn't exist or didn't contains it
348        if os.path.isfile("%s/efi.img" % img_iso_dir):
349            install_cmd = "install -m 0644 %s/efi.img %s/efi.img" % \
350                (img_iso_dir, isodir)
351            exec_cmd(install_cmd)
352        else:
353            # Default to 100 blocks of extra space for file system overhead
354            esp_extra_blocks = int(source_params.get('esp_extra_blocks', '100'))
355
356            du_cmd = "du -bks %s/EFI" % isodir
357            out = exec_cmd(du_cmd)
358            blocks = int(out.split()[0])
359            blocks += esp_extra_blocks
360            logger.debug("Added 100 extra blocks to %s to get to %d "
361                         "total blocks", part.mountpoint, blocks)
362
363            # dosfs image for EFI boot
364            bootimg = "%s/efi.img" % isodir
365
366            esp_label = source_params.get('esp_label', 'EFIimg')
367
368            dosfs_cmd = 'mkfs.vfat -n \'%s\' -S 512 -C %s %d' \
369                        % (esp_label, bootimg, blocks)
370            exec_native_cmd(dosfs_cmd, native_sysroot)
371
372            mmd_cmd = "mmd -i %s ::/EFI" % bootimg
373            exec_native_cmd(mmd_cmd, native_sysroot)
374
375            mcopy_cmd = "mcopy -i %s -s %s/EFI/* ::/EFI/" \
376                        % (bootimg, isodir)
377            exec_native_cmd(mcopy_cmd, native_sysroot)
378
379            chmod_cmd = "chmod 644 %s" % bootimg
380            exec_cmd(chmod_cmd)
381
382        # Prepare files for legacy boot
383        syslinux_dir = get_bitbake_var("STAGING_DATADIR")
384        if not syslinux_dir:
385            raise WicError("Couldn't find STAGING_DATADIR, exiting.")
386
387        if os.path.exists("%s/isolinux" % isodir):
388            shutil.rmtree("%s/isolinux" % isodir)
389
390        install_cmd = "install -d %s/isolinux" % isodir
391        exec_cmd(install_cmd)
392
393        cls.do_configure_syslinux(creator, cr_workdir)
394
395        install_cmd = "install -m 444 %s/syslinux/ldlinux.sys " % syslinux_dir
396        install_cmd += "%s/isolinux/ldlinux.sys" % isodir
397        exec_cmd(install_cmd)
398
399        install_cmd = "install -m 444 %s/syslinux/isohdpfx.bin " % syslinux_dir
400        install_cmd += "%s/isolinux/isohdpfx.bin" % isodir
401        exec_cmd(install_cmd)
402
403        install_cmd = "install -m 644 %s/syslinux/isolinux.bin " % syslinux_dir
404        install_cmd += "%s/isolinux/isolinux.bin" % isodir
405        exec_cmd(install_cmd)
406
407        install_cmd = "install -m 644 %s/syslinux/ldlinux.c32 " % syslinux_dir
408        install_cmd += "%s/isolinux/ldlinux.c32" % isodir
409        exec_cmd(install_cmd)
410
411        #create ISO image
412        iso_img = "%s/tempiso_img.iso" % cr_workdir
413        iso_bootimg = "isolinux/isolinux.bin"
414        iso_bootcat = "isolinux/boot.cat"
415        efi_img = "efi.img"
416
417        mkisofs_cmd = "mkisofs -V %s " % part.label
418        mkisofs_cmd += "-o %s -U " % iso_img
419        mkisofs_cmd += "-J -joliet-long -r -iso-level 2 -b %s " % iso_bootimg
420        mkisofs_cmd += "-c %s -no-emul-boot -boot-load-size 4 " % iso_bootcat
421        mkisofs_cmd += "-boot-info-table -eltorito-alt-boot "
422        mkisofs_cmd += "-eltorito-platform 0xEF -eltorito-boot %s " % efi_img
423        mkisofs_cmd += "-no-emul-boot %s " % isodir
424
425        logger.debug("running command: %s", mkisofs_cmd)
426        exec_native_cmd(mkisofs_cmd, native_sysroot)
427
428        shutil.rmtree(isodir)
429
430        du_cmd = "du -Lbks %s" % iso_img
431        out = exec_cmd(du_cmd)
432        isoimg_size = int(out.split()[0])
433
434        part.size = isoimg_size
435        part.source_file = iso_img
436
437    @classmethod
438    def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
439                        bootimg_dir, kernel_dir, native_sysroot):
440        """
441        Called after all partitions have been prepared and assembled into a
442        disk image.  In this case, we insert/modify the MBR using isohybrid
443        utility for booting via BIOS from disk storage devices.
444        """
445
446        iso_img = "%s.p1" % disk.path
447        full_path = creator._full_path(workdir, disk_name, "direct")
448        full_path_iso = creator._full_path(workdir, disk_name, "iso")
449
450        isohybrid_cmd = "isohybrid -u %s" % iso_img
451        logger.debug("running command: %s", isohybrid_cmd)
452        exec_native_cmd(isohybrid_cmd, native_sysroot)
453
454        # Replace the image created by direct plugin with the one created by
455        # mkisofs command. This is necessary because the iso image created by
456        # mkisofs has a very specific MBR is system area of the ISO image, and
457        # direct plugin adds and configures an another MBR.
458        logger.debug("Replaceing the image created by direct plugin\n")
459        os.remove(disk.path)
460        shutil.copy2(iso_img, full_path_iso)
461        shutil.copy2(full_path_iso, full_path)
462