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