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
16import re
17
18from glob import glob
19
20from wic import WicError
21from wic.engine import get_custom_config
22from wic.pluginbase import SourcePlugin
23from wic.misc import (exec_cmd, exec_native_cmd,
24                      get_bitbake_var, BOOTDD_EXTRA_SPACE)
25
26logger = logging.getLogger('wic')
27
28class BootimgEFIPlugin(SourcePlugin):
29    """
30    Create EFI boot partition.
31    This plugin supports GRUB 2 and systemd-boot bootloaders.
32    """
33
34    name = 'bootimg-efi'
35
36    @classmethod
37    def do_configure_grubefi(cls, hdddir, creator, cr_workdir, source_params):
38        """
39        Create loader-specific (grub-efi) config
40        """
41        configfile = creator.ks.bootloader.configfile
42        custom_cfg = None
43        if configfile:
44            custom_cfg = get_custom_config(configfile)
45            if custom_cfg:
46                # Use a custom configuration for grub
47                grubefi_conf = custom_cfg
48                logger.debug("Using custom configuration file "
49                             "%s for grub.cfg", configfile)
50            else:
51                raise WicError("configfile is specified but failed to "
52                               "get it from %s." % configfile)
53
54        initrd = source_params.get('initrd')
55
56        if initrd:
57            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
58            if not bootimg_dir:
59                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
60
61            initrds = initrd.split(';')
62            for rd in initrds:
63                cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
64                exec_cmd(cp_cmd, True)
65        else:
66            logger.debug("Ignoring missing initrd")
67
68        if not custom_cfg:
69            # Create grub configuration using parameters from wks file
70            bootloader = creator.ks.bootloader
71            title = source_params.get('title')
72
73            grubefi_conf = ""
74            grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
75            grubefi_conf += "default=boot\n"
76            grubefi_conf += "timeout=%s\n" % bootloader.timeout
77            grubefi_conf += "menuentry '%s'{\n" % (title if title else "boot")
78
79            kernel = get_bitbake_var("KERNEL_IMAGETYPE")
80            if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
81                if get_bitbake_var("INITRAMFS_IMAGE"):
82                    kernel = "%s-%s.bin" % \
83                        (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
84
85            label = source_params.get('label')
86            label_conf = "root=%s" % creator.rootdev
87            if label:
88                label_conf = "LABEL=%s" % label
89
90            grubefi_conf += "linux /%s %s rootwait %s\n" \
91                % (kernel, label_conf, bootloader.append)
92
93            if initrd:
94                initrds = initrd.split(';')
95                grubefi_conf += "initrd"
96                for rd in initrds:
97                    grubefi_conf += " /%s" % rd
98                grubefi_conf += "\n"
99
100            grubefi_conf += "}\n"
101
102        logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
103                     cr_workdir)
104        cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
105        cfg.write(grubefi_conf)
106        cfg.close()
107
108    @classmethod
109    def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
110        """
111        Create loader-specific systemd-boot/gummiboot config
112        """
113        install_cmd = "install -d %s/loader" % hdddir
114        exec_cmd(install_cmd)
115
116        install_cmd = "install -d %s/loader/entries" % hdddir
117        exec_cmd(install_cmd)
118
119        bootloader = creator.ks.bootloader
120
121        loader_conf = ""
122        loader_conf += "default boot\n"
123        loader_conf += "timeout %d\n" % bootloader.timeout
124
125        initrd = source_params.get('initrd')
126
127        if initrd:
128            # obviously we need to have a common common deploy var
129            bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
130            if not bootimg_dir:
131                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
132
133            initrds = initrd.split(';')
134            for rd in initrds:
135                cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
136                exec_cmd(cp_cmd, True)
137        else:
138            logger.debug("Ignoring missing initrd")
139
140        logger.debug("Writing systemd-boot config "
141                     "%s/hdd/boot/loader/loader.conf", cr_workdir)
142        cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
143        cfg.write(loader_conf)
144        cfg.close()
145
146        configfile = creator.ks.bootloader.configfile
147        custom_cfg = None
148        if configfile:
149            custom_cfg = get_custom_config(configfile)
150            if custom_cfg:
151                # Use a custom configuration for systemd-boot
152                boot_conf = custom_cfg
153                logger.debug("Using custom configuration file "
154                             "%s for systemd-boots's boot.conf", configfile)
155            else:
156                raise WicError("configfile is specified but failed to "
157                               "get it from %s.", configfile)
158
159        if not custom_cfg:
160            # Create systemd-boot configuration using parameters from wks file
161            kernel = get_bitbake_var("KERNEL_IMAGETYPE")
162            if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
163                if get_bitbake_var("INITRAMFS_IMAGE"):
164                    kernel = "%s-%s.bin" % \
165                        (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
166
167            title = source_params.get('title')
168
169            boot_conf = ""
170            boot_conf += "title %s\n" % (title if title else "boot")
171            boot_conf += "linux /%s\n" % kernel
172
173            label = source_params.get('label')
174            label_conf = "LABEL=Boot root=%s" % creator.rootdev
175            if label:
176                label_conf = "LABEL=%s" % label
177
178            boot_conf += "options %s %s\n" % \
179                             (label_conf, bootloader.append)
180
181            if initrd:
182                initrds = initrd.split(';')
183                for rd in initrds:
184                    boot_conf += "initrd /%s\n" % rd
185
186        logger.debug("Writing systemd-boot config "
187                     "%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
188        cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
189        cfg.write(boot_conf)
190        cfg.close()
191
192
193    @classmethod
194    def do_configure_partition(cls, part, source_params, creator, cr_workdir,
195                               oe_builddir, bootimg_dir, kernel_dir,
196                               native_sysroot):
197        """
198        Called before do_prepare_partition(), creates loader-specific config
199        """
200        hdddir = "%s/hdd/boot" % cr_workdir
201
202        install_cmd = "install -d %s/EFI/BOOT" % hdddir
203        exec_cmd(install_cmd)
204
205        try:
206            if source_params['loader'] == 'grub-efi':
207                cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params)
208            elif source_params['loader'] == 'systemd-boot':
209                cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
210            else:
211                raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
212        except KeyError:
213            raise WicError("bootimg-efi requires a loader, none specified")
214
215        if get_bitbake_var("IMAGE_BOOT_FILES") is None:
216            logger.debug('No boot files defined in IMAGE_BOOT_FILES')
217        else:
218            boot_files = None
219            for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)):
220                if fmt:
221                    var = fmt % id
222                else:
223                    var = ""
224
225                boot_files = get_bitbake_var("IMAGE_BOOT_FILES" + var)
226                if boot_files:
227                    break
228
229            logger.debug('Boot files: %s', boot_files)
230
231            # list of tuples (src_name, dst_name)
232            deploy_files = []
233            for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
234                if ';' in src_entry:
235                    dst_entry = tuple(src_entry.split(';'))
236                    if not dst_entry[0] or not dst_entry[1]:
237                        raise WicError('Malformed boot file entry: %s' % src_entry)
238                else:
239                    dst_entry = (src_entry, src_entry)
240
241                logger.debug('Destination entry: %r', dst_entry)
242                deploy_files.append(dst_entry)
243
244            cls.install_task = [];
245            for deploy_entry in deploy_files:
246                src, dst = deploy_entry
247                if '*' in src:
248                    # by default install files under their basename
249                    entry_name_fn = os.path.basename
250                    if dst != src:
251                        # unless a target name was given, then treat name
252                        # as a directory and append a basename
253                        entry_name_fn = lambda name: \
254                                        os.path.join(dst,
255                                                     os.path.basename(name))
256
257                    srcs = glob(os.path.join(kernel_dir, src))
258
259                    logger.debug('Globbed sources: %s', ', '.join(srcs))
260                    for entry in srcs:
261                        src = os.path.relpath(entry, kernel_dir)
262                        entry_dst_name = entry_name_fn(entry)
263                        cls.install_task.append((src, entry_dst_name))
264                else:
265                    cls.install_task.append((src, dst))
266
267    @classmethod
268    def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
269                             oe_builddir, bootimg_dir, kernel_dir,
270                             rootfs_dir, native_sysroot):
271        """
272        Called to do the actual content population for a partition i.e. it
273        'prepares' the partition to be incorporated into the image.
274        In this case, prepare content for an EFI (grub) boot partition.
275        """
276        if not kernel_dir:
277            kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
278            if not kernel_dir:
279                raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
280
281        staging_kernel_dir = kernel_dir
282
283        hdddir = "%s/hdd/boot" % cr_workdir
284
285        kernel = get_bitbake_var("KERNEL_IMAGETYPE")
286        if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
287            if get_bitbake_var("INITRAMFS_IMAGE"):
288                kernel = "%s-%s.bin" % \
289                    (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
290
291        install_cmd = "install -m 0644 %s/%s %s/%s" % \
292            (staging_kernel_dir, kernel, hdddir, kernel)
293        exec_cmd(install_cmd)
294
295        if get_bitbake_var("IMAGE_BOOT_FILES"):
296            for src_path, dst_path in cls.install_task:
297                install_cmd = "install -m 0644 -D %s %s" \
298                              % (os.path.join(kernel_dir, src_path),
299                                 os.path.join(hdddir, dst_path))
300                exec_cmd(install_cmd)
301
302        try:
303            if source_params['loader'] == 'grub-efi':
304                shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
305                                "%s/grub.cfg" % cr_workdir)
306                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
307                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
308                    exec_cmd(cp_cmd, True)
309                shutil.move("%s/grub.cfg" % cr_workdir,
310                            "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
311            elif source_params['loader'] == 'systemd-boot':
312                for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
313                    cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
314                    exec_cmd(cp_cmd, True)
315            else:
316                raise WicError("unrecognized bootimg-efi loader: %s" %
317                               source_params['loader'])
318        except KeyError:
319            raise WicError("bootimg-efi requires a loader, none specified")
320
321        startup = os.path.join(kernel_dir, "startup.nsh")
322        if os.path.exists(startup):
323            cp_cmd = "cp %s %s/" % (startup, hdddir)
324            exec_cmd(cp_cmd, True)
325
326        du_cmd = "du -bks %s" % hdddir
327        out = exec_cmd(du_cmd)
328        blocks = int(out.split()[0])
329
330        extra_blocks = part.get_extra_block_count(blocks)
331
332        if extra_blocks < BOOTDD_EXTRA_SPACE:
333            extra_blocks = BOOTDD_EXTRA_SPACE
334
335        blocks += extra_blocks
336
337        logger.debug("Added %d extra blocks to %s to get to %d total blocks",
338                     extra_blocks, part.mountpoint, blocks)
339
340        # dosfs image, created by mkdosfs
341        bootimg = "%s/boot.img" % cr_workdir
342
343        label = part.label if part.label else "ESP"
344
345        dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \
346                    (label, part.fsuuid, bootimg, blocks)
347        exec_native_cmd(dosfs_cmd, native_sysroot)
348
349        mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
350        exec_native_cmd(mcopy_cmd, native_sysroot)
351
352        chmod_cmd = "chmod 644 %s" % bootimg
353        exec_cmd(chmod_cmd)
354
355        du_cmd = "du -Lbks %s" % bootimg
356        out = exec_cmd(du_cmd)
357        bootimg_size = out.split()[0]
358
359        part.size = int(bootimg_size)
360        part.source_file = bootimg
361