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            initrds = initrd.split(';')
59            for rd in initrds:
60                cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
61                exec_cmd(cp_cmd, True)
62        else:
63            logger.debug("Ignoring missing initrd")
64
65        if not custom_cfg:
66            # Create grub configuration using parameters from wks file
67            bootloader = creator.ks.bootloader
68            title = source_params.get('title')
69
70            grubefi_conf = ""
71            grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
72            grubefi_conf += "default=boot\n"
73            grubefi_conf += "timeout=%s\n" % bootloader.timeout
74            grubefi_conf += "menuentry '%s'{\n" % (title if title else "boot")
75
76            kernel = get_bitbake_var("KERNEL_IMAGETYPE")
77            if not kernel:
78                kernel = "bzImage"
79
80            label = source_params.get('label')
81            label_conf = "root=%s" % creator.rootdev
82            if label:
83                label_conf = "LABEL=%s" % label
84
85            grubefi_conf += "linux /%s %s rootwait %s\n" \
86                % (kernel, label_conf, bootloader.append)
87
88            if initrd:
89                initrds = initrd.split(';')
90                grubefi_conf += "initrd"
91                for rd in initrds:
92                    grubefi_conf += " /%s" % rd
93                grubefi_conf += "\n"
94
95            grubefi_conf += "}\n"
96
97        logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
98                     cr_workdir)
99        cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
100        cfg.write(grubefi_conf)
101        cfg.close()
102
103    @classmethod
104    def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
105        """
106        Create loader-specific systemd-boot/gummiboot config
107        """
108        install_cmd = "install -d %s/loader" % hdddir
109        exec_cmd(install_cmd)
110
111        install_cmd = "install -d %s/loader/entries" % hdddir
112        exec_cmd(install_cmd)
113
114        bootloader = creator.ks.bootloader
115
116        loader_conf = ""
117        loader_conf += "default boot\n"
118        loader_conf += "timeout %d\n" % bootloader.timeout
119
120        initrd = source_params.get('initrd')
121
122        if initrd:
123            # obviously we need to have a common common deploy var
124            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
125            if not bootimg_dir:
126                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
127
128            initrds = initrd.split(';')
129            for rd in initrds:
130                cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
131                exec_cmd(cp_cmd, True)
132        else:
133            logger.debug("Ignoring missing initrd")
134
135        logger.debug("Writing systemd-boot config "
136                     "%s/hdd/boot/loader/loader.conf", cr_workdir)
137        cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
138        cfg.write(loader_conf)
139        cfg.close()
140
141        configfile = creator.ks.bootloader.configfile
142        custom_cfg = None
143        if configfile:
144            custom_cfg = get_custom_config(configfile)
145            if custom_cfg:
146                # Use a custom configuration for systemd-boot
147                boot_conf = custom_cfg
148                logger.debug("Using custom configuration file "
149                             "%s for systemd-boots's boot.conf", configfile)
150            else:
151                raise WicError("configfile is specified but failed to "
152                               "get it from %s.", configfile)
153
154        if not custom_cfg:
155            # Create systemd-boot configuration using parameters from wks file
156            kernel = get_bitbake_var("KERNEL_IMAGETYPE")
157            if not kernel:
158                kernel = "bzImage"
159
160            title = source_params.get('title')
161
162            boot_conf = ""
163            boot_conf += "title %s\n" % (title if title else "boot")
164            boot_conf += "linux /%s\n" % kernel
165
166            label = source_params.get('label')
167            label_conf = "LABEL=Boot root=%s" % creator.rootdev
168            if label:
169                label_conf = "LABEL=%s" % label
170
171            boot_conf += "options %s %s\n" % \
172                             (label_conf, bootloader.append)
173
174            if initrd:
175                initrds = initrd.split(';')
176                for rd in initrds:
177                    boot_conf += "initrd /%s\n" % rd
178
179        logger.debug("Writing systemd-boot config "
180                     "%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
181        cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
182        cfg.write(boot_conf)
183        cfg.close()
184
185
186    @classmethod
187    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
188                               oe_builddir, bootimg_dir, kernel_dir,
189                               native_sysroot):
190        """
191        Called before do_prepare_partition(), creates loader-specific config
192        """
193        hdddir = "%s/hdd/boot" % cr_workdir
194
195        install_cmd = "install -d %s/EFI/BOOT" % hdddir
196        exec_cmd(install_cmd)
197
198        try:
199            if source_params['loader'] == 'grub-efi':
200                cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params)
201            elif source_params['loader'] == 'systemd-boot':
202                cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
203            else:
204                raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
205        except KeyError:
206            raise WicError("bootimg-efi requires a loader, none specified")
207
208
209    @classmethod
210    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
211                             oe_builddir, bootimg_dir, kernel_dir,
212                             rootfs_dir, native_sysroot):
213        """
214        Called to do the actual content population for a partition i.e. it
215        'prepares' the partition to be incorporated into the image.
216        In this case, prepare content for an EFI (grub) boot partition.
217        """
218        if not kernel_dir:
219            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
220            if not kernel_dir:
221                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
222
223        staging_kernel_dir = kernel_dir
224
225        hdddir = "%s/hdd/boot" % cr_workdir
226
227        kernel = get_bitbake_var("KERNEL_IMAGETYPE")
228        if not kernel:
229            kernel = "bzImage"
230
231        install_cmd = "install -m 0644 %s/%s %s/%s" % \
232            (staging_kernel_dir, kernel, hdddir, kernel)
233        exec_cmd(install_cmd)
234
235
236        try:
237            if source_params['loader'] == 'grub-efi':
238                shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
239                                "%s/grub.cfg" % cr_workdir)
240                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
241                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
242                    exec_cmd(cp_cmd, True)
243                shutil.move("%s/grub.cfg" % cr_workdir,
244                            "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
245            elif source_params['loader'] == 'systemd-boot':
246                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
247                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
248                    exec_cmd(cp_cmd, True)
249            else:
250                raise WicError("unrecognized bootimg-efi loader: %s" %
251                               source_params['loader'])
252        except KeyError:
253            raise WicError("bootimg-efi requires a loader, none specified")
254
255        startup = os.path.join(kernel_dir, "startup.nsh")
256        if os.path.exists(startup):
257            cp_cmd = "cp %s %s/" % (startup, hdddir)
258            exec_cmd(cp_cmd, True)
259
260        du_cmd = "du -bks %s" % hdddir
261        out = exec_cmd(du_cmd)
262        blocks = int(out.split()[0])
263
264        extra_blocks = part.get_extra_block_count(blocks)
265
266        if extra_blocks < BOOTDD_EXTRA_SPACE:
267            extra_blocks = BOOTDD_EXTRA_SPACE
268
269        blocks += extra_blocks
270
271        logger.debug("Added %d extra blocks to %s to get to %d total blocks",
272                     extra_blocks, part.mountpoint, blocks)
273
274        # dosfs image, created by mkdosfs
275        bootimg = "%s/boot.img" % cr_workdir
276
277        label = part.label if part.label else "ESP"
278
279        dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \
280                    (label, part.fsuuid, bootimg, blocks)
281        exec_native_cmd(dosfs_cmd, native_sysroot)
282
283        mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
284        exec_native_cmd(mcopy_cmd, native_sysroot)
285
286        chmod_cmd = "chmod 644 %s" % bootimg
287        exec_cmd(chmod_cmd)
288
289        du_cmd = "du -Lbks %s" % bootimg
290        out = exec_cmd(du_cmd)
291        bootimg_size = out.split()[0]
292
293        part.size = int(bootimg_size)
294        part.source_file = bootimg
295