3 # Copyright (c) 2011 Intel, Inc.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; version 2 of the License
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc., 59
16 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 from mic import kickstart, msger
23 from mic.utils import fs_related, rpmmisc, runner, misc
24 from mic.utils.errors import CreatorError
25 from mic.imager.loop import LoopImageCreator
28 class LiveImageCreatorBase(LoopImageCreator):
29 """A base class for LiveCD image creators.
31 This class serves as a base class for the architecture-specific LiveCD
32 image creator subclass, LiveImageCreator.
34 LiveImageCreator creates a bootable ISO containing the system image,
35 bootloader, bootloader configuration, kernel and initramfs.
38 def __init__(self, creatoropts = None, pkgmgr = None):
39 """Initialise a LiveImageCreator instance.
41 This method takes the same arguments as ImageCreator.__init__().
43 LoopImageCreator.__init__(self, creatoropts, pkgmgr)
45 #Controls whether to use squashfs to compress the image.
46 self.skip_compression = False
48 #Controls whether an image minimizing snapshot should be created.
50 #This snapshot can be used when copying the system image from the ISO in
51 #order to minimize the amount of data that needs to be copied; simply,
52 #it makes it possible to create a version of the image's filesystem with
54 self.skip_minimize = False
56 #A flag which indicates i act as a convertor default false
57 self.actasconvertor = False
59 #The bootloader timeout from kickstart.
61 self._timeout = kickstart.get_timeout(self.ks, 10)
65 #The default kernel type from kickstart.
67 self._default_kernel = kickstart.get_default_kernel(self.ks,
70 self._default_kernel = None
73 parts = kickstart.get_partitions(self.ks)
75 raise CreatorError("Can't support multi partitions in ks file "
76 "for this image type")
77 # FIXME: rename rootfs img to self.name,
78 # else can't find files when create iso
79 self._instloops[0]['name'] = self.name + ".img"
83 self.__modules = ["=ata",
92 self.__modules.extend(kickstart.get_modules(self.ks))
94 self._dep_checks.extend(["isohybrid",
101 # Hooks for subclasses
103 def _configure_bootloader(self, isodir):
104 """Create the architecture specific booloader configuration.
106 This is the hook where subclasses must create the booloader
107 configuration in order to allow a bootable ISO to be built.
109 isodir -- the directory where the contents of the ISO are to
112 raise CreatorError("Bootloader configuration is arch-specific, "
113 "but not implemented for this arch!")
114 def _get_menu_options(self):
115 """Return a menu options string for syslinux configuration.
118 return "liveinst autoinst"
119 r = kickstart.get_menu_args(self.ks)
122 def _get_kernel_options(self):
123 """Return a kernel options string for bootloader configuration.
125 This is the hook where subclasses may specify a set of kernel
126 options which should be included in the images bootloader
129 A sensible default implementation is provided.
133 r = "ro rd.live.image"
135 r = kickstart.get_kernel_args(self.ks)
139 def _get_mkisofs_options(self, isodir):
140 """Return the architecture specific mkisosfs options.
142 This is the hook where subclasses may specify additional arguments
143 to mkisofs, e.g. to enable a bootable ISO to be built.
145 By default, an empty list is returned.
150 # Helpers for subclasses
152 def _has_checkisomd5(self):
153 """Check whether checkisomd5 is available in the install root."""
155 return os.path.exists(self._instroot + path)
157 if _exists("/usr/bin/checkisomd5") and os.path.exists("/usr/bin/implantisomd5"):
162 def __restore_file(self,path):
167 if os.path.exists(path + '.rpmnew'):
168 os.rename(path + '.rpmnew', path)
170 def _mount_instroot(self, base_on = None):
171 LoopImageCreator._mount_instroot(self, base_on)
172 self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
173 self.__write_dracut_conf(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
175 def _unmount_instroot(self):
176 self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd")
177 self.__restore_file(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
178 LoopImageCreator._unmount_instroot(self)
180 def __ensure_isodir(self):
181 if self.__isodir is None:
182 self.__isodir = self._mkdtemp("iso-")
185 def _get_isodir(self):
186 return self.__ensure_isodir()
188 def _set_isodir(self, isodir = None):
189 self.__isodir = isodir
191 def _create_bootconfig(self):
192 """Configure the image so that it's bootable."""
193 self._configure_bootloader(self.__ensure_isodir())
195 def _get_post_scripts_env(self, in_chroot):
196 env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
199 env["LIVE_ROOT"] = self.__ensure_isodir()
202 def __write_dracut_conf(self, path):
203 if not os.path.exists(os.path.dirname(path)):
204 fs_related.makedirs(os.path.dirname(path))
206 f.write('add_dracutmodules+=" dmsquash-live pollcdrom "')
209 def __write_initrd_conf(self, path):
211 if not os.path.exists(os.path.dirname(path)):
212 fs_related.makedirs(os.path.dirname(path))
215 content += 'LIVEOS="yes"\n'
216 content += 'PROBE="no"\n'
217 content += 'MODULES+="squashfs ext3 ext2 vfat msdos "\n'
218 content += 'MODULES+="sr_mod sd_mod ide-cd cdrom "\n'
220 for module in self.__modules:
222 content += 'MODULES+="ehci_hcd uhci_hcd ohci_hcd "\n'
223 content += 'MODULES+="usb_storage usbhid "\n'
224 elif module == "=firewire":
225 content += 'MODULES+="firewire-sbp2 firewire-ohci "\n'
226 content += 'MODULES+="sbp2 ohci1394 ieee1394 "\n'
227 elif module == "=mmc":
228 content += 'MODULES+="mmc_block sdhci sdhci-pci "\n'
229 elif module == "=pcmcia":
230 content += 'MODULES+="pata_pcmcia "\n'
232 content += 'MODULES+="' + module + ' "\n'
236 def __create_iso(self, isodir):
237 iso = self._outdir + "/" + self.name + ".iso"
238 genisoimage = fs_related.find_binary_path("genisoimage")
241 "-hide-rr-moved", "-hide-joliet-trans-tbl",
245 args.extend(self._get_mkisofs_options(isodir))
249 if runner.show(args) != 0:
250 raise CreatorError("ISO creation failed!")
252 """ It should be ok still even if you haven't isohybrid """
255 isohybrid = fs_related.find_binary_path("isohybrid")
260 args = [isohybrid, "-partok", iso ]
261 if runner.show(args) != 0:
262 raise CreatorError("Hybrid ISO creation failed!")
264 self.__implant_md5sum(iso)
266 def __implant_md5sum(self, iso):
267 """Implant an isomd5sum."""
268 if os.path.exists("/usr/bin/implantisomd5"):
269 implantisomd5 = "/usr/bin/implantisomd5"
271 msger.warning("isomd5sum not installed; not setting up mediacheck")
275 runner.show([implantisomd5, iso])
277 def _stage_final_image(self):
279 fs_related.makedirs(self.__ensure_isodir() + "/LiveOS")
281 minimal_size = self._resparse()
283 if not self.skip_minimize:
284 fs_related.create_image_minimizer(self.__isodir + \
289 if self.skip_compression:
290 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
292 fs_related.makedirs(os.path.join(
293 os.path.dirname(self._image),
295 shutil.move(self._image,
296 os.path.join(os.path.dirname(self._image),
297 "LiveOS", "ext3fs.img"))
298 fs_related.mksquashfs(os.path.dirname(self._image),
299 self.__isodir + "/LiveOS/squashfs.img")
301 self.__create_iso(self.__isodir)
304 isoimg = os.path.join(self._outdir, self.name + ".iso")
305 packimg = os.path.join(self._outdir, self.pack_to)
306 misc.packing(packimg, isoimg)
310 shutil.rmtree(self.__isodir, ignore_errors = True)
313 class x86LiveImageCreator(LiveImageCreatorBase):
314 """ImageCreator for x86 machines"""
315 def _get_mkisofs_options(self, isodir):
316 return [ "-b", "isolinux/isolinux.bin",
317 "-c", "isolinux/boot.cat",
318 "-no-emul-boot", "-boot-info-table",
319 "-boot-load-size", "4" ]
321 def _get_required_packages(self):
322 return ["syslinux", "syslinux-extlinux"] + \
323 LiveImageCreatorBase._get_required_packages(self)
325 def _get_isolinux_stanzas(self, isodir):
328 def __find_syslinux_menu(self):
329 for menu in ["vesamenu.c32", "menu.c32"]:
330 if os.path.isfile(self._instroot + "/usr/share/syslinux/" + menu):
333 raise CreatorError("syslinux not installed : "
334 "no suitable /usr/share/syslinux/*menu.c32 found")
336 def __find_syslinux_mboot(self):
338 # We only need the mboot module if we have any xen hypervisors
340 if not glob.glob(self._instroot + "/boot/xen.gz*"):
345 def __copy_syslinux_files(self, isodir, menu, mboot = None):
346 files = ["isolinux.bin", menu]
351 path = self._instroot + "/usr/share/syslinux/" + f
353 if not os.path.isfile(path):
354 raise CreatorError("syslinux not installed : "
355 "%s not found" % path)
357 shutil.copy(path, isodir + "/isolinux/")
359 def __copy_syslinux_background(self, isodest):
360 background_path = self._instroot + \
361 "/usr/share/branding/default/syslinux/syslinux-vesa-splash.jpg"
363 if not os.path.exists(background_path):
366 shutil.copyfile(background_path, isodest)
370 def __copy_kernel_and_initramfs(self, isodir, version, index):
371 bootdir = self._instroot + "/boot"
374 if self._alt_initrd_name:
375 src_initrd_path = os.path.join(bootdir, self._alt_initrd_name)
377 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
378 src_initrd_path = os.path.join(bootdir, "initramfs-" +version+ ".img")
381 src_initrd_path = os.path.join(bootdir, "initrd-" +version+ ".img")
384 msger.debug("copy %s to %s" % (bootdir + "/vmlinuz-" + version, isodir + "/isolinux/vmlinuz" + index))
385 shutil.copyfile(bootdir + "/vmlinuz-" + version,
386 isodir + "/isolinux/vmlinuz" + index)
388 msger.debug("copy %s to %s" % (src_initrd_path, isodir + "/isolinux/initrd" + index + ".img"))
389 shutil.copyfile(src_initrd_path,
390 isodir + "/isolinux/initrd" + index + ".img")
392 raise CreatorError("Unable to copy valid kernels or initrds, "
393 "please check the repo.")
396 if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
397 shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
398 isodir + "/isolinux/xen" + index + ".gz")
401 return (is_xen,isDracut)
403 def __is_default_kernel(self, kernel, kernels):
404 if len(kernels) == 1:
407 if kernel == self._default_kernel:
410 if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
415 def __get_basic_syslinux_config(self, **args):
421 menu title Welcome to %(distroname)s!
422 menu color border 0 #ffffffff #00000000
423 menu color sel 7 #ff000000 #ffffffff
424 menu color title 0 #ffffffff #00000000
425 menu color tabmsg 0 #ffffffff #00000000
426 menu color unsel 0 #ffffffff #00000000
427 menu color hotsel 0 #ff000000 #ffffffff
428 menu color hotkey 7 #ffffffff #ff000000
429 menu color timeout_msg 0 #ffffffff #00000000
430 menu color timeout 0 #ffffffff #00000000
431 menu color cmdline 0 #ffffffff #00000000
436 def __get_image_stanza(self, is_xen, isDracut, **args):
438 args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args
440 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
442 template = """label %(short)s
444 kernel vmlinuz%(index)s
445 append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
448 template = """label %(short)s
451 append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s --- initrd%(index)s.img
453 return template % args
455 def __get_image_stanzas(self, isodir):
457 kernels = self._get_kernel_versions()
458 for kernel in kernels:
459 for version in kernels[kernel]:
460 versions.append(version)
463 raise CreatorError("Unable to find valid kernels, "
464 "please check the repo")
466 kernel_options = self._get_kernel_options()
468 """ menu can be customized highly, the format is:
470 short_name1:long_name1:extra_opts1;short_name2:long_name2:extra_opts2
472 e.g.: autoinst:InstallationOnly:systemd.unit=installer-graphical.service
473 but in order to keep compatible with old format, these are still ok:
477 liveinst::;autoinst::
479 oldmenus = {"basic": {
481 "long": "Installation Only (Text based)",
482 "extra": "basic nosplash 4"
486 "long": "Installation Only",
487 "extra": "liveinst nosplash 4"
491 "long": "Autoinstall (Deletes all existing content)",
492 "extra": "autoinst nosplash 4"
496 "long": "Network Installation",
501 "long": "Verify and",
505 menu_options = self._get_menu_options()
506 menus = menu_options.split(";")
507 for i in range(len(menus)):
508 menus[i] = menus[i].split(":")
509 if len(menus) == 1 and len(menus[0]) == 1:
510 """ Keep compatible with the old usage way """
511 menus = menu_options.split()
512 for i in range(len(menus)):
513 menus[i] = [menus[i]]
517 default_version = None
521 for version in versions:
522 (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index)
524 self._isDracut = isDracut
526 default = self.__is_default_kernel(kernel, kernels)
529 long = "Boot %s" % self.distro_name
530 elif kernel.startswith("kernel-"):
531 long = "Boot %s(%s)" % (self.name, kernel[7:])
533 long = "Boot %s(%s)" % (self.name, kernel)
535 oldmenus["verify"]["long"] = "%s %s" % (oldmenus["verify"]["long"],
537 # tell dracut not to ask for LUKS passwords or activate mdraid sets
539 kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0"
541 kern_opts = kernel_options
543 cfg += self.__get_image_stanza(is_xen, isDracut,
544 fslabel = self.fslabel,
545 liveargs = kern_opts,
547 short = "linux" + index,
552 cfg += "menu default\n"
553 default_version = version
554 default_index = index
559 short = menu[0] + index
564 if menu[0] in oldmenus.keys():
565 if menu[0] == "verify" and not self._has_checkisomd5():
567 if menu[0] == "netinst":
568 netinst = oldmenus[menu[0]]
570 long = oldmenus[menu[0]]["long"]
571 extra = oldmenus[menu[0]]["extra"]
573 long = short.upper() + " X" + index
577 extra = ' '.join(menu[2:])
579 cfg += self.__get_image_stanza(is_xen, isDracut,
580 fslabel = self.fslabel,
581 liveargs = kernel_options,
587 index = str(int(index) + 1)
589 if not default_version:
590 default_version = versions[0]
591 if not default_index:
595 cfg += self.__get_image_stanza(is_xen, isDracut,
596 fslabel = self.fslabel,
597 liveargs = kernel_options,
598 long = netinst["long"],
599 short = netinst["short"],
600 extra = netinst["extra"],
601 index = default_index)
605 def __get_memtest_stanza(self, isodir):
606 memtest = glob.glob(self._instroot + "/boot/memtest86*")
610 shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")
612 return """label memtest
613 menu label Memory Test
617 def __get_local_stanza(self, isodir):
618 return """label local
619 menu label Boot from local drive
623 def _configure_syslinux_bootloader(self, isodir):
624 """configure the boot loader"""
625 fs_related.makedirs(isodir + "/isolinux")
627 menu = self.__find_syslinux_menu()
629 self.__copy_syslinux_files(isodir, menu,
630 self.__find_syslinux_mboot())
633 if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
634 background = "menu background splash.jpg"
636 cfg = self.__get_basic_syslinux_config(menu = menu,
637 background = background,
639 timeout = self._timeout * 10,
640 distroname = self.distro_name)
642 cfg += self.__get_image_stanzas(isodir)
643 cfg += self.__get_memtest_stanza(isodir)
644 cfg += self.__get_local_stanza(isodir)
645 cfg += self._get_isolinux_stanzas(isodir)
647 cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
651 def __copy_efi_files(self, isodir):
652 if not os.path.exists(self._instroot + "/boot/efi/EFI/redhat/grub.efi"):
654 shutil.copy(self._instroot + "/boot/efi/EFI/redhat/grub.efi",
655 isodir + "/EFI/boot/grub.efi")
656 shutil.copy(self._instroot + "/boot/grub/splash.xpm.gz",
657 isodir + "/EFI/boot/splash.xpm.gz")
661 def __get_basic_efi_config(self, **args):
664 splashimage=/EFI/boot/splash.xpm.gz
670 def __get_efi_image_stanza(self, **args):
671 return """title %(long)s
672 kernel /EFI/boot/vmlinuz%(index)s root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
673 initrd /EFI/boot/initrd%(index)s.img
676 def __get_efi_image_stanzas(self, isodir, name):
677 # FIXME: this only supports one kernel right now...
679 kernel_options = self._get_kernel_options()
680 checkisomd5 = self._has_checkisomd5()
684 for index in range(0, 9):
685 # we don't support xen kernels
686 if os.path.exists("%s/EFI/boot/xen%d.gz" %(isodir, index)):
688 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
689 liveargs = kernel_options,
691 extra = "", index = index)
693 cfg += self.__get_efi_image_stanza(
694 fslabel = self.fslabel,
695 liveargs = kernel_options,
696 long = "Verify and Boot " + name,
703 def _configure_efi_bootloader(self, isodir):
704 """Set up the configuration for an EFI bootloader"""
705 fs_related.makedirs(isodir + "/EFI/boot")
707 if not self.__copy_efi_files(isodir):
708 shutil.rmtree(isodir + "/EFI")
711 for f in os.listdir(isodir + "/isolinux"):
712 os.link("%s/isolinux/%s" %(isodir, f),
713 "%s/EFI/boot/%s" %(isodir, f))
716 cfg = self.__get_basic_efi_config(name = self.name,
717 timeout = self._timeout)
718 cfg += self.__get_efi_image_stanzas(isodir, self.name)
720 cfgf = open(isodir + "/EFI/boot/grub.conf", "w")
724 # first gen mactel machines get the bootloader name wrong apparently
725 if rpmmisc.getBaseArch() == "i386":
726 os.link(isodir + "/EFI/boot/grub.efi",
727 isodir + "/EFI/boot/boot.efi")
728 os.link(isodir + "/EFI/boot/grub.conf",
729 isodir + "/EFI/boot/boot.conf")
731 # for most things, we want them named boot$efiarch
732 efiarch = {"i386": "ia32", "x86_64": "x64"}
733 efiname = efiarch[rpmmisc.getBaseArch()]
734 os.rename(isodir + "/EFI/boot/grub.efi",
735 isodir + "/EFI/boot/boot%s.efi" %(efiname,))
736 os.link(isodir + "/EFI/boot/grub.conf",
737 isodir + "/EFI/boot/boot%s.conf" %(efiname,))
740 def _configure_bootloader(self, isodir):
741 self._configure_syslinux_bootloader(isodir)
742 self._configure_efi_bootloader(isodir)
744 arch = rpmmisc.getBaseArch()
745 if arch in ("i386", "x86_64"):
746 LiveCDImageCreator = x86LiveImageCreator
747 elif arch.startswith("arm"):
748 LiveCDImageCreator = LiveImageCreatorBase
750 raise CreatorError("Architecture not supported!")