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