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