bootimg_efi.py 17 KB

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