initial import code into git
[platform/upstream/mic.git] / micng / imager / BaseImageCreator.py
1 #
2 # creator.py : ImageCreator and LoopImageCreator base classes
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 os.path
21 import stat
22 import sys
23 import tempfile
24 import shutil
25 import logging
26 import subprocess
27 import re
28 import tarfile
29 import glob
30
31 import rpm
32
33 from micng.utils.errors import *
34 from micng.utils.fs_related import *
35 from micng.utils import kickstart
36 from micng.utils import pkgmanagers
37 from micng.utils.rpmmisc import *
38 from micng.utils.misc import *
39
40 FSLABEL_MAXLEN = 32
41 """The maximum string length supported for LoopImageCreator.fslabel."""
42
43 class ImageCreator(object):
44     """Installs a system to a chroot directory.
45
46     ImageCreator is the simplest creator class available; it will install and
47     configure a system image according to the supplied kickstart file.
48
49     e.g.
50
51       import micng.imgcreate as imgcreate
52       ks = imgcreate.read_kickstart("foo.ks")
53       imgcreate.ImageCreator(ks, "foo").create()
54
55     """
56
57     def __init__(self, ks, name):
58         """Initialize an ImageCreator instance.
59
60         ks -- a pykickstart.KickstartParser instance; this instance will be
61               used to drive the install by e.g. providing the list of packages
62               to be installed, the system configuration and %post scripts
63
64         name -- a name for the image; used for e.g. image filenames or
65                 filesystem labels
66
67         """
68
69         """ Initialize package managers """
70 #package plugin manager
71         self.pkgmgr = pkgmanagers.pkgManager()
72         self.pkgmgr.load_pkg_managers()
73
74         self.ks = ks
75         """A pykickstart.KickstartParser instance."""
76
77         self.name = name
78         """A name for the image."""
79
80         self.distro_name = "MeeGo"
81
82         """Output image file names"""
83         self.outimage = []
84
85         """A flag to generate checksum"""
86         self._genchecksum = False
87
88         self.tmpdir = "/var/tmp"
89         """The directory in which all temporary files will be created."""
90
91         self.cachedir = None
92
93         self._alt_initrd_name = None
94
95         self.__builddir = None
96         self.__bindmounts = []
97
98         """ Contains the compression method that is used to compress
99         the disk image after creation, e.g., bz2.
100         This value is set with compression_method function. """
101         self.__img_compression_method = None
102
103         # dependent commands to check
104         self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
105
106         self._recording_pkgs = None
107
108         self._include_src = None
109
110         self._local_pkgs_path = None
111
112         # available size in root fs, init to 0
113         self._root_fs_avail = 0
114
115         # target arch for non-x86 image
116         self.target_arch = None
117
118         """ Name of the disk image file that is created. """
119         self._img_name = None
120
121         """ Image format """
122         self.image_format = None
123
124         """ Save qemu emulator file name in order to clean up it finally """
125         self.qemu_emulator = None
126
127         """ No ks provided when called by convertor, so skip the dependency check """
128         if self.ks:
129             """ If we have btrfs partition we need to check that we have toosl for those """
130             for part in self.ks.handler.partition.partitions:
131                 if part.fstype and part.fstype == "btrfs":
132                     self._dep_checks.append("mkfs.btrfs")
133                     break
134
135     def set_target_arch(self, arch):
136         if arch not in arches.keys():
137             return False
138         self.target_arch = arch
139         if self.target_arch.startswith("arm"):
140             for dep in self._dep_checks:
141                 if dep == "extlinux":
142                     self._dep_checks.remove(dep)
143
144             if not os.path.exists("/usr/bin/qemu-arm") or not is_statically_linked("/usr/bin/qemu-arm"):
145                 self._dep_checks.append("qemu-arm-static")
146                 
147             if os.path.exists("/proc/sys/vm/vdso_enabled"):
148                 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
149                 vdso_value = vdso_fh.read().strip()
150                 vdso_fh.close()
151                 if (int)(vdso_value) == 1:
152                     print "\n= WARNING ="
153                     print "vdso is enabled on your host, which might cause problems with arm emulations."
154                     print "You can disable vdso with following command before starting image build:"
155                     print "echo 0 | sudo tee /proc/sys/vm/vdso_enabled"
156                     print "= WARNING =\n"
157
158         return True
159
160
161     def __del__(self):
162         self.cleanup()
163
164     #
165     # Properties
166     #
167     def __get_instroot(self):
168         if self.__builddir is None:
169             raise CreatorError("_instroot is not valid before calling mount()")
170         return self.__builddir + "/install_root"
171     _instroot = property(__get_instroot)
172     """The location of the install root directory.
173
174     This is the directory into which the system is installed. Subclasses may
175     mount a filesystem image here or copy files to/from here.
176
177     Note, this directory does not exist before ImageCreator.mount() is called.
178
179     Note also, this is a read-only attribute.
180
181     """
182
183     def __get_outdir(self):
184         if self.__builddir is None:
185             raise CreatorError("_outdir is not valid before calling mount()")
186         return self.__builddir + "/out"
187     _outdir = property(__get_outdir)
188     """The staging location for the final image.
189
190     This is where subclasses should stage any files that are part of the final
191     image. ImageCreator.package() will copy any files found here into the
192     requested destination directory.
193
194     Note, this directory does not exist before ImageCreator.mount() is called.
195
196     Note also, this is a read-only attribute.
197
198     """
199
200     #
201     # Hooks for subclasses
202     #
203     def _mount_instroot(self, base_on = None):
204         """Mount or prepare the install root directory.
205
206         This is the hook where subclasses may prepare the install root by e.g.
207         mounting creating and loopback mounting a filesystem image to
208         _instroot.
209
210         There is no default implementation.
211
212         base_on -- this is the value passed to mount() and can be interpreted
213                    as the subclass wishes; it might e.g. be the location of
214                    a previously created ISO containing a system image.
215
216         """
217         pass
218
219     def _unmount_instroot(self):
220         """Undo anything performed in _mount_instroot().
221
222         This is the hook where subclasses must undo anything which was done
223         in _mount_instroot(). For example, if a filesystem image was mounted
224         onto _instroot, it should be unmounted here.
225
226         There is no default implementation.
227
228         """
229         pass
230
231     def _create_bootconfig(self):
232         """Configure the image so that it's bootable.
233
234         This is the hook where subclasses may prepare the image for booting by
235         e.g. creating an initramfs and bootloader configuration.
236
237         This hook is called while the install root is still mounted, after the
238         packages have been installed and the kickstart configuration has been
239         applied, but before the %post scripts have been executed.
240
241         There is no default implementation.
242
243         """
244         pass
245
246     def _stage_final_image(self):
247         """Stage the final system image in _outdir.
248
249         This is the hook where subclasses should place the image in _outdir
250         so that package() can copy it to the requested destination directory.
251
252         By default, this moves the install root into _outdir.
253
254         """
255         shutil.move(self._instroot, self._outdir + "/" + self.name)
256
257     def get_installed_packages(self):
258         return self._pkgs_content.keys()
259
260     def _save_recording_pkgs(self, destdir):
261         """Save the list or content of installed packages to file.
262         """
263         if self._recording_pkgs not in ('content', 'name'):
264             return
265
266         pkgs = self._pkgs_content.keys()
267         pkgs.sort() # inplace op
268
269         # save package name list anyhow
270         if not os.path.exists(destdir):
271             makedirs(destdir)
272
273         namefile = os.path.join(destdir, self.name + '-pkgs.txt')
274         f = open(namefile, "w")
275         content = '\n'.join(pkgs)
276         f.write(content)
277         f.close()
278         self.outimage.append(namefile);
279
280         # if 'content', save more details
281         if self._recording_pkgs == 'content':
282             contfile = os.path.join(destdir, self.name + '-pkgs-content.txt')
283             f = open(contfile, "w")
284
285             for pkg in pkgs:
286                 content = pkg + '\n'
287
288                 pkgcont = self._pkgs_content[pkg]
289                 items = []
290                 if pkgcont.has_key('dir'):
291                     items = map(lambda x:x+'/', pkgcont['dir'])
292                 if pkgcont.has_key('file'):
293                     items.extend(pkgcont['file'])
294
295                 if items:
296                     content += '    '
297                     content += '\n    '.join(items)
298                     content += '\n'
299
300                 content += '\n'
301                 f.write(content)
302             f.close()
303             self.outimage.append(contfile)
304
305     def _get_required_packages(self):
306         """Return a list of required packages.
307
308         This is the hook where subclasses may specify a set of packages which
309         it requires to be installed.
310
311         This returns an empty list by default.
312
313         Note, subclasses should usually chain up to the base class
314         implementation of this hook.
315
316         """
317         return []
318
319     def _get_excluded_packages(self):
320         """Return a list of excluded packages.
321
322         This is the hook where subclasses may specify a set of packages which
323         it requires _not_ to be installed.
324
325         This returns an empty list by default.
326
327         Note, subclasses should usually chain up to the base class
328         implementation of this hook.
329
330         """
331         excluded_packages = []
332         for rpm_path in self._get_local_packages():
333             rpm_name = os.path.basename(rpm_path)
334             package_name = splitFilename(rpm_name)[0]
335             excluded_packages += [package_name]
336         return excluded_packages
337
338     def _get_local_packages(self):
339         """Return a list of rpm path to be local installed.
340
341         This is the hook where subclasses may specify a set of rpms which
342         it requires to be installed locally.
343
344         This returns an empty list by default.
345
346         Note, subclasses should usually chain up to the base class
347         implementation of this hook.
348
349         """
350         if self._local_pkgs_path:
351             if os.path.isdir(self._local_pkgs_path):
352                 return glob.glob(
353                         os.path.join(self._local_pkgs_path, '*.rpm'))
354             elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
355                 return [self._local_pkgs_path]
356
357         return []
358
359     def _get_fstab(self):
360         """Return the desired contents of /etc/fstab.
361
362         This is the hook where subclasses may specify the contents of
363         /etc/fstab by returning a string containing the desired contents.
364
365         A sensible default implementation is provided.
366
367         """
368         s =  "/dev/root  /         %s    %s 0 0\n" % (self._fstype, "defaults,noatime" if not self._fsopts else self._fsopts)
369         s += self._get_fstab_special()
370         return s
371
372     def _get_fstab_special(self):
373         s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
374         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
375         s += "proc       /proc     proc    defaults         0 0\n"
376         s += "sysfs      /sys      sysfs   defaults         0 0\n"
377         return s
378
379     def _get_post_scripts_env(self, in_chroot):
380         """Return an environment dict for %post scripts.
381
382         This is the hook where subclasses may specify some environment
383         variables for %post scripts by return a dict containing the desired
384         environment.
385
386         By default, this returns an empty dict.
387
388         in_chroot -- whether this %post script is to be executed chroot()ed
389                      into _instroot.
390
391         """
392         return {}
393
394     def __get_imgname(self):
395         return self.name
396     _name = property(__get_imgname)
397     """The name of the image file.
398
399     """
400
401     def _get_kernel_versions(self):
402         """Return a dict detailing the available kernel types/versions.
403
404         This is the hook where subclasses may override what kernel types and
405         versions should be available for e.g. creating the booloader
406         configuration.
407
408         A dict should be returned mapping the available kernel types to a list
409         of the available versions for those kernels.
410
411         The default implementation uses rpm to iterate over everything
412         providing 'kernel', finds /boot/vmlinuz-* and returns the version
413         obtained from the vmlinuz filename. (This can differ from the kernel
414         RPM's n-v-r in the case of e.g. xen)
415
416         """
417         def get_version(header):
418             version = None
419             for f in header['filenames']:
420                 if f.startswith('/boot/vmlinuz-'):
421                     version = f[14:]
422             return version
423
424         ts = rpm.TransactionSet(self._instroot)
425
426         ret = {}
427         for header in ts.dbMatch('provides', 'kernel'):
428             version = get_version(header)
429             if version is None:
430                 continue
431
432             name = header['name']
433             if not name in ret:
434                 ret[name] = [version]
435             elif not version in ret[name]:
436                 ret[name].append(version)
437
438         return ret
439
440     #
441     # Helpers for subclasses
442     #
443     def _do_bindmounts(self):
444         """Mount various system directories onto _instroot.
445
446         This method is called by mount(), but may also be used by subclasses
447         in order to re-mount the bindmounts after modifying the underlying
448         filesystem.
449
450         """
451         for b in self.__bindmounts:
452             b.mount()
453
454     def _undo_bindmounts(self):
455         """Unmount the bind-mounted system directories from _instroot.
456
457         This method is usually only called by unmount(), but may also be used
458         by subclasses in order to gain access to the filesystem obscured by
459         the bindmounts - e.g. in order to create device nodes on the image
460         filesystem.
461
462         """
463         self.__bindmounts.reverse()
464         for b in self.__bindmounts:
465             b.unmount()
466
467     def _chroot(self):
468         """Chroot into the install root.
469
470         This method may be used by subclasses when executing programs inside
471         the install root e.g.
472
473           subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
474
475         """
476         os.chroot(self._instroot)
477         os.chdir("/")
478
479     def _mkdtemp(self, prefix = "tmp-"):
480         """Create a temporary directory.
481
482         This method may be used by subclasses to create a temporary directory
483         for use in building the final image - e.g. a subclass might create
484         a temporary directory in order to bundle a set of files into a package.
485
486         The subclass may delete this directory if it wishes, but it will be
487         automatically deleted by cleanup().
488
489         The absolute path to the temporary directory is returned.
490
491         Note, this method should only be called after mount() has been called.
492
493         prefix -- a prefix which should be used when creating the directory;
494                   defaults to "tmp-".
495
496         """
497         self.__ensure_builddir()
498         return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
499
500     def _mkstemp(self, prefix = "tmp-"):
501         """Create a temporary file.
502
503         This method may be used by subclasses to create a temporary file
504         for use in building the final image - e.g. a subclass might need
505         a temporary location to unpack a compressed file.
506
507         The subclass may delete this file if it wishes, but it will be
508         automatically deleted by cleanup().
509
510         A tuple containing a file descriptor (returned from os.open() and the
511         absolute path to the temporary directory is returned.
512
513         Note, this method should only be called after mount() has been called.
514
515         prefix -- a prefix which should be used when creating the file;
516                   defaults to "tmp-".
517
518         """
519         self.__ensure_builddir()
520         return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
521
522     def _mktemp(self, prefix = "tmp-"):
523         """Create a temporary file.
524
525         This method simply calls _mkstemp() and closes the returned file
526         descriptor.
527
528         The absolute path to the temporary file is returned.
529
530         Note, this method should only be called after mount() has been called.
531
532         prefix -- a prefix which should be used when creating the file;
533                   defaults to "tmp-".
534
535         """
536
537         (f, path) = self._mkstemp(prefix)
538         os.close(f)
539         return path
540
541     #
542     # Actual implementation
543     #
544     def __ensure_builddir(self):
545         if not self.__builddir is None:
546             return
547
548         try:
549             self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
550                                                prefix = "imgcreate-")
551         except OSError, (err, msg):
552             raise CreatorError("Failed create build directory in %s: %s" %
553                                (self.tmpdir, msg))
554
555     def get_cachedir(self, cachedir = None):
556         if self.cachedir:
557             return self.cachedir
558
559         self.__ensure_builddir()
560         if cachedir:
561             self.cachedir = cachedir
562         else:
563             self.cachedir = self.__builddir + "/yum-cache"
564         makedirs(self.cachedir)
565         return self.cachedir
566
567     def __sanity_check(self):
568         """Ensure that the config we've been given is sane."""
569         if not (kickstart.get_packages(self.ks) or
570                 kickstart.get_groups(self.ks)):
571             raise CreatorError("No packages or groups specified")
572
573         kickstart.convert_method_to_repo(self.ks)
574
575         if not kickstart.get_repos(self.ks):
576             raise CreatorError("No repositories specified")
577
578     def __write_fstab(self):
579         fstab = open(self._instroot + "/etc/fstab", "w")
580         fstab.write(self._get_fstab())
581         fstab.close()
582
583     def __create_minimal_dev(self):
584         """Create a minimal /dev so that we don't corrupt the host /dev"""
585         origumask = os.umask(0000)
586         devices = (('null',   1, 3, 0666),
587                    ('urandom',1, 9, 0666),
588                    ('random', 1, 8, 0666),
589                    ('full',   1, 7, 0666),
590                    ('ptmx',   5, 2, 0666),
591                    ('tty',    5, 0, 0666),
592                    ('zero',   1, 5, 0666))
593         links = (("/proc/self/fd", "/dev/fd"),
594                  ("/proc/self/fd/0", "/dev/stdin"),
595                  ("/proc/self/fd/1", "/dev/stdout"),
596                  ("/proc/self/fd/2", "/dev/stderr"))
597
598         for (node, major, minor, perm) in devices:
599             if not os.path.exists(self._instroot + "/dev/" + node):
600                 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
601         for (src, dest) in links:
602             if not os.path.exists(self._instroot + dest):
603                 os.symlink(src, self._instroot + dest)
604         os.umask(origumask)
605
606
607     def mount(self, base_on = None, cachedir = None):
608         """Setup the target filesystem in preparation for an install.
609
610         This function sets up the filesystem which the ImageCreator will
611         install into and configure. The ImageCreator class merely creates an
612         install root directory, bind mounts some system directories (e.g. /dev)
613         and writes out /etc/fstab. Other subclasses may also e.g. create a
614         sparse file, format it and loopback mount it to the install root.
615
616         base_on -- a previous install on which to base this install; defaults
617                    to None, causing a new image to be created
618
619         cachedir -- a directory in which to store the Yum cache; defaults to
620                     None, causing a new cache to be created; by setting this
621                     to another directory, the same cache can be reused across
622                     multiple installs.
623
624         """
625         self.__ensure_builddir()
626
627         makedirs(self._instroot)
628         makedirs(self._outdir)
629
630         self._mount_instroot(base_on)
631
632         for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/usr/bin"):
633             makedirs(self._instroot + d)
634
635         if self.target_arch and self.target_arch.startswith("arm"):
636             self.qemu_emulator = setup_qemu_emulator(self._instroot, self.target_arch)
637
638         self.get_cachedir(cachedir)
639
640         # bind mount system directories into _instroot
641         for (f, dest) in [("/sys", None), ("/proc", None), ("/proc/sys/fs/binfmt_misc", None),
642                           ("/dev/pts", None),
643                           (self.get_cachedir(), "/var/cache/yum")]:
644             self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
645
646
647         self._do_bindmounts()
648
649         self.__create_minimal_dev()
650
651         if os.path.exists(self._instroot + "/etc/mtab"):
652             os.unlink(self._instroot + "/etc/mtab")
653         os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
654
655         self.__write_fstab()
656
657         # get size of available space in 'instroot' fs
658         self._root_fs_avail = get_filesystem_avail(self._instroot)
659
660     def unmount(self):
661         """Unmounts the target filesystem.
662
663         The ImageCreator class detaches the system from the install root, but
664         other subclasses may also detach the loopback mounted filesystem image
665         from the install root.
666
667         """
668         try:
669             os.unlink(self._instroot + "/etc/mtab")
670             if self.qemu_emulator:
671                 os.unlink(self._instroot + self.qemu_emulator)
672             """ Clean up yum garbage """
673             instroot_pdir = os.path.dirname(self._instroot + self._instroot)
674             if os.path.exists(instroot_pdir):
675                 shutil.rmtree(instroot_pdir, ignore_errors = True)
676         except OSError:
677             pass
678
679
680         self._undo_bindmounts()
681
682         self._unmount_instroot()
683
684     def cleanup(self):
685         """Unmounts the target filesystem and deletes temporary files.
686
687         This method calls unmount() and then deletes any temporary files and
688         directories that were created on the host system while building the
689         image.
690
691         Note, make sure to call this method once finished with the creator
692         instance in order to ensure no stale files are left on the host e.g.:
693
694           creator = ImageCreator(ks, name)
695           try:
696               creator.create()
697           finally:
698               creator.cleanup()
699
700         """
701         if not self.__builddir:
702             return
703
704         self.unmount()
705
706         shutil.rmtree(self.__builddir, ignore_errors = True)
707         self.__builddir = None
708
709     def __is_excluded_pkg(self, pkg):
710         if pkg in self._excluded_pkgs:
711             self._excluded_pkgs.remove(pkg)
712             return True
713
714         for xpkg in self._excluded_pkgs:
715             if xpkg.endswith('*'):
716                 if pkg.startswith(xpkg[:-1]):
717                     return True
718             elif xpkg.startswith('*'):
719                 if pkg.endswith(xpkg[1:]):
720                     return True
721
722         return None
723
724     def __select_packages(self, pkg_manager):
725         skipped_pkgs = []
726         for pkg in self._required_pkgs:
727             e = pkg_manager.selectPackage(pkg)
728             if e:
729                 if kickstart.ignore_missing(self.ks):
730                     skipped_pkgs.append(pkg)
731                 elif self.__is_excluded_pkg(pkg):
732                     skipped_pkgs.append(pkg)
733                 else:
734                     raise CreatorError("Failed to find package '%s' : %s" %
735                                        (pkg, e))
736
737         for pkg in skipped_pkgs:
738             logging.warn("Skipping missing package '%s'" % (pkg,))
739
740     def __select_groups(self, pkg_manager):
741         skipped_groups = []
742         for group in self._required_groups:
743             e = pkg_manager.selectGroup(group.name, group.include)
744             if e:
745                 if kickstart.ignore_missing(self.ks):
746                     skipped_groups.append(group)
747                 else:
748                     raise CreatorError("Failed to find group '%s' : %s" %
749                                        (group.name, e))
750
751         for group in skipped_groups:
752             logging.warn("Skipping missing group '%s'" % (group.name,))
753
754     def __deselect_packages(self, pkg_manager):
755         for pkg in self._excluded_pkgs:
756             pkg_manager.deselectPackage(pkg)
757
758     def __localinst_packages(self, pkg_manager):
759         for rpm_path in self._get_local_packages():
760             pkg_manager.installLocal(rpm_path)
761
762     def install(self, repo_urls = {}):
763         """Install packages into the install root.
764
765         This function installs the packages listed in the supplied kickstart
766         into the install root. By default, the packages are installed from the
767         repository URLs specified in the kickstart.
768
769         repo_urls -- a dict which maps a repository name to a repository URL;
770                      if supplied, this causes any repository URLs specified in
771                      the kickstart to be overridden.
772
773         """
774
775
776         # initialize pkg list to install
777         #import pdb
778         #pdb.set_trace()
779         if self.ks:
780             self.__sanity_check()
781
782             self._required_pkgs = \
783                 kickstart.get_packages(self.ks, self._get_required_packages())
784             self._excluded_pkgs = \
785                 kickstart.get_excluded(self.ks, self._get_excluded_packages())
786             self._required_groups = kickstart.get_groups(self.ks)
787         else:
788             self._required_pkgs = None
789             self._excluded_pkgs = None
790             self._required_groups = None
791
792         yum_conf = self._mktemp(prefix = "yum.conf-")
793
794         keep_record = None
795         if self._include_src:
796             keep_record = 'include_src'
797         if self._recording_pkgs in ('name', 'content'):
798             keep_record = self._recording_pkgs
799
800         pkg_manager = self.get_pkg_manager(keep_record)
801         pkg_manager.setup(yum_conf, self._instroot)
802
803         for repo in kickstart.get_repos(self.ks, repo_urls):
804             (name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable) = repo
805
806             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy, proxy_username, proxy_password, inc, exc)
807         
808         if kickstart.exclude_docs(self.ks):
809             rpm.addMacro("_excludedocs", "1")
810         rpm.addMacro("__file_context_path", "%{nil}")
811         if kickstart.inst_langs(self.ks) != None:
812             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
813
814         try:
815             try:
816                 #import pdb
817                 #pdb.set_trace()
818                 self.__select_packages(pkg_manager)
819                 self.__select_groups(pkg_manager)
820                 self.__deselect_packages(pkg_manager)
821                 self.__localinst_packages(pkg_manager)
822
823                 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
824                 checksize = self._root_fs_avail
825                 if checksize:
826                     checksize -= BOOT_SAFEGUARD
827                 if self.target_arch:
828                     pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
829                 pkg_manager.runInstall(checksize)
830             except CreatorError, e:
831                 raise CreatorError("%s" % (e,))
832         finally:
833             if keep_record:
834                 self._pkgs_content = pkg_manager.getAllContent()
835
836             pkg_manager.closeRpmDB()
837             pkg_manager.close()
838             os.unlink(yum_conf)
839
840         # do some clean up to avoid lvm info leakage.  this sucks.
841         for subdir in ("cache", "backup", "archive"):
842             lvmdir = self._instroot + "/etc/lvm/" + subdir
843             try:
844                 for f in os.listdir(lvmdir):
845                     os.unlink(lvmdir + "/" + f)
846             except:
847                 pass
848
849     def __run_post_scripts(self):
850         print "Running scripts"
851         for s in kickstart.get_post_scripts(self.ks):
852             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
853                                           dir = self._instroot + "/tmp")
854
855             s.script = s.script.replace("\r", "")
856             os.write(fd, s.script)
857             os.close(fd)
858             os.chmod(path, 0700)
859
860             env = self._get_post_scripts_env(s.inChroot)
861
862             if not s.inChroot:
863                 env["INSTALL_ROOT"] = self._instroot
864                 env["IMG_NAME"] = self._name
865                 preexec = None
866                 script = path
867             else:
868                 preexec = self._chroot
869                 script = "/tmp/" + os.path.basename(path)
870
871             try:
872                 try:
873                     subprocess.call([s.interp, script],
874                                     preexec_fn = preexec, env = env, stdout = sys.stdout, stderr = sys.stderr)
875                 except OSError, (err, msg):
876                     raise CreatorError("Failed to execute %%post script "
877                                        "with '%s' : %s" % (s.interp, msg))
878             finally:
879                 os.unlink(path)
880
881     def __save_repo_keys(self, repodata):
882         if not repodata:
883             return None
884         gpgkeydir = "/etc/pki/rpm-gpg"
885         makedirs(self._instroot + gpgkeydir)
886         for repo in repodata:
887             if repo["repokey"]:
888                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
889                 shutil.copy(repo["repokey"], self._instroot + repokey)
890
891     def configure(self, repodata = None):
892         """Configure the system image according to the kickstart.
893
894         This method applies the (e.g. keyboard or network) configuration
895         specified in the kickstart and executes the kickstart %post scripts.
896
897         If neccessary, it also prepares the image to be bootable by e.g.
898         creating an initrd and bootloader configuration.
899
900         """
901         ksh = self.ks.handler
902
903         try:
904             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
905             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
906             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
907             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
908             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
909             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
910             kickstart.UserConfig(self._instroot).apply(ksh.user)
911             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
912             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
913             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
914             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
915             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
916             self.__save_repo_keys(repodata)
917             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
918         except:
919             print "Failed to apply configuration to image"
920             raise
921
922         self._create_bootconfig()
923         self.__run_post_scripts()
924
925     def launch_shell(self, launch):
926         """Launch a shell in the install root.
927
928         This method is launches a bash shell chroot()ed in the install root;
929         this can be useful for debugging.
930
931         """
932         if launch:
933             print "Launching shell. Exit to continue."
934             print "----------------------------------"
935             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
936
937     def do_genchecksum(self, image_name):
938         if not self._genchecksum:
939             return
940
941         """ Generate md5sum if /usr/bin/md5sum is available """
942         if os.path.exists("/usr/bin/md5sum"):
943             p = subprocess.Popen(["/usr/bin/md5sum", "-b", image_name],
944                                  stdout=subprocess.PIPE)
945             (md5sum, errorstr) = p.communicate()
946             if p.returncode != 0:
947                 logging.warning("Can't generate md5sum for image %s" % image_name)
948             else:
949                 pattern = re.compile("\*.*$")
950                 md5sum = pattern.sub("*" + os.path.basename(image_name), md5sum)
951                 fd = open(image_name + ".md5sum", "w")
952                 fd.write(md5sum)
953                 fd.close()
954                 self.outimage.append(image_name+".md5sum")
955
956     def package(self, destdir = "."):
957         """Prepares the created image for final delivery.
958
959         In its simplest form, this method merely copies the install root to the
960         supplied destination directory; other subclasses may choose to package
961         the image by e.g. creating a bootable ISO containing the image and
962         bootloader configuration.
963
964         destdir -- the directory into which the final image should be moved;
965                    this defaults to the current directory.
966
967         """
968         self._stage_final_image()
969
970         if self.__img_compression_method:
971             if not self._img_name:
972                 raise CreatorError("Image name not set.")
973             rc = None
974             img_location = os.path.join(self._outdir,self._img_name)
975             if self.__img_compression_method == "bz2":
976                 bzip2 = find_binary_path('bzip2')
977                 print "Compressing %s with bzip2. Please wait..." % img_location
978                 rc = subprocess.call([bzip2, "-f", img_location])
979                 if rc:
980                     raise CreatorError("Failed to compress image %s with %s." % (img_location, self.__img_compression_method))
981                 for bootimg in glob.glob(os.path.dirname(img_location) + "/*-boot.bin"):
982                     print "Compressing %s with bzip2. Please wait..." % bootimg
983                     rc = subprocess.call([bzip2, "-f", bootimg])
984                     if rc:
985                         raise CreatorError("Failed to compress image %s with %s." % (bootimg, self.__img_compression_method))
986
987         if self._recording_pkgs:
988             self._save_recording_pkgs(destdir)
989
990         """ For image formats with two or multiple image files, it will be better to put them under a directory """
991         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
992             destdir = os.path.join(destdir, "%s-%s" % (self.name, self.image_format))
993             logging.debug("creating destination dir: %s" % destdir)
994             makedirs(destdir)
995
996         # Ensure all data is flushed to _outdir
997         synccmd = find_binary_path("sync")
998         subprocess.call([synccmd])
999
1000         for f in os.listdir(self._outdir):
1001             shutil.move(os.path.join(self._outdir, f),
1002                         os.path.join(destdir, f))
1003             self.outimage.append(os.path.join(destdir, f))
1004             self.do_genchecksum(os.path.join(destdir, f))
1005
1006     def create(self):
1007         """Install, configure and package an image.
1008
1009         This method is a utility method which creates and image by calling some
1010         of the other methods in the following order - mount(), install(),
1011         configure(), unmount and package().
1012
1013         """
1014         self.mount()
1015         self.install()
1016         self.configure()
1017         self.unmount()
1018         self.package()
1019
1020     def print_outimage_info(self):
1021         print "Your new image can be found here:"
1022         self.outimage.sort()
1023         for file in self.outimage:
1024             print os.path.abspath(file)
1025
1026     def check_depend_tools(self):
1027         for tool in self._dep_checks:
1028             find_binary_path(tool)
1029
1030     def package_output(self, image_format, destdir = ".", package="none"):
1031         if not package or package == "none":
1032             return
1033
1034         destdir = os.path.abspath(os.path.expanduser(destdir))
1035         (pkg, comp) = os.path.splitext(package)
1036         if comp:
1037             comp=comp.lstrip(".")
1038
1039         if pkg == "tar":
1040             if comp:
1041                 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1042             else:
1043                 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1044             print "creating %s" % dst
1045             tar = tarfile.open(dst, "w:" + comp)
1046
1047             for file in self.outimage:
1048                 print "adding %s to %s" % (file, dst)
1049                 tar.add(file, arcname=os.path.join("%s-%s" % (self.name, image_format), os.path.basename(file)))
1050                 if os.path.isdir(file):
1051                     shutil.rmtree(file, ignore_errors = True)
1052                 else:
1053                     os.remove(file)
1054
1055
1056             tar.close()
1057
1058             '''All the file in outimage has been packaged into tar.* file'''
1059             self.outimage = [dst]
1060
1061     def release_output(self, config, destdir, name, release):
1062         self.outimage = create_release(config, destdir, name, self.outimage, release)
1063
1064     def save_kernel(self, destdir):
1065         if not os.path.exists(destdir):
1066             makedirs(destdir)
1067         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1068             kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1069             shutil.copy(kernel, kernelfilename)
1070             self.outimage.append(kernelfilename)
1071
1072     def compress_disk_image(self, compression_method):
1073         """
1074         With this you can set the method that is used to compress the disk
1075         image after it is created.
1076         """
1077
1078         if compression_method not in ('bz2'):
1079             raise CreatorError("Given disk image compression method ('%s') is not valid." % (compression_method))
1080
1081         self.__img_compression_method = compression_method
1082
1083     def set_pkg_manager(self, name):
1084         self.pkgmgr.set_default_pkg_manager(name)
1085
1086     def get_pkg_manager(self, recording_pkgs=None):
1087         pkgmgr_instance = self.pkgmgr.get_default_pkg_manager()
1088         if not pkgmgr_instance:
1089             raise CreatorError("No package manager available")
1090         return pkgmgr_instance(creator = self, recording_pkgs = recording_pkgs)
1091
1092 class LoopImageCreator(ImageCreator):
1093     """Installs a system into a loopback-mountable filesystem image.
1094
1095     LoopImageCreator is a straightforward ImageCreator subclass; the system
1096     is installed into an ext3 filesystem on a sparse file which can be
1097     subsequently loopback-mounted.
1098
1099     """
1100
1101     def __init__(self, ks, name, fslabel = None):
1102         """Initialize a LoopImageCreator instance.
1103
1104         This method takes the same arguments as ImageCreator.__init__() with
1105         the addition of:
1106
1107         fslabel -- A string used as a label for any filesystems created.
1108
1109         """
1110         ImageCreator.__init__(self, ks, name)
1111
1112         self.__fslabel = None
1113         self.fslabel = fslabel
1114
1115         self.__minsize_KB = 0
1116         self.__blocksize = 4096
1117         if self.ks:
1118             self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
1119             self.__fsopts = kickstart.get_image_fsopts(self.ks, "defaults,noatime")
1120         else:
1121             self.__fstype = None
1122             self.__fsopts = None
1123
1124         self.__instloop = None
1125         self.__imgdir = None
1126
1127         if self.ks:
1128             self.__image_size = kickstart.get_image_size(self.ks,
1129                                                          4096L * 1024 * 1024)
1130         else:
1131             self.__image_size = 0
1132
1133         self._img_name = self.name + ".img"
1134
1135     def _set_fstype(self, fstype):
1136         self.__fstype = fstype
1137
1138     def _set_image_size(self, imgsize):
1139         self.__image_size = imgsize
1140
1141     #
1142     # Properties
1143     #
1144     def __get_fslabel(self):
1145         if self.__fslabel is None:
1146             return self.name
1147         else:
1148             return self.__fslabel
1149     def __set_fslabel(self, val):
1150         if val is None:
1151             self.__fslabel = None
1152         else:
1153             self.__fslabel = val[:FSLABEL_MAXLEN]
1154     fslabel = property(__get_fslabel, __set_fslabel)
1155     """A string used to label any filesystems created.
1156
1157     Some filesystems impose a constraint on the maximum allowed size of the
1158     filesystem label. In the case of ext3 it's 16 characters, but in the case
1159     of ISO9660 it's 32 characters.
1160
1161     mke2fs silently truncates the label, but mkisofs aborts if the label is too
1162     long. So, for convenience sake, any string assigned to this attribute is
1163     silently truncated to FSLABEL_MAXLEN (32) characters.
1164
1165     """
1166
1167     def __get_image(self):
1168         if self.__imgdir is None:
1169             raise CreatorError("_image is not valid before calling mount()")
1170         return self.__imgdir + "/meego.img"
1171     _image = property(__get_image)
1172     """The location of the image file.
1173
1174     This is the path to the filesystem image. Subclasses may use this path
1175     in order to package the image in _stage_final_image().
1176
1177     Note, this directory does not exist before ImageCreator.mount() is called.
1178
1179     Note also, this is a read-only attribute.
1180
1181     """
1182
1183     def __get_blocksize(self):
1184         return self.__blocksize
1185     def __set_blocksize(self, val):
1186         if self.__instloop:
1187             raise CreatorError("_blocksize must be set before calling mount()")
1188         try:
1189             self.__blocksize = int(val)
1190         except ValueError:
1191             raise CreatorError("'%s' is not a valid integer value "
1192                                "for _blocksize" % val)
1193     _blocksize = property(__get_blocksize, __set_blocksize)
1194     """The block size used by the image's filesystem.
1195
1196     This is the block size used when creating the filesystem image. Subclasses
1197     may change this if they wish to use something other than a 4k block size.
1198
1199     Note, this attribute may only be set before calling mount().
1200
1201     """
1202
1203     def __get_fstype(self):
1204         return self.__fstype
1205     def __set_fstype(self, val):
1206         if val != "ext2" and val != "ext3":
1207             raise CreatorError("Unknown _fstype '%s' supplied" % val)
1208         self.__fstype = val
1209     _fstype = property(__get_fstype, __set_fstype)
1210     """The type of filesystem used for the image.
1211
1212     This is the filesystem type used when creating the filesystem image.
1213     Subclasses may change this if they wish to use something other ext3.
1214
1215     Note, only ext2 and ext3 are currently supported.
1216
1217     Note also, this attribute may only be set before calling mount().
1218
1219     """
1220
1221     def __get_fsopts(self):
1222         return self.__fsopts
1223     def __set_fsopts(self, val):
1224         self.__fsopts = val
1225     _fsopts = property(__get_fsopts, __set_fsopts)
1226     """Mount options of filesystem used for the image.
1227
1228     This can be specified by --fsoptions="xxx,yyy" in part command in
1229     kickstart file.
1230     """
1231
1232     #
1233     # Helpers for subclasses
1234     #
1235     def _resparse(self, size = None):
1236         """Rebuild the filesystem image to be as sparse as possible.
1237
1238         This method should be used by subclasses when staging the final image
1239         in order to reduce the actual space taken up by the sparse image file
1240         to be as little as possible.
1241
1242         This is done by resizing the filesystem to the minimal size (thereby
1243         eliminating any space taken up by deleted files) and then resizing it
1244         back to the supplied size.
1245
1246         size -- the size in, in bytes, which the filesystem image should be
1247                 resized to after it has been minimized; this defaults to None,
1248                 causing the original size specified by the kickstart file to
1249                 be used (or 4GiB if not specified in the kickstart).
1250
1251         """
1252         return self.__instloop.resparse(size)
1253
1254     def _base_on(self, base_on):
1255         shutil.copyfile(base_on, self._image)
1256
1257     #
1258     # Actual implementation
1259     #
1260     def _mount_instroot(self, base_on = None):
1261         self.__imgdir = self._mkdtemp()
1262
1263         if not base_on is None:
1264             self._base_on(base_on)
1265
1266         if self.__fstype in ("ext2", "ext3", "ext4"):
1267             MyDiskMount = ExtDiskMount
1268         elif self.__fstype == "btrfs":
1269             MyDiskMount = BtrfsDiskMount
1270
1271         self.__instloop = MyDiskMount(SparseLoopbackDisk(self._image, self.__image_size),
1272                                        self._instroot,
1273                                        self.__fstype,
1274                                        self.__blocksize,
1275                                        self.fslabel)
1276
1277         try:
1278             self.__instloop.mount()
1279         except MountError, e:
1280             raise CreatorError("Failed to loopback mount '%s' : %s" %
1281                                (self._image, e))
1282
1283     def _unmount_instroot(self):
1284         if not self.__instloop is None:
1285             self.__instloop.cleanup()
1286
1287     def _stage_final_image(self):
1288         self._resparse()
1289         shutil.move(self._image, self._outdir + "/" + self._img_name)
1290
1291 class LiveImageCreatorBase(LoopImageCreator):
1292     """A base class for LiveCD image creators.
1293
1294     This class serves as a base class for the architecture-specific LiveCD
1295     image creator subclass, LiveImageCreator.
1296
1297     LiveImageCreator creates a bootable ISO containing the system image,
1298     bootloader, bootloader configuration, kernel and initramfs.
1299
1300     """
1301
1302     def __init__(self, *args):
1303         """Initialise a LiveImageCreator instance.
1304
1305         This method takes the same arguments as ImageCreator.__init__().
1306
1307         """
1308         LoopImageCreator.__init__(self, *args)
1309
1310         self.skip_compression = False
1311         """Controls whether to use squashfs to compress the image."""
1312
1313         self.skip_minimize = False
1314         """Controls whether an image minimizing snapshot should be created.
1315
1316         This snapshot can be used when copying the system image from the ISO in
1317         order to minimize the amount of data that needs to be copied; simply,
1318         it makes it possible to create a version of the image's filesystem with
1319         no spare space.
1320
1321         """
1322
1323         self.actasconvertor = False
1324         """A flag which indicates i act as a convertor"""
1325
1326         if self.ks:
1327             self._timeout = kickstart.get_timeout(self.ks, 10)
1328         else:
1329             self._timeout = 10
1330         """The bootloader timeout from kickstart."""
1331
1332         if self.ks:
1333             self._default_kernel = kickstart.get_default_kernel(self.ks, "kernel")
1334         else:
1335             self._default_kernel = None
1336         """The default kernel type from kickstart."""
1337
1338         self.__isodir = None
1339
1340         self.__modules = ["=ata", "sym53c8xx", "aic7xxx", "=usb", "=firewire", "=mmc", "=pcmcia", "mptsas"]
1341         if self.ks:
1342             self.__modules.extend(kickstart.get_modules(self.ks))
1343
1344         self._dep_checks.extend(["isohybrid", "unsquashfs", "mksquashfs", "dd", "genisoimage"])
1345
1346     #
1347     # Hooks for subclasses
1348     #
1349     def _configure_bootloader(self, isodir):
1350         """Create the architecture specific booloader configuration.
1351
1352         This is the hook where subclasses must create the booloader
1353         configuration in order to allow a bootable ISO to be built.
1354
1355         isodir -- the directory where the contents of the ISO are to be staged
1356
1357         """
1358         raise CreatorError("Bootloader configuration is arch-specific, "
1359                            "but not implemented for this arch!")
1360     def _get_menu_options(self):
1361         """Return a menu options string for syslinux configuration.
1362
1363         """
1364         r = kickstart.get_menu_args(self.ks)
1365         return r
1366
1367     def _get_kernel_options(self):
1368         """Return a kernel options string for bootloader configuration.
1369
1370         This is the hook where subclasses may specify a set of kernel options
1371         which should be included in the images bootloader configuration.
1372
1373         A sensible default implementation is provided.
1374
1375         """
1376         r = kickstart.get_kernel_args(self.ks)
1377         if os.path.exists(self._instroot + "/usr/bin/rhgb") or \
1378            os.path.exists(self._instroot + "/usr/bin/plymouth"):
1379             r += " rhgb"
1380         return r
1381
1382     def _get_mkisofs_options(self, isodir):
1383         """Return the architecture specific mkisosfs options.
1384
1385         This is the hook where subclasses may specify additional arguments to
1386         mkisofs, e.g. to enable a bootable ISO to be built.
1387
1388         By default, an empty list is returned.
1389
1390         """
1391         return []
1392
1393     #
1394     # Helpers for subclasses
1395     #
1396     def _has_checkisomd5(self):
1397         """Check whether checkisomd5 is available in the install root."""
1398         def exists(instroot, path):
1399             return os.path.exists(instroot + path)
1400
1401         if (exists(self._instroot, "/usr/lib/moblin-installer-runtime/checkisomd5") or
1402             exists(self._instroot, "/usr/bin/checkisomd5")):
1403             if (os.path.exists("/usr/bin/implantisomd5") or
1404                os.path.exists("/usr/lib/anaconda-runtime/implantisomd5")):
1405                 return True
1406
1407         return False
1408
1409     def _uncompress_squashfs(self, squashfsimg, outdir):
1410         """Uncompress file system from squshfs image"""
1411         unsquashfs = find_binary_path("unsquashfs")
1412         args = [unsquashfs, "-d", outdir, squashfsimg ]
1413         rc = subprocess.call(args)
1414         if (rc != 0):
1415             raise CreatorError("Failed to uncompress %s." % squashfsimg)
1416     #
1417     # Actual implementation
1418     #
1419     def _base_on(self, base_on):
1420         """Support Image Convertor"""
1421         if self.actasconvertor:
1422             if os.path.exists(base_on) and not os.path.isfile(base_on):
1423                 ddcmd = find_binary_path("dd")
1424                 args = [ ddcmd, "if=%s" % base_on, "of=%s" % self._image ]
1425                 print "dd %s -> %s" % (base_on, self._image)
1426                 rc = subprocess.call(args)
1427                 if rc != 0:
1428                     raise CreatorError("Failed to dd from %s to %s" % (base_on, self._image))
1429                 self._set_image_size(get_file_size(self._image) * 1024L * 1024L)
1430             if os.path.isfile(base_on):
1431                 print "Copying file system..."
1432                 shutil.copyfile(base_on, self._image)
1433                 self._set_image_size(get_file_size(self._image) * 1024L * 1024L)
1434             return
1435
1436         """helper function to extract ext3 file system from a live CD ISO"""
1437         isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())
1438
1439         try:
1440             isoloop.mount()
1441         except MountError, e:
1442             raise CreatorError("Failed to loopback mount '%s' : %s" %
1443                                (base_on, e))
1444
1445         # legacy LiveOS filesystem layout support, remove for F9 or F10
1446         if os.path.exists(isoloop.mountdir + "/squashfs.img"):
1447             squashimg = isoloop.mountdir + "/squashfs.img"
1448         else:
1449             squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
1450
1451         tmpoutdir = self._mkdtemp()
1452         # unsquashfs requires outdir mustn't exist
1453         shutil.rmtree(tmpoutdir, ignore_errors = True)
1454         self._uncompress_squashfs(squashimg, tmpoutdir)
1455
1456         try:
1457             # legacy LiveOS filesystem layout support, remove for F9 or F10
1458             if os.path.exists(tmpoutdir + "/os.img"):
1459                 os_image = tmpoutdir + "/os.img"
1460             else:
1461                 os_image = tmpoutdir + "/LiveOS/ext3fs.img"
1462
1463             if not os.path.exists(os_image):
1464                 raise CreatorError("'%s' is not a valid live CD ISO : neither "
1465                                    "LiveOS/ext3fs.img nor os.img exist" %
1466                                    base_on)
1467
1468             print "Copying file system..."
1469             shutil.copyfile(os_image, self._image)
1470             self._set_image_size(get_file_size(self._image) * 1024L * 1024L)
1471         finally:
1472             shutil.rmtree(tmpoutdir, ignore_errors = True)
1473             isoloop.cleanup()
1474
1475     def _mount_instroot(self, base_on = None):
1476         LoopImageCreator._mount_instroot(self, base_on)
1477         self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
1478
1479     def _unmount_instroot(self):
1480         try:
1481             os.unlink(self._instroot + "/etc/sysconfig/mkinitrd")
1482         except:
1483             pass
1484         LoopImageCreator._unmount_instroot(self)
1485
1486     def __ensure_isodir(self):
1487         if self.__isodir is None:
1488             self.__isodir = self._mkdtemp("iso-")
1489         return self.__isodir
1490
1491     def _get_isodir(self):
1492         return self.__ensure_isodir()
1493
1494     def _set_isodir(self, isodir = None):
1495         self.__isodir = isodir
1496
1497     def _create_bootconfig(self):
1498         """Configure the image so that it's bootable."""
1499         self._configure_bootloader(self.__ensure_isodir())
1500
1501     def _get_post_scripts_env(self, in_chroot):
1502         env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
1503
1504         if not in_chroot:
1505             env["LIVE_ROOT"] = self.__ensure_isodir()
1506
1507         return env
1508
1509     def __write_initrd_conf(self, path):
1510         content = ""
1511         if not os.path.exists(os.path.dirname(path)):
1512             makedirs(os.path.dirname(path))
1513         f = open(path, "w")
1514
1515         content += 'LIVEOS="yes"\n'
1516         content += 'PROBE="no"\n'
1517         content += 'MODULES+="squashfs ext3 ext2 vfat msdos "\n'
1518         content += 'MODULES+="sr_mod sd_mod ide-cd cdrom "\n'
1519
1520         for module in self.__modules:
1521             if module == "=usb":
1522                 content += 'MODULES+="ehci_hcd uhci_hcd ohci_hcd "\n'
1523                 content += 'MODULES+="usb_storage usbhid "\n'
1524             elif module == "=firewire":
1525                 content += 'MODULES+="firewire-sbp2 firewire-ohci "\n'
1526                 content += 'MODULES+="sbp2 ohci1394 ieee1394 "\n'
1527             elif module == "=mmc":
1528                 content += 'MODULES+="mmc_block sdhci sdhci-pci "\n'
1529             elif module == "=pcmcia":
1530                 content += 'MODULES+="pata_pcmcia  "\n'
1531             else:
1532                 content += 'MODULES+="' + module + ' "\n'
1533         f.write(content)
1534         f.close()
1535
1536     def __create_iso(self, isodir):
1537         iso = self._outdir + "/" + self.name + ".iso"
1538         genisoimage = find_binary_path("genisoimage")
1539         args = [genisoimage,
1540                 "-J", "-r",
1541                 "-hide-rr-moved", "-hide-joliet-trans-tbl",
1542                 "-V", self.fslabel,
1543                 "-o", iso]
1544
1545         args.extend(self._get_mkisofs_options(isodir))
1546
1547         args.append(isodir)
1548
1549         if subprocess.call(args) != 0:
1550             raise CreatorError("ISO creation failed!")
1551
1552         """ It should be ok still even if you haven't isohybrid """
1553         isohybrid = None
1554         try:
1555             isohybrid = find_binary_path("isohybrid")
1556         except:
1557             pass
1558
1559         if isohybrid:
1560             args = [isohybrid, "-partok", iso ]
1561             if subprocess.call(args) != 0:
1562                 raise CreatorError("Hybrid ISO creation failed!")
1563
1564         self.__implant_md5sum(iso)
1565
1566     def __implant_md5sum(self, iso):
1567         """Implant an isomd5sum."""
1568         if os.path.exists("/usr/bin/implantisomd5"):
1569             implantisomd5 = "/usr/bin/implantisomd5"
1570         elif os.path.exists("/usr/lib/anaconda-runtime/implantisomd5"):
1571             implantisomd5 = "/usr/lib/anaconda-runtime/implantisomd5"
1572         else:
1573             logging.warn("isomd5sum not installed; not setting up mediacheck")
1574             implantisomd5 = ""
1575             return
1576
1577         subprocess.call([implantisomd5, iso], stdout=sys.stdout, stderr=sys.stderr)
1578
1579     def _stage_final_image(self):
1580         try:
1581             makedirs(self.__ensure_isodir() + "/LiveOS")
1582
1583             minimal_size = self._resparse()
1584
1585             if not self.skip_minimize:
1586                 create_image_minimizer(self.__isodir + "/LiveOS/osmin.img",
1587                                        self._image, minimal_size)
1588
1589             if self.skip_compression:
1590                 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
1591             else:
1592                 makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
1593                 shutil.move(self._image,
1594                             os.path.join(os.path.dirname(self._image),
1595                                          "LiveOS", "ext3fs.img"))
1596                 mksquashfs(os.path.dirname(self._image),
1597                            self.__isodir + "/LiveOS/squashfs.img")
1598
1599             self.__create_iso(self.__isodir)
1600         finally:
1601             shutil.rmtree(self.__isodir, ignore_errors = True)
1602             self.__isodir = None
1603