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