Ensure MIC works well when "/etc/mic/mic.conf" does not exist
[platform/upstream/mic.git] / mic / imager / baseimager.py
1
2 #!/usr/bin/python -tt
3 #
4 # Copyright (c) 2007 Red Hat  Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 #
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 # for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 from __future__ import with_statement
21 import os, sys
22 import stat
23 import tempfile
24 import shutil
25 import subprocess
26 import re
27 import tarfile
28 import glob
29
30 import rpm
31
32 from mic import kickstart
33 from mic import msger
34 from mic.utils.errors import CreatorError, Abort
35 from mic.utils import misc, grabber, runner, fs_related as fs
36 from mic.chroot import kill_proc_inchroot
37
38 class BaseImageCreator(object):
39     """Installs a system to a chroot directory.
40
41     ImageCreator is the simplest creator class available; it will install and
42     configure a system image according to the supplied kickstart file.
43
44     e.g.
45
46       import mic.imgcreate as imgcreate
47       ks = imgcreate.read_kickstart("foo.ks")
48       imgcreate.ImageCreator(ks, "foo").create()
49
50     """
51
52     def __del__(self):
53         self.cleanup()
54
55     def __init__(self, createopts = None, pkgmgr = None):
56         """Initialize an ImageCreator instance.
57
58         ks -- a pykickstart.KickstartParser instance; this instance will be
59               used to drive the install by e.g. providing the list of packages
60               to be installed, the system configuration and %post scripts
61
62         name -- a name for the image; used for e.g. image filenames or
63                 filesystem labels
64         """
65
66         self.pkgmgr = pkgmgr
67         self.distro_name = ""
68
69         self.__builddir = None
70         self.__bindmounts = []
71
72         self.ks = None
73         self.name = "target"
74         self.tmpdir = "/var/tmp/mic"
75         self.cachedir = "/var/tmp/mic/cache"
76         self.workdir = "/var/tmp/mic/build"
77         self.destdir = "."
78         self.installerfw_prefix = "INSTALLERFW_"
79         self.target_arch = "noarch"
80         self._local_pkgs_path = None
81         self.pack_to = None
82         self.repourl = {}
83
84         # If the kernel is save to the destdir when copy_kernel cmd is called.
85         self._need_copy_kernel = False
86         # setup tmpfs tmpdir when enabletmpfs is True
87         self.enabletmpfs = False
88
89         if createopts:
90             # Mapping table for variables that have different names.
91             optmap = {"pkgmgr" : "pkgmgr_name",
92                       "outdir" : "destdir",
93                       "arch" : "target_arch",
94                       "local_pkgs_path" : "_local_pkgs_path",
95                       "copy_kernel" : "_need_copy_kernel",
96                      }
97
98             # update setting from createopts
99             for key in createopts.keys():
100                 if key in optmap:
101                     option = optmap[key]
102                 else:
103                     option = key
104                 setattr(self, option, createopts[key])
105
106             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
107
108             if 'release' in createopts and createopts['release']:
109                 self.name = createopts['release'] + '_' + self.name
110
111             if self.pack_to:
112                 if '@NAME@' in self.pack_to:
113                     self.pack_to = self.pack_to.replace('@NAME@', self.name)
114                 (tar, ext) = os.path.splitext(self.pack_to)
115                 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
116                     ext = ".tar" + ext
117                 if ext not in misc.pack_formats:
118                     self.pack_to += ".tar"
119
120         self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
121
122         # Output image file names
123         self.outimage = []
124
125         # A flag to generate checksum
126         self._genchecksum = False
127
128         self._alt_initrd_name = None
129
130         self._recording_pkgs = []
131
132         # available size in root fs, init to 0
133         self._root_fs_avail = 0
134
135         # Name of the disk image file that is created.
136         self._img_name = None
137
138         self.image_format = None
139
140         # Save qemu emulator file name in order to clean up it finally
141         self.qemu_emulator = None
142
143         # No ks provided when called by convertor, so skip the dependency check
144         if self.ks:
145             # If we have btrfs partition we need to check necessary tools
146             for part in self.ks.handler.partition.partitions:
147                 if part.fstype and part.fstype == "btrfs":
148                     self._dep_checks.append("mkfs.btrfs")
149                     break
150
151         if self.target_arch and self.target_arch.startswith("arm"):
152             for dep in self._dep_checks:
153                 if dep == "extlinux":
154                     self._dep_checks.remove(dep)
155
156             if not os.path.exists("/usr/bin/qemu-arm") or \
157                not misc.is_statically_linked("/usr/bin/qemu-arm"):
158                 self._dep_checks.append("qemu-arm-static")
159
160             if os.path.exists("/proc/sys/vm/vdso_enabled"):
161                 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
162                 vdso_value = vdso_fh.read().strip()
163                 vdso_fh.close()
164                 if (int)(vdso_value) == 1:
165                     msger.warning("vdso is enabled on your host, which might "
166                         "cause problems with arm emulations.\n"
167                         "\tYou can disable vdso with following command before "
168                         "starting image build:\n"
169                         "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
170
171         # make sure the specified tmpdir and cachedir exist
172         if not os.path.exists(self.tmpdir):
173             os.makedirs(self.tmpdir)
174         if not os.path.exists(self.cachedir):
175             os.makedirs(self.cachedir)
176
177
178     #
179     # Properties
180     #
181     def __get_instroot(self):
182         if self.__builddir is None:
183             raise CreatorError("_instroot is not valid before calling mount()")
184         return self.__builddir + "/install_root"
185     _instroot = property(__get_instroot)
186     """The location of the install root directory.
187
188     This is the directory into which the system is installed. Subclasses may
189     mount a filesystem image here or copy files to/from here.
190
191     Note, this directory does not exist before ImageCreator.mount() is called.
192
193     Note also, this is a read-only attribute.
194
195     """
196
197     def __get_outdir(self):
198         if self.__builddir is None:
199             raise CreatorError("_outdir is not valid before calling mount()")
200         return self.__builddir + "/out"
201     _outdir = property(__get_outdir)
202     """The staging location for the final image.
203
204     This is where subclasses should stage any files that are part of the final
205     image. ImageCreator.package() will copy any files found here into the
206     requested destination directory.
207
208     Note, this directory does not exist before ImageCreator.mount() is called.
209
210     Note also, this is a read-only attribute.
211
212     """
213
214
215     #
216     # Hooks for subclasses
217     #
218     def _mount_instroot(self, base_on = None):
219         """Mount or prepare the install root directory.
220
221         This is the hook where subclasses may prepare the install root by e.g.
222         mounting creating and loopback mounting a filesystem image to
223         _instroot.
224
225         There is no default implementation.
226
227         base_on -- this is the value passed to mount() and can be interpreted
228                    as the subclass wishes; it might e.g. be the location of
229                    a previously created ISO containing a system image.
230
231         """
232         pass
233
234     def _unmount_instroot(self):
235         """Undo anything performed in _mount_instroot().
236
237         This is the hook where subclasses must undo anything which was done
238         in _mount_instroot(). For example, if a filesystem image was mounted
239         onto _instroot, it should be unmounted here.
240
241         There is no default implementation.
242
243         """
244         pass
245
246     def _create_bootconfig(self):
247         """Configure the image so that it's bootable.
248
249         This is the hook where subclasses may prepare the image for booting by
250         e.g. creating an initramfs and bootloader configuration.
251
252         This hook is called while the install root is still mounted, after the
253         packages have been installed and the kickstart configuration has been
254         applied, but before the %post scripts have been executed.
255
256         There is no default implementation.
257
258         """
259         pass
260
261     def _stage_final_image(self):
262         """Stage the final system image in _outdir.
263
264         This is the hook where subclasses should place the image in _outdir
265         so that package() can copy it to the requested destination directory.
266
267         By default, this moves the install root into _outdir.
268
269         """
270         shutil.move(self._instroot, self._outdir + "/" + self.name)
271
272     def get_installed_packages(self):
273         return self._pkgs_content.keys()
274
275     def _save_recording_pkgs(self, destdir):
276         """Save the list or content of installed packages to file.
277         """
278         pkgs = self._pkgs_content.keys()
279         pkgs.sort() # inplace op
280
281         if not os.path.exists(destdir):
282             os.makedirs(destdir)
283
284         content = None
285         if 'vcs' in self._recording_pkgs:
286             vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
287             content = '\n'.join(sorted(vcslst))
288         elif 'name' in self._recording_pkgs:
289             content = '\n'.join(pkgs)
290         if content:
291             namefile = os.path.join(destdir, self.name + '.packages')
292             f = open(namefile, "w")
293             f.write(content)
294             f.close()
295             self.outimage.append(namefile);
296
297         # if 'content', save more details
298         if 'content' in self._recording_pkgs:
299             contfile = os.path.join(destdir, self.name + '.files')
300             f = open(contfile, "w")
301
302             for pkg in pkgs:
303                 content = pkg + '\n'
304
305                 pkgcont = self._pkgs_content[pkg]
306                 content += '    '
307                 content += '\n    '.join(pkgcont)
308                 content += '\n'
309
310                 content += '\n'
311                 f.write(content)
312             f.close()
313             self.outimage.append(contfile)
314
315         if 'license' in self._recording_pkgs:
316             licensefile = os.path.join(destdir, self.name + '.license')
317             f = open(licensefile, "w")
318
319             f.write('Summary:\n')
320             for license in reversed(sorted(self._pkgs_license, key=\
321                             lambda license: len(self._pkgs_license[license]))):
322                 f.write("    - %s: %s\n" \
323                         % (license, len(self._pkgs_license[license])))
324
325             f.write('\nDetails:\n')
326             for license in reversed(sorted(self._pkgs_license, key=\
327                             lambda license: len(self._pkgs_license[license]))):
328                 f.write("    - %s:\n" % (license))
329                 for pkg in sorted(self._pkgs_license[license]):
330                     f.write("        - %s\n" % (pkg))
331                 f.write('\n')
332
333             f.close()
334             self.outimage.append(licensefile)
335
336     def _get_required_packages(self):
337         """Return a list of required packages.
338
339         This is the hook where subclasses may specify a set of packages which
340         it requires to be installed.
341
342         This returns an empty list by default.
343
344         Note, subclasses should usually chain up to the base class
345         implementation of this hook.
346
347         """
348         return []
349
350     def _get_excluded_packages(self):
351         """Return a list of excluded packages.
352
353         This is the hook where subclasses may specify a set of packages which
354         it requires _not_ to be installed.
355
356         This returns an empty list by default.
357
358         Note, subclasses should usually chain up to the base class
359         implementation of this hook.
360
361         """
362         return []
363
364     def _get_local_packages(self):
365         """Return a list of rpm path to be local installed.
366
367         This is the hook where subclasses may specify a set of rpms which
368         it requires to be installed locally.
369
370         This returns an empty list by default.
371
372         Note, subclasses should usually chain up to the base class
373         implementation of this hook.
374
375         """
376         if self._local_pkgs_path:
377             if os.path.isdir(self._local_pkgs_path):
378                 return glob.glob(
379                         os.path.join(self._local_pkgs_path, '*.rpm'))
380             elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
381                 return [self._local_pkgs_path]
382
383         return []
384
385     def _get_fstab(self):
386         """Return the desired contents of /etc/fstab.
387
388         This is the hook where subclasses may specify the contents of
389         /etc/fstab by returning a string containing the desired contents.
390
391         A sensible default implementation is provided.
392
393         """
394         s =  "/dev/root  /         %s    %s 0 0\n" \
395              % (self._fstype,
396                 "defaults,noatime" if not self._fsopts else self._fsopts)
397         s += self._get_fstab_special()
398         return s
399
400     def _get_fstab_special(self):
401         s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
402         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
403         s += "proc       /proc     proc    defaults         0 0\n"
404         s += "sysfs      /sys      sysfs   defaults         0 0\n"
405         return s
406
407     def _set_part_env(self, pnum, prop, value):
408         """ This is a helper function which generates an environment variable
409         for a property "prop" with value "value" of a partition number "pnum".
410
411         The naming convention is:
412            * Variables start with INSTALLERFW_PART
413            * Then goes the partition number, the order is the same as
414              specified in the KS file
415            * Then goes the property name
416         """
417
418         if value == None:
419             value = ""
420         else:
421             value = str(value)
422
423         name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
424         return { name : value }
425
426     def _get_post_scripts_env(self, in_chroot):
427         """Return an environment dict for %post scripts.
428
429         This is the hook where subclasses may specify some environment
430         variables for %post scripts by return a dict containing the desired
431         environment.
432
433         in_chroot -- whether this %post script is to be executed chroot()ed
434                      into _instroot.
435         """
436
437         env = {}
438         pnum = 0
439
440         for p in kickstart.get_partitions(self.ks):
441             env.update(self._set_part_env(pnum, "SIZE", p.size))
442             env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
443             env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
444             env.update(self._set_part_env(pnum, "LABEL", p.label))
445             env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
446             env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
447             env.update(self._set_part_env(pnum, "ALIGN", p.align))
448             env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
449             env.update(self._set_part_env(pnum, "DEVNODE",
450                                           "/dev/%s%d" % (p.disk, pnum + 1)))
451             pnum += 1
452
453         # Count of paritions
454         env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
455
456         # Partition table format
457         ptable_format = self.ks.handler.bootloader.ptable
458         env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
459
460         # The kerned boot parameters
461         kernel_opts = self.ks.handler.bootloader.appendLine
462         env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
463
464         # Name of the distribution
465         env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
466
467         # Name of the image creation tool
468         env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
469
470         # The real current location of the mounted file-systems
471         if in_chroot:
472             mount_prefix = "/"
473         else:
474             mount_prefix = self._instroot
475         env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
476
477         # These are historical variables which lack the common name prefix
478         if not in_chroot:
479             env["INSTALL_ROOT"] = self._instroot
480             env["IMG_NAME"] = self._name
481
482         return env
483
484     def __get_imgname(self):
485         return self.name
486     _name = property(__get_imgname)
487     """The name of the image file.
488
489     """
490
491     def _get_kernel_versions(self):
492         """Return a dict detailing the available kernel types/versions.
493
494         This is the hook where subclasses may override what kernel types and
495         versions should be available for e.g. creating the booloader
496         configuration.
497
498         A dict should be returned mapping the available kernel types to a list
499         of the available versions for those kernels.
500
501         The default implementation uses rpm to iterate over everything
502         providing 'kernel', finds /boot/vmlinuz-* and returns the version
503         obtained from the vmlinuz filename. (This can differ from the kernel
504         RPM's n-v-r in the case of e.g. xen)
505
506         """
507         def get_kernel_versions(instroot):
508             ret = {}
509             versions = set()
510             files = glob.glob(instroot + "/boot/vmlinuz-*")
511             for file in files:
512                 version = os.path.basename(file)[8:]
513                 if version is None:
514                     continue
515                 versions.add(version)
516             ret["kernel"] = list(versions)
517             return ret
518
519         def get_version(header):
520             version = None
521             for f in header['filenames']:
522                 if f.startswith('/boot/vmlinuz-'):
523                     version = f[14:]
524             return version
525
526         if self.ks is None:
527             return get_kernel_versions(self._instroot)
528
529         ts = rpm.TransactionSet(self._instroot)
530
531         ret = {}
532         for header in ts.dbMatch('provides', 'kernel'):
533             version = get_version(header)
534             if version is None:
535                 continue
536
537             name = header['name']
538             if not name in ret:
539                 ret[name] = [version]
540             elif not version in ret[name]:
541                 ret[name].append(version)
542
543         return ret
544
545
546     #
547     # Helpers for subclasses
548     #
549     def _do_bindmounts(self):
550         """Mount various system directories onto _instroot.
551
552         This method is called by mount(), but may also be used by subclasses
553         in order to re-mount the bindmounts after modifying the underlying
554         filesystem.
555
556         """
557         for b in self.__bindmounts:
558             b.mount()
559
560     def _undo_bindmounts(self):
561         """Unmount the bind-mounted system directories from _instroot.
562
563         This method is usually only called by unmount(), but may also be used
564         by subclasses in order to gain access to the filesystem obscured by
565         the bindmounts - e.g. in order to create device nodes on the image
566         filesystem.
567
568         """
569         self.__bindmounts.reverse()
570         for b in self.__bindmounts:
571             b.unmount()
572
573     def _chroot(self):
574         """Chroot into the install root.
575
576         This method may be used by subclasses when executing programs inside
577         the install root e.g.
578
579           subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
580
581         """
582         os.chroot(self._instroot)
583         os.chdir("/")
584
585     def _mkdtemp(self, prefix = "tmp-"):
586         """Create a temporary directory.
587
588         This method may be used by subclasses to create a temporary directory
589         for use in building the final image - e.g. a subclass might create
590         a temporary directory in order to bundle a set of files into a package.
591
592         The subclass may delete this directory if it wishes, but it will be
593         automatically deleted by cleanup().
594
595         The absolute path to the temporary directory is returned.
596
597         Note, this method should only be called after mount() has been called.
598
599         prefix -- a prefix which should be used when creating the directory;
600                   defaults to "tmp-".
601
602         """
603         self.__ensure_builddir()
604         return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
605
606     def _mkstemp(self, prefix = "tmp-"):
607         """Create a temporary file.
608
609         This method may be used by subclasses to create a temporary file
610         for use in building the final image - e.g. a subclass might need
611         a temporary location to unpack a compressed file.
612
613         The subclass may delete this file if it wishes, but it will be
614         automatically deleted by cleanup().
615
616         A tuple containing a file descriptor (returned from os.open() and the
617         absolute path to the temporary directory is returned.
618
619         Note, this method should only be called after mount() has been called.
620
621         prefix -- a prefix which should be used when creating the file;
622                   defaults to "tmp-".
623
624         """
625         self.__ensure_builddir()
626         return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
627
628     def _mktemp(self, prefix = "tmp-"):
629         """Create a temporary file.
630
631         This method simply calls _mkstemp() and closes the returned file
632         descriptor.
633
634         The absolute path to the temporary file is returned.
635
636         Note, this method should only be called after mount() has been called.
637
638         prefix -- a prefix which should be used when creating the file;
639                   defaults to "tmp-".
640
641         """
642
643         (f, path) = self._mkstemp(prefix)
644         os.close(f)
645         return path
646
647
648     #
649     # Actual implementation
650     #
651     def __ensure_builddir(self):
652         if not self.__builddir is None:
653             return
654
655         try:
656             self.workdir = os.path.join(self.tmpdir, "build")
657             if not os.path.exists(self.workdir):
658                 os.makedirs(self.workdir)
659             self.__builddir = tempfile.mkdtemp(dir = self.workdir,
660                                                prefix = "imgcreate-")
661         except OSError, (err, msg):
662             raise CreatorError("Failed create build directory in %s: %s" %
663                                (self.tmpdir, msg))
664
665     def get_cachedir(self, cachedir = None):
666         if self.cachedir:
667             return self.cachedir
668
669         self.__ensure_builddir()
670         if cachedir:
671             self.cachedir = cachedir
672         else:
673             self.cachedir = self.__builddir + "/mic-cache"
674         fs.makedirs(self.cachedir)
675         return self.cachedir
676
677     def __sanity_check(self):
678         """Ensure that the config we've been given is sane."""
679         if not (kickstart.get_packages(self.ks) or
680                 kickstart.get_groups(self.ks)):
681             raise CreatorError("No packages or groups specified")
682
683         kickstart.convert_method_to_repo(self.ks)
684
685         if not kickstart.get_repos(self.ks):
686             raise CreatorError("No repositories specified")
687
688     def __write_fstab(self):
689         fstab_contents = self._get_fstab()
690         if fstab_contents:
691             fstab = open(self._instroot + "/etc/fstab", "w")
692             fstab.write(fstab_contents)
693             fstab.close()
694
695     def __create_minimal_dev(self):
696         """Create a minimal /dev so that we don't corrupt the host /dev"""
697         origumask = os.umask(0000)
698         devices = (('null',   1, 3, 0666),
699                    ('urandom',1, 9, 0666),
700                    ('random', 1, 8, 0666),
701                    ('full',   1, 7, 0666),
702                    ('ptmx',   5, 2, 0666),
703                    ('tty',    5, 0, 0666),
704                    ('zero',   1, 5, 0666))
705
706         links = (("/proc/self/fd", "/dev/fd"),
707                  ("/proc/self/fd/0", "/dev/stdin"),
708                  ("/proc/self/fd/1", "/dev/stdout"),
709                  ("/proc/self/fd/2", "/dev/stderr"))
710
711         for (node, major, minor, perm) in devices:
712             if not os.path.exists(self._instroot + "/dev/" + node):
713                 os.mknod(self._instroot + "/dev/" + node,
714                          perm | stat.S_IFCHR,
715                          os.makedev(major,minor))
716
717         for (src, dest) in links:
718             if not os.path.exists(self._instroot + dest):
719                 os.symlink(src, self._instroot + dest)
720
721         os.umask(origumask)
722
723     def __setup_tmpdir(self):
724         if not self.enabletmpfs:
725             return
726
727         runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
728
729     def __clean_tmpdir(self):
730         if not self.enabletmpfs:
731             return
732
733         runner.show('umount -l %s' % self.workdir)
734
735     def mount(self, base_on = None, cachedir = None):
736         """Setup the target filesystem in preparation for an install.
737
738         This function sets up the filesystem which the ImageCreator will
739         install into and configure. The ImageCreator class merely creates an
740         install root directory, bind mounts some system directories (e.g. /dev)
741         and writes out /etc/fstab. Other subclasses may also e.g. create a
742         sparse file, format it and loopback mount it to the install root.
743
744         base_on -- a previous install on which to base this install; defaults
745                    to None, causing a new image to be created
746
747         cachedir -- a directory in which to store the Yum cache; defaults to
748                     None, causing a new cache to be created; by setting this
749                     to another directory, the same cache can be reused across
750                     multiple installs.
751
752         """
753         self.__setup_tmpdir()
754         self.__ensure_builddir()
755
756         # prevent popup dialog in Ubuntu(s)
757         misc.hide_loopdev_presentation()
758
759         fs.makedirs(self._instroot)
760         fs.makedirs(self._outdir)
761
762         self._mount_instroot(base_on)
763
764         for d in ("/dev/pts",
765                   "/etc",
766                   "/boot",
767                   "/var/log",
768                   "/sys",
769                   "/proc",
770                   "/usr/bin"):
771             fs.makedirs(self._instroot + d)
772
773         if self.target_arch and self.target_arch.startswith("arm"):
774             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
775                                                           self.target_arch)
776
777
778         self.get_cachedir(cachedir)
779
780         # bind mount system directories into _instroot
781         for (f, dest) in [("/sys", None),
782                           ("/proc", None),
783                           ("/proc/sys/fs/binfmt_misc", None),
784                           ("/dev/pts", None)]:
785             self.__bindmounts.append(
786                     fs.BindChrootMount(
787                         f, self._instroot, dest))
788
789         self._do_bindmounts()
790
791         self.__create_minimal_dev()
792
793         if os.path.exists(self._instroot + "/etc/mtab"):
794             os.unlink(self._instroot + "/etc/mtab")
795         os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
796
797         self.__write_fstab()
798
799         # get size of available space in 'instroot' fs
800         self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
801
802     def unmount(self):
803         """Unmounts the target filesystem.
804
805         The ImageCreator class detaches the system from the install root, but
806         other subclasses may also detach the loopback mounted filesystem image
807         from the install root.
808
809         """
810         try:
811             mtab = self._instroot + "/etc/mtab"
812             if not os.path.islink(mtab):
813                 os.unlink(self._instroot + "/etc/mtab")
814
815             if self.qemu_emulator:
816                 os.unlink(self._instroot + self.qemu_emulator)
817         except OSError:
818             pass
819
820         self._undo_bindmounts()
821
822         """ Clean up yum garbage """
823         try:
824             instroot_pdir = os.path.dirname(self._instroot + self._instroot)
825             if os.path.exists(instroot_pdir):
826                 shutil.rmtree(instroot_pdir, ignore_errors = True)
827             yumlibdir = self._instroot + "/var/lib/yum"
828             if os.path.exists(yumlibdir):
829                 shutil.rmtree(yumlibdir, ignore_errors = True)
830         except OSError:
831             pass
832
833         self._unmount_instroot()
834
835         # reset settings of popup dialog in Ubuntu(s)
836         misc.unhide_loopdev_presentation()
837
838
839     def cleanup(self):
840         """Unmounts the target filesystem and deletes temporary files.
841
842         This method calls unmount() and then deletes any temporary files and
843         directories that were created on the host system while building the
844         image.
845
846         Note, make sure to call this method once finished with the creator
847         instance in order to ensure no stale files are left on the host e.g.:
848
849           creator = ImageCreator(ks, name)
850           try:
851               creator.create()
852           finally:
853               creator.cleanup()
854
855         """
856         if not self.__builddir:
857             return
858
859         kill_proc_inchroot(self._instroot)
860
861         self.unmount()
862
863         shutil.rmtree(self.__builddir, ignore_errors = True)
864         self.__builddir = None
865
866         self.__clean_tmpdir()
867
868     def __is_excluded_pkg(self, pkg):
869         if pkg in self._excluded_pkgs:
870             self._excluded_pkgs.remove(pkg)
871             return True
872
873         for xpkg in self._excluded_pkgs:
874             if xpkg.endswith('*'):
875                 if pkg.startswith(xpkg[:-1]):
876                     return True
877             elif xpkg.startswith('*'):
878                 if pkg.endswith(xpkg[1:]):
879                     return True
880
881         return None
882
883     def __select_packages(self, pkg_manager):
884         skipped_pkgs = []
885         for pkg in self._required_pkgs:
886             e = pkg_manager.selectPackage(pkg)
887             if e:
888                 if kickstart.ignore_missing(self.ks):
889                     skipped_pkgs.append(pkg)
890                 elif self.__is_excluded_pkg(pkg):
891                     skipped_pkgs.append(pkg)
892                 else:
893                     raise CreatorError("Failed to find package '%s' : %s" %
894                                        (pkg, e))
895
896         for pkg in skipped_pkgs:
897             msger.warning("Skipping missing package '%s'" % (pkg,))
898
899     def __select_groups(self, pkg_manager):
900         skipped_groups = []
901         for group in self._required_groups:
902             e = pkg_manager.selectGroup(group.name, group.include)
903             if e:
904                 if kickstart.ignore_missing(self.ks):
905                     skipped_groups.append(group)
906                 else:
907                     raise CreatorError("Failed to find group '%s' : %s" %
908                                        (group.name, e))
909
910         for group in skipped_groups:
911             msger.warning("Skipping missing group '%s'" % (group.name,))
912
913     def __deselect_packages(self, pkg_manager):
914         for pkg in self._excluded_pkgs:
915             pkg_manager.deselectPackage(pkg)
916
917     def __localinst_packages(self, pkg_manager):
918         for rpm_path in self._get_local_packages():
919             pkg_manager.installLocal(rpm_path)
920
921     def __preinstall_packages(self, pkg_manager):
922         if not self.ks:
923             return
924
925         self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
926         for pkg in self._preinstall_pkgs:
927             pkg_manager.preInstall(pkg)
928
929     def __check_packages(self, pkg_manager):
930         for pkg in self.check_pkgs:
931             pkg_manager.checkPackage(pkg)
932
933     def __attachment_packages(self, pkg_manager):
934         if not self.ks:
935             return
936
937         self._attachment = []
938         for item in kickstart.get_attachment(self.ks):
939             if item.startswith('/'):
940                 fpaths = os.path.join(self._instroot, item.lstrip('/'))
941                 for fpath in glob.glob(fpaths):
942                     self._attachment.append(fpath)
943                 continue
944
945             filelist = pkg_manager.getFilelist(item)
946             if filelist:
947                 # found rpm in rootfs
948                 for pfile in pkg_manager.getFilelist(item):
949                     fpath = os.path.join(self._instroot, pfile.lstrip('/'))
950                     self._attachment.append(fpath)
951                 continue
952
953             # try to retrieve rpm file
954             (url, proxies) = pkg_manager.package_url(item)
955             if not url:
956                 msger.warning("Can't get url from repo for %s" % item)
957                 continue
958             fpath = os.path.join(self.cachedir, os.path.basename(url))
959             if not os.path.exists(fpath):
960                 # download pkgs
961                 try:
962                     fpath = grabber.myurlgrab(url, fpath, proxies, None)
963                 except CreatorError:
964                     raise
965
966             tmpdir = self._mkdtemp()
967             misc.extract_rpm(fpath, tmpdir)
968             for (root, dirs, files) in os.walk(tmpdir):
969                 for fname in files:
970                     fpath = os.path.join(root, fname)
971                     self._attachment.append(fpath)
972
973     def install(self, repo_urls=None):
974         """Install packages into the install root.
975
976         This function installs the packages listed in the supplied kickstart
977         into the install root. By default, the packages are installed from the
978         repository URLs specified in the kickstart.
979
980         repo_urls -- a dict which maps a repository name to a repository URL;
981                      if supplied, this causes any repository URLs specified in
982                      the kickstart to be overridden.
983
984         """
985
986         # initialize pkg list to install
987         if self.ks:
988             self.__sanity_check()
989
990             self._required_pkgs = \
991                 kickstart.get_packages(self.ks, self._get_required_packages())
992             self._excluded_pkgs = \
993                 kickstart.get_excluded(self.ks, self._get_excluded_packages())
994             self._required_groups = kickstart.get_groups(self.ks)
995         else:
996             self._required_pkgs = None
997             self._excluded_pkgs = None
998             self._required_groups = None
999
1000         pkg_manager = self.get_pkg_manager()
1001         pkg_manager.setup()
1002
1003         if hasattr(self, 'install_pkgs') and self.install_pkgs:
1004             if 'debuginfo' in self.install_pkgs:
1005                 pkg_manager.install_debuginfo = True
1006
1007         for repo in kickstart.get_repos(self.ks, repo_urls):
1008             (name, baseurl, mirrorlist, inc, exc,
1009              proxy, proxy_username, proxy_password, debuginfo,
1010              source, gpgkey, disable, ssl_verify, nocache,
1011              cost, priority) = repo
1012
1013             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1014                         proxy_username, proxy_password, inc, exc, ssl_verify,
1015                         nocache, cost, priority)
1016
1017         if kickstart.exclude_docs(self.ks):
1018             rpm.addMacro("_excludedocs", "1")
1019         rpm.addMacro("_dbpath", "/var/lib/rpm")
1020         rpm.addMacro("__file_context_path", "%{nil}")
1021         if kickstart.inst_langs(self.ks) != None:
1022             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1023
1024         try:
1025             self.__preinstall_packages(pkg_manager)
1026             self.__select_packages(pkg_manager)
1027             self.__select_groups(pkg_manager)
1028             self.__deselect_packages(pkg_manager)
1029             self.__localinst_packages(pkg_manager)
1030             self.__check_packages(pkg_manager)
1031
1032             BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1033             checksize = self._root_fs_avail
1034             if checksize:
1035                 checksize -= BOOT_SAFEGUARD
1036             if self.target_arch:
1037                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1038             pkg_manager.runInstall(checksize)
1039         except CreatorError, e:
1040             raise
1041         except  KeyboardInterrupt:
1042             raise
1043         else:
1044             self._pkgs_content = pkg_manager.getAllContent()
1045             self._pkgs_license = pkg_manager.getPkgsLicense()
1046             self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1047             self.__attachment_packages(pkg_manager)
1048         finally:
1049             pkg_manager.close()
1050
1051         # hook post install
1052         self.postinstall()
1053
1054         # do some clean up to avoid lvm info leakage.  this sucks.
1055         for subdir in ("cache", "backup", "archive"):
1056             lvmdir = self._instroot + "/etc/lvm/" + subdir
1057             try:
1058                 for f in os.listdir(lvmdir):
1059                     os.unlink(lvmdir + "/" + f)
1060             except:
1061                 pass
1062
1063     def postinstall(self):
1064         self.copy_attachment()
1065
1066     def __run_post_scripts(self):
1067         msger.info("Running scripts ...")
1068         if os.path.exists(self._instroot + "/tmp"):
1069             shutil.rmtree(self._instroot + "/tmp")
1070         os.mkdir (self._instroot + "/tmp", 0755)
1071         for s in kickstart.get_post_scripts(self.ks):
1072             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1073                                           dir = self._instroot + "/tmp")
1074
1075             s.script = s.script.replace("\r", "")
1076             os.write(fd, s.script)
1077             os.close(fd)
1078             os.chmod(path, 0700)
1079
1080             env = self._get_post_scripts_env(s.inChroot)
1081
1082             if not s.inChroot:
1083                 preexec = None
1084                 script = path
1085             else:
1086                 preexec = self._chroot
1087                 script = "/tmp/" + os.path.basename(path)
1088
1089             try:
1090                 try:
1091                     p = subprocess.Popen([s.interp, script],
1092                                        preexec_fn = preexec,
1093                                        env = env,
1094                                        stdout = subprocess.PIPE,
1095                                        stderr = subprocess.STDOUT)
1096                     for entry in p.communicate()[0].splitlines():
1097                         msger.info(entry)
1098                 except OSError, (err, msg):
1099                     raise CreatorError("Failed to execute %%post script "
1100                                        "with '%s' : %s" % (s.interp, msg))
1101             finally:
1102                 os.unlink(path)
1103
1104     def __save_repo_keys(self, repodata):
1105         if not repodata:
1106             return None
1107
1108         gpgkeydir = "/etc/pki/rpm-gpg"
1109         fs.makedirs(self._instroot + gpgkeydir)
1110         for repo in repodata:
1111             if repo["repokey"]:
1112                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1113                 shutil.copy(repo["repokey"], self._instroot + repokey)
1114
1115     def configure(self, repodata = None):
1116         """Configure the system image according to the kickstart.
1117
1118         This method applies the (e.g. keyboard or network) configuration
1119         specified in the kickstart and executes the kickstart %post scripts.
1120
1121         If necessary, it also prepares the image to be bootable by e.g.
1122         creating an initrd and bootloader configuration.
1123
1124         """
1125         ksh = self.ks.handler
1126
1127         msger.info('Applying configurations ...')
1128         try:
1129             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1130             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1131             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1132             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1133             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1134             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1135             kickstart.UserConfig(self._instroot).apply(ksh.user)
1136             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1137             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1138             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1139             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1140             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1141             self.__save_repo_keys(repodata)
1142             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1143         except:
1144             msger.warning("Failed to apply configuration to image")
1145             raise
1146
1147         self._create_bootconfig()
1148         self.__run_post_scripts()
1149
1150     def launch_shell(self, launch):
1151         """Launch a shell in the install root.
1152
1153         This method is launches a bash shell chroot()ed in the install root;
1154         this can be useful for debugging.
1155
1156         """
1157         if launch:
1158             msger.info("Launching shell. Exit to continue.")
1159             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1160
1161     def do_genchecksum(self, image_name):
1162         if not self._genchecksum:
1163             return
1164
1165         md5sum = misc.get_md5sum(image_name)
1166         with open(image_name + ".md5sum", "w") as f:
1167             f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1168         self.outimage.append(image_name+".md5sum")
1169
1170     def package(self, destdir = "."):
1171         """Prepares the created image for final delivery.
1172
1173         In its simplest form, this method merely copies the install root to the
1174         supplied destination directory; other subclasses may choose to package
1175         the image by e.g. creating a bootable ISO containing the image and
1176         bootloader configuration.
1177
1178         destdir -- the directory into which the final image should be moved;
1179                    this defaults to the current directory.
1180
1181         """
1182         self._stage_final_image()
1183
1184         if not os.path.exists(destdir):
1185             fs.makedirs(destdir)
1186
1187         if self._recording_pkgs:
1188             self._save_recording_pkgs(destdir)
1189
1190         # For image formats with two or multiple image files, it will be
1191         # better to put them under a directory
1192         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1193             destdir = os.path.join(destdir, "%s-%s" \
1194                                             % (self.name, self.image_format))
1195             msger.debug("creating destination dir: %s" % destdir)
1196             fs.makedirs(destdir)
1197
1198         # Ensure all data is flushed to _outdir
1199         runner.quiet('sync')
1200
1201         misc.check_space_pre_cp(self._outdir, destdir)
1202         for f in os.listdir(self._outdir):
1203             shutil.move(os.path.join(self._outdir, f),
1204                         os.path.join(destdir, f))
1205             self.outimage.append(os.path.join(destdir, f))
1206             self.do_genchecksum(os.path.join(destdir, f))
1207
1208     def print_outimage_info(self):
1209         msg = "The new image can be found here:\n"
1210         self.outimage.sort()
1211         for file in self.outimage:
1212             msg += '  %s\n' % os.path.abspath(file)
1213
1214         msger.info(msg)
1215
1216     def check_depend_tools(self):
1217         for tool in self._dep_checks:
1218             fs.find_binary_path(tool)
1219
1220     def package_output(self, image_format, destdir = ".", package="none"):
1221         if not package or package == "none":
1222             return
1223
1224         destdir = os.path.abspath(os.path.expanduser(destdir))
1225         (pkg, comp) = os.path.splitext(package)
1226         if comp:
1227             comp=comp.lstrip(".")
1228
1229         if pkg == "tar":
1230             if comp:
1231                 dst = "%s/%s-%s.tar.%s" %\
1232                       (destdir, self.name, image_format, comp)
1233             else:
1234                 dst = "%s/%s-%s.tar" %\
1235                       (destdir, self.name, image_format)
1236
1237             msger.info("creating %s" % dst)
1238             tar = tarfile.open(dst, "w:" + comp)
1239
1240             for file in self.outimage:
1241                 msger.info("adding %s to %s" % (file, dst))
1242                 tar.add(file,
1243                         arcname=os.path.join("%s-%s" \
1244                                            % (self.name, image_format),
1245                                               os.path.basename(file)))
1246                 if os.path.isdir(file):
1247                     shutil.rmtree(file, ignore_errors = True)
1248                 else:
1249                     os.remove(file)
1250
1251             tar.close()
1252
1253             '''All the file in outimage has been packaged into tar.* file'''
1254             self.outimage = [dst]
1255
1256     def release_output(self, config, destdir, release):
1257         """ Create release directory and files
1258         """
1259
1260         def _rpath(fn):
1261             """ release path """
1262             return os.path.join(destdir, fn)
1263
1264         outimages = self.outimage
1265
1266         # new ks
1267         new_kspath = _rpath(self.name+'.ks')
1268         with open(config) as fr:
1269             with open(new_kspath, "w") as wf:
1270                 # When building a release we want to make sure the .ks
1271                 # file generates the same build even when --release not used.
1272                 wf.write(fr.read().replace("@BUILD_ID@", release))
1273         outimages.append(new_kspath)
1274
1275         # save log file, logfile is only available in creator attrs
1276         if hasattr(self, 'logfile') and not self.logfile:
1277             log_path = _rpath(self.name + ".log")
1278             # touch the log file, else outimages will filter it out
1279             with open(log_path, 'w') as wf:
1280                 wf.write('')
1281             msger.set_logfile(log_path)
1282             outimages.append(_rpath(self.name + ".log"))
1283
1284         # rename iso and usbimg
1285         for f in os.listdir(destdir):
1286             if f.endswith(".iso"):
1287                 newf = f[:-4] + '.img'
1288             elif f.endswith(".usbimg"):
1289                 newf = f[:-7] + '.img'
1290             else:
1291                 continue
1292             os.rename(_rpath(f), _rpath(newf))
1293             outimages.append(_rpath(newf))
1294
1295         # generate MD5SUMS
1296         with open(_rpath("MD5SUMS"), "w") as wf:
1297             for f in os.listdir(destdir):
1298                 if f == "MD5SUMS":
1299                     continue
1300
1301                 if os.path.isdir(os.path.join(destdir, f)):
1302                     continue
1303
1304                 md5sum = misc.get_md5sum(_rpath(f))
1305                 # There needs to be two spaces between the sum and
1306                 # filepath to match the syntax with md5sum.
1307                 # This way also md5sum -c MD5SUMS can be used by users
1308                 wf.write("%s *%s\n" % (md5sum, f))
1309
1310         outimages.append("%s/MD5SUMS" % destdir)
1311
1312         # Filter out the nonexist file
1313         for fp in outimages[:]:
1314             if not os.path.exists("%s" % fp):
1315                 outimages.remove(fp)
1316
1317     def copy_kernel(self):
1318         """ Copy kernel files to the outimage directory.
1319         NOTE: This needs to be called before unmounting the instroot.
1320         """
1321
1322         if not self._need_copy_kernel:
1323             return
1324
1325         if not os.path.exists(self.destdir):
1326             os.makedirs(self.destdir)
1327
1328         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1329             kernelfilename = "%s/%s-%s" % (self.destdir,
1330                                            self.name,
1331                                            os.path.basename(kernel))
1332             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1333                                                       kernelfilename))
1334             shutil.copy(kernel, kernelfilename)
1335             self.outimage.append(kernelfilename)
1336
1337     def copy_attachment(self):
1338         """ Subclass implement it to handle attachment files
1339         NOTE: This needs to be called before unmounting the instroot.
1340         """
1341         pass
1342
1343     def get_pkg_manager(self):
1344         return self.pkgmgr(target_arch = self.target_arch,
1345                            instroot = self._instroot,
1346                            cachedir = self.cachedir)