isoimage-isohybrid.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. #
  2. # Copyright OpenEmbedded Contributors
  3. #
  4. # SPDX-License-Identifier: GPL-2.0-only
  5. #
  6. # DESCRIPTION
  7. # This implements the 'isoimage-isohybrid' source plugin class for 'wic'
  8. #
  9. # AUTHORS
  10. # Mihaly Varga <mihaly.varga (at] ni.com>
  11. import glob
  12. import logging
  13. import os
  14. import re
  15. import shutil
  16. from wic import WicError
  17. from wic.engine import get_custom_config
  18. from wic.pluginbase import SourcePlugin
  19. from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
  20. logger = logging.getLogger('wic')
  21. class IsoImagePlugin(SourcePlugin):
  22. """
  23. Create a bootable ISO image
  24. This plugin creates a hybrid, legacy and EFI bootable ISO image. The
  25. generated image can be used on optical media as well as USB media.
  26. Legacy boot uses syslinux and EFI boot uses grub or gummiboot (not
  27. implemented yet) as bootloader. The plugin creates the directories required
  28. by bootloaders and populates them by creating and configuring the
  29. bootloader files.
  30. Example kickstart file:
  31. part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi, \\
  32. image_name= IsoImage" --ondisk cd --label LIVECD
  33. bootloader --timeout=10 --append=" "
  34. In --sourceparams "loader" specifies the bootloader used for booting in EFI
  35. mode, while "image_name" specifies the name of the generated image. In the
  36. example above, wic creates an ISO image named IsoImage-cd.direct (default
  37. extension added by direct imeger plugin) and a file named IsoImage-cd.iso
  38. """
  39. name = 'isoimage-isohybrid'
  40. @classmethod
  41. def do_configure_syslinux(cls, creator, cr_workdir):
  42. """
  43. Create loader-specific (syslinux) config
  44. """
  45. splash = os.path.join(cr_workdir, "ISO/boot/splash.jpg")
  46. if os.path.exists(splash):
  47. splashline = "menu background splash.jpg"
  48. else:
  49. splashline = ""
  50. bootloader = creator.ks.bootloader
  51. syslinux_conf = ""
  52. syslinux_conf += "PROMPT 0\n"
  53. syslinux_conf += "TIMEOUT %s \n" % (bootloader.timeout or 10)
  54. syslinux_conf += "\n"
  55. syslinux_conf += "ALLOWOPTIONS 1\n"
  56. syslinux_conf += "SERIAL 0 115200\n"
  57. syslinux_conf += "\n"
  58. if splashline:
  59. syslinux_conf += "%s\n" % splashline
  60. syslinux_conf += "DEFAULT boot\n"
  61. syslinux_conf += "LABEL boot\n"
  62. kernel = get_bitbake_var("KERNEL_IMAGETYPE")
  63. if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
  64. if get_bitbake_var("INITRAMFS_IMAGE"):
  65. kernel = "%s-%s.bin" % \
  66. (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
  67. syslinux_conf += "KERNEL /" + kernel + "\n"
  68. syslinux_conf += "APPEND initrd=/initrd LABEL=boot %s\n" \
  69. % bootloader.append
  70. logger.debug("Writing syslinux config %s/ISO/isolinux/isolinux.cfg",
  71. cr_workdir)
  72. with open("%s/ISO/isolinux/isolinux.cfg" % cr_workdir, "w") as cfg:
  73. cfg.write(syslinux_conf)
  74. @classmethod
  75. def do_configure_grubefi(cls, part, creator, target_dir):
  76. """
  77. Create loader-specific (grub-efi) config
  78. """
  79. configfile = creator.ks.bootloader.configfile
  80. if configfile:
  81. grubefi_conf = get_custom_config(configfile)
  82. if grubefi_conf:
  83. logger.debug("Using custom configuration file %s for grub.cfg",
  84. configfile)
  85. else:
  86. raise WicError("configfile is specified "
  87. "but failed to get it from %s", configfile)
  88. else:
  89. splash = os.path.join(target_dir, "splash.jpg")
  90. if os.path.exists(splash):
  91. splashline = "menu background splash.jpg"
  92. else:
  93. splashline = ""
  94. bootloader = creator.ks.bootloader
  95. grubefi_conf = ""
  96. grubefi_conf += "serial --unit=0 --speed=115200 --word=8 "
  97. grubefi_conf += "--parity=no --stop=1\n"
  98. grubefi_conf += "default=boot\n"
  99. grubefi_conf += "timeout=%s\n" % (bootloader.timeout or 10)
  100. grubefi_conf += "\n"
  101. grubefi_conf += "search --set=root --label %s " % part.label
  102. grubefi_conf += "\n"
  103. grubefi_conf += "menuentry 'boot'{\n"
  104. kernel = get_bitbake_var("KERNEL_IMAGETYPE")
  105. if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
  106. if get_bitbake_var("INITRAMFS_IMAGE"):
  107. kernel = "%s-%s.bin" % \
  108. (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
  109. grubefi_conf += "linux /%s rootwait %s\n" \
  110. % (kernel, bootloader.append)
  111. grubefi_conf += "initrd /initrd \n"
  112. grubefi_conf += "}\n"
  113. if splashline:
  114. grubefi_conf += "%s\n" % splashline
  115. cfg_path = os.path.join(target_dir, "grub.cfg")
  116. logger.debug("Writing grubefi config %s", cfg_path)
  117. with open(cfg_path, "w") as cfg:
  118. cfg.write(grubefi_conf)
  119. @staticmethod
  120. def _build_initramfs_path(rootfs_dir, cr_workdir):
  121. """
  122. Create path for initramfs image
  123. """
  124. initrd = get_bitbake_var("INITRD_LIVE") or get_bitbake_var("INITRD")
  125. if not initrd:
  126. initrd_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
  127. if not initrd_dir:
  128. raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting.")
  129. image_name = get_bitbake_var("IMAGE_BASENAME")
  130. if not image_name:
  131. raise WicError("Couldn't find IMAGE_BASENAME, exiting.")
  132. image_type = get_bitbake_var("INITRAMFS_FSTYPES")
  133. if not image_type:
  134. raise WicError("Couldn't find INITRAMFS_FSTYPES, exiting.")
  135. machine = os.path.basename(initrd_dir)
  136. pattern = '%s/%s*%s.%s' % (initrd_dir, image_name, machine, image_type)
  137. files = glob.glob(pattern)
  138. if files:
  139. initrd = files[0]
  140. if not initrd or not os.path.exists(initrd):
  141. # Create initrd from rootfs directory
  142. initrd = "%s/initrd.cpio.gz" % cr_workdir
  143. initrd_dir = "%s/INITRD" % cr_workdir
  144. shutil.copytree("%s" % rootfs_dir, \
  145. "%s" % initrd_dir, symlinks=True)
  146. if os.path.isfile("%s/init" % rootfs_dir):
  147. shutil.copy2("%s/init" % rootfs_dir, "%s/init" % initrd_dir)
  148. elif os.path.lexists("%s/init" % rootfs_dir):
  149. os.symlink(os.readlink("%s/init" % rootfs_dir), \
  150. "%s/init" % initrd_dir)
  151. elif os.path.isfile("%s/sbin/init" % rootfs_dir):
  152. shutil.copy2("%s/sbin/init" % rootfs_dir, \
  153. "%s" % initrd_dir)
  154. elif os.path.lexists("%s/sbin/init" % rootfs_dir):
  155. os.symlink(os.readlink("%s/sbin/init" % rootfs_dir), \
  156. "%s/init" % initrd_dir)
  157. else:
  158. raise WicError("Couldn't find or build initrd, exiting.")
  159. exec_cmd("cd %s && find . | cpio -o -H newc -R root:root >%s/initrd.cpio " \
  160. % (initrd_dir, cr_workdir), as_shell=True)
  161. exec_cmd("gzip -f -9 %s/initrd.cpio" % cr_workdir, as_shell=True)
  162. shutil.rmtree(initrd_dir)
  163. return initrd
  164. @classmethod
  165. def do_configure_partition(cls, part, source_params, creator, cr_workdir,
  166. oe_builddir, bootimg_dir, kernel_dir,
  167. native_sysroot):
  168. """
  169. Called before do_prepare_partition(), creates loader-specific config
  170. """
  171. isodir = "%s/ISO/" % cr_workdir
  172. if os.path.exists(isodir):
  173. shutil.rmtree(isodir)
  174. install_cmd = "install -d %s " % isodir
  175. exec_cmd(install_cmd)
  176. # Overwrite the name of the created image
  177. logger.debug(source_params)
  178. if 'image_name' in source_params and \
  179. source_params['image_name'].strip():
  180. creator.name = source_params['image_name'].strip()
  181. logger.debug("The name of the image is: %s", creator.name)
  182. @staticmethod
  183. def _install_payload(source_params, iso_dir):
  184. """
  185. Copies contents of payload directory (as specified in 'payload_dir' param) into iso_dir
  186. """
  187. if source_params.get('payload_dir'):
  188. payload_dir = source_params['payload_dir']
  189. logger.debug("Payload directory: %s", payload_dir)
  190. shutil.copytree(payload_dir, iso_dir, symlinks=True, dirs_exist_ok=True)
  191. @classmethod
  192. def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
  193. oe_builddir, bootimg_dir, kernel_dir,
  194. rootfs_dir, native_sysroot):
  195. """
  196. Called to do the actual content population for a partition i.e. it
  197. 'prepares' the partition to be incorporated into the image.
  198. In this case, prepare content for a bootable ISO image.
  199. """
  200. isodir = "%s/ISO" % cr_workdir
  201. cls._install_payload(source_params, isodir)
  202. if part.rootfs_dir is None:
  203. if not 'ROOTFS_DIR' in rootfs_dir:
  204. raise WicError("Couldn't find --rootfs-dir, exiting.")
  205. rootfs_dir = rootfs_dir['ROOTFS_DIR']
  206. else:
  207. if part.rootfs_dir in rootfs_dir:
  208. rootfs_dir = rootfs_dir[part.rootfs_dir]
  209. elif part.rootfs_dir:
  210. rootfs_dir = part.rootfs_dir
  211. else:
  212. raise WicError("Couldn't find --rootfs-dir=%s connection "
  213. "or it is not a valid path, exiting." %
  214. part.rootfs_dir)
  215. if not os.path.isdir(rootfs_dir):
  216. rootfs_dir = get_bitbake_var("IMAGE_ROOTFS")
  217. if not os.path.isdir(rootfs_dir):
  218. raise WicError("Couldn't find IMAGE_ROOTFS, exiting.")
  219. part.rootfs_dir = rootfs_dir
  220. deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
  221. img_iso_dir = get_bitbake_var("ISODIR")
  222. # Remove the temporary file created by part.prepare_rootfs()
  223. if os.path.isfile(part.source_file):
  224. os.remove(part.source_file)
  225. # Support using a different initrd other than default
  226. if source_params.get('initrd'):
  227. initrd = source_params['initrd']
  228. if not deploy_dir:
  229. raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
  230. cp_cmd = "cp %s/%s %s" % (deploy_dir, initrd, cr_workdir)
  231. exec_cmd(cp_cmd)
  232. else:
  233. # Prepare initial ramdisk
  234. initrd = "%s/initrd" % deploy_dir
  235. if not os.path.isfile(initrd):
  236. initrd = "%s/initrd" % img_iso_dir
  237. if not os.path.isfile(initrd):
  238. initrd = cls._build_initramfs_path(rootfs_dir, cr_workdir)
  239. install_cmd = "install -m 0644 %s %s/initrd" % (initrd, isodir)
  240. exec_cmd(install_cmd)
  241. # Remove the temporary file created by _build_initramfs_path function
  242. if os.path.isfile("%s/initrd.cpio.gz" % cr_workdir):
  243. os.remove("%s/initrd.cpio.gz" % cr_workdir)
  244. kernel = get_bitbake_var("KERNEL_IMAGETYPE")
  245. if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
  246. if get_bitbake_var("INITRAMFS_IMAGE"):
  247. kernel = "%s-%s.bin" % \
  248. (get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
  249. install_cmd = "install -m 0644 %s/%s %s/%s" % \
  250. (kernel_dir, kernel, isodir, kernel)
  251. exec_cmd(install_cmd)
  252. #Create bootloader for efi boot
  253. try:
  254. target_dir = "%s/EFI/BOOT" % isodir
  255. if os.path.exists(target_dir):
  256. shutil.rmtree(target_dir)
  257. os.makedirs(target_dir)
  258. if source_params['loader'] == 'grub-efi':
  259. # Builds bootx64.efi/bootia32.efi if ISODIR didn't exist or
  260. # didn't contains it
  261. target_arch = get_bitbake_var("TARGET_SYS")
  262. if not target_arch:
  263. raise WicError("Coludn't find target architecture")
  264. if re.match("x86_64", target_arch):
  265. grub_src_image = "grub-efi-bootx64.efi"
  266. grub_dest_image = "bootx64.efi"
  267. elif re.match('i.86', target_arch):
  268. grub_src_image = "grub-efi-bootia32.efi"
  269. grub_dest_image = "bootia32.efi"
  270. else:
  271. raise WicError("grub-efi is incompatible with target %s" %
  272. target_arch)
  273. grub_target = os.path.join(target_dir, grub_dest_image)
  274. if not os.path.isfile(grub_target):
  275. grub_src = os.path.join(deploy_dir, grub_src_image)
  276. if not os.path.exists(grub_src):
  277. raise WicError("Grub loader %s is not found in %s. "
  278. "Please build grub-efi first" % (grub_src_image, deploy_dir))
  279. shutil.copy(grub_src, grub_target)
  280. if not os.path.isfile(os.path.join(target_dir, "boot.cfg")):
  281. cls.do_configure_grubefi(part, creator, target_dir)
  282. else:
  283. raise WicError("unrecognized bootimg-efi loader: %s" %
  284. source_params['loader'])
  285. except KeyError:
  286. raise WicError("bootimg-efi requires a loader, none specified")
  287. # Create efi.img that contains bootloader files for EFI booting
  288. # if ISODIR didn't exist or didn't contains it
  289. if os.path.isfile("%s/efi.img" % img_iso_dir):
  290. install_cmd = "install -m 0644 %s/efi.img %s/efi.img" % \
  291. (img_iso_dir, isodir)
  292. exec_cmd(install_cmd)
  293. else:
  294. # Default to 100 blocks of extra space for file system overhead
  295. esp_extra_blocks = int(source_params.get('esp_extra_blocks', '100'))
  296. du_cmd = "du -bks %s/EFI" % isodir
  297. out = exec_cmd(du_cmd)
  298. blocks = int(out.split()[0])
  299. blocks += esp_extra_blocks
  300. logger.debug("Added 100 extra blocks to %s to get to %d "
  301. "total blocks", part.mountpoint, blocks)
  302. # dosfs image for EFI boot
  303. bootimg = "%s/efi.img" % isodir
  304. esp_label = source_params.get('esp_label', 'EFIimg')
  305. dosfs_cmd = 'mkfs.vfat -n \'%s\' -S 512 -C %s %d' \
  306. % (esp_label, bootimg, blocks)
  307. exec_native_cmd(dosfs_cmd, native_sysroot)
  308. mmd_cmd = "mmd -i %s ::/EFI" % bootimg
  309. exec_native_cmd(mmd_cmd, native_sysroot)
  310. mcopy_cmd = "mcopy -i %s -s %s/EFI/* ::/EFI/" \
  311. % (bootimg, isodir)
  312. exec_native_cmd(mcopy_cmd, native_sysroot)
  313. chmod_cmd = "chmod 644 %s" % bootimg
  314. exec_cmd(chmod_cmd)
  315. # Prepare files for legacy boot
  316. syslinux_dir = get_bitbake_var("STAGING_DATADIR")
  317. if not syslinux_dir:
  318. raise WicError("Couldn't find STAGING_DATADIR, exiting.")
  319. if os.path.exists("%s/isolinux" % isodir):
  320. shutil.rmtree("%s/isolinux" % isodir)
  321. install_cmd = "install -d %s/isolinux" % isodir
  322. exec_cmd(install_cmd)
  323. cls.do_configure_syslinux(creator, cr_workdir)
  324. install_cmd = "install -m 444 %s/syslinux/ldlinux.sys " % syslinux_dir
  325. install_cmd += "%s/isolinux/ldlinux.sys" % isodir
  326. exec_cmd(install_cmd)
  327. install_cmd = "install -m 444 %s/syslinux/isohdpfx.bin " % syslinux_dir
  328. install_cmd += "%s/isolinux/isohdpfx.bin" % isodir
  329. exec_cmd(install_cmd)
  330. install_cmd = "install -m 644 %s/syslinux/isolinux.bin " % syslinux_dir
  331. install_cmd += "%s/isolinux/isolinux.bin" % isodir
  332. exec_cmd(install_cmd)
  333. install_cmd = "install -m 644 %s/syslinux/ldlinux.c32 " % syslinux_dir
  334. install_cmd += "%s/isolinux/ldlinux.c32" % isodir
  335. exec_cmd(install_cmd)
  336. #create ISO image
  337. iso_img = "%s/tempiso_img.iso" % cr_workdir
  338. iso_bootimg = "isolinux/isolinux.bin"
  339. iso_bootcat = "isolinux/boot.cat"
  340. efi_img = "efi.img"
  341. mkisofs_cmd = "mkisofs -V %s " % part.label
  342. mkisofs_cmd += "-o %s -U " % iso_img
  343. mkisofs_cmd += "-J -joliet-long -r -iso-level 2 -b %s " % iso_bootimg
  344. mkisofs_cmd += "-c %s -no-emul-boot -boot-load-size 4 " % iso_bootcat
  345. mkisofs_cmd += "-boot-info-table -eltorito-alt-boot "
  346. mkisofs_cmd += "-eltorito-platform 0xEF -eltorito-boot %s " % efi_img
  347. mkisofs_cmd += "-no-emul-boot %s " % isodir
  348. logger.debug("running command: %s", mkisofs_cmd)
  349. exec_native_cmd(mkisofs_cmd, native_sysroot)
  350. shutil.rmtree(isodir)
  351. du_cmd = "du -Lbks %s" % iso_img
  352. out = exec_cmd(du_cmd)
  353. isoimg_size = int(out.split()[0])
  354. part.size = isoimg_size
  355. part.source_file = iso_img
  356. @classmethod
  357. def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
  358. bootimg_dir, kernel_dir, native_sysroot):
  359. """
  360. Called after all partitions have been prepared and assembled into a
  361. disk image. In this case, we insert/modify the MBR using isohybrid
  362. utility for booting via BIOS from disk storage devices.
  363. """
  364. iso_img = "%s.p1" % disk.path
  365. full_path = creator._full_path(workdir, disk_name, "direct")
  366. full_path_iso = creator._full_path(workdir, disk_name, "iso")
  367. isohybrid_cmd = "isohybrid -u %s" % iso_img
  368. logger.debug("running command: %s", isohybrid_cmd)
  369. exec_native_cmd(isohybrid_cmd, native_sysroot)
  370. # Replace the image created by direct plugin with the one created by
  371. # mkisofs command. This is necessary because the iso image created by
  372. # mkisofs has a very specific MBR is system area of the ISO image, and
  373. # direct plugin adds and configures an another MBR.
  374. logger.debug("Replaceing the image created by direct plugin\n")
  375. os.remove(disk.path)
  376. shutil.copy2(iso_img, full_path_iso)
  377. shutil.copy2(full_path_iso, full_path)