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