rename all micng to mic
[tools/mic.git] / mic / imager / livecd.py
1 #
2 # livecd.py : LiveCDImageCreator class for creating Live CD images
3 #
4 # Copyright 2007, Red Hat  Inc.
5 #
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.
9 #
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.
14 #
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.
18
19 import os
20 import sys
21 import glob
22 import shutil
23 import subprocess
24 import logging
25 import re
26 import time
27
28 import mic.utils.kickstart as kickstart 
29 import mic.utils.fs_related as fs_related
30 import mic.utils.rpmmisc as rpmmisc
31 import mic.utils.misc as misc
32 from mic.utils.errors import *
33 from loop import LoopImageCreator
34
35 class LiveImageCreatorBase(LoopImageCreator):
36     """A base class for LiveCD image creators.
37
38         This class serves as a base class for the architecture-specific LiveCD
39         image creator subclass, LiveImageCreator.
40     
41         LiveImageCreator creates a bootable ISO containing the system image,
42         bootloader, bootloader configuration, kernel and initramfs.
43     """
44
45     def __init__(self, creatoropts = None, pkgmgr = None):
46         """Initialise a LiveImageCreator instance.
47
48            This method takes the same arguments as ImageCreator.__init__().
49         """
50         LoopImageCreator.__init__(self, creatoropts, pkgmgr)
51
52         #Controls whether to use squashfs to compress the image.
53         self.skip_compression = False 
54
55         #Controls whether an image minimizing snapshot should be created.
56         #
57         #This snapshot can be used when copying the system image from the ISO in
58         #order to minimize the amount of data that needs to be copied; simply,
59         #it makes it possible to create a version of the image's filesystem with
60         #no spare space.
61         self.skip_minimize = False 
62
63         #A flag which indicates i act as a convertor default false
64         self.actasconvertor = False
65         
66         #The bootloader timeout from kickstart.
67         if self.ks:
68             self._timeout = kickstart.get_timeout(self.ks, 10)
69         else:
70             self._timeout = 10
71
72         #The default kernel type from kickstart.
73         if self.ks:
74             self._default_kernel = kickstart.get_default_kernel(self.ks, "kernel")
75         else:
76             self._default_kernel = None
77
78
79         self.__isodir = None
80
81         self.__modules = ["=ata", "sym53c8xx", "aic7xxx", "=usb", "=firewire", "=mmc", "=pcmcia", "mptsas"]
82         if self.ks:
83             self.__modules.extend(kickstart.get_modules(self.ks))
84
85         self._dep_checks.extend(["isohybrid", "unsquashfs", "mksquashfs", "dd", "genisoimage"])
86
87     #
88     # Hooks for subclasses
89     #
90     def _configure_bootloader(self, isodir):
91         """Create the architecture specific booloader configuration.
92
93             This is the hook where subclasses must create the booloader
94             configuration in order to allow a bootable ISO to be built.
95     
96             isodir -- the directory where the contents of the ISO are to be staged
97         """
98         raise CreatorError("Bootloader configuration is arch-specific, "
99                            "but not implemented for this arch!")
100     def _get_menu_options(self):
101         """Return a menu options string for syslinux configuration.
102         """
103         if self.ks is None:
104             return "bootinstall autoinst"
105         r = kickstart.get_menu_args(self.ks)
106         return r
107
108     def _get_kernel_options(self):
109         """Return a kernel options string for bootloader configuration.
110
111             This is the hook where subclasses may specify a set of kernel options
112             which should be included in the images bootloader configuration.
113     
114             A sensible default implementation is provided.
115         """
116         if self.ks is None:
117             r = "ro liveimg quiet"
118             if os.path.exists(instroot + "/usr/bin/rhgb"):
119                 r += " rhgb"
120             if os.path.exists(instroot + "/usr/bin/plymouth"):
121                 r += " rhgb"
122             return r
123         r = kickstart.get_kernel_args(self.ks)
124         if os.path.exists(self._instroot + "/usr/bin/rhgb") or \
125            os.path.exists(self._instroot + "/usr/bin/plymouth"):
126             r += " rhgb"
127         return r
128
129     def _get_mkisofs_options(self, isodir):
130         """Return the architecture specific mkisosfs options.
131
132             This is the hook where subclasses may specify additional arguments to
133             mkisofs, e.g. to enable a bootable ISO to be built.
134     
135             By default, an empty list is returned.
136         """
137         return []
138
139     #
140     # Helpers for subclasses
141     #
142     def _has_checkisomd5(self):
143         """Check whether checkisomd5 is available in the install root."""
144         def exists(instroot, path):
145             return os.path.exists(instroot + path)
146
147         if (exists(self._instroot, "/usr/lib/moblin-installer-runtime/checkisomd5") or
148             exists(self._instroot, "/usr/bin/checkisomd5")):
149             if (os.path.exists("/usr/bin/implantisomd5") or
150                os.path.exists("/usr/lib/anaconda-runtime/implantisomd5")):
151                 return True
152
153         return False
154
155     def _uncompress_squashfs(self, squashfsimg, outdir):
156         """Uncompress file system from squshfs image"""
157         unsquashfs = fs_related.find_binary_path("unsquashfs")
158         args = [unsquashfs, "-d", outdir, squashfsimg ]
159         rc = subprocess.call(args)
160         if (rc != 0):
161             raise CreatorError("Failed to uncompress %s." % squashfsimg)
162     #
163     # Actual implementation
164     #
165     def _base_on(self, base_on):
166         """Support Image Convertor"""
167         if self.actasconvertor:
168             if os.path.exists(base_on) and not os.path.isfile(base_on):
169                 ddcmd = fs_related.find_binary_path("dd")
170                 args = [ ddcmd, "if=%s" % base_on, "of=%s" % self._image ]
171                 print "dd %s -> %s" % (base_on, self._image)
172                 rc = subprocess.call(args)
173                 if rc != 0:
174                     raise CreatorError("Failed to dd from %s to %s" % (base_on, self._image))
175                 self._set_image_size(misc.get_file_size(self._image) * 1024L * 1024L)
176             if os.path.isfile(base_on):
177                 print "Copying file system..."
178                 shutil.copyfile(base_on, self._image)
179                 self._set_image_size(get_file_size(self._image) * 1024L * 1024L)
180             return
181
182         #helper function to extract ext3 file system from a live CD ISO
183         isoloop = fs_related.DiskMount(fs_related.LoopbackDisk(base_on, 0), self._mkdtemp())
184
185         try:
186             isoloop.mount()
187         except MountError, e:
188             raise CreatorError("Failed to loopback mount '%s' : %s" %
189                                (base_on, e))
190
191         # legacy LiveOS filesystem layout support, remove for F9 or F10
192         if os.path.exists(isoloop.mountdir + "/squashfs.img"):
193             squashimg = isoloop.mountdir + "/squashfs.img"
194         else:
195             squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
196
197         tmpoutdir = self._mkdtemp()
198         # unsquashfs requires outdir mustn't exist
199         shutil.rmtree(tmpoutdir, ignore_errors = True)
200         self._uncompress_squashfs(squashimg, tmpoutdir)
201
202         try:
203             # legacy LiveOS filesystem layout support, remove for F9 or F10
204             if os.path.exists(tmpoutdir + "/os.img"):
205                 os_image = tmpoutdir + "/os.img"
206             else:
207                 os_image = tmpoutdir + "/LiveOS/ext3fs.img"
208
209             if not os.path.exists(os_image):
210                 raise CreatorError("'%s' is not a valid live CD ISO : neither "
211                                    "LiveOS/ext3fs.img nor os.img exist" %
212                                    base_on)
213
214             print "Copying file system..."
215             shutil.copyfile(os_image, self._image)
216             self._set_image_size(get_file_size(self._image) * 1024L * 1024L)
217         finally:
218             shutil.rmtree(tmpoutdir, ignore_errors = True)
219             isoloop.cleanup()
220
221     def _mount_instroot(self, base_on = None):
222         LoopImageCreator._mount_instroot(self, base_on)
223         self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
224
225     def _unmount_instroot(self):
226         try:
227             os.unlink(self._instroot + "/etc/sysconfig/mkinitrd")
228         except:
229             pass
230         LoopImageCreator._unmount_instroot(self)
231
232     def __ensure_isodir(self):
233         if self.__isodir is None:
234             self.__isodir = self._mkdtemp("iso-")
235         return self.__isodir
236
237     def _get_isodir(self):
238         return self.__ensure_isodir()
239
240     def _set_isodir(self, isodir = None):
241         self.__isodir = isodir
242
243     def _create_bootconfig(self):
244         """Configure the image so that it's bootable."""
245         self._configure_bootloader(self.__ensure_isodir())
246
247     def _get_post_scripts_env(self, in_chroot):
248         env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
249
250         if not in_chroot:
251             env["LIVE_ROOT"] = self.__ensure_isodir()
252
253         return env
254
255     def __write_initrd_conf(self, path):
256         content = ""
257         if not os.path.exists(os.path.dirname(path)):
258             fs_related.makedirs(os.path.dirname(path))
259         f = open(path, "w")
260
261         content += 'LIVEOS="yes"\n'
262         content += 'PROBE="no"\n'
263         content += 'MODULES+="squashfs ext3 ext2 vfat msdos "\n'
264         content += 'MODULES+="sr_mod sd_mod ide-cd cdrom "\n'
265
266         for module in self.__modules:
267             if module == "=usb":
268                 content += 'MODULES+="ehci_hcd uhci_hcd ohci_hcd "\n'
269                 content += 'MODULES+="usb_storage usbhid "\n'
270             elif module == "=firewire":
271                 content += 'MODULES+="firewire-sbp2 firewire-ohci "\n'
272                 content += 'MODULES+="sbp2 ohci1394 ieee1394 "\n'
273             elif module == "=mmc":
274                 content += 'MODULES+="mmc_block sdhci sdhci-pci "\n'
275             elif module == "=pcmcia":
276                 content += 'MODULES+="pata_pcmcia  "\n'
277             else:
278                 content += 'MODULES+="' + module + ' "\n'
279         f.write(content)
280         f.close()
281
282     def __create_iso(self, isodir):
283         iso = self._outdir + "/" + self.name + ".iso"
284         genisoimage = fs_related.find_binary_path("genisoimage")
285         args = [genisoimage,
286                 "-J", "-r",
287                 "-hide-rr-moved", "-hide-joliet-trans-tbl",
288                 "-V", self.fslabel,
289                 "-o", iso]
290
291         args.extend(self._get_mkisofs_options(isodir))
292
293         args.append(isodir)
294
295         if subprocess.call(args) != 0:
296             raise CreatorError("ISO creation failed!")
297
298         """ It should be ok still even if you haven't isohybrid """
299         isohybrid = None
300         try:
301             isohybrid = fs_related.find_binary_path("isohybrid")
302         except:
303             pass
304
305         if isohybrid:
306             args = [isohybrid, "-partok", iso ]
307             if subprocess.call(args) != 0:
308                 raise CreatorError("Hybrid ISO creation failed!")
309
310         self.__implant_md5sum(iso)
311
312     def __implant_md5sum(self, iso):
313         """Implant an isomd5sum."""
314         if os.path.exists("/usr/bin/implantisomd5"):
315             implantisomd5 = "/usr/bin/implantisomd5"
316         elif os.path.exists("/usr/lib/anaconda-runtime/implantisomd5"):
317             implantisomd5 = "/usr/lib/anaconda-runtime/implantisomd5"
318         else:
319             logging.warn("isomd5sum not installed; not setting up mediacheck")
320             implantisomd5 = ""
321             return
322
323         subprocess.call([implantisomd5, iso], stdout=sys.stdout, stderr=sys.stderr)
324
325     def _stage_final_image(self):
326         try:
327             fs_related.makedirs(self.__ensure_isodir() + "/LiveOS")
328
329             minimal_size = self._resparse()
330
331             if not self.skip_minimize:
332                 fs_related.create_image_minimizer(self.__isodir + "/LiveOS/osmin.img",
333                                        self._image, minimal_size)
334
335             if self.skip_compression:
336                 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
337             else:
338                 fs_related.makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
339                 shutil.move(self._image,
340                             os.path.join(os.path.dirname(self._image),
341                                          "LiveOS", "ext3fs.img"))
342                 fs_related.mksquashfs(os.path.dirname(self._image),
343                            self.__isodir + "/LiveOS/squashfs.img")
344
345             self.__create_iso(self.__isodir)
346         finally:
347             shutil.rmtree(self.__isodir, ignore_errors = True)
348             self.__isodir = None
349
350 class x86LiveImageCreator(LiveImageCreatorBase):
351     """ImageCreator for x86 machines"""
352     def _get_mkisofs_options(self, isodir):
353         return [ "-b", "isolinux/isolinux.bin",
354                  "-c", "isolinux/boot.cat",
355                  "-no-emul-boot", "-boot-info-table",
356                  "-boot-load-size", "4" ]
357
358     def _get_required_packages(self):
359         return ["syslinux", "syslinux-extlinux"] + LiveImageCreatorBase._get_required_packages(self)
360
361     def _get_isolinux_stanzas(self, isodir):
362         return ""
363
364     def __find_syslinux_menu(self):
365         for menu in ["vesamenu.c32", "menu.c32"]:
366             if os.path.isfile(self._instroot + "/usr/share/syslinux/" + menu):
367                 return menu
368
369         raise CreatorError("syslinux not installed : "
370                            "no suitable /usr/share/syslinux/*menu.c32 found")
371
372     def __find_syslinux_mboot(self):
373         #
374         # We only need the mboot module if we have any xen hypervisors
375         #
376         if not glob.glob(self._instroot + "/boot/xen.gz*"):
377             return None
378
379         return "mboot.c32"
380
381     def __copy_syslinux_files(self, isodir, menu, mboot = None):
382         files = ["isolinux.bin", menu]
383         if mboot:
384             files += [mboot]
385
386         for f in files:
387             path = self._instroot + "/usr/share/syslinux/" + f
388
389             if not os.path.isfile(path):
390                 raise CreatorError("syslinux not installed : "
391                                    "%s not found" % path)
392
393             shutil.copy(path, isodir + "/isolinux/")
394
395     def __copy_syslinux_background(self, isodest):
396         background_path = self._instroot + \
397                           "/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg"
398
399         if not os.path.exists(background_path):
400             return False
401
402         shutil.copyfile(background_path, isodest)
403
404         return True
405
406     def __copy_kernel_and_initramfs(self, isodir, version, index):
407         bootdir = self._instroot + "/boot"
408
409         if self._alt_initrd_name:
410             src_initrd_path = os.path.join(bootdir, self._alt_initrd_name)
411         else:
412             src_initrd_path = os.path.join(bootdir, "initrd-" + version + ".img")
413
414         try:
415             shutil.copyfile(bootdir + "/vmlinuz-" + version,
416                             isodir + "/isolinux/vmlinuz" + index)
417             shutil.copyfile(src_initrd_path,
418                             isodir + "/isolinux/initrd" + index + ".img")
419         except:
420             raise CreatorError("Unable to copy valid kernels or initrds, please check the repo")
421
422         is_xen = False
423         if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
424             shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
425                             isodir + "/isolinux/xen" + index + ".gz")
426             is_xen = True
427
428         return is_xen
429
430     def __is_default_kernel(self, kernel, kernels):
431         if len(kernels) == 1:
432             return True
433
434         if kernel == self._default_kernel:
435             return True
436
437         if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
438             return True
439
440         return False
441
442     def __get_basic_syslinux_config(self, **args):
443         return """
444 default %(menu)s
445 timeout %(timeout)d
446
447 %(background)s
448 menu title Welcome to %(distroname)s!
449 menu color border 0 #ffffffff #00000000
450 menu color sel 7 #ffffffff #ff000000
451 menu color title 0 #ffffffff #00000000
452 menu color tabmsg 0 #ffffffff #00000000
453 menu color unsel 0 #ffffffff #00000000
454 menu color hotsel 0 #ff000000 #ffffffff
455 menu color hotkey 7 #ffffffff #ff000000
456 menu color timeout_msg 0 #ffffffff #00000000
457 menu color timeout 0 #ffffffff #00000000
458 menu color cmdline 0 #ffffffff #00000000
459 """ % args
460
461     def __get_image_stanza(self, is_xen, **args):
462         if not is_xen:
463             template = """label %(short)s
464   menu label %(long)s
465   kernel vmlinuz%(index)s
466   append initrd=initrd%(index)s.img root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
467 """
468         else:
469             template = """label %(short)s
470   menu label %(long)s
471   kernel mboot.c32
472   append xen%(index)s.gz --- vmlinuz%(index)s root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s --- initrd%(index)s.img
473 """
474         return template % args
475
476     def __get_image_stanzas(self, isodir):
477         versions = []
478         kernels = self._get_kernel_versions()
479         for kernel in kernels:
480             for version in kernels[kernel]:
481                 versions.append(version)
482
483         if not versions:
484             raise CreatorError("Unable to find valid kernels, please check the repo")
485
486         kernel_options = self._get_kernel_options()
487
488         """ menu can be customized highly, the format is
489
490               short_name1:long_name1:extra_options1;short_name2:long_name2:extra_options2
491
492             for example: autoinst:Installation only:systemd.unit=installer-graphical.service
493             but in order to keep compatible with old format, these are still ok:
494
495               liveinst autoinst
496               liveinst;autoinst
497               liveinst::;autoinst::
498         """
499         oldmenus = {"basic":{"short":"basic", "long":"Installation Only (Text based)", "extra":"basic nosplash 4"},
500                     "liveinst":{"short":"liveinst", "long":"Installation Only", "extra":"liveinst nosplash 4"},
501                     "autoinst":{"short":"autoinst", "long":"Autoinstall (Deletes all existing content)", "extra":"autoinst nosplash 4"},
502                     "netinst":{"short":"netinst", "long":"Network Installation", "extra":"netinst 4"},
503                     "verify":{"short":"check", "long":"Verify and", "extra":"check"}
504                    }
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]]
514
515         cfg = ""
516
517         default_version = None
518         default_index = None
519         index = "0"
520         netinst = None
521         for version in versions:
522             is_xen = self.__copy_kernel_and_initramfs(isodir, version, index)
523
524             default = self.__is_default_kernel(kernel, kernels)
525             
526                 
527             if default:
528                 long = "Boot %s" % self.distro_name
529             elif kernel.startswith("kernel-"):
530                 long = "Boot %s(%s)" % (self.name, kernel[7:])
531             else:
532                 long = "Boot %s(%s)" % (self.name, kernel)
533             oldmenus["verify"]["long"] = "%s %s" % (oldmenus["verify"]["long"], long)
534
535             cfg += self.__get_image_stanza(is_xen,
536                                            fslabel = self.fslabel,
537                                            liveargs = kernel_options,
538                                            long = long,
539                                            short = "linux" + index,
540                                            extra = "",
541                                            index = index)
542
543             if default:
544                 cfg += "menu default\n"
545                 default_version = version
546                 default_index = index
547
548             for menu in menus:
549                 if not menu[0]:
550                     continue
551                 short = menu[0] + index
552
553                 if len(menu) >= 2:
554                     long = menu[1]
555                 else:
556                     if menu[0] in oldmenus.keys():
557                         if menu[0] == "verify" and not self._has_checkisomd5():
558                             continue
559                         if menu[0] == "netinst":
560                             netinst = oldmenus[menu[0]]
561                             continue
562                         long = oldmenus[menu[0]]["long"]
563                         extra = oldmenus[menu[0]]["extra"]
564                     else:
565                         long = short.upper() + " X" + index
566                         extra = ""
567
568                 if len(menu) >= 3:
569                     extra = menu[2]
570
571                 cfg += self.__get_image_stanza(is_xen,
572                                                fslabel = self.fslabel,
573                                                liveargs = kernel_options,
574                                                long = long,
575                                                short = short,
576                                                extra = extra,
577                                                index = index)
578
579             index = str(int(index) + 1)
580
581         if not default_version:
582             default_version = versions[0]
583         if not default_index:
584             default_index = "0"
585
586         if netinst:
587             cfg += self.__get_image_stanza(is_xen,
588                                            fslabel = self.fslabel,
589                                            liveargs = kernel_options,
590                                            long = netinst["long"],
591                                            short = netinst["short"],
592                                            extra = netinst["extra"],
593                                            index = default_index)
594
595         return cfg
596
597     def __get_memtest_stanza(self, isodir):
598         memtest = glob.glob(self._instroot + "/boot/memtest86*")
599         if not memtest:
600             return ""
601
602         shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")
603
604         return """label memtest
605   menu label Memory Test
606   kernel memtest
607 """
608
609     def __get_local_stanza(self, isodir):
610         return """label local
611   menu label Boot from local drive
612   localboot 0xffff
613 """
614
615     def _configure_syslinux_bootloader(self, isodir):
616         """configure the boot loader"""
617         fs_related.makedirs(isodir + "/isolinux")
618
619         menu = self.__find_syslinux_menu()
620
621         self.__copy_syslinux_files(isodir, menu,
622                                    self.__find_syslinux_mboot())
623
624         background = ""
625         if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
626             background = "menu background splash.jpg"
627
628         cfg = self.__get_basic_syslinux_config(menu = menu,
629                                                background = background,
630                                                name = self.name,
631                                                timeout = self._timeout * 10,
632                                                distroname = self.distro_name)
633
634         cfg += self.__get_image_stanzas(isodir)
635         cfg += self.__get_memtest_stanza(isodir)
636         cfg += self.__get_local_stanza(isodir)
637         cfg += self._get_isolinux_stanzas(isodir)
638
639         cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
640         cfgf.write(cfg)
641         cfgf.close()
642
643     def __copy_efi_files(self, isodir):
644         if not os.path.exists(self._instroot + "/boot/efi/EFI/redhat/grub.efi"):
645             return False
646         shutil.copy(self._instroot + "/boot/efi/EFI/redhat/grub.efi",
647                     isodir + "/EFI/boot/grub.efi")
648         shutil.copy(self._instroot + "/boot/grub/splash.xpm.gz",
649                     isodir + "/EFI/boot/splash.xpm.gz")
650
651         return True
652
653     def __get_basic_efi_config(self, **args):
654         return """
655 default=0
656 splashimage=/EFI/boot/splash.xpm.gz
657 timeout %(timeout)d
658 hiddenmenu
659
660 """ %args
661
662     def __get_efi_image_stanza(self, **args):
663         return """title %(long)s
664   kernel /EFI/boot/vmlinuz%(index)s root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
665   initrd /EFI/boot/initrd%(index)s.img
666 """ %args
667
668     def __get_efi_image_stanzas(self, isodir, name):
669         # FIXME: this only supports one kernel right now...
670
671         kernel_options = self._get_kernel_options()
672         checkisomd5 = self._has_checkisomd5()
673
674         cfg = ""
675
676         for index in range(0, 9):
677             # we don't support xen kernels
678             if os.path.exists("%s/EFI/boot/xen%d.gz" %(isodir, index)):
679                 continue
680             cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
681                                                liveargs = kernel_options,
682                                                long = name,
683                                                extra = "", index = index)
684             if checkisomd5:
685                 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
686                                                    liveargs = kernel_options,
687                                                    long = "Verify and Boot " + name,
688                                                    extra = "check",
689                                                    index = index)
690             break
691
692         return cfg
693
694     def _configure_efi_bootloader(self, isodir):
695         """Set up the configuration for an EFI bootloader"""
696         fs_related.makedirs(isodir + "/EFI/boot")
697
698         if not self.__copy_efi_files(isodir):
699             shutil.rmtree(isodir + "/EFI")
700             return
701
702         for f in os.listdir(isodir + "/isolinux"):
703             os.link("%s/isolinux/%s" %(isodir, f),
704                     "%s/EFI/boot/%s" %(isodir, f))
705
706
707         cfg = self.__get_basic_efi_config(name = self.name,
708                                           timeout = self._timeout)
709         cfg += self.__get_efi_image_stanzas(isodir, self.name)
710
711         cfgf = open(isodir + "/EFI/boot/grub.conf", "w")
712         cfgf.write(cfg)
713         cfgf.close()
714
715         # first gen mactel machines get the bootloader name wrong apparently
716         if rpmmisc.getBaseArch() == "i386":
717             os.link(isodir + "/EFI/boot/grub.efi", isodir + "/EFI/boot/boot.efi")
718             os.link(isodir + "/EFI/boot/grub.conf", isodir + "/EFI/boot/boot.conf")
719
720         # for most things, we want them named boot$efiarch
721         efiarch = {"i386": "ia32", "x86_64": "x64"}
722         efiname = efiarch[rpmmisc.getBaseArch()]
723         os.rename(isodir + "/EFI/boot/grub.efi", isodir + "/EFI/boot/boot%s.efi" %(efiname,))
724         os.link(isodir + "/EFI/boot/grub.conf", isodir + "/EFI/boot/boot%s.conf" %(efiname,))
725
726
727     def _configure_bootloader(self, isodir):
728         self._configure_syslinux_bootloader(isodir)
729         self._configure_efi_bootloader(isodir)
730
731 class ppcLiveImageCreator(LiveImageCreatorBase):
732     def _get_mkisofs_options(self, isodir):
733         return [ "-hfs", "-nodesktop", "-part"
734                  "-map", isodir + "/ppc/mapping",
735                  "-hfs-bless", isodir + "/ppc/mac",
736                  "-hfs-volid", self.fslabel ]
737
738     def _get_required_packages(self):
739         return ["yaboot"] + \
740                LiveImageCreatorBase._get_required_packages(self)
741
742     def _get_excluded_packages(self):
743         # kind of hacky, but exclude memtest86+ on ppc so it can stay in cfg
744         return ["memtest86+"] + \
745                LiveImageCreatorBase._get_excluded_packages(self)
746
747     def __copy_boot_file(self, destdir, file):
748         for dir in ["/usr/share/ppc64-utils",
749                     "/usr/lib/moblin-installer-runtime/boot"]:
750             path = self._instroot + dir + "/" + file
751             if not os.path.exists(path):
752                 continue
753
754             fs_related.makedirs(destdir)
755             shutil.copy(path, destdir)
756             return
757
758         raise CreatorError("Unable to find boot file " + file)
759
760     def __kernel_bits(self, kernel):
761         testpath = (self._instroot + "/lib/modules/" +
762                     kernel + "/kernel/arch/powerpc/platforms")
763
764         if not os.path.exists(testpath):
765             return { "32" : True, "64" : False }
766         else:
767             return { "32" : False, "64" : True }
768
769     def __copy_kernel_and_initramfs(self, destdir, version):
770         bootdir = self._instroot + "/boot"
771
772         fs_related.makedirs(destdir)
773
774         shutil.copyfile(bootdir + "/vmlinuz-" + version,
775                         destdir + "/vmlinuz")
776
777         shutil.copyfile(bootdir + "/initrd-" + version + ".img",
778                         destdir + "/initrd.img")
779
780     def __get_basic_yaboot_config(self, **args):
781         return """
782 init-message = "Welcome to %(distroname)s!"
783 timeout=%(timeout)d
784 """ % args
785
786     def __get_image_stanza(self, **args):
787         return """
788
789 image=/ppc/ppc%(bit)s/vmlinuz
790   label=%(short)s
791   initrd=/ppc/ppc%(bit)s/initrd.img
792   read-only
793   append="root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s"
794 """ % args
795
796
797     def __write_yaboot_config(isodir, bit):
798         cfg = self.__get_basic_yaboot_config(name = self.name,
799                                              timeout = self._timeout * 100,
800                                              distroname = self.distro_name)
801
802         kernel_options = self._get_kernel_options()
803
804         cfg += self.__get_image_stanza(fslabel = self.fslabel,
805                                        short = "linux",
806                                        long = "Run from image",
807                                        extra = "",
808                                        bit = bit,
809                                        liveargs = kernel_options)
810
811         if self._has_checkisomd5():
812             cfg += self.__get_image_stanza(fslabel = self.fslabel,
813                                            short = "check",
814                                            long = "Verify and run from image",
815                                            extra = "check",
816                                            bit = bit,
817                                            liveargs = kernel_options)
818
819         f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
820         f.write(cfg)
821         f.close()
822
823     def __write_not_supported(isodir, bit):
824         fs_related.makedirs(isodir + "/ppc/ppc" + bit)
825
826         message = "Sorry, this LiveCD does not support your hardware"
827
828         f = open(isodir + "/ppc/ppc" + bit + "/yaboot.conf", "w")
829         f.write('init-message = "' + message + '"')
830         f.close()
831
832
833     def __write_dualbits_yaboot_config(isodir, **args):
834         cfg = """
835 init-message = "\nWelcome to %(name)s!\nUse 'linux32' for 32-bit kernel.\n\n"
836 timeout=%(timeout)d
837 default=linux
838
839 image=/ppc/ppc64/vmlinuz
840         label=linux64
841         alias=linux
842         initrd=/ppc/ppc64/initrd.img
843         read-only
844
845 image=/ppc/ppc32/vmlinuz
846         label=linux32
847         initrd=/ppc/ppc32/initrd.img
848         read-only
849 """ % args
850
851         f = open(isodir + "/etc/yaboot.conf", "w")
852         f.write(cfg)
853         f.close()
854
855     def _configure_bootloader(self, isodir):
856         """configure the boot loader"""
857         havekernel = { 32: False, 64: False }
858
859         self.__copy_boot_file("mapping", isodir + "/ppc")
860         self.__copy_boot_file("bootinfo.txt", isodir + "/ppc")
861         self.__copy_boot_file("ofboot.b", isodir + "/ppc/mac")
862
863         shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
864                         isodir + "/ppc/mac/yaboot")
865
866         fs_related.makedirs(isodir + "/ppc/chrp")
867         shutil.copyfile(self._instroot + "/usr/lib/yaboot/yaboot",
868                         isodir + "/ppc/chrp/yaboot")
869
870         subprocess.call(["/usr/sbin/addnote", isodir + "/ppc/chrp/yaboot"])
871
872         #
873         # FIXME: ppc should support multiple kernels too...
874         #
875         kernel = self._get_kernel_versions().values()[0][0]
876
877         kernel_bits = self.__kernel_bits(kernel)
878
879         for (bit, present) in kernel_bits.items():
880             if not present:
881                 self.__write_not_supported(isodir, bit)
882                 continue
883
884             self.__copy_kernel_and_initramfs(isodir + "/ppc/ppc" + bit, kernel)
885             self.__write_yaboot_config(isodir, bit)
886
887         fs_related.makedirs(isodir + "/etc")
888         if kernel_bits["32"] and not kernel_bits["64"]:
889             shutil.copyfile(isodir + "/ppc/ppc32/yaboot.conf",
890                             isodir + "/etc/yaboot.conf")
891         elif kernel_bits["64"] and not kernel_bits["32"]:
892             shutil.copyfile(isodir + "/ppc/ppc64/yaboot.conf",
893                             isodir + "/etc/yaboot.conf")
894         else:
895             self.__write_dualbits_yaboot_config(isodir,
896                                                 name = self.name,
897                                                 timeout = self._timeout * 100)
898
899         #
900         # FIXME: build 'netboot' images with kernel+initrd, like mk-images.ppc
901         #
902
903 class ppc64LiveImageCreator(ppcLiveImageCreator):
904     def _get_excluded_packages(self):
905         # FIXME:
906         #   while kernel.ppc and kernel.ppc64 co-exist,
907         #   we can't have both
908         return ["kernel.ppc"] + \
909                ppcLiveImageCreator._get_excluded_packages(self)
910
911 arch = rpmmisc.getBaseArch()
912 if arch in ("i386", "x86_64"):
913     LiveCDImageCreator = x86LiveImageCreator
914 elif arch in ("ppc",):
915     LiveCDImageCreator = ppcLiveImageCreator
916 elif arch in ("ppc64",):
917     LiveCDImageCreator = ppc64LiveImageCreator
918 elif arch.startswith("arm"):
919     LiveCDImageCreator = LiveImageCreatorBase
920 else:
921     raise CreatorError("Architecture not supported!")