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 dtb = source_params.get('dtb') 336 if dtb: 337 if ';' in dtb: 338 raise WicError("Only one DTB supported, exiting") 339 dtb_params = '--add-section .dtb=%s/%s --change-section-vma .dtb=0x40000' % \ 340 (deploy_dir, dtb) 341 else: 342 dtb_params = '' 343 344 # Searched by systemd-boot: 345 # https://systemd.io/BOOT_LOADER_SPECIFICATION/#type-2-efi-unified-kernel-images 346 install_cmd = "install -d %s/EFI/Linux" % hdddir 347 exec_cmd(install_cmd) 348 349 staging_dir_host = get_bitbake_var("STAGING_DIR_HOST") 350 target_sys = get_bitbake_var("TARGET_SYS") 351 352 # https://www.freedesktop.org/software/systemd/man/systemd-stub.html 353 objcopy_cmd = "%s-objcopy" % target_sys 354 objcopy_cmd += " --enable-deterministic-archives" 355 objcopy_cmd += " --preserve-dates" 356 objcopy_cmd += " --add-section .osrel=%s/usr/lib/os-release" % staging_dir_host 357 objcopy_cmd += " --change-section-vma .osrel=0x20000" 358 objcopy_cmd += " --add-section .cmdline=%s" % cmdline.name 359 objcopy_cmd += " --change-section-vma .cmdline=0x30000" 360 objcopy_cmd += dtb_params 361 objcopy_cmd += " --add-section .linux=%s/%s" % (staging_kernel_dir, kernel) 362 objcopy_cmd += " --change-section-vma .linux=0x2000000" 363 objcopy_cmd += " --add-section .initrd=%s" % initrd.name 364 objcopy_cmd += " --change-section-vma .initrd=0x3000000" 365 objcopy_cmd += " %s %s/EFI/Linux/linux.efi" % (efi_stub, hdddir) 366 exec_native_cmd(objcopy_cmd, native_sysroot) 367 else: 368 install_cmd = "install -m 0644 %s/%s %s/%s" % \ 369 (staging_kernel_dir, kernel, hdddir, kernel) 370 exec_cmd(install_cmd) 371 372 if get_bitbake_var("IMAGE_EFI_BOOT_FILES"): 373 for src_path, dst_path in cls.install_task: 374 install_cmd = "install -m 0644 -D %s %s" \ 375 % (os.path.join(kernel_dir, src_path), 376 os.path.join(hdddir, dst_path)) 377 exec_cmd(install_cmd) 378 379 try: 380 if source_params['loader'] == 'grub-efi': 381 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, 382 "%s/grub.cfg" % cr_workdir) 383 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]: 384 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:]) 385 exec_cmd(cp_cmd, True) 386 shutil.move("%s/grub.cfg" % cr_workdir, 387 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir) 388 elif source_params['loader'] == 'systemd-boot': 389 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]: 390 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:]) 391 exec_cmd(cp_cmd, True) 392 elif source_params['loader'] == 'uefi-kernel': 393 kernel = get_bitbake_var("KERNEL_IMAGETYPE") 394 if not kernel: 395 raise WicError("Empty KERNEL_IMAGETYPE %s\n" % target) 396 target = get_bitbake_var("TARGET_SYS") 397 if not target: 398 raise WicError("Unknown arch (TARGET_SYS) %s\n" % target) 399 400 if re.match("x86_64", target): 401 kernel_efi_image = "bootx64.efi" 402 elif re.match('i.86', target): 403 kernel_efi_image = "bootia32.efi" 404 elif re.match('aarch64', target): 405 kernel_efi_image = "bootaa64.efi" 406 elif re.match('arm', target): 407 kernel_efi_image = "bootarm.efi" 408 else: 409 raise WicError("UEFI stub kernel is incompatible with target %s" % target) 410 411 for mod in [x for x in os.listdir(kernel_dir) if x.startswith(kernel)]: 412 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, kernel_efi_image) 413 exec_cmd(cp_cmd, True) 414 else: 415 raise WicError("unrecognized bootimg-efi loader: %s" % 416 source_params['loader']) 417 except KeyError: 418 raise WicError("bootimg-efi requires a loader, none specified") 419 420 startup = os.path.join(kernel_dir, "startup.nsh") 421 if os.path.exists(startup): 422 cp_cmd = "cp %s %s/" % (startup, hdddir) 423 exec_cmd(cp_cmd, True) 424 425 for paths in part.include_path or []: 426 for path in paths: 427 cp_cmd = "cp -r %s %s/" % (path, hdddir) 428 exec_cmd(cp_cmd, True) 429 430 du_cmd = "du -bks %s" % hdddir 431 out = exec_cmd(du_cmd) 432 blocks = int(out.split()[0]) 433 434 extra_blocks = part.get_extra_block_count(blocks) 435 436 if extra_blocks < BOOTDD_EXTRA_SPACE: 437 extra_blocks = BOOTDD_EXTRA_SPACE 438 439 blocks += extra_blocks 440 441 logger.debug("Added %d extra blocks to %s to get to %d total blocks", 442 extra_blocks, part.mountpoint, blocks) 443 444 # required for compatibility with certain devices expecting file system 445 # block count to be equal to partition block count 446 if blocks < part.fixed_size: 447 blocks = part.fixed_size 448 logger.debug("Overriding %s to %d total blocks for compatibility", 449 part.mountpoint, blocks) 450 451 # dosfs image, created by mkdosfs 452 bootimg = "%s/boot.img" % cr_workdir 453 454 label = part.label if part.label else "ESP" 455 456 dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \ 457 (label, part.fsuuid, bootimg, blocks) 458 exec_native_cmd(dosfs_cmd, native_sysroot) 459 460 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir) 461 exec_native_cmd(mcopy_cmd, native_sysroot) 462 463 chmod_cmd = "chmod 644 %s" % bootimg 464 exec_cmd(chmod_cmd) 465 466 du_cmd = "du -Lbks %s" % bootimg 467 out = exec_cmd(du_cmd) 468 bootimg_size = out.split()[0] 469 470 part.size = int(bootimg_size) 471 part.source_file = bootimg 472