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 --fstype=ext2 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 +0:+0 >./initrd.cpio " \ 195 % initrd_dir, as_shell=True) 196 exec_cmd("gzip -f -9 -c %s/initrd.cpio > %s" \ 197 % (cr_workdir, initrd), as_shell=True) 198 shutil.rmtree(initrd_dir) 199 200 return initrd 201 202 @classmethod 203 def do_configure_partition(cls, part, source_params, creator, cr_workdir, 204 oe_builddir, bootimg_dir, kernel_dir, 205 native_sysroot): 206 """ 207 Called before do_prepare_partition(), creates loader-specific config 208 """ 209 isodir = "%s/ISO/" % cr_workdir 210 211 if os.path.exists(isodir): 212 shutil.rmtree(isodir) 213 214 install_cmd = "install -d %s " % isodir 215 exec_cmd(install_cmd) 216 217 # Overwrite the name of the created image 218 logger.debug(source_params) 219 if 'image_name' in source_params and \ 220 source_params['image_name'].strip(): 221 creator.name = source_params['image_name'].strip() 222 logger.debug("The name of the image is: %s", creator.name) 223 224 @classmethod 225 def do_prepare_partition(cls, part, source_params, creator, cr_workdir, 226 oe_builddir, bootimg_dir, kernel_dir, 227 rootfs_dir, native_sysroot): 228 """ 229 Called to do the actual content population for a partition i.e. it 230 'prepares' the partition to be incorporated into the image. 231 In this case, prepare content for a bootable ISO image. 232 """ 233 234 isodir = "%s/ISO" % cr_workdir 235 236 if part.rootfs_dir is None: 237 if not 'ROOTFS_DIR' in rootfs_dir: 238 raise WicError("Couldn't find --rootfs-dir, exiting.") 239 rootfs_dir = rootfs_dir['ROOTFS_DIR'] 240 else: 241 if part.rootfs_dir in rootfs_dir: 242 rootfs_dir = rootfs_dir[part.rootfs_dir] 243 elif part.rootfs_dir: 244 rootfs_dir = part.rootfs_dir 245 else: 246 raise WicError("Couldn't find --rootfs-dir=%s connection " 247 "or it is not a valid path, exiting." % 248 part.rootfs_dir) 249 250 if not os.path.isdir(rootfs_dir): 251 rootfs_dir = get_bitbake_var("IMAGE_ROOTFS") 252 if not os.path.isdir(rootfs_dir): 253 raise WicError("Couldn't find IMAGE_ROOTFS, exiting.") 254 255 part.rootfs_dir = rootfs_dir 256 257 # Prepare rootfs.img 258 deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") 259 img_iso_dir = get_bitbake_var("ISODIR") 260 rootfs_img = "%s/rootfs.img" % img_iso_dir 261 if not os.path.isfile(rootfs_img): 262 # check if rootfs.img is in deploydir 263 deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE") 264 image_name = get_bitbake_var("IMAGE_LINK_NAME") 265 rootfs_img = "%s/%s.%s" \ 266 % (deploy_dir, image_name, part.fstype) 267 268 if not os.path.isfile(rootfs_img): 269 # create image file with type specified by --fstype 270 # which contains rootfs 271 du_cmd = "du -bks %s" % rootfs_dir 272 out = exec_cmd(du_cmd) 273 part.size = int(out.split()[0]) 274 part.extra_space = 0 275 part.overhead_factor = 1.2 276 part.prepare_rootfs(cr_workdir, oe_builddir, rootfs_dir, \ 277 native_sysroot) 278 rootfs_img = part.source_file 279 280 install_cmd = "install -m 0644 %s %s/rootfs.img" \ 281 % (rootfs_img, isodir) 282 exec_cmd(install_cmd) 283 284 # Remove the temporary file created by part.prepare_rootfs() 285 if os.path.isfile(part.source_file): 286 os.remove(part.source_file) 287 288 # Support using a different initrd other than default 289 if source_params.get('initrd'): 290 initrd = source_params['initrd'] 291 if not deploy_dir: 292 raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting") 293 cp_cmd = "cp %s/%s %s" % (deploy_dir, initrd, cr_workdir) 294 exec_cmd(cp_cmd) 295 else: 296 # Prepare initial ramdisk 297 initrd = "%s/initrd" % deploy_dir 298 if not os.path.isfile(initrd): 299 initrd = "%s/initrd" % img_iso_dir 300 if not os.path.isfile(initrd): 301 initrd = cls._build_initramfs_path(rootfs_dir, cr_workdir) 302 303 install_cmd = "install -m 0644 %s %s/initrd" % (initrd, isodir) 304 exec_cmd(install_cmd) 305 306 # Remove the temporary file created by _build_initramfs_path function 307 if os.path.isfile("%s/initrd.cpio.gz" % cr_workdir): 308 os.remove("%s/initrd.cpio.gz" % cr_workdir) 309 310 # Install bzImage 311 install_cmd = "install -m 0644 %s/bzImage %s/bzImage" % \ 312 (kernel_dir, isodir) 313 exec_cmd(install_cmd) 314 315 #Create bootloader for efi boot 316 try: 317 target_dir = "%s/EFI/BOOT" % isodir 318 if os.path.exists(target_dir): 319 shutil.rmtree(target_dir) 320 321 os.makedirs(target_dir) 322 323 if source_params['loader'] == 'grub-efi': 324 # Builds bootx64.efi/bootia32.efi if ISODIR didn't exist or 325 # didn't contains it 326 target_arch = get_bitbake_var("TARGET_SYS") 327 if not target_arch: 328 raise WicError("Coludn't find target architecture") 329 330 if re.match("x86_64", target_arch): 331 grub_src_image = "grub-efi-bootx64.efi" 332 grub_dest_image = "bootx64.efi" 333 elif re.match('i.86', target_arch): 334 grub_src_image = "grub-efi-bootia32.efi" 335 grub_dest_image = "bootia32.efi" 336 else: 337 raise WicError("grub-efi is incompatible with target %s" % 338 target_arch) 339 340 grub_target = os.path.join(target_dir, grub_dest_image) 341 if not os.path.isfile(grub_target): 342 grub_src = os.path.join(deploy_dir, grub_src_image) 343 if not os.path.exists(grub_src): 344 raise WicError("Grub loader %s is not found in %s. " 345 "Please build grub-efi first" % (grub_image, deploy_dir)) 346 shutil.copy(grub_src, grub_target) 347 348 if not os.path.isfile(os.path.join(target_dir, "boot.cfg")): 349 cls.do_configure_grubefi(part, creator, target_dir) 350 351 else: 352 raise WicError("unrecognized bootimg-efi loader: %s" % 353 source_params['loader']) 354 except KeyError: 355 raise WicError("bootimg-efi requires a loader, none specified") 356 357 # Create efi.img that contains bootloader files for EFI booting 358 # if ISODIR didn't exist or didn't contains it 359 if os.path.isfile("%s/efi.img" % img_iso_dir): 360 install_cmd = "install -m 0644 %s/efi.img %s/efi.img" % \ 361 (img_iso_dir, isodir) 362 exec_cmd(install_cmd) 363 else: 364 du_cmd = "du -bks %s/EFI" % isodir 365 out = exec_cmd(du_cmd) 366 blocks = int(out.split()[0]) 367 # Add some extra space for file system overhead 368 blocks += 100 369 logger.debug("Added 100 extra blocks to %s to get to %d " 370 "total blocks", part.mountpoint, blocks) 371 372 # dosfs image for EFI boot 373 bootimg = "%s/efi.img" % isodir 374 375 dosfs_cmd = 'mkfs.vfat -n "EFIimg" -S 512 -C %s %d' \ 376 % (bootimg, blocks) 377 exec_native_cmd(dosfs_cmd, native_sysroot) 378 379 mmd_cmd = "mmd -i %s ::/EFI" % bootimg 380 exec_native_cmd(mmd_cmd, native_sysroot) 381 382 mcopy_cmd = "mcopy -i %s -s %s/EFI/* ::/EFI/" \ 383 % (bootimg, isodir) 384 exec_native_cmd(mcopy_cmd, native_sysroot) 385 386 chmod_cmd = "chmod 644 %s" % bootimg 387 exec_cmd(chmod_cmd) 388 389 # Prepare files for legacy boot 390 syslinux_dir = get_bitbake_var("STAGING_DATADIR") 391 if not syslinux_dir: 392 raise WicError("Couldn't find STAGING_DATADIR, exiting.") 393 394 if os.path.exists("%s/isolinux" % isodir): 395 shutil.rmtree("%s/isolinux" % isodir) 396 397 install_cmd = "install -d %s/isolinux" % isodir 398 exec_cmd(install_cmd) 399 400 cls.do_configure_syslinux(creator, cr_workdir) 401 402 install_cmd = "install -m 444 %s/syslinux/ldlinux.sys " % syslinux_dir 403 install_cmd += "%s/isolinux/ldlinux.sys" % isodir 404 exec_cmd(install_cmd) 405 406 install_cmd = "install -m 444 %s/syslinux/isohdpfx.bin " % syslinux_dir 407 install_cmd += "%s/isolinux/isohdpfx.bin" % isodir 408 exec_cmd(install_cmd) 409 410 install_cmd = "install -m 644 %s/syslinux/isolinux.bin " % syslinux_dir 411 install_cmd += "%s/isolinux/isolinux.bin" % isodir 412 exec_cmd(install_cmd) 413 414 install_cmd = "install -m 644 %s/syslinux/ldlinux.c32 " % syslinux_dir 415 install_cmd += "%s/isolinux/ldlinux.c32" % isodir 416 exec_cmd(install_cmd) 417 418 #create ISO image 419 iso_img = "%s/tempiso_img.iso" % cr_workdir 420 iso_bootimg = "isolinux/isolinux.bin" 421 iso_bootcat = "isolinux/boot.cat" 422 efi_img = "efi.img" 423 424 mkisofs_cmd = "mkisofs -V %s " % part.label 425 mkisofs_cmd += "-o %s -U " % iso_img 426 mkisofs_cmd += "-J -joliet-long -r -iso-level 2 -b %s " % iso_bootimg 427 mkisofs_cmd += "-c %s -no-emul-boot -boot-load-size 4 " % iso_bootcat 428 mkisofs_cmd += "-boot-info-table -eltorito-alt-boot " 429 mkisofs_cmd += "-eltorito-platform 0xEF -eltorito-boot %s " % efi_img 430 mkisofs_cmd += "-no-emul-boot %s " % isodir 431 432 logger.debug("running command: %s", mkisofs_cmd) 433 exec_native_cmd(mkisofs_cmd, native_sysroot) 434 435 shutil.rmtree(isodir) 436 437 du_cmd = "du -Lbks %s" % iso_img 438 out = exec_cmd(du_cmd) 439 isoimg_size = int(out.split()[0]) 440 441 part.size = isoimg_size 442 part.source_file = iso_img 443 444 @classmethod 445 def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir, 446 bootimg_dir, kernel_dir, native_sysroot): 447 """ 448 Called after all partitions have been prepared and assembled into a 449 disk image. In this case, we insert/modify the MBR using isohybrid 450 utility for booting via BIOS from disk storage devices. 451 """ 452 453 iso_img = "%s.p1" % disk.path 454 full_path = creator._full_path(workdir, disk_name, "direct") 455 full_path_iso = creator._full_path(workdir, disk_name, "iso") 456 457 isohybrid_cmd = "isohybrid -u %s" % iso_img 458 logger.debug("running command: %s", isohybrid_cmd) 459 exec_native_cmd(isohybrid_cmd, native_sysroot) 460 461 # Replace the image created by direct plugin with the one created by 462 # mkisofs command. This is necessary because the iso image created by 463 # mkisofs has a very specific MBR is system area of the ISO image, and 464 # direct plugin adds and configures an another MBR. 465 logger.debug("Replaceing the image created by direct plugin\n") 466 os.remove(disk.path) 467 shutil.copy2(iso_img, full_path_iso) 468 shutil.copy2(full_path_iso, full_path) 469