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