Enable ssl_veirify option in mic config file
[tools/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                       "arch" : "target_arch",
93                       "local_pkgs_path" : "_local_pkgs_path",
94                       "copy_kernel" : "_need_copy_kernel",
95                      }
96
97             # update setting from createopts
98             for key in createopts.keys():
99                 if key in optmap:
100                     option = optmap[key]
101                 else:
102                     option = key
103                 setattr(self, option, createopts[key])
104
105             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
106
107             if self.pack_to:
108                 if '@NAME@' in self.pack_to:
109                     self.pack_to = self.pack_to.replace('@NAME@', self.name)
110                 (tar, ext) = os.path.splitext(self.pack_to)
111                 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
112                     ext = ".tar" + ext
113                 if ext not in misc.pack_formats:
114                     self.pack_to += ".tar"
115
116         self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
117
118         # Output image file names
119         self.outimage = []
120
121         # A flag to generate checksum
122         self._genchecksum = False
123
124         self._alt_initrd_name = None
125
126         self._recording_pkgs = []
127
128         # available size in root fs, init to 0
129         self._root_fs_avail = 0
130
131         # Name of the disk image file that is created.
132         self._img_name = None
133
134         self.image_format = None
135
136         # Save qemu emulator file name in order to clean up it finally
137         self.qemu_emulator = None
138
139         # No ks provided when called by convertor, so skip the dependency check
140         if self.ks:
141             # If we have btrfs partition we need to check necessary tools
142             for part in self.ks.handler.partition.partitions:
143                 if part.fstype and part.fstype == "btrfs":
144                     self._dep_checks.append("mkfs.btrfs")
145                     break
146
147         if self.target_arch and self.target_arch.startswith("arm"):
148             for dep in self._dep_checks:
149                 if dep == "extlinux":
150                     self._dep_checks.remove(dep)
151
152             if not os.path.exists("/usr/bin/qemu-arm") or \
153                not misc.is_statically_linked("/usr/bin/qemu-arm"):
154                 self._dep_checks.append("qemu-arm-static")
155
156             if os.path.exists("/proc/sys/vm/vdso_enabled"):
157                 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
158                 vdso_value = vdso_fh.read().strip()
159                 vdso_fh.close()
160                 if (int)(vdso_value) == 1:
161                     msger.warning("vdso is enabled on your host, which might "
162                         "cause problems with arm emulations.\n"
163                         "\tYou can disable vdso with following command before "
164                         "starting image build:\n"
165                         "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
166
167         # make sure the specified tmpdir and cachedir exist
168         if not os.path.exists(self.tmpdir):
169             os.makedirs(self.tmpdir)
170         if not os.path.exists(self.cachedir):
171             os.makedirs(self.cachedir)
172
173
174     #
175     # Properties
176     #
177     def __get_instroot(self):
178         if self.__builddir is None:
179             raise CreatorError("_instroot is not valid before calling mount()")
180         return self.__builddir + "/install_root"
181     _instroot = property(__get_instroot)
182     """The location of the install root directory.
183
184     This is the directory into which the system is installed. Subclasses may
185     mount a filesystem image here or copy files to/from here.
186
187     Note, this directory does not exist before ImageCreator.mount() is called.
188
189     Note also, this is a read-only attribute.
190
191     """
192
193     def __get_outdir(self):
194         if self.__builddir is None:
195             raise CreatorError("_outdir is not valid before calling mount()")
196         return self.__builddir + "/out"
197     _outdir = property(__get_outdir)
198     """The staging location for the final image.
199
200     This is where subclasses should stage any files that are part of the final
201     image. ImageCreator.package() will copy any files found here into the
202     requested destination directory.
203
204     Note, this directory does not exist before ImageCreator.mount() is called.
205
206     Note also, this is a read-only attribute.
207
208     """
209
210
211     #
212     # Hooks for subclasses
213     #
214     def _mount_instroot(self, base_on = None):
215         """Mount or prepare the install root directory.
216
217         This is the hook where subclasses may prepare the install root by e.g.
218         mounting creating and loopback mounting a filesystem image to
219         _instroot.
220
221         There is no default implementation.
222
223         base_on -- this is the value passed to mount() and can be interpreted
224                    as the subclass wishes; it might e.g. be the location of
225                    a previously created ISO containing a system image.
226
227         """
228         pass
229
230     def _unmount_instroot(self):
231         """Undo anything performed in _mount_instroot().
232
233         This is the hook where subclasses must undo anything which was done
234         in _mount_instroot(). For example, if a filesystem image was mounted
235         onto _instroot, it should be unmounted here.
236
237         There is no default implementation.
238
239         """
240         pass
241
242     def _create_bootconfig(self):
243         """Configure the image so that it's bootable.
244
245         This is the hook where subclasses may prepare the image for booting by
246         e.g. creating an initramfs and bootloader configuration.
247
248         This hook is called while the install root is still mounted, after the
249         packages have been installed and the kickstart configuration has been
250         applied, but before the %post scripts have been executed.
251
252         There is no default implementation.
253
254         """
255         pass
256
257     def _stage_final_image(self):
258         """Stage the final system image in _outdir.
259
260         This is the hook where subclasses should place the image in _outdir
261         so that package() can copy it to the requested destination directory.
262
263         By default, this moves the install root into _outdir.
264
265         """
266         shutil.move(self._instroot, self._outdir + "/" + self.name)
267
268     def get_installed_packages(self):
269         return self._pkgs_content.keys()
270
271     def _save_recording_pkgs(self, destdir):
272         """Save the list or content of installed packages to file.
273         """
274         pkgs = self._pkgs_content.keys()
275         pkgs.sort() # inplace op
276
277         if not os.path.exists(destdir):
278             os.makedirs(destdir)
279
280         content = None
281         if 'vcs' in self._recording_pkgs:
282             vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
283             content = '\n'.join(sorted(vcslst))
284         elif 'name' in self._recording_pkgs:
285             content = '\n'.join(pkgs)
286         if content:
287             namefile = os.path.join(destdir, self.name + '.packages')
288             f = open(namefile, "w")
289             f.write(content)
290             f.close()
291             self.outimage.append(namefile);
292
293         # if 'content', save more details
294         if 'content' in self._recording_pkgs:
295             contfile = os.path.join(destdir, self.name + '.files')
296             f = open(contfile, "w")
297
298             for pkg in pkgs:
299                 content = pkg + '\n'
300
301                 pkgcont = self._pkgs_content[pkg]
302                 content += '    '
303                 content += '\n    '.join(pkgcont)
304                 content += '\n'
305
306                 content += '\n'
307                 f.write(content)
308             f.close()
309             self.outimage.append(contfile)
310
311         if 'license' in self._recording_pkgs:
312             licensefile = os.path.join(destdir, self.name + '.license')
313             f = open(licensefile, "w")
314
315             f.write('Summary:\n')
316             for license in reversed(sorted(self._pkgs_license, key=\
317                             lambda license: len(self._pkgs_license[license]))):
318                 f.write("    - %s: %s\n" \
319                         % (license, len(self._pkgs_license[license])))
320
321             f.write('\nDetails:\n')
322             for license in reversed(sorted(self._pkgs_license, key=\
323                             lambda license: len(self._pkgs_license[license]))):
324                 f.write("    - %s:\n" % (license))
325                 for pkg in sorted(self._pkgs_license[license]):
326                     f.write("        - %s\n" % (pkg))
327                 f.write('\n')
328
329             f.close()
330             self.outimage.append(licensefile)
331
332     def _get_required_packages(self):
333         """Return a list of required packages.
334
335         This is the hook where subclasses may specify a set of packages which
336         it requires to be installed.
337
338         This returns an empty list by default.
339
340         Note, subclasses should usually chain up to the base class
341         implementation of this hook.
342
343         """
344         return []
345
346     def _get_excluded_packages(self):
347         """Return a list of excluded packages.
348
349         This is the hook where subclasses may specify a set of packages which
350         it requires _not_ to be installed.
351
352         This returns an empty list by default.
353
354         Note, subclasses should usually chain up to the base class
355         implementation of this hook.
356
357         """
358         return []
359
360     def _get_local_packages(self):
361         """Return a list of rpm path to be local installed.
362
363         This is the hook where subclasses may specify a set of rpms which
364         it requires to be installed locally.
365
366         This returns an empty list by default.
367
368         Note, subclasses should usually chain up to the base class
369         implementation of this hook.
370
371         """
372         if self._local_pkgs_path:
373             if os.path.isdir(self._local_pkgs_path):
374                 return glob.glob(
375                         os.path.join(self._local_pkgs_path, '*.rpm'))
376             elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
377                 return [self._local_pkgs_path]
378
379         return []
380
381     def _get_fstab(self):
382         """Return the desired contents of /etc/fstab.
383
384         This is the hook where subclasses may specify the contents of
385         /etc/fstab by returning a string containing the desired contents.
386
387         A sensible default implementation is provided.
388
389         """
390         s =  "/dev/root  /         %s    %s 0 0\n" \
391              % (self._fstype,
392                 "defaults,noatime" if not self._fsopts else self._fsopts)
393         s += self._get_fstab_special()
394         return s
395
396     def _get_fstab_special(self):
397         s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
398         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
399         s += "proc       /proc     proc    defaults         0 0\n"
400         s += "sysfs      /sys      sysfs   defaults         0 0\n"
401         return s
402
403     def _set_part_env(self, pnum, prop, value):
404         """ This is a helper function which generates an environment variable
405         for a property "prop" with value "value" of a partition number "pnum".
406
407         The naming convention is:
408            * Variables start with INSTALLERFW_PART
409            * Then goes the partition number, the order is the same as
410              specified in the KS file
411            * Then goes the property name
412         """
413
414         if value == None:
415             value = ""
416         else:
417             value = str(value)
418
419         name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
420         return { name : value }
421
422     def _get_post_scripts_env(self, in_chroot):
423         """Return an environment dict for %post scripts.
424
425         This is the hook where subclasses may specify some environment
426         variables for %post scripts by return a dict containing the desired
427         environment.
428
429         in_chroot -- whether this %post script is to be executed chroot()ed
430                      into _instroot.
431         """
432
433         env = {}
434         pnum = 0
435
436         for p in kickstart.get_partitions(self.ks):
437             env.update(self._set_part_env(pnum, "SIZE", p.size))
438             env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
439             env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
440             env.update(self._set_part_env(pnum, "LABEL", p.label))
441             env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
442             env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
443             env.update(self._set_part_env(pnum, "ALIGN", p.align))
444             env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
445             env.update(self._set_part_env(pnum, "UUID", p.uuid))
446             env.update(self._set_part_env(pnum, "DEVNODE",
447                                           "/dev/%s%d" % (p.disk, pnum + 1)))
448             env.update(self._set_part_env(pnum, "DISK_DEVNODE",
449                                           "/dev/%s" % p.disk))
450             pnum += 1
451
452         # Count of paritions
453         env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
454
455         # Partition table format
456         ptable_format = self.ks.handler.bootloader.ptable
457         env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
458
459         # The kerned boot parameters
460         kernel_opts = self.ks.handler.bootloader.appendLine
461         env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
462
463         # Name of the image creation tool
464         env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
465
466         # The real current location of the mounted file-systems
467         if in_chroot:
468             mount_prefix = "/"
469         else:
470             mount_prefix = self._instroot
471         env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
472
473         # These are historical variables which lack the common name prefix
474         if not in_chroot:
475             env["INSTALL_ROOT"] = self._instroot
476             env["IMG_NAME"] = self._name
477
478         return env
479
480     def __get_imgname(self):
481         return self.name
482     _name = property(__get_imgname)
483     """The name of the image file.
484
485     """
486
487     def _get_kernel_versions(self):
488         """Return a dict detailing the available kernel types/versions.
489
490         This is the hook where subclasses may override what kernel types and
491         versions should be available for e.g. creating the booloader
492         configuration.
493
494         A dict should be returned mapping the available kernel types to a list
495         of the available versions for those kernels.
496
497         The default implementation uses rpm to iterate over everything
498         providing 'kernel', finds /boot/vmlinuz-* and returns the version
499         obtained from the vmlinuz filename. (This can differ from the kernel
500         RPM's n-v-r in the case of e.g. xen)
501
502         """
503         def get_kernel_versions(instroot):
504             ret = {}
505             versions = set()
506             files = glob.glob(instroot + "/boot/vmlinuz-*")
507             for file in files:
508                 version = os.path.basename(file)[8:]
509                 if version is None:
510                     continue
511                 versions.add(version)
512             ret["kernel"] = list(versions)
513             return ret
514
515         def get_version(header):
516             version = None
517             for f in header['filenames']:
518                 if f.startswith('/boot/vmlinuz-'):
519                     version = f[14:]
520             return version
521
522         if self.ks is None:
523             return get_kernel_versions(self._instroot)
524
525         ts = rpm.TransactionSet(self._instroot)
526
527         ret = {}
528         for header in ts.dbMatch('provides', 'kernel'):
529             version = get_version(header)
530             if version is None:
531                 continue
532
533             name = header['name']
534             if not name in ret:
535                 ret[name] = [version]
536             elif not version in ret[name]:
537                 ret[name].append(version)
538
539         return ret
540
541
542     #
543     # Helpers for subclasses
544     #
545     def _do_bindmounts(self):
546         """Mount various system directories onto _instroot.
547
548         This method is called by mount(), but may also be used by subclasses
549         in order to re-mount the bindmounts after modifying the underlying
550         filesystem.
551
552         """
553         for b in self.__bindmounts:
554             b.mount()
555
556     def _undo_bindmounts(self):
557         """Unmount the bind-mounted system directories from _instroot.
558
559         This method is usually only called by unmount(), but may also be used
560         by subclasses in order to gain access to the filesystem obscured by
561         the bindmounts - e.g. in order to create device nodes on the image
562         filesystem.
563
564         """
565         self.__bindmounts.reverse()
566         for b in self.__bindmounts:
567             b.unmount()
568
569     def _chroot(self):
570         """Chroot into the install root.
571
572         This method may be used by subclasses when executing programs inside
573         the install root e.g.
574
575           subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
576
577         """
578         os.chroot(self._instroot)
579         os.chdir("/")
580
581     def _mkdtemp(self, prefix = "tmp-"):
582         """Create a temporary directory.
583
584         This method may be used by subclasses to create a temporary directory
585         for use in building the final image - e.g. a subclass might create
586         a temporary directory in order to bundle a set of files into a package.
587
588         The subclass may delete this directory if it wishes, but it will be
589         automatically deleted by cleanup().
590
591         The absolute path to the temporary directory is returned.
592
593         Note, this method should only be called after mount() has been called.
594
595         prefix -- a prefix which should be used when creating the directory;
596                   defaults to "tmp-".
597
598         """
599         self.__ensure_builddir()
600         return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
601
602     def _mkstemp(self, prefix = "tmp-"):
603         """Create a temporary file.
604
605         This method may be used by subclasses to create a temporary file
606         for use in building the final image - e.g. a subclass might need
607         a temporary location to unpack a compressed file.
608
609         The subclass may delete this file if it wishes, but it will be
610         automatically deleted by cleanup().
611
612         A tuple containing a file descriptor (returned from os.open() and the
613         absolute path to the temporary directory is returned.
614
615         Note, this method should only be called after mount() has been called.
616
617         prefix -- a prefix which should be used when creating the file;
618                   defaults to "tmp-".
619
620         """
621         self.__ensure_builddir()
622         return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
623
624     def _mktemp(self, prefix = "tmp-"):
625         """Create a temporary file.
626
627         This method simply calls _mkstemp() and closes the returned file
628         descriptor.
629
630         The absolute path to the temporary file is returned.
631
632         Note, this method should only be called after mount() has been called.
633
634         prefix -- a prefix which should be used when creating the file;
635                   defaults to "tmp-".
636
637         """
638
639         (f, path) = self._mkstemp(prefix)
640         os.close(f)
641         return path
642
643
644     #
645     # Actual implementation
646     #
647     def __ensure_builddir(self):
648         if not self.__builddir is None:
649             return
650
651         try:
652             self.workdir = os.path.join(self.tmpdir, "build")
653             if not os.path.exists(self.workdir):
654                 os.makedirs(self.workdir)
655             self.__builddir = tempfile.mkdtemp(dir = self.workdir,
656                                                prefix = "imgcreate-")
657         except OSError, (err, msg):
658             raise CreatorError("Failed create build directory in %s: %s" %
659                                (self.tmpdir, msg))
660
661     def get_cachedir(self, cachedir = None):
662         if self.cachedir:
663             return self.cachedir
664
665         self.__ensure_builddir()
666         if cachedir:
667             self.cachedir = cachedir
668         else:
669             self.cachedir = self.__builddir + "/mic-cache"
670         fs.makedirs(self.cachedir)
671         return self.cachedir
672
673     def __sanity_check(self):
674         """Ensure that the config we've been given is sane."""
675         if not (kickstart.get_packages(self.ks) or
676                 kickstart.get_groups(self.ks)):
677             raise CreatorError("No packages or groups specified")
678
679         kickstart.convert_method_to_repo(self.ks)
680
681         if not kickstart.get_repos(self.ks):
682             raise CreatorError("No repositories specified")
683
684     def __write_fstab(self):
685         if kickstart.use_installerfw(self.ks, "fstab"):
686             # The fstab file will be generated by installer framework scripts
687             # instead.
688             return None
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") or \
774             self.target_arch == "aarch64":
775             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
776                                                           self.target_arch)
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.full, 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;
981                      if supplied, this causes any repository URLs specified in
982                      the kickstart to be overridden.
983
984         """
985         def get_ssl_verify(ssl_verify=None):
986             if ssl_verify is not None:
987                 return not ssl_verify.lower().strip() == 'no'
988             else:
989                 return not self.ssl_verify.lower().strip() == 'no'
990
991         # initialize pkg list to install
992         if self.ks:
993             self.__sanity_check()
994
995             self._required_pkgs = \
996                 kickstart.get_packages(self.ks, self._get_required_packages())
997             self._excluded_pkgs = \
998                 kickstart.get_excluded(self.ks, self._get_excluded_packages())
999             self._required_groups = kickstart.get_groups(self.ks)
1000         else:
1001             self._required_pkgs = None
1002             self._excluded_pkgs = None
1003             self._required_groups = None
1004
1005         if not repo_urls:
1006             repo_urls = self.extrarepos
1007
1008         pkg_manager = self.get_pkg_manager()
1009         pkg_manager.setup()
1010
1011         if hasattr(self, 'install_pkgs') and self.install_pkgs:
1012             if 'debuginfo' in self.install_pkgs:
1013                 pkg_manager.install_debuginfo = True
1014
1015         for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1016             (name, baseurl, mirrorlist, inc, exc,
1017              proxy, proxy_username, proxy_password, debuginfo,
1018              source, gpgkey, disable, ssl_verify, nocache,
1019              cost, priority) = repo
1020
1021             ssl_verify = get_ssl_verify(ssl_verify)
1022             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1023                         proxy_username, proxy_password, inc, exc, ssl_verify,
1024                         nocache, cost, priority)
1025
1026         if kickstart.exclude_docs(self.ks):
1027             rpm.addMacro("_excludedocs", "1")
1028         rpm.addMacro("_dbpath", "/var/lib/rpm")
1029         rpm.addMacro("__file_context_path", "%{nil}")
1030         if kickstart.inst_langs(self.ks) != None:
1031             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1032
1033         try:
1034             self.__preinstall_packages(pkg_manager)
1035             self.__select_packages(pkg_manager)
1036             self.__select_groups(pkg_manager)
1037             self.__deselect_packages(pkg_manager)
1038             self.__localinst_packages(pkg_manager)
1039             self.__check_packages(pkg_manager)
1040
1041             BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1042             checksize = self._root_fs_avail
1043             if checksize:
1044                 checksize -= BOOT_SAFEGUARD
1045             if self.target_arch:
1046                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1047             pkg_manager.runInstall(checksize)
1048         except CreatorError, e:
1049             raise
1050         except  KeyboardInterrupt:
1051             raise
1052         else:
1053             self._pkgs_content = pkg_manager.getAllContent()
1054             self._pkgs_license = pkg_manager.getPkgsLicense()
1055             self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1056             self.__attachment_packages(pkg_manager)
1057         finally:
1058             pkg_manager.close()
1059
1060         # hook post install
1061         self.postinstall()
1062
1063         # do some clean up to avoid lvm info leakage.  this sucks.
1064         for subdir in ("cache", "backup", "archive"):
1065             lvmdir = self._instroot + "/etc/lvm/" + subdir
1066             try:
1067                 for f in os.listdir(lvmdir):
1068                     os.unlink(lvmdir + "/" + f)
1069             except:
1070                 pass
1071
1072     def postinstall(self):
1073         self.copy_attachment()
1074
1075     def __run_post_scripts(self):
1076         msger.info("Running scripts ...")
1077         if os.path.exists(self._instroot + "/tmp"):
1078             shutil.rmtree(self._instroot + "/tmp")
1079         os.mkdir (self._instroot + "/tmp", 0755)
1080         for s in kickstart.get_post_scripts(self.ks):
1081             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1082                                           dir = self._instroot + "/tmp")
1083
1084             s.script = s.script.replace("\r", "")
1085             os.write(fd, s.script)
1086             os.close(fd)
1087             os.chmod(path, 0700)
1088
1089             env = self._get_post_scripts_env(s.inChroot)
1090             if 'PATH' not in env:
1091                 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1092
1093             if not s.inChroot:
1094                 preexec = None
1095                 script = path
1096             else:
1097                 preexec = self._chroot
1098                 script = "/tmp/" + os.path.basename(path)
1099
1100             try:
1101                 try:
1102                     p = subprocess.Popen([s.interp, script],
1103                                        preexec_fn = preexec,
1104                                        env = env,
1105                                        stdout = subprocess.PIPE,
1106                                        stderr = subprocess.STDOUT)
1107                     for entry in p.communicate()[0].splitlines():
1108                         msger.info(entry)
1109                 except OSError, (err, msg):
1110                     raise CreatorError("Failed to execute %%post script "
1111                                        "with '%s' : %s" % (s.interp, msg))
1112             finally:
1113                 os.unlink(path)
1114
1115     def __save_repo_keys(self, repodata):
1116         if not repodata:
1117             return None
1118
1119         gpgkeydir = "/etc/pki/rpm-gpg"
1120         fs.makedirs(self._instroot + gpgkeydir)
1121         for repo in repodata:
1122             if repo["repokey"]:
1123                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1124                 shutil.copy(repo["repokey"], self._instroot + repokey)
1125
1126     def configure(self, repodata = None):
1127         """Configure the system image according to the kickstart.
1128
1129         This method applies the (e.g. keyboard or network) configuration
1130         specified in the kickstart and executes the kickstart %post scripts.
1131
1132         If necessary, it also prepares the image to be bootable by e.g.
1133         creating an initrd and bootloader configuration.
1134
1135         """
1136         ksh = self.ks.handler
1137
1138         msger.info('Applying configurations ...')
1139         try:
1140             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1141             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1142             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1143             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1144             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1145             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1146             kickstart.UserConfig(self._instroot).apply(ksh.user)
1147             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1148             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1149             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1150             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1151             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1152             self.__save_repo_keys(repodata)
1153             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1154         except:
1155             msger.warning("Failed to apply configuration to image")
1156             raise
1157
1158         self._create_bootconfig()
1159         self.__run_post_scripts()
1160
1161     def launch_shell(self, launch):
1162         """Launch a shell in the install root.
1163
1164         This method is launches a bash shell chroot()ed in the install root;
1165         this can be useful for debugging.
1166
1167         """
1168         if launch:
1169             msger.info("Launching shell. Exit to continue.")
1170             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1171
1172     def do_genchecksum(self, image_name):
1173         if not self._genchecksum:
1174             return
1175
1176         md5sum = misc.get_md5sum(image_name)
1177         with open(image_name + ".md5sum", "w") as f:
1178             f.write("%s  %s" % (md5sum, os.path.basename(image_name)))
1179         self.outimage.append(image_name+".md5sum")
1180
1181     def package(self, destdir = "."):
1182         """Prepares the created image for final delivery.
1183
1184         In its simplest form, this method merely copies the install root to the
1185         supplied destination directory; other subclasses may choose to package
1186         the image by e.g. creating a bootable ISO containing the image and
1187         bootloader configuration.
1188
1189         destdir -- the directory into which the final image should be moved;
1190                    this defaults to the current directory.
1191
1192         """
1193         self._stage_final_image()
1194
1195         if not os.path.exists(destdir):
1196             fs.makedirs(destdir)
1197
1198         if self._recording_pkgs:
1199             self._save_recording_pkgs(destdir)
1200
1201         # For image formats with two or multiple image files, it will be
1202         # better to put them under a directory
1203         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1204             destdir = os.path.join(destdir, "%s-%s" \
1205                                             % (self.name, self.image_format))
1206             msger.debug("creating destination dir: %s" % destdir)
1207             fs.makedirs(destdir)
1208
1209         # Ensure all data is flushed to _outdir
1210         runner.quiet('sync')
1211
1212         misc.check_space_pre_cp(self._outdir, destdir)
1213         for f in os.listdir(self._outdir):
1214             shutil.move(os.path.join(self._outdir, f),
1215                         os.path.join(destdir, f))
1216             self.outimage.append(os.path.join(destdir, f))
1217             self.do_genchecksum(os.path.join(destdir, f))
1218
1219     def print_outimage_info(self):
1220         msg = "The new image can be found here:\n"
1221         self.outimage.sort()
1222         for file in self.outimage:
1223             msg += '  %s\n' % os.path.abspath(file)
1224
1225         msger.info(msg)
1226
1227     def check_depend_tools(self):
1228         for tool in self._dep_checks:
1229             fs.find_binary_path(tool)
1230
1231     def package_output(self, image_format, destdir = ".", package="none"):
1232         if not package or package == "none":
1233             return
1234
1235         destdir = os.path.abspath(os.path.expanduser(destdir))
1236         (pkg, comp) = os.path.splitext(package)
1237         if comp:
1238             comp=comp.lstrip(".")
1239
1240         if pkg == "tar":
1241             if comp:
1242                 dst = "%s/%s-%s.tar.%s" %\
1243                       (destdir, self.name, image_format, comp)
1244             else:
1245                 dst = "%s/%s-%s.tar" %\
1246                       (destdir, self.name, image_format)
1247
1248             msger.info("creating %s" % dst)
1249             tar = tarfile.open(dst, "w:" + comp)
1250
1251             for file in self.outimage:
1252                 msger.info("adding %s to %s" % (file, dst))
1253                 tar.add(file,
1254                         arcname=os.path.join("%s-%s" \
1255                                            % (self.name, image_format),
1256                                               os.path.basename(file)))
1257                 if os.path.isdir(file):
1258                     shutil.rmtree(file, ignore_errors = True)
1259                 else:
1260                     os.remove(file)
1261
1262             tar.close()
1263
1264             '''All the file in outimage has been packaged into tar.* file'''
1265             self.outimage = [dst]
1266
1267     def release_output(self, config, destdir, release):
1268         """ Create release directory and files
1269         """
1270
1271         def _rpath(fn):
1272             """ release path """
1273             return os.path.join(destdir, fn)
1274
1275         outimages = self.outimage
1276
1277         # new ks
1278         new_kspath = _rpath(self.name+'.ks')
1279         with open(config) as fr:
1280             with open(new_kspath, "w") as wf:
1281                 # When building a release we want to make sure the .ks
1282                 # file generates the same build even when --release not used.
1283                 wf.write(fr.read().replace("@BUILD_ID@", release))
1284         outimages.append(new_kspath)
1285
1286         # save log file, logfile is only available in creator attrs
1287         if hasattr(self, 'releaselog') and self.releaselog:
1288             final_logfile = _rpath(self.name+'.log')
1289             shutil.move(self.logfile, final_logfile)
1290             self.logfile = final_logfile
1291             outimages.append(self.logfile)
1292
1293         # rename iso and usbimg
1294         for f in os.listdir(destdir):
1295             if f.endswith(".iso"):
1296                 newf = f[:-4] + '.img'
1297             elif f.endswith(".usbimg"):
1298                 newf = f[:-7] + '.img'
1299             else:
1300                 continue
1301             os.rename(_rpath(f), _rpath(newf))
1302             outimages.append(_rpath(newf))
1303
1304         # generate MD5SUMS
1305         with open(_rpath("MD5SUMS"), "w") as wf:
1306             for f in os.listdir(destdir):
1307                 if f == "MD5SUMS":
1308                     continue
1309
1310                 if os.path.isdir(os.path.join(destdir, f)):
1311                     continue
1312
1313                 md5sum = misc.get_md5sum(_rpath(f))
1314                 # There needs to be two spaces between the sum and
1315                 # filepath to match the syntax with md5sum.
1316                 # This way also md5sum -c MD5SUMS can be used by users
1317                 wf.write("%s  %s\n" % (md5sum, f))
1318
1319         outimages.append("%s/MD5SUMS" % destdir)
1320
1321         # Filter out the nonexist file
1322         for fp in outimages[:]:
1323             if not os.path.exists("%s" % fp):
1324                 outimages.remove(fp)
1325
1326     def copy_kernel(self):
1327         """ Copy kernel files to the outimage directory.
1328         NOTE: This needs to be called before unmounting the instroot.
1329         """
1330
1331         if not self._need_copy_kernel:
1332             return
1333
1334         if not os.path.exists(self.destdir):
1335             os.makedirs(self.destdir)
1336
1337         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1338             kernelfilename = "%s/%s-%s" % (self.destdir,
1339                                            self.name,
1340                                            os.path.basename(kernel))
1341             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1342                                                       kernelfilename))
1343             shutil.copy(kernel, kernelfilename)
1344             self.outimage.append(kernelfilename)
1345
1346     def copy_attachment(self):
1347         """ Subclass implement it to handle attachment files
1348         NOTE: This needs to be called before unmounting the instroot.
1349         """
1350         pass
1351
1352     def get_pkg_manager(self):
1353         return self.pkgmgr(target_arch = self.target_arch,
1354                            instroot = self._instroot,
1355                            cachedir = self.cachedir)