1#
2# Copyright (c) 2014, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# DESCRIPTION
7# This implements the 'bootimg-efi' source plugin class for 'wic'
8#
9# AUTHORS
10# Tom Zanussi <tom.zanussi (at] linux.intel.com>
11#
12
13import logging
14import os
15import shutil
16
17from wic import WicError
18from wic.engine import get_custom_config
19from wic.pluginbase import SourcePlugin
20from wic.misc import (exec_cmd, exec_native_cmd,
21                      get_bitbake_var, BOOTDD_EXTRA_SPACE)
22
23logger = logging.getLogger('wic')
24
25class BootimgEFIPlugin(SourcePlugin):
26    """
27    Create EFI boot partition.
28    This plugin supports GRUB 2 and systemd-boot bootloaders.
29    """
30
31    name = 'bootimg-efi'
32
33    @classmethod
34    def do_configure_grubefi(cls, hdddir, creator, cr_workdir, source_params):
35        """
36        Create loader-specific (grub-efi) config
37        """
38        configfile = creator.ks.bootloader.configfile
39        custom_cfg = None
40        if configfile:
41            custom_cfg = get_custom_config(configfile)
42            if custom_cfg:
43                # Use a custom configuration for grub
44                grubefi_conf = custom_cfg
45                logger.debug("Using custom configuration file "
46                             "%s for grub.cfg", configfile)
47            else:
48                raise WicError("configfile is specified but failed to "
49                               "get it from %s." % configfile)
50
51        initrd = source_params.get('initrd')
52
53        if initrd:
54            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
55            if not bootimg_dir:
56                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
57
58            cp_cmd = "cp %s/%s %s" % (bootimg_dir, initrd, hdddir)
59            exec_cmd(cp_cmd, True)
60        else:
61            logger.debug("Ignoring missing initrd")
62
63        if not custom_cfg:
64            # Create grub configuration using parameters from wks file
65            bootloader = creator.ks.bootloader
66            title = source_params.get('title')
67
68            grubefi_conf = ""
69            grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
70            grubefi_conf += "default=boot\n"
71            grubefi_conf += "timeout=%s\n" % bootloader.timeout
72            grubefi_conf += "menuentry '%s'{\n" % (title if title else "boot")
73
74            kernel = "/bzImage"
75
76            grubefi_conf += "linux %s root=%s rootwait %s\n" \
77                % (kernel, creator.rootdev, bootloader.append)
78
79            if initrd:
80               grubefi_conf += "initrd /%s\n" % initrd
81
82            grubefi_conf += "}\n"
83
84        logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
85                     cr_workdir)
86        cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
87        cfg.write(grubefi_conf)
88        cfg.close()
89
90    @classmethod
91    def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
92        """
93        Create loader-specific systemd-boot/gummiboot config
94        """
95        install_cmd = "install -d %s/loader" % hdddir
96        exec_cmd(install_cmd)
97
98        install_cmd = "install -d %s/loader/entries" % hdddir
99        exec_cmd(install_cmd)
100
101        bootloader = creator.ks.bootloader
102
103        loader_conf = ""
104        loader_conf += "default boot\n"
105        loader_conf += "timeout %d\n" % bootloader.timeout
106
107        initrd = source_params.get('initrd')
108
109        if initrd:
110            # obviously we need to have a common common deploy var
111            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
112            if not bootimg_dir:
113                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
114
115            cp_cmd = "cp %s/%s %s" % (bootimg_dir, initrd, hdddir)
116            exec_cmd(cp_cmd, True)
117        else:
118            logger.debug("Ignoring missing initrd")
119
120        logger.debug("Writing systemd-boot config "
121                     "%s/hdd/boot/loader/loader.conf", cr_workdir)
122        cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
123        cfg.write(loader_conf)
124        cfg.close()
125
126        configfile = creator.ks.bootloader.configfile
127        custom_cfg = None
128        if configfile:
129            custom_cfg = get_custom_config(configfile)
130            if custom_cfg:
131                # Use a custom configuration for systemd-boot
132                boot_conf = custom_cfg
133                logger.debug("Using custom configuration file "
134                             "%s for systemd-boots's boot.conf", configfile)
135            else:
136                raise WicError("configfile is specified but failed to "
137                               "get it from %s.", configfile)
138
139        if not custom_cfg:
140            # Create systemd-boot configuration using parameters from wks file
141            kernel = "/bzImage"
142            title = source_params.get('title')
143
144            boot_conf = ""
145            boot_conf += "title %s\n" % (title if title else "boot")
146            boot_conf += "linux %s\n" % kernel
147            boot_conf += "options LABEL=Boot root=%s %s\n" % \
148                             (creator.rootdev, bootloader.append)
149
150            if initrd:
151                boot_conf += "initrd /%s\n" % initrd
152
153        logger.debug("Writing systemd-boot config "
154                     "%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
155        cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
156        cfg.write(boot_conf)
157        cfg.close()
158
159
160    @classmethod
161    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
162                               oe_builddir, bootimg_dir, kernel_dir,
163                               native_sysroot):
164        """
165        Called before do_prepare_partition(), creates loader-specific config
166        """
167        hdddir = "%s/hdd/boot" % cr_workdir
168
169        install_cmd = "install -d %s/EFI/BOOT" % hdddir
170        exec_cmd(install_cmd)
171
172        try:
173            if source_params['loader'] == 'grub-efi':
174                cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params)
175            elif source_params['loader'] == 'systemd-boot':
176                cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
177            else:
178                raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
179        except KeyError:
180            raise WicError("bootimg-efi requires a loader, none specified")
181
182
183    @classmethod
184    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
185                             oe_builddir, bootimg_dir, kernel_dir,
186                             rootfs_dir, native_sysroot):
187        """
188        Called to do the actual content population for a partition i.e. it
189        'prepares' the partition to be incorporated into the image.
190        In this case, prepare content for an EFI (grub) boot partition.
191        """
192        if not kernel_dir:
193            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
194            if not kernel_dir:
195                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
196
197        staging_kernel_dir = kernel_dir
198
199        hdddir = "%s/hdd/boot" % cr_workdir
200
201        install_cmd = "install -m 0644 %s/bzImage %s/bzImage" % \
202            (staging_kernel_dir, hdddir)
203        exec_cmd(install_cmd)
204
205
206        try:
207            if source_params['loader'] == 'grub-efi':
208                shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
209                                "%s/grub.cfg" % cr_workdir)
210                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
211                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
212                    exec_cmd(cp_cmd, True)
213                shutil.move("%s/grub.cfg" % cr_workdir,
214                            "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
215            elif source_params['loader'] == 'systemd-boot':
216                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
217                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
218                    exec_cmd(cp_cmd, True)
219            else:
220                raise WicError("unrecognized bootimg-efi loader: %s" %
221                               source_params['loader'])
222        except KeyError:
223            raise WicError("bootimg-efi requires a loader, none specified")
224
225        startup = os.path.join(kernel_dir, "startup.nsh")
226        if os.path.exists(startup):
227            cp_cmd = "cp %s %s/" % (startup, hdddir)
228            exec_cmd(cp_cmd, True)
229
230        du_cmd = "du -bks %s" % hdddir
231        out = exec_cmd(du_cmd)
232        blocks = int(out.split()[0])
233
234        extra_blocks = part.get_extra_block_count(blocks)
235
236        if extra_blocks < BOOTDD_EXTRA_SPACE:
237            extra_blocks = BOOTDD_EXTRA_SPACE
238
239        blocks += extra_blocks
240
241        logger.debug("Added %d extra blocks to %s to get to %d total blocks",
242                     extra_blocks, part.mountpoint, blocks)
243
244        # dosfs image, created by mkdosfs
245        bootimg = "%s/boot.img" % cr_workdir
246
247        label = part.label if part.label else "ESP"
248
249        dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \
250                    (label, part.fsuuid, bootimg, blocks)
251        exec_native_cmd(dosfs_cmd, native_sysroot)
252
253        mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
254        exec_native_cmd(mcopy_cmd, native_sysroot)
255
256        chmod_cmd = "chmod 644 %s" % bootimg
257        exec_cmd(chmod_cmd)
258
259        du_cmd = "du -Lbks %s" % bootimg
260        out = exec_cmd(du_cmd)
261        bootimg_size = out.split()[0]
262
263        part.size = int(bootimg_size)
264        part.source_file = bootimg
265