relocate create.logfile to conf module
[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
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         if not repo_urls:
1001             repo_urls = self.extrarepos
1002
1003         pkg_manager = self.get_pkg_manager()
1004         pkg_manager.setup()
1005
1006         if hasattr(self, 'install_pkgs') and self.install_pkgs:
1007             if 'debuginfo' in self.install_pkgs:
1008                 pkg_manager.install_debuginfo = True
1009
1010         for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1011             (name, baseurl, mirrorlist, inc, exc,
1012              proxy, proxy_username, proxy_password, debuginfo,
1013              source, gpgkey, disable, ssl_verify, nocache,
1014              cost, priority) = repo
1015
1016             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1017                         proxy_username, proxy_password, inc, exc, ssl_verify,
1018                         nocache, cost, priority)
1019
1020         if kickstart.exclude_docs(self.ks):
1021             rpm.addMacro("_excludedocs", "1")
1022         rpm.addMacro("_dbpath", "/var/lib/rpm")
1023         rpm.addMacro("__file_context_path", "%{nil}")
1024         if kickstart.inst_langs(self.ks) != None:
1025             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1026
1027         try:
1028             self.__preinstall_packages(pkg_manager)
1029             self.__select_packages(pkg_manager)
1030             self.__select_groups(pkg_manager)
1031             self.__deselect_packages(pkg_manager)
1032             self.__localinst_packages(pkg_manager)
1033             self.__check_packages(pkg_manager)
1034
1035             BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1036             checksize = self._root_fs_avail
1037             if checksize:
1038                 checksize -= BOOT_SAFEGUARD
1039             if self.target_arch:
1040                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1041             pkg_manager.runInstall(checksize)
1042         except CreatorError, e:
1043             raise
1044         except  KeyboardInterrupt:
1045             raise
1046         else:
1047             self._pkgs_content = pkg_manager.getAllContent()
1048             self._pkgs_license = pkg_manager.getPkgsLicense()
1049             self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1050             self.__attachment_packages(pkg_manager)
1051         finally:
1052             pkg_manager.close()
1053
1054         # hook post install
1055         self.postinstall()
1056
1057         # do some clean up to avoid lvm info leakage.  this sucks.
1058         for subdir in ("cache", "backup", "archive"):
1059             lvmdir = self._instroot + "/etc/lvm/" + subdir
1060             try:
1061                 for f in os.listdir(lvmdir):
1062                     os.unlink(lvmdir + "/" + f)
1063             except:
1064                 pass
1065
1066     def postinstall(self):
1067         self.copy_attachment()
1068
1069     def __run_post_scripts(self):
1070         msger.info("Running scripts ...")
1071         if os.path.exists(self._instroot + "/tmp"):
1072             shutil.rmtree(self._instroot + "/tmp")
1073         os.mkdir (self._instroot + "/tmp", 0755)
1074         for s in kickstart.get_post_scripts(self.ks):
1075             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1076                                           dir = self._instroot + "/tmp")
1077
1078             s.script = s.script.replace("\r", "")
1079             os.write(fd, s.script)
1080             os.close(fd)
1081             os.chmod(path, 0700)
1082
1083             env = self._get_post_scripts_env(s.inChroot)
1084             if 'PATH' not in env:
1085                 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1086
1087             if not s.inChroot:
1088                 preexec = None
1089                 script = path
1090             else:
1091                 preexec = self._chroot
1092                 script = "/tmp/" + os.path.basename(path)
1093
1094             try:
1095                 try:
1096                     p = subprocess.Popen([s.interp, script],
1097                                        preexec_fn = preexec,
1098                                        env = env,
1099                                        stdout = subprocess.PIPE,
1100                                        stderr = subprocess.STDOUT)
1101                     for entry in p.communicate()[0].splitlines():
1102                         msger.info(entry)
1103                 except OSError, (err, msg):
1104                     raise CreatorError("Failed to execute %%post script "
1105                                        "with '%s' : %s" % (s.interp, msg))
1106             finally:
1107                 os.unlink(path)
1108
1109     def __save_repo_keys(self, repodata):
1110         if not repodata:
1111             return None
1112
1113         gpgkeydir = "/etc/pki/rpm-gpg"
1114         fs.makedirs(self._instroot + gpgkeydir)
1115         for repo in repodata:
1116             if repo["repokey"]:
1117                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1118                 shutil.copy(repo["repokey"], self._instroot + repokey)
1119
1120     def configure(self, repodata = None):
1121         """Configure the system image according to the kickstart.
1122
1123         This method applies the (e.g. keyboard or network) configuration
1124         specified in the kickstart and executes the kickstart %post scripts.
1125
1126         If necessary, it also prepares the image to be bootable by e.g.
1127         creating an initrd and bootloader configuration.
1128
1129         """
1130         ksh = self.ks.handler
1131
1132         msger.info('Applying configurations ...')
1133         try:
1134             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1135             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1136             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1137             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1138             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1139             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1140             kickstart.UserConfig(self._instroot).apply(ksh.user)
1141             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1142             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1143             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1144             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1145             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1146             self.__save_repo_keys(repodata)
1147             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1148         except:
1149             msger.warning("Failed to apply configuration to image")
1150             raise
1151
1152         self._create_bootconfig()
1153         self.__run_post_scripts()
1154
1155     def launch_shell(self, launch):
1156         """Launch a shell in the install root.
1157
1158         This method is launches a bash shell chroot()ed in the install root;
1159         this can be useful for debugging.
1160
1161         """
1162         if launch:
1163             msger.info("Launching shell. Exit to continue.")
1164             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1165
1166     def do_genchecksum(self, image_name):
1167         if not self._genchecksum:
1168             return
1169
1170         md5sum = misc.get_md5sum(image_name)
1171         with open(image_name + ".md5sum", "w") as f:
1172             f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1173         self.outimage.append(image_name+".md5sum")
1174
1175     def package(self, destdir = "."):
1176         """Prepares the created image for final delivery.
1177
1178         In its simplest form, this method merely copies the install root to the
1179         supplied destination directory; other subclasses may choose to package
1180         the image by e.g. creating a bootable ISO containing the image and
1181         bootloader configuration.
1182
1183         destdir -- the directory into which the final image should be moved;
1184                    this defaults to the current directory.
1185
1186         """
1187         self._stage_final_image()
1188
1189         if not os.path.exists(destdir):
1190             fs.makedirs(destdir)
1191
1192         if self._recording_pkgs:
1193             self._save_recording_pkgs(destdir)
1194
1195         # For image formats with two or multiple image files, it will be
1196         # better to put them under a directory
1197         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1198             destdir = os.path.join(destdir, "%s-%s" \
1199                                             % (self.name, self.image_format))
1200             msger.debug("creating destination dir: %s" % destdir)
1201             fs.makedirs(destdir)
1202
1203         # Ensure all data is flushed to _outdir
1204         runner.quiet('sync')
1205
1206         misc.check_space_pre_cp(self._outdir, destdir)
1207         for f in os.listdir(self._outdir):
1208             shutil.move(os.path.join(self._outdir, f),
1209                         os.path.join(destdir, f))
1210             self.outimage.append(os.path.join(destdir, f))
1211             self.do_genchecksum(os.path.join(destdir, f))
1212
1213     def print_outimage_info(self):
1214         msg = "The new image can be found here:\n"
1215         self.outimage.sort()
1216         for file in self.outimage:
1217             msg += '  %s\n' % os.path.abspath(file)
1218
1219         msger.info(msg)
1220
1221     def check_depend_tools(self):
1222         for tool in self._dep_checks:
1223             fs.find_binary_path(tool)
1224
1225     def package_output(self, image_format, destdir = ".", package="none"):
1226         if not package or package == "none":
1227             return
1228
1229         destdir = os.path.abspath(os.path.expanduser(destdir))
1230         (pkg, comp) = os.path.splitext(package)
1231         if comp:
1232             comp=comp.lstrip(".")
1233
1234         if pkg == "tar":
1235             if comp:
1236                 dst = "%s/%s-%s.tar.%s" %\
1237                       (destdir, self.name, image_format, comp)
1238             else:
1239                 dst = "%s/%s-%s.tar" %\
1240                       (destdir, self.name, image_format)
1241
1242             msger.info("creating %s" % dst)
1243             tar = tarfile.open(dst, "w:" + comp)
1244
1245             for file in self.outimage:
1246                 msger.info("adding %s to %s" % (file, dst))
1247                 tar.add(file,
1248                         arcname=os.path.join("%s-%s" \
1249                                            % (self.name, image_format),
1250                                               os.path.basename(file)))
1251                 if os.path.isdir(file):
1252                     shutil.rmtree(file, ignore_errors = True)
1253                 else:
1254                     os.remove(file)
1255
1256             tar.close()
1257
1258             '''All the file in outimage has been packaged into tar.* file'''
1259             self.outimage = [dst]
1260
1261     def release_output(self, config, destdir, release):
1262         """ Create release directory and files
1263         """
1264
1265         def _rpath(fn):
1266             """ release path """
1267             return os.path.join(destdir, fn)
1268
1269         outimages = self.outimage
1270
1271         # new ks
1272         new_kspath = _rpath(self.name+'.ks')
1273         with open(config) as fr:
1274             with open(new_kspath, "w") as wf:
1275                 # When building a release we want to make sure the .ks
1276                 # file generates the same build even when --release not used.
1277                 wf.write(fr.read().replace("@BUILD_ID@", release))
1278         outimages.append(new_kspath)
1279
1280         # save log file, logfile is only available in creator attrs
1281         if hasattr(self, 'releaselog') and self.releaselog:
1282             final_logfile = _rpath(self.name+'.log')
1283             shutil.move(self.logfile, final_logfile)
1284             self.logfile = final_logfile
1285             outimages.append(self.logfile)
1286
1287         # rename iso and usbimg
1288         for f in os.listdir(destdir):
1289             if f.endswith(".iso"):
1290                 newf = f[:-4] + '.img'
1291             elif f.endswith(".usbimg"):
1292                 newf = f[:-7] + '.img'
1293             else:
1294                 continue
1295             os.rename(_rpath(f), _rpath(newf))
1296             outimages.append(_rpath(newf))
1297
1298         # generate MD5SUMS
1299         with open(_rpath("MD5SUMS"), "w") as wf:
1300             for f in os.listdir(destdir):
1301                 if f == "MD5SUMS":
1302                     continue
1303
1304                 if os.path.isdir(os.path.join(destdir, f)):
1305                     continue
1306
1307                 md5sum = misc.get_md5sum(_rpath(f))
1308                 # There needs to be two spaces between the sum and
1309                 # filepath to match the syntax with md5sum.
1310                 # This way also md5sum -c MD5SUMS can be used by users
1311                 wf.write("%s *%s\n" % (md5sum, f))
1312
1313         outimages.append("%s/MD5SUMS" % destdir)
1314
1315         # Filter out the nonexist file
1316         for fp in outimages[:]:
1317             if not os.path.exists("%s" % fp):
1318                 outimages.remove(fp)
1319
1320     def copy_kernel(self):
1321         """ Copy kernel files to the outimage directory.
1322         NOTE: This needs to be called before unmounting the instroot.
1323         """
1324
1325         if not self._need_copy_kernel:
1326             return
1327
1328         if not os.path.exists(self.destdir):
1329             os.makedirs(self.destdir)
1330
1331         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1332             kernelfilename = "%s/%s-%s" % (self.destdir,
1333                                            self.name,
1334                                            os.path.basename(kernel))
1335             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1336                                                       kernelfilename))
1337             shutil.copy(kernel, kernelfilename)
1338             self.outimage.append(kernelfilename)
1339
1340     def copy_attachment(self):
1341         """ Subclass implement it to handle attachment files
1342         NOTE: This needs to be called before unmounting the instroot.
1343         """
1344         pass
1345
1346     def get_pkg_manager(self):
1347         return self.pkgmgr(target_arch = self.target_arch,
1348                            instroot = self._instroot,
1349                            cachedir = self.cachedir)