Note lz4 compression in help
[livecd.git] / imgcreate / live.py
blob260fc6116562c9e962ce41a15e9536910f2274f3
2 # live.py : LiveImageCreator class for creating Live CD images
4 # Copyright 2007-2012, Red Hat, Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 import sys
20 import os
21 import os.path
22 import glob
23 import shutil
24 import subprocess
25 import logging
26 import re
28 from imgcreate.errors import *
29 from imgcreate.fs import *
30 from imgcreate.creator import *
32 class LiveImageCreatorBase(LoopImageCreator):
33 """A base class for LiveCD image creators.
35 This class serves as a base class for the architecture-specific LiveCD
36 image creator subclass, LiveImageCreator.
38 LiveImageCreator creates a bootable ISO containing the system image,
39 bootloader, bootloader configuration, kernel and initramfs.
41 """
43 def __init__(self, ks, name, fslabel=None, releasever=None, tmpdir="/tmp",
44 title="Linux", product="Linux", useplugins=False, cacheonly=False,
45 docleanup=True):
46 """Initialise a LiveImageCreator instance.
48 This method takes the same arguments as LoopImageCreator.__init__().
50 """
51 LoopImageCreator.__init__(self, ks, name,
52 fslabel=fslabel,
53 releasever=releasever,
54 tmpdir=tmpdir,
55 useplugins=useplugins,
56 cacheonly=cacheonly,
57 docleanup=docleanup)
59 self.compress_type = "xz"
60 """mksquashfs compressor to use."""
62 self.skip_compression = False
63 """Controls whether to use squashfs to compress the image."""
65 self.skip_minimize = False
66 """Controls whether an image minimizing snapshot should be created.
68 This snapshot can be used when copying the system image from the ISO in
69 order to minimize the amount of data that needs to be copied; simply,
70 it makes it possible to create a version of the image's filesystem with
71 no spare space.
73 """
75 self._timeout = kickstart.get_timeout(self.ks, 10)
76 """The bootloader timeout from kickstart."""
78 self._default_kernel = kickstart.get_default_kernel(self.ks, "kernel")
79 """The default kernel type from kickstart."""
81 self.__isodir = None
83 self.__modules = ["=ata", "sym53c8xx", "aic7xxx", "=usb", "=firewire",
84 "=mmc", "=pcmcia", "mptsas", "udf", "virtio_blk",
85 "virtio_pci", "virtio_scsi", "virtio_net", "virtio_mmio",
86 "virtio_balloon", "virtio-rng"]
87 self.__modules.extend(kickstart.get_modules(self.ks))
89 self._isofstype = "iso9660"
90 self.base_on = False
92 self.title = title
93 self.product = product
96 # Hooks for subclasses
98 def _configure_bootloader(self, isodir):
99 """Create the architecture specific booloader configuration.
101 This is the hook where subclasses must create the booloader
102 configuration in order to allow a bootable ISO to be built.
104 isodir -- the directory where the contents of the ISO are to be staged
107 raise CreatorError("Bootloader configuration is arch-specific, "
108 "but not implemented for this arch!")
110 def _get_kernel_options(self):
111 """Return a kernel options string for bootloader configuration.
113 This is the hook where subclasses may specify a set of kernel options
114 which should be included in the images bootloader configuration.
116 A sensible default implementation is provided.
119 r = kickstart.get_kernel_args(self.ks)
120 if os.path.exists(self._instroot + "/usr/bin/rhgb"):
121 r += " rhgb"
122 if os.path.exists(self._instroot + "/usr/bin/plymouth"):
123 r += " rhgb"
124 return r
126 def _get_mkisofs_options(self, isodir):
127 """Return the architecture specific mkisosfs options.
129 This is the hook where subclasses may specify additional arguments to
130 mkisofs, e.g. to enable a bootable ISO to be built.
132 By default, an empty list is returned.
135 return []
138 # Helpers for subclasses
140 def _has_checkisomd5(self):
141 """Check whether checkisomd5 is available in the install root."""
142 def exists(instroot, path):
143 return os.path.exists(instroot + path)
145 if (exists(self._instroot, "/usr/lib/anaconda-runtime/checkisomd5") or
146 exists(self._instroot, "/usr/bin/checkisomd5")):
147 return True
149 return False
152 # Actual implementation
154 def _base_on(self, base_on):
155 """helper function to extract ext3 file system from a live CD ISO"""
156 isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())
158 try:
159 isoloop.mount()
160 except MountError, e:
161 raise CreatorError("Failed to loopback mount '%s' : %s" %
162 (base_on, e))
164 # Copy the initrd%d.img and xen%d.gz files over to /isolinux
165 # This is because the originals in /boot are removed when the
166 # original .iso was created.
167 src = isoloop.mountdir + "/isolinux/"
168 dest = self.__ensure_isodir() + "/isolinux/"
169 makedirs(dest)
170 pattern = re.compile(r"(initrd\d+\.img|xen\d+\.gz)")
171 files = [f for f in os.listdir(src) if pattern.search(f)
172 and os.path.isfile(src+f)]
173 for f in files:
174 shutil.copyfile(src+f, dest+f)
176 # legacy LiveOS filesystem layout support, remove for F9 or F10
177 if os.path.exists(isoloop.mountdir + "/squashfs.img"):
178 squashimg = isoloop.mountdir + "/squashfs.img"
179 else:
180 squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
182 squashloop = DiskMount(LoopbackDisk(squashimg, 0), self._mkdtemp(), "squashfs")
184 # 'self.compress_type = None' will force reading it from base_on.
185 if self.compress_type is None:
186 self.compress_type = squashfs_compression_type(squashimg)
187 if self.compress_type == 'undetermined':
188 # Default to 'gzip' for compatibility with older versions.
189 self.compress_type = 'gzip'
190 try:
191 if not squashloop.disk.exists():
192 raise CreatorError("'%s' is not a valid live CD ISO : "
193 "squashfs.img doesn't exist" % base_on)
195 try:
196 squashloop.mount()
197 except MountError, e:
198 raise CreatorError("Failed to loopback mount squashfs.img "
199 "from '%s' : %s" % (base_on, e))
201 # legacy LiveOS filesystem layout support, remove for F9 or F10
202 if os.path.exists(squashloop.mountdir + "/os.img"):
203 os_image = squashloop.mountdir + "/os.img"
204 else:
205 os_image = squashloop.mountdir + "/LiveOS/ext3fs.img"
207 if not os.path.exists(os_image):
208 raise CreatorError("'%s' is not a valid live CD ISO : neither "
209 "LiveOS/ext3fs.img nor os.img exist" %
210 base_on)
212 try:
213 shutil.copyfile(os_image, self._image)
214 except IOError, e:
215 raise CreatorError("Failed to copy base live image to %s for modification: %s" %(self._image, e))
216 finally:
217 squashloop.cleanup()
218 isoloop.cleanup()
220 def _mount_instroot(self, base_on = None):
221 self.base_on = True
222 LoopImageCreator._mount_instroot(self, base_on)
223 self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
224 self.__write_dracut_conf(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
226 def _unmount_instroot(self):
227 self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd")
228 self.__restore_file(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
229 LoopImageCreator._unmount_instroot(self)
231 def __ensure_isodir(self):
232 if self.__isodir is None:
233 self.__isodir = self._mkdtemp("iso-")
234 return self.__isodir
236 def _generate_efiboot(self, isodir):
237 """Generate EFI boot images."""
238 if not glob.glob(self._instroot+"/boot/efi/EFI/*/shim.efi"):
239 logging.error("Missing shim.efi, skipping efiboot.img creation.")
240 return
242 # XXX-BCL: does this need --label?
243 subprocess.call(["mkefiboot", isodir + "/EFI/BOOT",
244 isodir + "/isolinux/efiboot.img"])
245 subprocess.call(["mkefiboot", "-a", isodir + "/EFI/BOOT",
246 isodir + "/isolinux/macboot.img", "-l", self.product,
247 "-n", "/usr/share/pixmaps/bootloader/fedora-media.vol",
248 "-i", "/usr/share/pixmaps/bootloader/fedora.icns",
249 "-p", self.product])
251 def _create_bootconfig(self):
252 """Configure the image so that it's bootable."""
253 self._configure_bootloader(self.__ensure_isodir())
254 self._generate_efiboot(self.__ensure_isodir())
256 def _get_post_scripts_env(self, in_chroot):
257 env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
259 if not in_chroot:
260 env["LIVE_ROOT"] = self.__ensure_isodir()
262 return env
264 def __extra_filesystems(self):
265 return "vfat msdos isofs ext4 xfs btrfs";
267 def __extra_drivers(self):
268 retval = "sr_mod sd_mod ide-cd cdrom "
269 for module in self.__modules:
270 if module == "=usb":
271 retval = retval + "ehci_hcd uhci_hcd ohci_hcd "
272 retval = retval + "usb_storage usbhid "
273 elif module == "=firewire":
274 retval = retval + "firewire-sbp2 firewire-ohci "
275 retval = retval + "sbp2 ohci1394 ieee1394 "
276 elif module == "=mmc":
277 retval = retval + "mmc_block sdhci sdhci-pci "
278 elif module == "=pcmcia":
279 retval = retval + "pata_pcmcia "
280 else:
281 retval = retval + module + " "
282 return retval
284 def __restore_file(self,path):
285 try:
286 os.unlink(path)
287 except:
288 pass
289 if os.path.exists(path + '.rpmnew'):
290 os.rename(path + '.rpmnew', path)
292 def __write_initrd_conf(self, path):
293 if not os.path.exists(os.path.dirname(path)):
294 makedirs(os.path.dirname(path))
295 f = open(path, "a")
296 f.write('LIVEOS="yes"\n')
297 f.write('PROBE="no"\n')
298 f.write('MODULES+="' + self.__extra_filesystems() + '"\n')
299 f.write('MODULES+="' + self.__extra_drivers() + '"\n')
300 f.close()
302 def __write_dracut_conf(self, path):
303 if not os.path.exists(os.path.dirname(path)):
304 makedirs(os.path.dirname(path))
305 f = open(path, "a")
306 f.write('filesystems+="' + self.__extra_filesystems() + ' "\n')
307 f.write('drivers+="' + self.__extra_drivers() + ' "\n')
308 f.write('add_dracutmodules+=" dmsquash-live pollcdrom "\n')
309 f.write('hostonly="no"\n')
310 f.write('dracut_rescue_image="no"\n')
311 f.close()
313 def __create_iso(self, isodir):
314 iso = self._outdir + "/" + self.name + ".iso"
316 args = ["/usr/bin/mkisofs",
317 "-J", "-r",
318 "-hide-rr-moved", "-hide-joliet-trans-tbl",
319 "-V", self.fslabel,
320 "-o", iso]
322 args.extend(self._get_mkisofs_options(isodir))
323 if self._isofstype == "udf":
324 args.append("-allow-limited-size")
326 args.append(isodir)
328 if subprocess.call(args) != 0:
329 raise CreatorError("ISO creation failed!")
331 if os.path.exists("/usr/bin/isohybrid"):
332 if os.path.exists(isodir + "/isolinux/efiboot.img"):
333 subprocess.call(["/usr/bin/isohybrid", "-u", "-m", iso])
334 else:
335 subprocess.call(["/usr/bin/isohybrid", iso])
337 self.__implant_md5sum(iso)
339 def __implant_md5sum(self, iso):
340 """Implant an isomd5sum."""
341 if os.path.exists("/usr/bin/implantisomd5"):
342 implantisomd5 = "/usr/bin/implantisomd5"
343 elif os.path.exists("/usr/lib/anaconda-runtime/implantisomd5"):
344 implantisomd5 = "/usr/lib/anaconda-runtime/implantisomd5"
345 else:
346 logging.warn("isomd5sum not installed; not setting up mediacheck")
347 return
349 subprocess.call([implantisomd5, iso])
351 def _stage_final_image(self):
352 try:
353 makedirs(self.__ensure_isodir() + "/LiveOS")
355 self._resparse()
357 if not self.skip_minimize:
358 create_image_minimizer(self.__isodir + "/LiveOS/osmin.img",
359 self._image, self.compress_type,
360 tmpdir = self.tmpdir)
362 if self.skip_compression:
363 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
364 if os.stat(self.__isodir + "/LiveOS/ext3fs.img").st_size >= 4*1024*1024*1024:
365 self._isofstype = "udf"
366 logging.warn("Switching to UDF due to size of LiveOS/ext3fs.img")
367 else:
368 makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
369 shutil.move(self._image,
370 os.path.join(os.path.dirname(self._image),
371 "LiveOS", "ext3fs.img"))
372 mksquashfs(os.path.dirname(self._image),
373 self.__isodir + "/LiveOS/squashfs.img",
374 self.compress_type)
375 if os.stat(self.__isodir + "/LiveOS/squashfs.img").st_size >= 4*1024*1024*1024:
376 self._isofstype = "udf"
377 logging.warn("Switching to UDF due to size of LiveOS/squashfs.img")
380 self.__create_iso(self.__isodir)
381 finally:
382 shutil.rmtree(self.__isodir, ignore_errors = True)
383 self.__isodir = None
385 class x86LiveImageCreator(LiveImageCreatorBase):
386 """ImageCreator for x86 machines"""
387 def __init__(self, *args, **kwargs):
388 LiveImageCreatorBase.__init__(self, *args, **kwargs)
389 self._efiarch = None
391 def _get_mkisofs_options(self, isodir):
392 options = [ "-b", "isolinux/isolinux.bin",
393 "-c", "isolinux/boot.cat",
394 "-no-emul-boot", "-boot-info-table",
395 "-boot-load-size", "4" ]
396 if os.path.exists(isodir + "/isolinux/efiboot.img"):
397 options.extend([ "-eltorito-alt-boot",
398 "-e", "isolinux/efiboot.img",
399 "-no-emul-boot",
400 "-eltorito-alt-boot",
401 "-e", "isolinux/macboot.img",
402 "-no-emul-boot"])
403 return options
405 def _get_required_packages(self):
406 return ["syslinux"] \
407 + LiveImageCreatorBase._get_required_packages(self)
409 def _get_isolinux_stanzas(self, isodir):
410 return ""
412 def __find_syslinux_menu(self):
413 for menu in ("vesamenu.c32", "menu.c32"):
414 for dir in ("/usr/lib/syslinux/", "/usr/share/syslinux/"):
415 if os.path.isfile(self._instroot + dir + menu):
416 return menu
418 raise CreatorError("syslinux not installed : "
419 "no suitable *menu.c32 found")
421 def __find_syslinux_mboot(self):
423 # We only need the mboot module if we have any xen hypervisors
425 if not glob.glob(self._instroot + "/boot/xen.gz*"):
426 return None
428 return "mboot.c32"
430 def __copy_syslinux_files(self, isodir, menu, mboot = None):
431 files = ["isolinux.bin", "ldlinux.c32", "libcom32.c32", "libutil.c32", menu]
432 if mboot:
433 files += [mboot]
435 for f in files:
436 if os.path.exists(self._instroot + "/usr/lib/syslinux/" + f):
437 path = self._instroot + "/usr/lib/syslinux/" + f
438 elif os.path.exists(self._instroot + "/usr/share/syslinux/" + f):
439 path = self._instroot + "/usr/share/syslinux/" + f
440 if not os.path.isfile(path):
441 raise CreatorError("syslinux not installed : "
442 "%s not found" % path)
444 shutil.copy(path, isodir + "/isolinux/")
446 def __copy_syslinux_background(self, isodest):
447 background_path = self._instroot + \
448 "/usr/share/anaconda/boot/syslinux-vesa-splash.jpg"
450 if not os.path.exists(background_path):
451 # fallback to F13 location
452 background_path = self._instroot + \
453 "/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg"
455 if not os.path.exists(background_path):
456 return False
458 shutil.copyfile(background_path, isodest)
460 return True
462 def __copy_kernel_and_initramfs(self, isodir, version, index):
463 bootdir = self._instroot + "/boot"
465 shutil.copyfile(bootdir + "/vmlinuz-" + version,
466 isodir + "/isolinux/vmlinuz" + index)
468 isDracut = False
469 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
470 shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
471 isodir + "/isolinux/initrd" + index + ".img")
472 isDracut = True
473 elif os.path.exists(bootdir + "/initrd-" + version + ".img"):
474 shutil.copyfile(bootdir + "/initrd-" + version + ".img",
475 isodir + "/isolinux/initrd" + index + ".img")
476 elif not self.base_on:
477 logging.error("No initrd or initramfs found for %s" % (version,))
479 is_xen = False
480 if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
481 shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
482 isodir + "/isolinux/xen" + index + ".gz")
483 is_xen = True
485 return (is_xen, isDracut)
487 def __is_default_kernel(self, kernel, kernels):
488 if len(kernels) == 1:
489 return True
491 if kernel == self._default_kernel:
492 return True
494 if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
495 return True
497 return False
499 def __get_basic_syslinux_config(self, **args):
500 return """
501 default %(menu)s
502 timeout %(timeout)d
503 menu background %(background)s
504 menu autoboot Starting %(title)s in # second{,s}. Press any key to interrupt.
506 menu clear
507 menu title %(title)s
508 menu vshift 8
509 menu rows 18
510 menu margin 8
511 #menu hidden
512 menu helpmsgrow 15
513 menu tabmsgrow 13
515 menu color border * #00000000 #00000000 none
516 menu color sel 0 #ffffffff #00000000 none
517 menu color title 0 #ff7ba3d0 #00000000 none
518 menu color tabmsg 0 #ff3a6496 #00000000 none
519 menu color unsel 0 #84b8ffff #00000000 none
520 menu color hotsel 0 #84b8ffff #00000000 none
521 menu color hotkey 0 #ffffffff #00000000 none
522 menu color help 0 #ffffffff #00000000 none
523 menu color scrollbar 0 #ffffffff #ff355594 none
524 menu color timeout 0 #ffffffff #00000000 none
525 menu color timeout_msg 0 #ffffffff #00000000 none
526 menu color cmdmark 0 #84b8ffff #00000000 none
527 menu color cmdline 0 #ffffffff #00000000 none
529 menu tabmsg Press Tab for full configuration options on menu items.
530 menu separator
531 """ % args
533 def __get_image_stanza(self, is_xen, isDracut, **args):
534 if isDracut:
535 args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args
536 else:
537 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
539 if not is_xen:
540 template = """label %(short)s
541 menu label %(long)s
542 kernel vmlinuz%(index)s
543 append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s
545 else:
546 template = """label %(short)s
547 menu label %(long)s
548 kernel mboot.c32
549 append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s --- initrd%(index)s.img
551 if args.get("help"):
552 template += """ text help
553 %(help)s
554 endtext
556 return template % args
558 def __get_image_stanzas(self, isodir):
559 kernels = self._get_kernel_versions()
560 kernel_options = self._get_kernel_options()
561 checkisomd5 = self._has_checkisomd5()
563 # Stanzas for insertion into the config template
564 linux = []
565 basic = []
566 check = []
568 index = "0"
569 for kernel, version in ((k,v) for k in kernels for v in kernels[k]):
570 (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index)
571 if index == "0":
572 self._isDracut = isDracut
574 default = self.__is_default_kernel(kernel, kernels)
576 if default:
577 long = self.product
578 elif kernel.startswith("kernel-"):
579 long = "%s (%s)" % (self.product, kernel[7:])
580 else:
581 long = "%s (%s)" % (self.product, kernel)
583 # tell dracut not to ask for LUKS passwords or activate mdraid sets
584 if isDracut:
585 kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0"
586 else:
587 kern_opts = kernel_options
589 linux.append(self.__get_image_stanza(is_xen, isDracut,
590 fslabel = self.fslabel,
591 isofstype = "auto",
592 liveargs = kern_opts,
593 long = "^Start " + long,
594 short = "linux" + index,
595 extra = "",
596 help = "",
597 index = index))
599 if default:
600 linux[-1] += " menu default\n"
602 basic.append(self.__get_image_stanza(is_xen, isDracut,
603 fslabel = self.fslabel,
604 isofstype = "auto",
605 liveargs = kern_opts,
606 long = "Start " + long + " in ^basic graphics mode.",
607 short = "basic" + index,
608 extra = "nomodeset",
609 help = "Try this option out if you're having trouble starting.",
610 index = index))
612 if checkisomd5:
613 check.append(self.__get_image_stanza(is_xen, isDracut,
614 fslabel = self.fslabel,
615 isofstype = "auto",
616 liveargs = kern_opts,
617 long = "^Test this media & start " + long,
618 short = "check" + index,
619 extra = "rd.live.check",
620 help = "",
621 index = index))
622 else:
623 check.append(None)
625 index = str(int(index) + 1)
627 return (linux, basic, check)
629 def __get_memtest_stanza(self, isodir):
630 memtest = glob.glob(self._instroot + "/boot/memtest86*")
631 if not memtest:
632 return ""
634 shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")
636 return """label memtest
637 menu label Run a ^memory test.
638 text help
639 If your system is having issues, an problem with your
640 system's memory may be the cause. Use this utility to
641 see if the memory is working correctly.
642 endtext
643 kernel memtest
646 def __get_local_stanza(self, isodir):
647 return """label local
648 menu label Boot from ^local drive
649 localboot 0xffff
652 def _configure_syslinux_bootloader(self, isodir):
653 """configure the boot loader"""
654 makedirs(isodir + "/isolinux")
656 menu = self.__find_syslinux_menu()
658 self.__copy_syslinux_files(isodir, menu,
659 self.__find_syslinux_mboot())
661 background = ""
662 if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
663 background = "splash.jpg"
665 cfg = self.__get_basic_syslinux_config(menu = menu,
666 background = background,
667 title = self.title,
668 timeout = self._timeout * 10)
669 cfg += "menu separator\n"
671 linux, basic, check = self.__get_image_stanzas(isodir)
672 # Add linux stanzas to main menu
673 for s in linux:
674 cfg += s
675 cfg += "menu separator\n"
677 cfg += """menu begin ^Troubleshooting
678 menu title Troubleshooting
680 # Add basic video and check to submenu
681 for b, c in zip(basic, check):
682 cfg += b
683 if c:
684 cfg += c
686 cfg += self.__get_memtest_stanza(isodir)
687 cfg += "menu separator\n"
689 cfg += self.__get_local_stanza(isodir)
690 cfg += self._get_isolinux_stanzas(isodir)
692 cfg += """menu separator
693 label returntomain
694 menu label Return to ^main menu.
695 menu exit
696 menu end
698 cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
699 cfgf.write(cfg)
700 cfgf.close()
702 @property
703 def efiarch(self):
704 if not self._efiarch:
705 # for most things, we want them named boot$efiarch
706 efiarch = {"i386": "IA32", "x86_64": "X64"}
707 self._efiarch = efiarch[rpmUtils.arch.getBaseArch()]
708 return self._efiarch
710 def __copy_efi_files(self, isodir):
711 """ Copy the efi files into /EFI/BOOT/
712 If any of them are missing, return False.
713 requires:
714 shim.efi
715 gcdx64.efi
716 fonts/unicode.pf2
718 fail = False
719 missing = []
720 files = [("/boot/efi/EFI/*/shim.efi", "/EFI/BOOT/BOOT%s.EFI" % (self.efiarch,)),
721 ("/boot/efi/EFI/*/gcdx64.efi", "/EFI/BOOT/grubx64.efi"),
722 ("/boot/efi/EFI/*/fonts/unicode.pf2", "/EFI/BOOT/fonts/"),
724 makedirs(isodir+"/EFI/BOOT/fonts/")
725 for src, dest in files:
726 src_glob = glob.glob(self._instroot+src)
727 if not src_glob:
728 missing.append("Missing EFI file (%s)" % (src,))
729 fail = True
730 else:
731 shutil.copy(src_glob[0], isodir+dest)
732 map(logging.error, missing)
733 return fail
735 def __get_basic_efi_config(self, **args):
736 return """
737 set default="1"
739 function load_video {
740 insmod efi_gop
741 insmod efi_uga
742 insmod video_bochs
743 insmod video_cirrus
744 insmod all_video
747 load_video
748 set gfxpayload=keep
749 insmod gzio
750 insmod part_gpt
751 insmod ext2
753 set timeout=%(timeout)d
754 ### END /etc/grub.d/00_header ###
756 search --no-floppy --set=root -l '%(isolabel)s'
758 ### BEGIN /etc/grub.d/10_linux ###
759 """ %args
761 def __get_efi_image_stanza(self, **args):
762 if self._isDracut:
763 args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
764 else:
765 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
766 return """menuentry '%(long)s' --class fedora --class gnu-linux --class gnu --class os {
767 linuxefi /isolinux/vmlinuz%(index)s root=%(rootlabel)s %(liveargs)s %(extra)s
768 initrdefi /isolinux/initrd%(index)s.img
770 """ %args
772 def __get_efi_image_stanzas(self, isodir, name):
773 # FIXME: this only supports one kernel right now...
775 kernel_options = self._get_kernel_options()
776 checkisomd5 = self._has_checkisomd5()
778 cfg = ""
780 for index in range(0, 9):
781 # we don't support xen kernels
782 if os.path.exists("%s/EFI/BOOT/xen%d.gz" %(isodir, index)):
783 continue
784 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
785 liveargs = kernel_options,
786 long = "Start " + self.product,
787 extra = "", index = index)
788 if checkisomd5:
789 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
790 liveargs = kernel_options,
791 long = "Test this media & start " + self.product,
792 extra = "rd.live.check",
793 index = index)
794 cfg += """
795 submenu 'Troubleshooting -->' {
797 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
798 liveargs = kernel_options,
799 long = "Start " + self.product + " in basic graphics mode",
800 extra = "nomodeset", index = index)
802 cfg+= """}
804 break
806 return cfg
808 def _configure_efi_bootloader(self, isodir):
809 """Set up the configuration for an EFI bootloader"""
810 if self.__copy_efi_files(isodir):
811 shutil.rmtree(isodir + "/EFI")
812 logging.warn("Failed to copy EFI files, no EFI Support will be included.")
813 return
815 cfg = self.__get_basic_efi_config(isolabel = self.fslabel,
816 timeout = self._timeout)
817 cfg += self.__get_efi_image_stanzas(isodir, self.name)
819 cfgf = open(isodir + "/EFI/BOOT/grub.cfg", "w")
820 cfgf.write(cfg)
821 cfgf.close()
823 # first gen mactel machines get the bootloader name wrong apparently
824 if rpmUtils.arch.getBaseArch() == "i386":
825 os.link(isodir + "/EFI/BOOT/BOOT%s.EFI" % (self.efiarch),
826 isodir + "/EFI/BOOT/BOOT.EFI")
829 def _configure_bootloader(self, isodir):
830 self._configure_syslinux_bootloader(isodir)
831 self._configure_efi_bootloader(isodir)
833 class ppcLiveImageCreator(LiveImageCreatorBase):
834 def _get_mkisofs_options(self, isodir):
835 return [ "-hfs", "-no-desktop", "-part",
836 "-map", isodir + "/ppc/mapping",
837 "-hfs-bless", isodir + "/ppc/mac",
838 "-hfs-volid", self.fslabel ]
840 def _get_required_packages(self):
841 return ["yaboot"] + \
842 LiveImageCreatorBase._get_required_packages(self)
844 def _get_excluded_packages(self):
845 # kind of hacky, but exclude memtest86+ on ppc so it can stay in cfg
846 return ["memtest86+"] + \
847 LiveImageCreatorBase._get_excluded_packages(self)
849 def __copy_boot_file(self, destdir, file):
850 for dir in [self._instroot+"/usr/share/ppc64-utils",
851 self._instroot+"/usr/lib/anaconda-runtime/boot",
852 "/usr/share/lorax/config_files/ppc"]:
853 path = self._instroot + dir + "/" + file
854 if not os.path.exists(path):
855 continue
857 makedirs(destdir)
858 shutil.copy(path, destdir)
859 return
861 raise CreatorError("Unable to find boot file " + file)
863 def __kernel_bits(self, kernel):
864 testpath = (self._instroot + "/lib/modules/" +
865 kernel + "/kernel/arch/powerpc/platforms")
867 if not os.path.exists(testpath):
868 return { "32" : True, "64" : False }
869 else:
870 return { "32" : False, "64" : True }
872 def __copy_kernel_and_initramfs(self, destdir, version):
873 isDracut = False
874 bootdir = self._instroot + "/boot"
876 makedirs(destdir)
878 shutil.copyfile(bootdir + "/vmlinuz-" + version,
879 destdir + "/vmlinuz")
881 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
882 shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
883 destdir + "/initrd.img")
884 isDracut = True
885 else:
886 shutil.copyfile(bootdir + "/initrd-" + version + ".img",
887 destdir + "/initrd.img")
889 return isDracut
891 def __get_basic_yaboot_config(self, **args):
892 return """
893 init-message = "Welcome to %(name)s"
894 timeout=%(timeout)d
895 """ % args
897 def __get_image_stanza(self, **args):
898 if args["isDracut"]:
899 args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
900 else:
901 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
902 return """
904 image=/ppc/ppc%(bit)s/vmlinuz
905 label=%(short)s
906 initrd=/ppc/ppc%(bit)s/initrd.img
907 read-only
908 append="root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s"
909 """ % args
912 def __write_yaboot_config(self, isodir, bit, isDracut = False):
913 cfg = self.__get_basic_yaboot_config(name = self.name,
914 timeout = self._timeout * 100)
916 kernel_options = self._get_kernel_options()
918 cfg += self.__get_image_stanza(fslabel = self.fslabel,
919 isofstype = "auto",
920 short = "linux",
921 long = "Run from image",
922 extra = "",
923 bit = bit,
924 liveargs = kernel_options,
925 isDracut = isDracut)
927 if self._has_checkisomd5():
928 cfg += self.__get_image_stanza(fslabel = self.fslabel,
929 isofstype = "auto",
930 short = "rd.live.check",
931 long = "Verify and run from image",
932 extra = "rd.live.check",
933 bit = bit,
934 liveargs = kernel_options,
935 isDracut = isDracut)
937 f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
938 f.write(cfg)
939 f.close()
941 def __write_not_supported(self, isodir, bit):
942 makedirs(isodir + "/ppc/ppc" + bit)
944 message = "Sorry, this LiveCD does not support your hardware"
946 f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
947 f.write('init-message = "' + message + '"')
948 f.close()
951 def __write_dualbits_yaboot_config(isodir, **args):
952 cfg = """
953 init-message = "\nWelcome to %(name)s!\nUse 'linux32' for 32-bit kernel.\n\n"
954 timeout=%(timeout)d
955 default=linux
957 image=/ppc/ppc64/vmlinuz
958 label=linux64
959 alias=linux
960 initrd=/ppc/ppc64/initrd.img
961 read-only
963 image=/ppc/ppc32/vmlinuz
964 label=linux32
965 initrd=/ppc/ppc32/initrd.img
966 read-only
967 """ % args
969 f = open(isodir + "/etc/yaboot.conf", "w")
970 f.write(cfg)
971 f.close()
973 def _configure_bootloader(self, isodir):
974 """configure the boot loader"""
975 havekernel = { 32: False, 64: False }
977 self.__copy_boot_file(isodir + "/ppc", "mapping")
978 self.__copy_boot_file(isodir + "/ppc", "bootinfo.txt")
979 self.__copy_boot_file(isodir + "/ppc/mac", "ofboot.b")
981 shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
982 isodir + "/ppc/mac/yaboot")
984 makedirs(isodir + "/ppc/chrp")
985 shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
986 isodir + "/ppc/chrp/yaboot")
988 subprocess.call(["/usr/sbin/addnote", isodir + "/ppc/chrp/yaboot"])
991 # FIXME: ppc should support multiple kernels too...
993 kernel = self._get_kernel_versions().values()[0][0]
995 kernel_bits = self.__kernel_bits(kernel)
997 for (bit, present) in kernel_bits.items():
998 if not present:
999 self.__write_not_supported(isodir, bit)
1000 continue
1002 isDracut = self.__copy_kernel_and_initramfs(isodir + "/ppc/ppc" + bit, kernel)
1003 self.__write_yaboot_config(isodir, bit, isDracut)
1005 makedirs(isodir + "/etc")
1006 if kernel_bits["32"] and not kernel_bits["64"]:
1007 shutil.copyfile(isodir + "/ppc/ppc32/yaboot.conf",
1008 isodir + "/etc/yaboot.conf")
1009 elif kernel_bits["64"] and not kernel_bits["32"]:
1010 shutil.copyfile(isodir + "/ppc/ppc64/yaboot.conf",
1011 isodir + "/etc/yaboot.conf")
1012 else:
1013 self.__write_dualbits_yaboot_config(isodir,
1014 name = self.name,
1015 timeout = self._timeout * 100)
1018 # FIXME: build 'netboot' images with kernel+initrd, like mk-images.ppc
1021 class ppc64LiveImageCreator(ppcLiveImageCreator):
1022 def _get_excluded_packages(self):
1023 # FIXME:
1024 # while kernel.ppc and kernel.ppc64 co-exist,
1025 # we can't have both
1026 return ["kernel.ppc"] + \
1027 ppcLiveImageCreator._get_excluded_packages(self)
1029 arch = rpmUtils.arch.getBaseArch()
1030 if arch in ("i386", "x86_64"):
1031 LiveImageCreator = x86LiveImageCreator
1032 elif arch in ("ppc",):
1033 LiveImageCreator = ppcLiveImageCreator
1034 elif arch in ("ppc64",):
1035 LiveImageCreator = ppc64LiveImageCreator
1036 elif arch.startswith('arm'):
1037 LiveImageCreator = LiveImageCreatorBase
1039 else:
1040 raise CreatorError("Architecture not supported!")