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 elif source_params['loader'] == 'uefi-kernel': 224 pass 225 else: 226 raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader']) 227 except KeyError: 228 raise WicError("bootimg-efi requires a loader, none specified") 229 230 if get_bitbake_var("IMAGE_EFI_BOOT_FILES") is None: 231 logger.debug('No boot files defined in IMAGE_EFI_BOOT_FILES') 232 else: 233 boot_files = None 234 for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)): 235 if fmt: 236 var = fmt % id 237 else: 238 var = "" 239 240 boot_files = get_bitbake_var("IMAGE_EFI_BOOT_FILES" + var) 241 if boot_files: 242 break 243 244 logger.debug('Boot files: %s', boot_files) 245 246 # list of tuples (src_name, dst_name) 247 deploy_files = [] 248 for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files): 249 if ';' in src_entry: 250 dst_entry = tuple(src_entry.split(';')) 251 if not dst_entry[0] or not dst_entry[1]: 252 raise WicError('Malformed boot file entry: %s' % src_entry) 253 else: 254 dst_entry = (src_entry, src_entry) 255 256 logger.debug('Destination entry: %r', dst_entry) 257 deploy_files.append(dst_entry) 258 259 cls.install_task = []; 260 for deploy_entry in deploy_files: 261 src, dst = deploy_entry 262 if '*' in src: 263 # by default install files under their basename 264 entry_name_fn = os.path.basename 265 if dst != src: 266 # unless a target name was given, then treat name 267 # as a directory and append a basename 268 entry_name_fn = lambda name: \ 269 os.path.join(dst, 270 os.path.basename(name)) 271 272 srcs = glob(os.path.join(kernel_dir, src)) 273 274 logger.debug('Globbed sources: %s', ', '.join(srcs)) 275 for entry in srcs: 276 src = os.path.relpath(entry, kernel_dir) 277 entry_dst_name = entry_name_fn(entry) 278 cls.install_task.append((src, entry_dst_name)) 279 else: 280 cls.install_task.append((src, dst)) 281 282 @classmethod 283 def do_prepare_partition(cls, part, source_params, creator, cr_workdir, 284 oe_builddir, bootimg_dir, kernel_dir, 285 rootfs_dir, native_sysroot): 286 """ 287 Called to do the actual content population for a partition i.e. it 288 'prepares' the partition to be incorporated into the image. 289 In this case, prepare content for an EFI (grub) boot partition. 290 """ 291 if not kernel_dir: 292 kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") 293 if not kernel_dir: 294 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting") 295 296 staging_kernel_dir = kernel_dir 297 298 hdddir = "%s/hdd/boot" % cr_workdir 299 300 kernel = get_bitbake_var("KERNEL_IMAGETYPE") 301 if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1": 302 if get_bitbake_var("INITRAMFS_IMAGE"): 303 kernel = "%s-%s.bin" % \ 304 (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME")) 305 306 if source_params.get('create-unified-kernel-image') == "true": 307 initrd = source_params.get('initrd') 308 if not initrd: 309 raise WicError("initrd= must be specified when create-unified-kernel-image=true, exiting") 310 311 deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") 312 efi_stub = glob("%s/%s" % (deploy_dir, "linux*.efi.stub")) 313 if len(efi_stub) == 0: 314 raise WicError("Unified Kernel Image EFI stub not found, exiting") 315 efi_stub = efi_stub[0] 316 317 with tempfile.TemporaryDirectory() as tmp_dir: 318 label = source_params.get('label') 319 label_conf = "root=%s" % creator.rootdev 320 if label: 321 label_conf = "LABEL=%s" % label 322 323 bootloader = creator.ks.bootloader 324 cmdline = open("%s/cmdline" % tmp_dir, "w") 325 cmdline.write("%s %s" % (label_conf, bootloader.append)) 326 cmdline.close() 327 328 initrds = initrd.split(';') 329 initrd = open("%s/initrd" % tmp_dir, "wb") 330 for f in initrds: 331 with open("%s/%s" % (deploy_dir, f), 'rb') as in_file: 332 shutil.copyfileobj(in_file, initrd) 333 initrd.close() 334 335 # Searched by systemd-boot: 336 # https://systemd.io/BOOT_LOADER_SPECIFICATION/#type-2-efi-unified-kernel-images 337 install_cmd = "install -d %s/EFI/Linux" % hdddir 338 exec_cmd(install_cmd) 339 340 staging_dir_host = get_bitbake_var("STAGING_DIR_HOST") 341 target_sys = get_bitbake_var("TARGET_SYS") 342 343 objdump_cmd = "%s-objdump" % target_sys 344 objdump_cmd += " -p %s" % efi_stub 345 objdump_cmd += " | awk '{ if ($1 == \"SectionAlignment\"){print $2} }'" 346 347 ret, align_str = exec_native_cmd(objdump_cmd, native_sysroot) 348 align = int(align_str, 16) 349 350 objdump_cmd = "%s-objdump" % target_sys 351 objdump_cmd += " -h %s | tail -2" % efi_stub 352 ret, output = exec_native_cmd(objdump_cmd, native_sysroot) 353 354 offset = int(output.split()[2], 16) + int(output.split()[3], 16) 355 356 osrel_off = offset + align - offset % align 357 osrel_path = "%s/usr/lib/os-release" % staging_dir_host 358 osrel_sz = os.stat(osrel_path).st_size 359 360 cmdline_off = osrel_off + osrel_sz 361 cmdline_off = cmdline_off + align - cmdline_off % align 362 cmdline_sz = os.stat(cmdline.name).st_size 363 364 dtb_off = cmdline_off + cmdline_sz 365 dtb_off = dtb_off + align - dtb_off % align 366 367 dtb = source_params.get('dtb') 368 if dtb: 369 if ';' in dtb: 370 raise WicError("Only one DTB supported, exiting") 371 dtb_path = "%s/%s" % (deploy_dir, dtb) 372 dtb_params = '--add-section .dtb=%s --change-section-vma .dtb=0x%x' % \ 373 (dtb_path, dtb_off) 374 linux_off = dtb_off + os.stat(dtb_path).st_size 375 linux_off = linux_off + align - linux_off % align 376 else: 377 dtb_params = '' 378 linux_off = dtb_off 379 380 linux_path = "%s/%s" % (staging_kernel_dir, kernel) 381 linux_sz = os.stat(linux_path).st_size 382 383 initrd_off = linux_off + linux_sz 384 initrd_off = initrd_off + align - initrd_off % align 385 386 # https://www.freedesktop.org/software/systemd/man/systemd-stub.html 387 objcopy_cmd = "%s-objcopy" % target_sys 388 objcopy_cmd += " --enable-deterministic-archives" 389 objcopy_cmd += " --preserve-dates" 390 objcopy_cmd += " --add-section .osrel=%s" % osrel_path 391 objcopy_cmd += " --change-section-vma .osrel=0x%x" % osrel_off 392 objcopy_cmd += " --add-section .cmdline=%s" % cmdline.name 393 objcopy_cmd += " --change-section-vma .cmdline=0x%x" % cmdline_off 394 objcopy_cmd += dtb_params 395 objcopy_cmd += " --add-section .linux=%s" % linux_path 396 objcopy_cmd += " --change-section-vma .linux=0x%x" % linux_off 397 objcopy_cmd += " --add-section .initrd=%s" % initrd.name 398 objcopy_cmd += " --change-section-vma .initrd=0x%x" % initrd_off 399 objcopy_cmd += " %s %s/EFI/Linux/linux.efi" % (efi_stub, hdddir) 400 401 exec_native_cmd(objcopy_cmd, native_sysroot) 402 else: 403 install_cmd = "install -m 0644 %s/%s %s/%s" % \ 404 (staging_kernel_dir, kernel, hdddir, kernel) 405 exec_cmd(install_cmd) 406 407 if get_bitbake_var("IMAGE_EFI_BOOT_FILES"): 408 for src_path, dst_path in cls.install_task: 409 install_cmd = "install -m 0644 -D %s %s" \ 410 % (os.path.join(kernel_dir, src_path), 411 os.path.join(hdddir, dst_path)) 412 exec_cmd(install_cmd) 413 414 try: 415 if source_params['loader'] == 'grub-efi': 416 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, 417 "%s/grub.cfg" % cr_workdir) 418 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]: 419 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:]) 420 exec_cmd(cp_cmd, True) 421 shutil.move("%s/grub.cfg" % cr_workdir, 422 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir) 423 elif source_params['loader'] == 'systemd-boot': 424 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]: 425 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:]) 426 exec_cmd(cp_cmd, True) 427 elif source_params['loader'] == 'uefi-kernel': 428 kernel = get_bitbake_var("KERNEL_IMAGETYPE") 429 if not kernel: 430 raise WicError("Empty KERNEL_IMAGETYPE %s\n" % target) 431 target = get_bitbake_var("TARGET_SYS") 432 if not target: 433 raise WicError("Unknown arch (TARGET_SYS) %s\n" % target) 434 435 if re.match("x86_64", target): 436 kernel_efi_image = "bootx64.efi" 437 elif re.match('i.86', target): 438 kernel_efi_image = "bootia32.efi" 439 elif re.match('aarch64', target): 440 kernel_efi_image = "bootaa64.efi" 441 elif re.match('arm', target): 442 kernel_efi_image = "bootarm.efi" 443 else: 444 raise WicError("UEFI stub kernel is incompatible with target %s" % target) 445 446 for mod in [x for x in os.listdir(kernel_dir) if x.startswith(kernel)]: 447 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, kernel_efi_image) 448 exec_cmd(cp_cmd, True) 449 else: 450 raise WicError("unrecognized bootimg-efi loader: %s" % 451 source_params['loader']) 452 except KeyError: 453 raise WicError("bootimg-efi requires a loader, none specified") 454 455 startup = os.path.join(kernel_dir, "startup.nsh") 456 if os.path.exists(startup): 457 cp_cmd = "cp %s %s/" % (startup, hdddir) 458 exec_cmd(cp_cmd, True) 459 460 for paths in part.include_path or []: 461 for path in paths: 462 cp_cmd = "cp -r %s %s/" % (path, hdddir) 463 exec_cmd(cp_cmd, True) 464 465 du_cmd = "du -bks %s" % hdddir 466 out = exec_cmd(du_cmd) 467 blocks = int(out.split()[0]) 468 469 extra_blocks = part.get_extra_block_count(blocks) 470 471 if extra_blocks < BOOTDD_EXTRA_SPACE: 472 extra_blocks = BOOTDD_EXTRA_SPACE 473 474 blocks += extra_blocks 475 476 logger.debug("Added %d extra blocks to %s to get to %d total blocks", 477 extra_blocks, part.mountpoint, blocks) 478 479 # required for compatibility with certain devices expecting file system 480 # block count to be equal to partition block count 481 if blocks < part.fixed_size: 482 blocks = part.fixed_size 483 logger.debug("Overriding %s to %d total blocks for compatibility", 484 part.mountpoint, blocks) 485 486 # dosfs image, created by mkdosfs 487 bootimg = "%s/boot.img" % cr_workdir 488 489 label = part.label if part.label else "ESP" 490 491 dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \ 492 (label, part.fsuuid, bootimg, blocks) 493 exec_native_cmd(dosfs_cmd, native_sysroot) 494 495 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir) 496 exec_native_cmd(mcopy_cmd, native_sysroot) 497 498 chmod_cmd = "chmod 644 %s" % bootimg 499 exec_cmd(chmod_cmd) 500 501 du_cmd = "du -Lbks %s" % bootimg 502 out = exec_cmd(du_cmd) 503 bootimg_size = out.split()[0] 504 505 part.size = int(bootimg_size) 506 part.source_file = bootimg 507