fix md5sum format able to check by 'md5sum -c'
[platform/upstream/mic.git] / mic / imager / baseimager.py
1
2 #!/usr/bin/python -tt
3 #
4 # Copyright (c) 2007 Red Hat  Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 #
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 # for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 from __future__ import with_statement
21 import os, sys
22 import stat
23 import tempfile
24 import shutil
25 import subprocess
26 import re
27 import tarfile
28 import glob
29 import json
30 from datetime import datetime
31
32 import rpm
33
34 from mic import kickstart
35 from mic import msger, __version__ as VERSION
36 from mic.utils.errors import CreatorError, Abort
37 from mic.utils import misc, grabber, runner, fs_related as fs
38 from mic.chroot import kill_proc_inchroot
39 from mic.archive import get_archive_suffixes
40
41 class BaseImageCreator(object):
42     """Installs a system to a chroot directory.
43
44     ImageCreator is the simplest creator class available; it will install and
45     configure a system image according to the supplied kickstart file.
46
47     e.g.
48
49       import mic.imgcreate as imgcreate
50       ks = imgcreate.read_kickstart("foo.ks")
51       imgcreate.ImageCreator(ks, "foo").create()
52
53     """
54     # Output image format
55     img_format = ''
56
57     def __del__(self):
58         self.cleanup()
59
60     def __init__(self, createopts = None, pkgmgr = None):
61         """Initialize an ImageCreator instance.
62
63         ks -- a pykickstart.KickstartParser instance; this instance will be
64               used to drive the install by e.g. providing the list of packages
65               to be installed, the system configuration and %post scripts
66
67         name -- a name for the image; used for e.g. image filenames or
68                 filesystem labels
69         """
70
71         self.pkgmgr = pkgmgr
72         self.distro_name = ""
73
74         self.__builddir = None
75         self.__bindmounts = []
76
77         self.ks = None
78         self.name = "target"
79         self.tmpdir = "/var/tmp/mic"
80         self.cachedir = "/var/tmp/mic/cache"
81         self.workdir = "/var/tmp/mic/build"
82         self.destdir = "."
83         self.installerfw_prefix = "INSTALLERFW_"
84         self.target_arch = "noarch"
85         self._local_pkgs_path = None
86         self.pack_to = None
87         self.repourl = {}
88
89         # If the kernel is save to the destdir when copy_kernel cmd is called.
90         self._need_copy_kernel = False
91         # setup tmpfs tmpdir when enabletmpfs is True
92         self.enabletmpfs = False
93
94         if createopts:
95             # Mapping table for variables that have different names.
96             optmap = {"pkgmgr" : "pkgmgr_name",
97                       "arch" : "target_arch",
98                       "local_pkgs_path" : "_local_pkgs_path",
99                       "copy_kernel" : "_need_copy_kernel",
100                      }
101
102             # update setting from createopts
103             for key in createopts.keys():
104                 if key in optmap:
105                     option = optmap[key]
106                 else:
107                     option = key
108                 setattr(self, option, createopts[key])
109
110             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
111
112             if self.pack_to:
113                 if '@NAME@' in self.pack_to:
114                     self.pack_to = self.pack_to.replace('@NAME@', self.name)
115                 (tar, ext) = os.path.splitext(self.pack_to)
116                 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
117                     ext = ".tar" + ext
118                 if ext not in get_archive_suffixes():
119                     self.pack_to += ".tar"
120
121         self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
122
123         # Output image file names
124         self.outimage = []
125         # Output info related with manifest
126         self.image_files = {}
127         # A flag to generate checksum
128         self._genchecksum = False
129
130         self._alt_initrd_name = None
131
132         self._recording_pkgs = []
133
134         # available size in root fs, init to 0
135         self._root_fs_avail = 0
136
137         # Name of the disk image file that is created.
138         self._img_name = None
139
140         self.image_format = None
141
142         # Save qemu emulator file name in order to clean up it finally
143         self.qemu_emulator = None
144
145         # No ks provided when called by convertor, so skip the dependency check
146         if self.ks:
147             # If we have btrfs partition we need to check necessary tools
148             for part in self.ks.handler.partition.partitions:
149                 if part.fstype and part.fstype == "btrfs":
150                     self._dep_checks.append("mkfs.btrfs")
151                     break
152
153         if self.target_arch and self.target_arch.startswith("arm"):
154             for dep in self._dep_checks:
155                 if dep == "extlinux":
156                     self._dep_checks.remove(dep)
157
158             if not os.path.exists("/usr/bin/qemu-arm") or \
159                not misc.is_statically_linked("/usr/bin/qemu-arm"):
160                 self._dep_checks.append("qemu-arm-static")
161
162             if os.path.exists("/proc/sys/vm/vdso_enabled"):
163                 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
164                 vdso_value = vdso_fh.read().strip()
165                 vdso_fh.close()
166                 if (int)(vdso_value) == 1:
167                     msger.warning("vdso is enabled on your host, which might "
168                         "cause problems with arm emulations.\n"
169                         "\tYou can disable vdso with following command before "
170                         "starting image build:\n"
171                         "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
172
173         # make sure the specified tmpdir and cachedir exist
174         if not os.path.exists(self.tmpdir):
175             os.makedirs(self.tmpdir)
176         if not os.path.exists(self.cachedir):
177             os.makedirs(self.cachedir)
178
179
180     #
181     # Properties
182     #
183     def __get_instroot(self):
184         if self.__builddir is None:
185             raise CreatorError("_instroot is not valid before calling mount()")
186         return self.__builddir + "/install_root"
187     _instroot = property(__get_instroot)
188     """The location of the install root directory.
189
190     This is the directory into which the system is installed. Subclasses may
191     mount a filesystem image here or copy files to/from here.
192
193     Note, this directory does not exist before ImageCreator.mount() is called.
194
195     Note also, this is a read-only attribute.
196
197     """
198
199     def __get_outdir(self):
200         if self.__builddir is None:
201             raise CreatorError("_outdir is not valid before calling mount()")
202         return self.__builddir + "/out"
203     _outdir = property(__get_outdir)
204     """The staging location for the final image.
205
206     This is where subclasses should stage any files that are part of the final
207     image. ImageCreator.package() will copy any files found here into the
208     requested destination directory.
209
210     Note, this directory does not exist before ImageCreator.mount() is called.
211
212     Note also, this is a read-only attribute.
213
214     """
215
216
217     #
218     # Hooks for subclasses
219     #
220     def _mount_instroot(self, base_on = None):
221         """Mount or prepare the install root directory.
222
223         This is the hook where subclasses may prepare the install root by e.g.
224         mounting creating and loopback mounting a filesystem image to
225         _instroot.
226
227         There is no default implementation.
228
229         base_on -- this is the value passed to mount() and can be interpreted
230                    as the subclass wishes; it might e.g. be the location of
231                    a previously created ISO containing a system image.
232
233         """
234         pass
235
236     def _unmount_instroot(self):
237         """Undo anything performed in _mount_instroot().
238
239         This is the hook where subclasses must undo anything which was done
240         in _mount_instroot(). For example, if a filesystem image was mounted
241         onto _instroot, it should be unmounted here.
242
243         There is no default implementation.
244
245         """
246         pass
247
248     def _create_bootconfig(self):
249         """Configure the image so that it's bootable.
250
251         This is the hook where subclasses may prepare the image for booting by
252         e.g. creating an initramfs and bootloader configuration.
253
254         This hook is called while the install root is still mounted, after the
255         packages have been installed and the kickstart configuration has been
256         applied, but before the %post scripts have been executed.
257
258         There is no default implementation.
259
260         """
261         pass
262
263     def _stage_final_image(self):
264         """Stage the final system image in _outdir.
265
266         This is the hook where subclasses should place the image in _outdir
267         so that package() can copy it to the requested destination directory.
268
269         By default, this moves the install root into _outdir.
270
271         """
272         shutil.move(self._instroot, self._outdir + "/" + self.name)
273
274     def get_installed_packages(self):
275         return self._pkgs_content.keys()
276
277     def _save_recording_pkgs(self, destdir):
278         """Save the list or content of installed packages to file.
279         """
280         pkgs = self._pkgs_content.keys()
281         pkgs.sort() # inplace op
282
283         if not os.path.exists(destdir):
284             os.makedirs(destdir)
285
286         content = None
287         if 'vcs' in self._recording_pkgs:
288             vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
289             content = '\n'.join(sorted(vcslst))
290         elif 'name' in self._recording_pkgs:
291             content = '\n'.join(pkgs)
292         if content:
293             namefile = os.path.join(destdir, self.name + '.packages')
294             f = open(namefile, "w")
295             f.write(content)
296             f.close()
297             self.outimage.append(namefile);
298
299         # if 'content', save more details
300         if 'content' in self._recording_pkgs:
301             contfile = os.path.join(destdir, self.name + '.files')
302             f = open(contfile, "w")
303
304             for pkg in pkgs:
305                 content = pkg + '\n'
306
307                 pkgcont = self._pkgs_content[pkg]
308                 content += '    '
309                 content += '\n    '.join(pkgcont)
310                 content += '\n'
311
312                 content += '\n'
313                 f.write(content)
314             f.close()
315             self.outimage.append(contfile)
316
317         if 'license' in self._recording_pkgs:
318             licensefile = os.path.join(destdir, self.name + '.license')
319             f = open(licensefile, "w")
320
321             f.write('Summary:\n')
322             for license in reversed(sorted(self._pkgs_license, key=\
323                             lambda license: len(self._pkgs_license[license]))):
324                 f.write("    - %s: %s\n" \
325                         % (license, len(self._pkgs_license[license])))
326
327             f.write('\nDetails:\n')
328             for license in reversed(sorted(self._pkgs_license, key=\
329                             lambda license: len(self._pkgs_license[license]))):
330                 f.write("    - %s:\n" % (license))
331                 for pkg in sorted(self._pkgs_license[license]):
332                     f.write("        - %s\n" % (pkg))
333                 f.write('\n')
334
335             f.close()
336             self.outimage.append(licensefile)
337
338     def _get_required_packages(self):
339         """Return a list of required packages.
340
341         This is the hook where subclasses may specify a set of packages which
342         it requires to be installed.
343
344         This returns an empty list by default.
345
346         Note, subclasses should usually chain up to the base class
347         implementation of this hook.
348
349         """
350         return []
351
352     def _get_excluded_packages(self):
353         """Return a list of excluded packages.
354
355         This is the hook where subclasses may specify a set of packages which
356         it requires _not_ to be installed.
357
358         This returns an empty list by default.
359
360         Note, subclasses should usually chain up to the base class
361         implementation of this hook.
362
363         """
364         return []
365
366     def _get_local_packages(self):
367         """Return a list of rpm path to be local installed.
368
369         This is the hook where subclasses may specify a set of rpms which
370         it requires to be installed locally.
371
372         This returns an empty list by default.
373
374         Note, subclasses should usually chain up to the base class
375         implementation of this hook.
376
377         """
378         if self._local_pkgs_path:
379             if os.path.isdir(self._local_pkgs_path):
380                 return glob.glob(
381                         os.path.join(self._local_pkgs_path, '*.rpm'))
382             elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
383                 return [self._local_pkgs_path]
384
385         return []
386
387     def _get_fstab(self):
388         """Return the desired contents of /etc/fstab.
389
390         This is the hook where subclasses may specify the contents of
391         /etc/fstab by returning a string containing the desired contents.
392
393         A sensible default implementation is provided.
394
395         """
396         s =  "/dev/root  /         %s    %s 0 0\n" \
397              % (self._fstype,
398                 "defaults,noatime" if not self._fsopts else self._fsopts)
399         s += self._get_fstab_special()
400         return s
401
402     def _get_fstab_special(self):
403         s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
404         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
405         s += "proc       /proc     proc    defaults         0 0\n"
406         s += "sysfs      /sys      sysfs   defaults         0 0\n"
407         return s
408
409     def _set_part_env(self, pnum, prop, value):
410         """ This is a helper function which generates an environment variable
411         for a property "prop" with value "value" of a partition number "pnum".
412
413         The naming convention is:
414            * Variables start with INSTALLERFW_PART
415            * Then goes the partition number, the order is the same as
416              specified in the KS file
417            * Then goes the property name
418         """
419
420         if value == None:
421             value = ""
422         else:
423             value = str(value)
424
425         name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
426         return { name : value }
427
428     def _get_post_scripts_env(self, in_chroot):
429         """Return an environment dict for %post scripts.
430
431         This is the hook where subclasses may specify some environment
432         variables for %post scripts by return a dict containing the desired
433         environment.
434
435         in_chroot -- whether this %post script is to be executed chroot()ed
436                      into _instroot.
437         """
438
439         env = {}
440         pnum = 0
441
442         for p in kickstart.get_partitions(self.ks):
443             env.update(self._set_part_env(pnum, "SIZE", p.size))
444             env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
445             env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
446             env.update(self._set_part_env(pnum, "LABEL", p.label))
447             env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
448             env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
449             env.update(self._set_part_env(pnum, "ALIGN", p.align))
450             env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
451             env.update(self._set_part_env(pnum, "UUID", p.uuid))
452             env.update(self._set_part_env(pnum, "DEVNODE",
453                                           "/dev/%s%d" % (p.disk, pnum + 1)))
454             env.update(self._set_part_env(pnum, "DISK_DEVNODE",
455                                           "/dev/%s" % p.disk))
456             pnum += 1
457
458         # Count of paritions
459         env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
460
461         # Partition table format
462         ptable_format = self.ks.handler.bootloader.ptable
463         env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
464
465         # The kerned boot parameters
466         kernel_opts = self.ks.handler.bootloader.appendLine
467         env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
468
469         # Name of the image creation tool
470         env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
471
472         # The real current location of the mounted file-systems
473         if in_chroot:
474             mount_prefix = "/"
475         else:
476             mount_prefix = self._instroot
477         env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
478
479         # These are historical variables which lack the common name prefix
480         if not in_chroot:
481             env["INSTALL_ROOT"] = self._instroot
482             env["IMG_NAME"] = self._name
483
484         return env
485
486     def __get_imgname(self):
487         return self.name
488     _name = property(__get_imgname)
489     """The name of the image file.
490
491     """
492
493     def _get_kernel_versions(self):
494         """Return a dict detailing the available kernel types/versions.
495
496         This is the hook where subclasses may override what kernel types and
497         versions should be available for e.g. creating the booloader
498         configuration.
499
500         A dict should be returned mapping the available kernel types to a list
501         of the available versions for those kernels.
502
503         The default implementation uses rpm to iterate over everything
504         providing 'kernel', finds /boot/vmlinuz-* and returns the version
505         obtained from the vmlinuz filename. (This can differ from the kernel
506         RPM's n-v-r in the case of e.g. xen)
507
508         """
509         def get_kernel_versions(instroot):
510             ret = {}
511             versions = set()
512             files = glob.glob(instroot + "/boot/vmlinuz-*")
513             for file in files:
514                 version = os.path.basename(file)[8:]
515                 if version is None:
516                     continue
517                 versions.add(version)
518             ret["kernel"] = list(versions)
519             return ret
520
521         def get_version(header):
522             version = None
523             for f in header['filenames']:
524                 if f.startswith('/boot/vmlinuz-'):
525                     version = f[14:]
526             return version
527
528         if self.ks is None:
529             return get_kernel_versions(self._instroot)
530
531         ts = rpm.TransactionSet(self._instroot)
532
533         ret = {}
534         for header in ts.dbMatch('provides', 'kernel'):
535             version = get_version(header)
536             if version is None:
537                 continue
538
539             name = header['name']
540             if not name in ret:
541                 ret[name] = [version]
542             elif not version in ret[name]:
543                 ret[name].append(version)
544
545         return ret
546
547
548     #
549     # Helpers for subclasses
550     #
551     def _do_bindmounts(self):
552         """Mount various system directories onto _instroot.
553
554         This method is called by mount(), but may also be used by subclasses
555         in order to re-mount the bindmounts after modifying the underlying
556         filesystem.
557
558         """
559         for b in self.__bindmounts:
560             b.mount()
561
562     def _undo_bindmounts(self):
563         """Unmount the bind-mounted system directories from _instroot.
564
565         This method is usually only called by unmount(), but may also be used
566         by subclasses in order to gain access to the filesystem obscured by
567         the bindmounts - e.g. in order to create device nodes on the image
568         filesystem.
569
570         """
571         self.__bindmounts.reverse()
572         for b in self.__bindmounts:
573             b.unmount()
574
575     def _chroot(self):
576         """Chroot into the install root.
577
578         This method may be used by subclasses when executing programs inside
579         the install root e.g.
580
581           subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
582
583         """
584         os.chroot(self._instroot)
585         os.chdir("/")
586
587     def _mkdtemp(self, prefix = "tmp-"):
588         """Create a temporary directory.
589
590         This method may be used by subclasses to create a temporary directory
591         for use in building the final image - e.g. a subclass might create
592         a temporary directory in order to bundle a set of files into a package.
593
594         The subclass may delete this directory if it wishes, but it will be
595         automatically deleted by cleanup().
596
597         The absolute path to the temporary directory is returned.
598
599         Note, this method should only be called after mount() has been called.
600
601         prefix -- a prefix which should be used when creating the directory;
602                   defaults to "tmp-".
603
604         """
605         self.__ensure_builddir()
606         return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
607
608     def _mkstemp(self, prefix = "tmp-"):
609         """Create a temporary file.
610
611         This method may be used by subclasses to create a temporary file
612         for use in building the final image - e.g. a subclass might need
613         a temporary location to unpack a compressed file.
614
615         The subclass may delete this file if it wishes, but it will be
616         automatically deleted by cleanup().
617
618         A tuple containing a file descriptor (returned from os.open() and the
619         absolute path to the temporary directory is returned.
620
621         Note, this method should only be called after mount() has been called.
622
623         prefix -- a prefix which should be used when creating the file;
624                   defaults to "tmp-".
625
626         """
627         self.__ensure_builddir()
628         return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
629
630     def _mktemp(self, prefix = "tmp-"):
631         """Create a temporary file.
632
633         This method simply calls _mkstemp() and closes the returned file
634         descriptor.
635
636         The absolute path to the temporary file is returned.
637
638         Note, this method should only be called after mount() has been called.
639
640         prefix -- a prefix which should be used when creating the file;
641                   defaults to "tmp-".
642
643         """
644
645         (f, path) = self._mkstemp(prefix)
646         os.close(f)
647         return path
648
649
650     #
651     # Actual implementation
652     #
653     def __ensure_builddir(self):
654         if not self.__builddir is None:
655             return
656
657         try:
658             self.workdir = os.path.join(self.tmpdir, "build")
659             if not os.path.exists(self.workdir):
660                 os.makedirs(self.workdir)
661             self.__builddir = tempfile.mkdtemp(dir = self.workdir,
662                                                prefix = "imgcreate-")
663         except OSError, (err, msg):
664             raise CreatorError("Failed create build directory in %s: %s" %
665                                (self.tmpdir, msg))
666
667     def get_cachedir(self, cachedir = None):
668         if self.cachedir:
669             return self.cachedir
670
671         self.__ensure_builddir()
672         if cachedir:
673             self.cachedir = cachedir
674         else:
675             self.cachedir = self.__builddir + "/mic-cache"
676         fs.makedirs(self.cachedir)
677         return self.cachedir
678
679     def __sanity_check(self):
680         """Ensure that the config we've been given is sane."""
681         if not (kickstart.get_packages(self.ks) or
682                 kickstart.get_groups(self.ks)):
683             raise CreatorError("No packages or groups specified")
684
685         kickstart.convert_method_to_repo(self.ks)
686
687         if not kickstart.get_repos(self.ks):
688             raise CreatorError("No repositories specified")
689
690     def __write_fstab(self):
691         if kickstart.use_installerfw(self.ks, "fstab"):
692             # The fstab file will be generated by installer framework scripts
693             # instead.
694             return None
695         fstab_contents = self._get_fstab()
696         if fstab_contents:
697             fstab = open(self._instroot + "/etc/fstab", "w")
698             fstab.write(fstab_contents)
699             fstab.close()
700
701     def __create_minimal_dev(self):
702         """Create a minimal /dev so that we don't corrupt the host /dev"""
703         origumask = os.umask(0000)
704         devices = (('null',   1, 3, 0666),
705                    ('urandom',1, 9, 0666),
706                    ('random', 1, 8, 0666),
707                    ('full',   1, 7, 0666),
708                    ('ptmx',   5, 2, 0666),
709                    ('tty',    5, 0, 0666),
710                    ('zero',   1, 5, 0666))
711
712         links = (("/proc/self/fd", "/dev/fd"),
713                  ("/proc/self/fd/0", "/dev/stdin"),
714                  ("/proc/self/fd/1", "/dev/stdout"),
715                  ("/proc/self/fd/2", "/dev/stderr"))
716
717         for (node, major, minor, perm) in devices:
718             if not os.path.exists(self._instroot + "/dev/" + node):
719                 os.mknod(self._instroot + "/dev/" + node,
720                          perm | stat.S_IFCHR,
721                          os.makedev(major,minor))
722
723         for (src, dest) in links:
724             if not os.path.exists(self._instroot + dest):
725                 os.symlink(src, self._instroot + dest)
726
727         os.umask(origumask)
728
729     def __setup_tmpdir(self):
730         if not self.enabletmpfs:
731             return
732
733         runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
734
735     def __clean_tmpdir(self):
736         if not self.enabletmpfs:
737             return
738
739         runner.show('umount -l %s' % self.workdir)
740
741     def mount(self, base_on = None, cachedir = None):
742         """Setup the target filesystem in preparation for an install.
743
744         This function sets up the filesystem which the ImageCreator will
745         install into and configure. The ImageCreator class merely creates an
746         install root directory, bind mounts some system directories (e.g. /dev)
747         and writes out /etc/fstab. Other subclasses may also e.g. create a
748         sparse file, format it and loopback mount it to the install root.
749
750         base_on -- a previous install on which to base this install; defaults
751                    to None, causing a new image to be created
752
753         cachedir -- a directory in which to store the Yum cache; defaults to
754                     None, causing a new cache to be created; by setting this
755                     to another directory, the same cache can be reused across
756                     multiple installs.
757
758         """
759         self.__setup_tmpdir()
760         self.__ensure_builddir()
761
762         # prevent popup dialog in Ubuntu(s)
763         misc.hide_loopdev_presentation()
764
765         fs.makedirs(self._instroot)
766         fs.makedirs(self._outdir)
767
768         self._mount_instroot(base_on)
769
770         for d in ("/dev/pts",
771                   "/etc",
772                   "/boot",
773                   "/var/log",
774                   "/sys",
775                   "/proc",
776                   "/usr/bin"):
777             fs.makedirs(self._instroot + d)
778
779         if self.target_arch and self.target_arch.startswith("arm") or \
780             self.target_arch == "aarch64":
781             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
782                                                           self.target_arch)
783
784         self.get_cachedir(cachedir)
785
786         # bind mount system directories into _instroot
787         for (f, dest) in [("/sys", None),
788                           ("/proc", None),
789                           ("/proc/sys/fs/binfmt_misc", None),
790                           ("/dev/pts", None)]:
791             self.__bindmounts.append(
792                     fs.BindChrootMount(
793                         f, self._instroot, dest))
794
795         self._do_bindmounts()
796
797         self.__create_minimal_dev()
798
799         if os.path.exists(self._instroot + "/etc/mtab"):
800             os.unlink(self._instroot + "/etc/mtab")
801         os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
802
803         self.__write_fstab()
804
805         # get size of available space in 'instroot' fs
806         self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
807
808     def unmount(self):
809         """Unmounts the target filesystem.
810
811         The ImageCreator class detaches the system from the install root, but
812         other subclasses may also detach the loopback mounted filesystem image
813         from the install root.
814
815         """
816         try:
817             mtab = self._instroot + "/etc/mtab"
818             if not os.path.islink(mtab):
819                 os.unlink(self._instroot + "/etc/mtab")
820
821             if self.qemu_emulator:
822                 os.unlink(self._instroot + self.qemu_emulator)
823         except OSError:
824             pass
825
826         self._undo_bindmounts()
827
828         """ Clean up yum garbage """
829         try:
830             instroot_pdir = os.path.dirname(self._instroot + self._instroot)
831             if os.path.exists(instroot_pdir):
832                 shutil.rmtree(instroot_pdir, ignore_errors = True)
833             yumlibdir = self._instroot + "/var/lib/yum"
834             if os.path.exists(yumlibdir):
835                 shutil.rmtree(yumlibdir, ignore_errors = True)
836         except OSError:
837             pass
838
839         self._unmount_instroot()
840
841         # reset settings of popup dialog in Ubuntu(s)
842         misc.unhide_loopdev_presentation()
843
844
845     def cleanup(self):
846         """Unmounts the target filesystem and deletes temporary files.
847
848         This method calls unmount() and then deletes any temporary files and
849         directories that were created on the host system while building the
850         image.
851
852         Note, make sure to call this method once finished with the creator
853         instance in order to ensure no stale files are left on the host e.g.:
854
855           creator = ImageCreator(ks, name)
856           try:
857               creator.create()
858           finally:
859               creator.cleanup()
860
861         """
862         if not self.__builddir:
863             return
864
865         kill_proc_inchroot(self._instroot)
866
867         self.unmount()
868
869         shutil.rmtree(self.__builddir, ignore_errors = True)
870         self.__builddir = None
871
872         self.__clean_tmpdir()
873
874     def __is_excluded_pkg(self, pkg):
875         if pkg in self._excluded_pkgs:
876             self._excluded_pkgs.remove(pkg)
877             return True
878
879         for xpkg in self._excluded_pkgs:
880             if xpkg.endswith('*'):
881                 if pkg.startswith(xpkg[:-1]):
882                     return True
883             elif xpkg.startswith('*'):
884                 if pkg.endswith(xpkg[1:]):
885                     return True
886
887         return None
888
889     def __select_packages(self, pkg_manager):
890         skipped_pkgs = []
891         for pkg in self._required_pkgs:
892             e = pkg_manager.selectPackage(pkg)
893             if e:
894                 if kickstart.ignore_missing(self.ks):
895                     skipped_pkgs.append(pkg)
896                 elif self.__is_excluded_pkg(pkg):
897                     skipped_pkgs.append(pkg)
898                 else:
899                     raise CreatorError("Failed to find package '%s' : %s" %
900                                        (pkg, e))
901
902         for pkg in skipped_pkgs:
903             msger.warning("Skipping missing package '%s'" % (pkg,))
904
905     def __select_groups(self, pkg_manager):
906         skipped_groups = []
907         for group in self._required_groups:
908             e = pkg_manager.selectGroup(group.name, group.include)
909             if e:
910                 if kickstart.ignore_missing(self.ks):
911                     skipped_groups.append(group)
912                 else:
913                     raise CreatorError("Failed to find group '%s' : %s" %
914                                        (group.name, e))
915
916         for group in skipped_groups:
917             msger.warning("Skipping missing group '%s'" % (group.name,))
918
919     def __deselect_packages(self, pkg_manager):
920         for pkg in self._excluded_pkgs:
921             pkg_manager.deselectPackage(pkg)
922
923     def __localinst_packages(self, pkg_manager):
924         for rpm_path in self._get_local_packages():
925             pkg_manager.installLocal(rpm_path)
926
927     def __preinstall_packages(self, pkg_manager):
928         if not self.ks:
929             return
930
931         self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
932         for pkg in self._preinstall_pkgs:
933             pkg_manager.preInstall(pkg)
934
935     def __check_packages(self, pkg_manager):
936         for pkg in self.check_pkgs:
937             pkg_manager.checkPackage(pkg)
938
939     def __attachment_packages(self, pkg_manager):
940         if not self.ks:
941             return
942
943         self._attachment = []
944         for item in kickstart.get_attachment(self.ks):
945             if item.startswith('/'):
946                 fpaths = os.path.join(self._instroot, item.lstrip('/'))
947                 for fpath in glob.glob(fpaths):
948                     self._attachment.append(fpath)
949                 continue
950
951             filelist = pkg_manager.getFilelist(item)
952             if filelist:
953                 # found rpm in rootfs
954                 for pfile in pkg_manager.getFilelist(item):
955                     fpath = os.path.join(self._instroot, pfile.lstrip('/'))
956                     self._attachment.append(fpath)
957                 continue
958
959             # try to retrieve rpm file
960             (url, proxies) = pkg_manager.package_url(item)
961             if not url:
962                 msger.warning("Can't get url from repo for %s" % item)
963                 continue
964             fpath = os.path.join(self.cachedir, os.path.basename(url))
965             if not os.path.exists(fpath):
966                 # download pkgs
967                 try:
968                     fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
969                 except CreatorError:
970                     raise
971
972             tmpdir = self._mkdtemp()
973             misc.extract_rpm(fpath, tmpdir)
974             for (root, dirs, files) in os.walk(tmpdir):
975                 for fname in files:
976                     fpath = os.path.join(root, fname)
977                     self._attachment.append(fpath)
978
979     def install(self, repo_urls=None):
980         """Install packages into the install root.
981
982         This function installs the packages listed in the supplied kickstart
983         into the install root. By default, the packages are installed from the
984         repository URLs specified in the kickstart.
985
986         repo_urls -- a dict which maps a repository name to a repository;
987                      if supplied, this causes any repository URLs specified in
988                      the kickstart to be overridden.
989
990         """
991
992         # initialize pkg list to install
993         if self.ks:
994             self.__sanity_check()
995
996             self._required_pkgs = \
997                 kickstart.get_packages(self.ks, self._get_required_packages())
998             self._excluded_pkgs = \
999                 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1000             self._required_groups = kickstart.get_groups(self.ks)
1001         else:
1002             self._required_pkgs = None
1003             self._excluded_pkgs = None
1004             self._required_groups = None
1005
1006         if not repo_urls:
1007             repo_urls = self.extrarepos
1008
1009         pkg_manager = self.get_pkg_manager()
1010         pkg_manager.setup()
1011
1012         if hasattr(self, 'install_pkgs') and self.install_pkgs:
1013             if 'debuginfo' in self.install_pkgs:
1014                 pkg_manager.install_debuginfo = True
1015
1016         for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1017             (name, baseurl, mirrorlist, inc, exc,
1018              proxy, proxy_username, proxy_password, debuginfo,
1019              source, gpgkey, disable, ssl_verify, nocache,
1020              cost, priority) = repo
1021
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)
1356
1357     def create_manifest(self):
1358         def get_pack_suffix():
1359             return '.' + self.pack_to.split('.', 1)[1]
1360
1361         if not os.path.exists(self.destdir):
1362             os.makedirs(self.destdir)
1363
1364         now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1365         manifest_dict = {'version': VERSION,
1366                          'created': now}
1367         if self.img_format:
1368             manifest_dict.update({'format': self.img_format})
1369
1370         if hasattr(self, 'logfile') and self.logfile:
1371             manifest_dict.update({'log_file': self.logfile})
1372
1373         if self.image_files:
1374             if self.pack_to:
1375                 self.image_files.update({'pack': get_pack_suffix()})
1376             manifest_dict.update({self.img_format: self.image_files})
1377
1378         msger.info('Creating manifest file...')
1379         manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1380         with open(manifest_file_path, 'w') as fest_file:
1381             json.dump(manifest_dict, fest_file, indent=4)
1382         self.outimage.append(manifest_file_path)