Add nocleanup option to retain temp files
[livecd.git] / imgcreate / live.py
blob73e34661faa396d7c2fa584d5b2661f3fd18eefc
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"]
86 self.__modules.extend(kickstart.get_modules(self.ks))
88 self._isofstype = "iso9660"
89 self.base_on = False
91 self.title = title
92 self.product = product
95 # Hooks for subclasses
97 def _configure_bootloader(self, isodir):
98 """Create the architecture specific booloader configuration.
100 This is the hook where subclasses must create the booloader
101 configuration in order to allow a bootable ISO to be built.
103 isodir -- the directory where the contents of the ISO are to be staged
106 raise CreatorError("Bootloader configuration is arch-specific, "
107 "but not implemented for this arch!")
109 def _get_kernel_options(self):
110 """Return a kernel options string for bootloader configuration.
112 This is the hook where subclasses may specify a set of kernel options
113 which should be included in the images bootloader configuration.
115 A sensible default implementation is provided.
118 r = kickstart.get_kernel_args(self.ks)
119 if os.path.exists(self._instroot + "/usr/bin/rhgb"):
120 r += " rhgb"
121 if os.path.exists(self._instroot + "/usr/bin/plymouth"):
122 r += " rhgb"
123 return r
125 def _get_mkisofs_options(self, isodir):
126 """Return the architecture specific mkisosfs options.
128 This is the hook where subclasses may specify additional arguments to
129 mkisofs, e.g. to enable a bootable ISO to be built.
131 By default, an empty list is returned.
134 return []
137 # Helpers for subclasses
139 def _has_checkisomd5(self):
140 """Check whether checkisomd5 is available in the install root."""
141 def exists(instroot, path):
142 return os.path.exists(instroot + path)
144 if (exists(self._instroot, "/usr/lib/anaconda-runtime/checkisomd5") or
145 exists(self._instroot, "/usr/bin/checkisomd5")):
146 return True
148 return False
151 # Actual implementation
153 def _base_on(self, base_on):
154 """helper function to extract ext3 file system from a live CD ISO"""
155 isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())
157 try:
158 isoloop.mount()
159 except MountError, e:
160 raise CreatorError("Failed to loopback mount '%s' : %s" %
161 (base_on, e))
163 # Copy the initrd%d.img and xen%d.gz files over to /isolinux
164 # This is because the originals in /boot are removed when the
165 # original .iso was created.
166 src = isoloop.mountdir + "/isolinux/"
167 dest = self.__ensure_isodir() + "/isolinux/"
168 makedirs(dest)
169 pattern = re.compile(r"(initrd\d+\.img|xen\d+\.gz)")
170 files = [f for f in os.listdir(src) if pattern.search(f)
171 and os.path.isfile(src+f)]
172 for f in files:
173 shutil.copyfile(src+f, dest+f)
175 # legacy LiveOS filesystem layout support, remove for F9 or F10
176 if os.path.exists(isoloop.mountdir + "/squashfs.img"):
177 squashimg = isoloop.mountdir + "/squashfs.img"
178 else:
179 squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
181 squashloop = DiskMount(LoopbackDisk(squashimg, 0), self._mkdtemp(), "squashfs")
183 # 'self.compress_type = None' will force reading it from base_on.
184 if self.compress_type is None:
185 self.compress_type = squashfs_compression_type(squashimg)
186 if self.compress_type == 'undetermined':
187 # Default to 'gzip' for compatibility with older versions.
188 self.compress_type = 'gzip'
189 try:
190 if not squashloop.disk.exists():
191 raise CreatorError("'%s' is not a valid live CD ISO : "
192 "squashfs.img doesn't exist" % base_on)
194 try:
195 squashloop.mount()
196 except MountError, e:
197 raise CreatorError("Failed to loopback mount squashfs.img "
198 "from '%s' : %s" % (base_on, e))
200 # legacy LiveOS filesystem layout support, remove for F9 or F10
201 if os.path.exists(squashloop.mountdir + "/os.img"):
202 os_image = squashloop.mountdir + "/os.img"
203 else:
204 os_image = squashloop.mountdir + "/LiveOS/ext3fs.img"
206 if not os.path.exists(os_image):
207 raise CreatorError("'%s' is not a valid live CD ISO : neither "
208 "LiveOS/ext3fs.img nor os.img exist" %
209 base_on)
211 try:
212 shutil.copyfile(os_image, self._image)
213 except IOError, e:
214 raise CreatorError("Failed to copy base live image to %s for modification: %s" %(self._image, e))
215 finally:
216 squashloop.cleanup()
217 isoloop.cleanup()
219 def _mount_instroot(self, base_on = None):
220 self.base_on = True
221 LoopImageCreator._mount_instroot(self, base_on)
222 self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
223 self.__write_dracut_conf(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
225 def _unmount_instroot(self):
226 self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd")
227 self.__restore_file(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
228 LoopImageCreator._unmount_instroot(self)
230 def __ensure_isodir(self):
231 if self.__isodir is None:
232 self.__isodir = self._mkdtemp("iso-")
233 return self.__isodir
235 def _generate_efiboot(self, isodir):
236 """Generate EFI boot images."""
237 if not glob.glob(self._instroot+"/boot/efi/EFI/*/shim.efi"):
238 logging.error("Missing shim.efi, skipping efiboot.img creation.")
239 return
241 # XXX-BCL: does this need --label?
242 subprocess.call(["mkefiboot", isodir + "/EFI/BOOT",
243 isodir + "/isolinux/efiboot.img"])
244 subprocess.call(["mkefiboot", "-a", isodir + "/EFI/BOOT",
245 isodir + "/isolinux/macboot.img", "-l", self.product,
246 "-n", "/usr/share/pixmaps/bootloader/fedora-media.vol",
247 "-i", "/usr/share/pixmaps/bootloader/fedora.icns",
248 "-p", self.product])
250 def _create_bootconfig(self):
251 """Configure the image so that it's bootable."""
252 self._configure_bootloader(self.__ensure_isodir())
253 self._generate_efiboot(self.__ensure_isodir())
255 def _get_post_scripts_env(self, in_chroot):
256 env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
258 if not in_chroot:
259 env["LIVE_ROOT"] = self.__ensure_isodir()
261 return env
263 def __extra_filesystems(self):
264 return "vfat msdos ";
266 def __extra_drivers(self):
267 retval = "sr_mod sd_mod ide-cd cdrom "
268 for module in self.__modules:
269 if module == "=usb":
270 retval = retval + "ehci_hcd uhci_hcd ohci_hcd "
271 retval = retval + "usb_storage usbhid "
272 elif module == "=firewire":
273 retval = retval + "firewire-sbp2 firewire-ohci "
274 retval = retval + "sbp2 ohci1394 ieee1394 "
275 elif module == "=mmc":
276 retval = retval + "mmc_block sdhci sdhci-pci "
277 elif module == "=pcmcia":
278 retval = retval + "pata_pcmcia "
279 else:
280 retval = retval + module + " "
281 return retval
283 def __restore_file(self,path):
284 try:
285 os.unlink(path)
286 except:
287 pass
288 if os.path.exists(path + '.rpmnew'):
289 os.rename(path + '.rpmnew', path)
291 def __write_initrd_conf(self, path):
292 if not os.path.exists(os.path.dirname(path)):
293 makedirs(os.path.dirname(path))
294 f = open(path, "a")
295 f.write('LIVEOS="yes"\n')
296 f.write('PROBE="no"\n')
297 f.write('MODULES+="' + self.__extra_filesystems() + '"\n')
298 f.write('MODULES+="' + self.__extra_drivers() + '"\n')
299 f.close()
301 def __write_dracut_conf(self, path):
302 if not os.path.exists(os.path.dirname(path)):
303 makedirs(os.path.dirname(path))
304 f = open(path, "a")
305 f.write('filesystems+="' + self.__extra_filesystems() + ' "\n')
306 f.write('drivers+="' + self.__extra_drivers() + ' "\n')
307 f.write('add_dracutmodules+=" dmsquash-live pollcdrom "')
308 f.close()
310 def __create_iso(self, isodir):
311 iso = self._outdir + "/" + self.name + ".iso"
313 args = ["/usr/bin/mkisofs",
314 "-J", "-r",
315 "-hide-rr-moved", "-hide-joliet-trans-tbl",
316 "-V", self.fslabel,
317 "-o", iso]
319 args.extend(self._get_mkisofs_options(isodir))
320 if self._isofstype == "udf":
321 args.append("-allow-limited-size")
323 args.append(isodir)
325 if subprocess.call(args) != 0:
326 raise CreatorError("ISO creation failed!")
328 if os.path.exists("/usr/bin/isohybrid"):
329 if os.path.exists(isodir + "/isolinux/efiboot.img"):
330 subprocess.call(["/usr/bin/isohybrid", "-u", "-m", iso])
331 else:
332 subprocess.call(["/usr/bin/isohybrid", iso])
334 self.__implant_md5sum(iso)
336 def __implant_md5sum(self, iso):
337 """Implant an isomd5sum."""
338 if os.path.exists("/usr/bin/implantisomd5"):
339 implantisomd5 = "/usr/bin/implantisomd5"
340 elif os.path.exists("/usr/lib/anaconda-runtime/implantisomd5"):
341 implantisomd5 = "/usr/lib/anaconda-runtime/implantisomd5"
342 else:
343 logging.warn("isomd5sum not installed; not setting up mediacheck")
344 return
346 subprocess.call([implantisomd5, iso])
348 def _stage_final_image(self):
349 try:
350 makedirs(self.__ensure_isodir() + "/LiveOS")
352 self._resparse()
354 if not self.skip_minimize:
355 create_image_minimizer(self.__isodir + "/LiveOS/osmin.img",
356 self._image, self.compress_type,
357 tmpdir = self.tmpdir)
359 if self.skip_compression:
360 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
361 if os.stat(self.__isodir + "/LiveOS/ext3fs.img").st_size >= 4*1024*1024*1024:
362 self._isofstype = "udf"
363 logging.warn("Switching to UDF due to size of LiveOS/ext3fs.img")
364 else:
365 makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
366 shutil.move(self._image,
367 os.path.join(os.path.dirname(self._image),
368 "LiveOS", "ext3fs.img"))
369 mksquashfs(os.path.dirname(self._image),
370 self.__isodir + "/LiveOS/squashfs.img",
371 self.compress_type)
372 if os.stat(self.__isodir + "/LiveOS/squashfs.img").st_size >= 4*1024*1024*1024:
373 self._isofstype = "udf"
374 logging.warn("Switching to UDF due to size of LiveOS/squashfs.img")
377 self.__create_iso(self.__isodir)
378 finally:
379 shutil.rmtree(self.__isodir, ignore_errors = True)
380 self.__isodir = None
382 class x86LiveImageCreator(LiveImageCreatorBase):
383 """ImageCreator for x86 machines"""
384 def __init__(self, *args, **kwargs):
385 LiveImageCreatorBase.__init__(self, *args, **kwargs)
386 self._efiarch = None
388 def _get_mkisofs_options(self, isodir):
389 options = [ "-b", "isolinux/isolinux.bin",
390 "-c", "isolinux/boot.cat",
391 "-no-emul-boot", "-boot-info-table",
392 "-boot-load-size", "4" ]
393 if os.path.exists(isodir + "/isolinux/efiboot.img"):
394 options.extend([ "-eltorito-alt-boot",
395 "-e", "isolinux/efiboot.img",
396 "-no-emul-boot",
397 "-eltorito-alt-boot",
398 "-e", "isolinux/macboot.img",
399 "-no-emul-boot"])
400 return options
402 def _get_required_packages(self):
403 return ["syslinux", "grub2-efi", "shim"] \
404 + LiveImageCreatorBase._get_required_packages(self)
406 def _get_isolinux_stanzas(self, isodir):
407 return ""
409 def __find_syslinux_menu(self):
410 for menu in ("vesamenu.c32", "menu.c32"):
411 for dir in ("/usr/lib/syslinux/", "/usr/share/syslinux/"):
412 if os.path.isfile(self._instroot + dir + menu):
413 return menu
415 raise CreatorError("syslinux not installed : "
416 "no suitable *menu.c32 found")
418 def __find_syslinux_mboot(self):
420 # We only need the mboot module if we have any xen hypervisors
422 if not glob.glob(self._instroot + "/boot/xen.gz*"):
423 return None
425 return "mboot.c32"
427 def __copy_syslinux_files(self, isodir, menu, mboot = None):
428 files = ["isolinux.bin", menu]
429 if mboot:
430 files += [mboot]
432 for f in files:
433 if os.path.exists(self._instroot + "/usr/lib/syslinux/" + f):
434 path = self._instroot + "/usr/lib/syslinux/" + f
435 elif os.path.exists(self._instroot + "/usr/share/syslinux/" + f):
436 path = self._instroot + "/usr/share/syslinux/" + f
437 if not os.path.isfile(path):
438 raise CreatorError("syslinux not installed : "
439 "%s not found" % path)
441 shutil.copy(path, isodir + "/isolinux/")
443 def __copy_syslinux_background(self, isodest):
444 background_path = self._instroot + \
445 "/usr/share/anaconda/boot/syslinux-vesa-splash.jpg"
447 if not os.path.exists(background_path):
448 # fallback to F13 location
449 background_path = self._instroot + \
450 "/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg"
452 if not os.path.exists(background_path):
453 return False
455 shutil.copyfile(background_path, isodest)
457 return True
459 def __copy_kernel_and_initramfs(self, isodir, version, index):
460 bootdir = self._instroot + "/boot"
462 shutil.copyfile(bootdir + "/vmlinuz-" + version,
463 isodir + "/isolinux/vmlinuz" + index)
465 isDracut = False
466 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
467 shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
468 isodir + "/isolinux/initrd" + index + ".img")
469 isDracut = True
470 elif os.path.exists(bootdir + "/initrd-" + version + ".img"):
471 shutil.copyfile(bootdir + "/initrd-" + version + ".img",
472 isodir + "/isolinux/initrd" + index + ".img")
473 elif not self.base_on:
474 logging.error("No initrd or initramfs found for %s" % (version,))
476 is_xen = False
477 if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
478 shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
479 isodir + "/isolinux/xen" + index + ".gz")
480 is_xen = True
482 return (is_xen, isDracut)
484 def __is_default_kernel(self, kernel, kernels):
485 if len(kernels) == 1:
486 return True
488 if kernel == self._default_kernel:
489 return True
491 if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
492 return True
494 return False
496 def __get_basic_syslinux_config(self, **args):
497 return """
498 default %(menu)s
499 timeout %(timeout)d
500 menu background %(background)s
501 menu autoboot Starting %(title)s in # second{,s}. Press any key to interrupt.
503 menu clear
504 menu title %(title)s
505 menu vshift 8
506 menu rows 18
507 menu margin 8
508 #menu hidden
509 menu helpmsgrow 15
510 menu tabmsgrow 13
512 menu color border * #00000000 #00000000 none
513 menu color sel 0 #ffffffff #00000000 none
514 menu color title 0 #ff7ba3d0 #00000000 none
515 menu color tabmsg 0 #ff3a6496 #00000000 none
516 menu color unsel 0 #84b8ffff #00000000 none
517 menu color hotsel 0 #84b8ffff #00000000 none
518 menu color hotkey 0 #ffffffff #00000000 none
519 menu color help 0 #ffffffff #00000000 none
520 menu color scrollbar 0 #ffffffff #ff355594 none
521 menu color timeout 0 #ffffffff #00000000 none
522 menu color timeout_msg 0 #ffffffff #00000000 none
523 menu color cmdmark 0 #84b8ffff #00000000 none
524 menu color cmdline 0 #ffffffff #00000000 none
526 menu tabmsg Press Tab for full configuration options on menu items.
527 menu separator
528 """ % args
530 def __get_image_stanza(self, is_xen, isDracut, **args):
531 if isDracut:
532 args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args
533 else:
534 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
536 if not is_xen:
537 template = """label %(short)s
538 menu label %(long)s
539 kernel vmlinuz%(index)s
540 append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s
542 else:
543 template = """label %(short)s
544 menu label %(long)s
545 kernel mboot.c32
546 append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s --- initrd%(index)s.img
548 if args.get("help"):
549 template += """ text help
550 %(help)s
551 endtext
553 return template % args
555 def __get_image_stanzas(self, isodir):
556 versions = []
557 kernels = self._get_kernel_versions()
558 for kernel in kernels:
559 for version in kernels[kernel]:
560 versions.append(version)
562 kernel_options = self._get_kernel_options()
564 checkisomd5 = self._has_checkisomd5()
566 # Stanzas for insertion into the config template
567 linux = []
568 basic = []
569 check = []
571 index = "0"
572 for version in versions:
573 (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index)
574 if index == "0":
575 self._isDracut = isDracut
577 default = self.__is_default_kernel(kernel, kernels)
579 if default:
580 long = self.product
581 elif kernel.startswith("kernel-"):
582 long = "%s (%s)" % (self.product, kernel[7:])
583 else:
584 long = "%s (%s)" % (self.product, kernel)
586 # tell dracut not to ask for LUKS passwords or activate mdraid sets
587 if isDracut:
588 kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0"
589 else:
590 kern_opts = kernel_options
592 linux.append(self.__get_image_stanza(is_xen, isDracut,
593 fslabel = self.fslabel,
594 isofstype = "auto",
595 liveargs = kern_opts,
596 long = "^Start " + long,
597 short = "linux" + index,
598 extra = "",
599 help = "",
600 index = index))
602 if default:
603 linux[-1] += " menu default\n"
605 basic.append(self.__get_image_stanza(is_xen, isDracut,
606 fslabel = self.fslabel,
607 isofstype = "auto",
608 liveargs = kern_opts,
609 long = "Start " + long + " in ^basic graphics mode.",
610 short = "basic" + index,
611 extra = "xdriver=vesa nomodeset",
612 help = "Try this option out if you're having trouble starting.",
613 index = index))
615 if checkisomd5:
616 check.append(self.__get_image_stanza(is_xen, isDracut,
617 fslabel = self.fslabel,
618 isofstype = "auto",
619 liveargs = kern_opts,
620 long = "^Test this media & start " + long,
621 short = "check" + index,
622 extra = "rd.live.check",
623 help = "",
624 index = index))
625 else:
626 check.append(None)
628 index = str(int(index) + 1)
630 return (linux, basic, check)
632 def __get_memtest_stanza(self, isodir):
633 memtest = glob.glob(self._instroot + "/boot/memtest86*")
634 if not memtest:
635 return ""
637 shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")
639 return """label memtest
640 menu label Run a ^memory test.
641 text help
642 If your system is having issues, an problem with your
643 system's memory may be the cause. Use this utility to
644 see if the memory is working correctly.
645 endtext
646 kernel memtest
649 def __get_local_stanza(self, isodir):
650 return """label local
651 menu label Boot from ^local drive
652 localboot 0xffff
655 def _configure_syslinux_bootloader(self, isodir):
656 """configure the boot loader"""
657 makedirs(isodir + "/isolinux")
659 menu = self.__find_syslinux_menu()
661 self.__copy_syslinux_files(isodir, menu,
662 self.__find_syslinux_mboot())
664 background = ""
665 if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
666 background = "splash.jpg"
668 cfg = self.__get_basic_syslinux_config(menu = menu,
669 background = background,
670 title = self.title,
671 timeout = self._timeout * 10)
672 cfg += "menu separator\n"
674 linux, basic, check = self.__get_image_stanzas(isodir)
675 # Add linux stanzas to main menu
676 for s in linux:
677 cfg += s
678 cfg += "menu separator\n"
680 cfg += """menu begin ^Troubleshooting
681 menu title Troubleshooting
683 # Add basic video and check to submenu
684 for b, c in zip(basic, check):
685 cfg += b
686 if c:
687 cfg += c
689 cfg += self.__get_memtest_stanza(isodir)
690 cfg += "menu separator\n"
692 cfg += self.__get_local_stanza(isodir)
693 cfg += self._get_isolinux_stanzas(isodir)
695 cfg += """menu separator
696 label returntomain
697 menu label Return to ^main menu.
698 menu exit
699 menu end
701 cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
702 cfgf.write(cfg)
703 cfgf.close()
705 @property
706 def efiarch(self):
707 if not self._efiarch:
708 # for most things, we want them named boot$efiarch
709 efiarch = {"i386": "IA32", "x86_64": "X64"}
710 self._efiarch = efiarch[rpmUtils.arch.getBaseArch()]
711 return self._efiarch
713 def __copy_efi_files(self, isodir):
714 """ Copy the efi files into /EFI/BOOT/
715 If any of them are missing, return False.
716 requires:
717 shim.efi
718 gcdx64.efi
719 fonts/unicode.pf2
720 grub/splash.xpm.gz
722 fail = False
723 missing = []
724 files = [("/boot/efi/EFI/*/shim.efi", "/EFI/BOOT/BOOT%s.efi" % (self.efiarch,)),
725 ("/boot/efi/EFI/*/gcdx64.efi", "/EFI/BOOT/grubx64.efi"),
726 ("/boot/efi/EFI/*/fonts/unicode.pf2", "/EFI/BOOT/fonts/"),
727 ("/boot/grub/splash.xpm.gz", "/EFI/BOOT"),
729 makedirs(isodir+"/EFI/BOOT/fonts/")
730 for src, dest in files:
731 src_glob = glob.glob(self._instroot+src)
732 if not src_glob:
733 missing.append("Missing EFI file (%s)" % (src,))
734 fail = True
735 else:
736 shutil.copy(src_glob[0], isodir+dest)
737 map(logging.error, missing)
738 return fail
740 def __get_basic_efi_config(self, **args):
741 return """
742 set default="0"
744 function load_video {
745 insmod efi_gop
746 insmod efi_uga
747 insmod video_bochs
748 insmod video_cirrus
749 insmod all_video
752 load_video
753 set gfxpayload=keep
754 insmod gzio
755 insmod part_gpt
756 insmod ext2
758 set timeout=%(timeout)d
759 ### END /etc/grub.d/00_header ###
761 search --no-floppy --set=root -l '%(isolabel)s'
763 ### BEGIN /etc/grub.d/10_linux ###
764 """ %args
766 def __get_efi_image_stanza(self, **args):
767 if self._isDracut:
768 args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
769 else:
770 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
771 return """menuentry '%(long)s' --class fedora --class gnu-linux --class gnu --class os {
772 linuxefi /isolinux/vmlinuz%(index)s root=%(rootlabel)s %(liveargs)s %(extra)s
773 initrdefi /isolinux/initrd%(index)s.img
775 """ %args
777 def __get_efi_image_stanzas(self, isodir, name):
778 # FIXME: this only supports one kernel right now...
780 kernel_options = self._get_kernel_options()
781 checkisomd5 = self._has_checkisomd5()
783 cfg = ""
785 for index in range(0, 9):
786 # we don't support xen kernels
787 if os.path.exists("%s/EFI/BOOT/xen%d.gz" %(isodir, index)):
788 continue
789 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
790 liveargs = kernel_options,
791 long = name,
792 extra = "", index = index)
793 if checkisomd5:
794 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
795 liveargs = kernel_options,
796 long = "Verify and Boot " + name,
797 extra = "rd.live.check",
798 index = index)
799 break
801 return cfg
803 def _configure_efi_bootloader(self, isodir):
804 """Set up the configuration for an EFI bootloader"""
805 if self.__copy_efi_files(isodir):
806 shutil.rmtree(isodir + "/EFI")
807 raise CreatorError("Failed to copy EFI files")
809 cfg = self.__get_basic_efi_config(isolabel = self.fslabel,
810 timeout = self._timeout)
811 cfg += self.__get_efi_image_stanzas(isodir, self.name)
813 cfgf = open(isodir + "/EFI/BOOT/grub.cfg", "w")
814 cfgf.write(cfg)
815 cfgf.close()
817 # first gen mactel machines get the bootloader name wrong apparently
818 if rpmUtils.arch.getBaseArch() == "i386":
819 os.link(isodir + "/EFI/BOOT/BOOT%s.efi" % (self.efiarch),
820 isodir + "/EFI/BOOT/BOOT.efi")
823 def _configure_bootloader(self, isodir):
824 self._configure_syslinux_bootloader(isodir)
825 self._configure_efi_bootloader(isodir)
827 class ppcLiveImageCreator(LiveImageCreatorBase):
828 def _get_mkisofs_options(self, isodir):
829 return [ "-hfs", "-no-desktop", "-part",
830 "-map", isodir + "/ppc/mapping",
831 "-hfs-bless", isodir + "/ppc/mac",
832 "-hfs-volid", self.fslabel ]
834 def _get_required_packages(self):
835 return ["yaboot"] + \
836 LiveImageCreatorBase._get_required_packages(self)
838 def _get_excluded_packages(self):
839 # kind of hacky, but exclude memtest86+ on ppc so it can stay in cfg
840 return ["memtest86+"] + \
841 LiveImageCreatorBase._get_excluded_packages(self)
843 def __copy_boot_file(self, destdir, file):
844 for dir in ["/usr/share/ppc64-utils",
845 "/usr/lib/anaconda-runtime/boot"]:
846 path = self._instroot + dir + "/" + file
847 if not os.path.exists(path):
848 continue
850 makedirs(destdir)
851 shutil.copy(path, destdir)
852 return
854 raise CreatorError("Unable to find boot file " + file)
856 def __kernel_bits(self, kernel):
857 testpath = (self._instroot + "/lib/modules/" +
858 kernel + "/kernel/arch/powerpc/platforms")
860 if not os.path.exists(testpath):
861 return { "32" : True, "64" : False }
862 else:
863 return { "32" : False, "64" : True }
865 def __copy_kernel_and_initramfs(self, destdir, version):
866 isDracut = False
867 bootdir = self._instroot + "/boot"
869 makedirs(destdir)
871 shutil.copyfile(bootdir + "/vmlinuz-" + version,
872 destdir + "/vmlinuz")
874 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
875 shutil.copyfile(bootdir + "/initramfs-" + version + ".img",
876 destdir + "/initrd.img")
877 isDracut = True
878 else:
879 shutil.copyfile(bootdir + "/initrd-" + version + ".img",
880 destdir + "/initrd.img")
882 return isDracut
884 def __get_basic_yaboot_config(self, **args):
885 return """
886 init-message = "Welcome to %(name)s"
887 timeout=%(timeout)d
888 """ % args
890 def __get_image_stanza(self, **args):
891 if args["isDracut"]:
892 args["rootlabel"] = "live:LABEL=%(fslabel)s" % args
893 else:
894 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
895 return """
897 image=/ppc/ppc%(bit)s/vmlinuz
898 label=%(short)s
899 initrd=/ppc/ppc%(bit)s/initrd.img
900 read-only
901 append="root=%(rootlabel)s rootfstype=%(isofstype)s %(liveargs)s %(extra)s"
902 """ % args
905 def __write_yaboot_config(self, isodir, bit, isDracut = False):
906 cfg = self.__get_basic_yaboot_config(name = self.name,
907 timeout = self._timeout * 100)
909 kernel_options = self._get_kernel_options()
911 cfg += self.__get_image_stanza(fslabel = self.fslabel,
912 isofstype = "auto",
913 short = "linux",
914 long = "Run from image",
915 extra = "",
916 bit = bit,
917 liveargs = kernel_options,
918 isDracut = isDracut)
920 if self._has_checkisomd5():
921 cfg += self.__get_image_stanza(fslabel = self.fslabel,
922 isofstype = "auto",
923 short = "rd.live.check",
924 long = "Verify and run from image",
925 extra = "rd.live.check",
926 bit = bit,
927 liveargs = kernel_options,
928 isDracut = isDracut)
930 f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
931 f.write(cfg)
932 f.close()
934 def __write_not_supported(self, isodir, bit):
935 makedirs(isodir + "/ppc/ppc" + bit)
937 message = "Sorry, this LiveCD does not support your hardware"
939 f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
940 f.write('init-message = "' + message + '"')
941 f.close()
944 def __write_dualbits_yaboot_config(isodir, **args):
945 cfg = """
946 init-message = "\nWelcome to %(name)s!\nUse 'linux32' for 32-bit kernel.\n\n"
947 timeout=%(timeout)d
948 default=linux
950 image=/ppc/ppc64/vmlinuz
951 label=linux64
952 alias=linux
953 initrd=/ppc/ppc64/initrd.img
954 read-only
956 image=/ppc/ppc32/vmlinuz
957 label=linux32
958 initrd=/ppc/ppc32/initrd.img
959 read-only
960 """ % args
962 f = open(isodir + "/etc/yaboot.conf", "w")
963 f.write(cfg)
964 f.close()
966 def _configure_bootloader(self, isodir):
967 """configure the boot loader"""
968 havekernel = { 32: False, 64: False }
970 self.__copy_boot_file(isodir + "/ppc", "mapping")
971 self.__copy_boot_file(isodir + "/ppc", "bootinfo.txt")
972 self.__copy_boot_file(isodir + "/ppc/mac", "ofboot.b")
974 shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
975 isodir + "/ppc/mac/yaboot")
977 makedirs(isodir + "/ppc/chrp")
978 shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
979 isodir + "/ppc/chrp/yaboot")
981 subprocess.call(["/usr/sbin/addnote", isodir + "/ppc/chrp/yaboot"])
984 # FIXME: ppc should support multiple kernels too...
986 kernel = self._get_kernel_versions().values()[0][0]
988 kernel_bits = self.__kernel_bits(kernel)
990 for (bit, present) in kernel_bits.items():
991 if not present:
992 self.__write_not_supported(isodir, bit)
993 continue
995 isDracut = self.__copy_kernel_and_initramfs(isodir + "/ppc/ppc" + bit, kernel)
996 self.__write_yaboot_config(isodir, bit, isDracut)
998 makedirs(isodir + "/etc")
999 if kernel_bits["32"] and not kernel_bits["64"]:
1000 shutil.copyfile(isodir + "/ppc/ppc32/yaboot.conf",
1001 isodir + "/etc/yaboot.conf")
1002 elif kernel_bits["64"] and not kernel_bits["32"]:
1003 shutil.copyfile(isodir + "/ppc/ppc64/yaboot.conf",
1004 isodir + "/etc/yaboot.conf")
1005 else:
1006 self.__write_dualbits_yaboot_config(isodir,
1007 name = self.name,
1008 timeout = self._timeout * 100)
1011 # FIXME: build 'netboot' images with kernel+initrd, like mk-images.ppc
1014 class ppc64LiveImageCreator(ppcLiveImageCreator):
1015 def _get_excluded_packages(self):
1016 # FIXME:
1017 # while kernel.ppc and kernel.ppc64 co-exist,
1018 # we can't have both
1019 return ["kernel.ppc"] + \
1020 ppcLiveImageCreator._get_excluded_packages(self)
1022 arch = rpmUtils.arch.getBaseArch()
1023 if arch in ("i386", "x86_64"):
1024 LiveImageCreator = x86LiveImageCreator
1025 elif arch in ("ppc",):
1026 LiveImageCreator = ppcLiveImageCreator
1027 elif arch in ("ppc64",):
1028 LiveImageCreator = ppc64LiveImageCreator
1029 elif arch.startswith('arm'):
1030 LiveImageCreator = LiveImageCreatorBase
1032 else:
1033 raise CreatorError("Architecture not supported!")