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 if source_params.get('install-kernel-into-boot-dir') != 'false': 404 install_cmd = "install -m 0644 %s/%s %s/%s" % \ 405 (staging_kernel_dir, kernel, hdddir, kernel) 406 exec_cmd(install_cmd) 407 408 if get_bitbake_var("IMAGE_EFI_BOOT_FILES"): 409 for src_path, dst_path in cls.install_task: 410 install_cmd = "install -m 0644 -D %s %s" \ 411 % (os.path.join(kernel_dir, src_path), 412 os.path.join(hdddir, dst_path)) 413 exec_cmd(install_cmd) 414 415 try: 416 if source_params['loader'] == 'grub-efi': 417 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, 418 "%s/grub.cfg" % cr_workdir) 419 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]: 420 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:]) 421 exec_cmd(cp_cmd, True) 422 shutil.move("%s/grub.cfg" % cr_workdir, 423 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir) 424 elif source_params['loader'] == 'systemd-boot': 425 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]: 426 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:]) 427 exec_cmd(cp_cmd, True) 428 elif source_params['loader'] == 'uefi-kernel': 429 kernel = get_bitbake_var("KERNEL_IMAGETYPE") 430 if not kernel: 431 raise WicError("Empty KERNEL_IMAGETYPE %s\n" % target) 432 target = get_bitbake_var("TARGET_SYS") 433 if not target: 434 raise WicError("Unknown arch (TARGET_SYS) %s\n" % target) 435 436 if re.match("x86_64", target): 437 kernel_efi_image = "bootx64.efi" 438 elif re.match('i.86', target): 439 kernel_efi_image = "bootia32.efi" 440 elif re.match('aarch64', target): 441 kernel_efi_image = "bootaa64.efi" 442 elif re.match('arm', target): 443 kernel_efi_image = "bootarm.efi" 444 else: 445 raise WicError("UEFI stub kernel is incompatible with target %s" % target) 446 447 for mod in [x for x in os.listdir(kernel_dir) if x.startswith(kernel)]: 448 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, kernel_efi_image) 449 exec_cmd(cp_cmd, True) 450 else: 451 raise WicError("unrecognized bootimg-efi loader: %s" % 452 source_params['loader']) 453 except KeyError: 454 raise WicError("bootimg-efi requires a loader, none specified") 455 456 startup = os.path.join(kernel_dir, "startup.nsh") 457 if os.path.exists(startup): 458 cp_cmd = "cp %s %s/" % (startup, hdddir) 459 exec_cmd(cp_cmd, True) 460 461 for paths in part.include_path or []: 462 for path in paths: 463 cp_cmd = "cp -r %s %s/" % (path, hdddir) 464 exec_cmd(cp_cmd, True) 465 466 du_cmd = "du -bks %s" % hdddir 467 out = exec_cmd(du_cmd) 468 blocks = int(out.split()[0]) 469 470 extra_blocks = part.get_extra_block_count(blocks) 471 472 if extra_blocks < BOOTDD_EXTRA_SPACE: 473 extra_blocks = BOOTDD_EXTRA_SPACE 474 475 blocks += extra_blocks 476 477 logger.debug("Added %d extra blocks to %s to get to %d total blocks", 478 extra_blocks, part.mountpoint, blocks) 479 480 # required for compatibility with certain devices expecting file system 481 # block count to be equal to partition block count 482 if blocks < part.fixed_size: 483 blocks = part.fixed_size 484 logger.debug("Overriding %s to %d total blocks for compatibility", 485 part.mountpoint, blocks) 486 487 # dosfs image, created by mkdosfs 488 bootimg = "%s/boot.img" % cr_workdir 489 490 label = part.label if part.label else "ESP" 491 492 dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \ 493 (label, part.fsuuid, bootimg, blocks) 494 exec_native_cmd(dosfs_cmd, native_sysroot) 495 496 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir) 497 exec_native_cmd(mcopy_cmd, native_sysroot) 498 499 chmod_cmd = "chmod 644 %s" % bootimg 500 exec_cmd(chmod_cmd) 501 502 du_cmd = "du -Lbks %s" % bootimg 503 out = exec_cmd(du_cmd) 504 bootimg_size = out.split()[0] 505 506 part.size = int(bootimg_size) 507 part.source_file = bootimg 508