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 330 # https://www.freedesktop.org/software/systemd/man/systemd-stub.html 331 objcopy_cmd = "objcopy \ 332 --add-section .osrel=%s --change-section-vma .osrel=0x20000 \ 333 --add-section .cmdline=%s --change-section-vma .cmdline=0x30000 \ 334 --add-section .linux=%s --change-section-vma .linux=0x2000000 \ 335 --add-section .initrd=%s --change-section-vma .initrd=0x3000000 \ 336 %s %s" % \ 337 ("%s/usr/lib/os-release" % staging_dir_host, 338 cmdline.name, 339 "%s/%s" % (staging_kernel_dir, kernel), 340 initrd.name, 341 efi_stub, 342 "%s/EFI/Linux/linux.efi" % hdddir) 343 exec_cmd(objcopy_cmd) 344 else: 345 install_cmd = "install -m 0644 %s/%s %s/%s" % \ 346 (staging_kernel_dir, kernel, hdddir, kernel) 347 exec_cmd(install_cmd) 348 349 if get_bitbake_var("IMAGE_EFI_BOOT_FILES"): 350 for src_path, dst_path in cls.install_task: 351 install_cmd = "install -m 0644 -D %s %s" \ 352 % (os.path.join(kernel_dir, src_path), 353 os.path.join(hdddir, dst_path)) 354 exec_cmd(install_cmd) 355 356 try: 357 if source_params['loader'] == 'grub-efi': 358 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, 359 "%s/grub.cfg" % cr_workdir) 360 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]: 361 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:]) 362 exec_cmd(cp_cmd, True) 363 shutil.move("%s/grub.cfg" % cr_workdir, 364 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir) 365 elif source_params['loader'] == 'systemd-boot': 366 for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]: 367 cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:]) 368 exec_cmd(cp_cmd, True) 369 else: 370 raise WicError("unrecognized bootimg-efi loader: %s" % 371 source_params['loader']) 372 except KeyError: 373 raise WicError("bootimg-efi requires a loader, none specified") 374 375 startup = os.path.join(kernel_dir, "startup.nsh") 376 if os.path.exists(startup): 377 cp_cmd = "cp %s %s/" % (startup, hdddir) 378 exec_cmd(cp_cmd, True) 379 380 du_cmd = "du -bks %s" % hdddir 381 out = exec_cmd(du_cmd) 382 blocks = int(out.split()[0]) 383 384 extra_blocks = part.get_extra_block_count(blocks) 385 386 if extra_blocks < BOOTDD_EXTRA_SPACE: 387 extra_blocks = BOOTDD_EXTRA_SPACE 388 389 blocks += extra_blocks 390 391 logger.debug("Added %d extra blocks to %s to get to %d total blocks", 392 extra_blocks, part.mountpoint, blocks) 393 394 # dosfs image, created by mkdosfs 395 bootimg = "%s/boot.img" % cr_workdir 396 397 label = part.label if part.label else "ESP" 398 399 dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \ 400 (label, part.fsuuid, bootimg, blocks) 401 exec_native_cmd(dosfs_cmd, native_sysroot) 402 403 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir) 404 exec_native_cmd(mcopy_cmd, native_sysroot) 405 406 chmod_cmd = "chmod 644 %s" % bootimg 407 exec_cmd(chmod_cmd) 408 409 du_cmd = "du -Lbks %s" % bootimg 410 out = exec_cmd(du_cmd) 411 bootimg_size = out.split()[0] 412 413 part.size = int(bootimg_size) 414 part.source_file = bootimg 415