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