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