Merge "Another method of install tpk." into devel
[tools/mic.git] / mic / imager / baseimager.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2007 Red Hat  Inc.
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 # for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 from __future__ import with_statement
20 import os, sys
21 import stat
22 import tempfile
23 import shutil
24 import subprocess
25 import re
26 import tarfile
27 import glob
28 import json
29 from datetime import datetime
30 import rpm
31 import time
32 from mic import kickstart
33 from mic import msger, __version__ as VERSION
34 from mic.utils.errors import CreatorError, KsError, Abort
35 from mic.utils import misc, grabber, runner, fs_related as fs
36 from mic.chroot import kill_proc_inchroot
37 from mic.archive import get_archive_suffixes
38 from mic.conf import configmgr
39 from mic.utils.grabber import myurlgrab
40 #post script max run time
41 MAX_RUN_TIME = 120
42
43 class BaseImageCreator(object):
44     """Installs a system to a chroot directory.
45
46     ImageCreator is the simplest creator class available; it will install and
47     configure a system image according to the supplied kickstart file.
48
49     e.g.
50
51       import mic.imgcreate as imgcreate
52       ks = imgcreate.read_kickstart("foo.ks")
53       imgcreate.ImageCreator(ks, "foo").create()
54
55     """
56     # Output image format
57     img_format = ''
58
59     def __del__(self):
60         self.cleanup()
61
62     def __init__(self, createopts = None, pkgmgr = None):
63         """Initialize an ImageCreator instance.
64
65         ks -- a pykickstart.KickstartParser instance; this instance will be
66               used to drive the install by e.g. providing the list of packages
67               to be installed, the system configuration and %post scripts
68
69         name -- a name for the image; used for e.g. image filenames or
70                 filesystem labels
71         """
72
73         self.pkgmgr = pkgmgr
74         self.distro_name = ""
75
76         self.__builddir = None
77         self.__bindmounts = []
78
79         self.ks = None
80         self.name = "target"
81         self.tmpdir = "/var/tmp/mic"
82         self.cachedir = "/var/tmp/mic/cache"
83         self.workdir = "/var/tmp/mic/build"
84         self.destdir = "."
85         self.installerfw_prefix = "INSTALLERFW_"
86         self.target_arch = "noarch"
87         self.strict_mode = False
88         self._local_pkgs_path = None
89         self.pack_to = None
90         self.repourl = {}
91         self.multiple_partitions = False
92         self._imgdir = None
93
94         # If the kernel is save to the destdir when copy_kernel cmd is called.
95         self._need_copy_kernel = False
96         # setup tmpfs tmpdir when enabletmpfs is True
97         self.enabletmpfs = False
98
99         if createopts:
100             # Mapping table for variables that have different names.
101             optmap = {"pkgmgr" : "pkgmgr_name",
102                       "arch" : "target_arch",
103                       "local_pkgs_path" : "_local_pkgs_path",
104                       "copy_kernel" : "_need_copy_kernel",
105                       "strict_mode" : "strict_mode",
106                      }
107
108             # update setting from createopts
109             for key in createopts.keys():
110                 if key in optmap:
111                     option = optmap[key]
112                 else:
113                     option = key
114                 setattr(self, option, createopts[key])
115
116             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
117
118             if self.pack_to:
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 names in order to clean up it finally
147         self.qemu_emulators = []
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 part.cpioopts:
157                     if part.fstype == "cpio": 
158                         part.fstype = "ext4"
159                     else:
160                         raise KsError("The '--fstype' in ks file need to set 'cpio' when you want to generate image by cpio.")
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
1090         for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1091             (name, baseurl, mirrorlist, inc, exc,
1092              proxy, proxy_username, proxy_password, debuginfo,
1093              source, gpgkey, disable, ssl_verify, nocache,
1094              cost, priority) = repo
1095
1096             ssl_verify = get_ssl_verify(ssl_verify)
1097             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1098                         proxy_username, proxy_password, inc, exc, ssl_verify,
1099                         nocache, cost, priority)
1100
1101         if kickstart.exclude_docs(self.ks):
1102             rpm.addMacro("_excludedocs", "1")
1103         rpm.addMacro("_dbpath", "/var/lib/rpm")
1104         rpm.addMacro("__file_context_path", "%{nil}")
1105         if kickstart.inst_langs(self.ks) != None:
1106             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1107
1108         try:
1109             self.__preinstall_packages(pkg_manager)
1110             self.__select_packages(pkg_manager)
1111             self.__select_groups(pkg_manager)
1112             self.__deselect_packages(pkg_manager)
1113             #self.__localinst_packages(pkg_manager)
1114             self.__check_packages(pkg_manager)
1115
1116             BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1117             checksize = self._root_fs_avail
1118             if checksize:
1119                 checksize -= BOOT_SAFEGUARD
1120             if self.target_arch:
1121                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1122
1123             # If we have multiple partitions, don't check diskspace when rpm run transaction
1124             # because rpm check '/' partition only.
1125             if self.multiple_partitions:
1126                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1127             pkg_manager.runInstall(checksize)
1128         except CreatorError, e:
1129             raise
1130         except  KeyboardInterrupt:
1131             raise
1132         else:
1133             self._pkgs_content = pkg_manager.getAllContent()
1134             self._pkgs_license = pkg_manager.getPkgsLicense()
1135             self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1136             self.__attachment_packages(pkg_manager)
1137         finally:
1138             pkg_manager.close()
1139
1140         if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1141             showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1142             raise CreatorError('scriptlet errors occurred')
1143             
1144         # hook post install
1145         self.postinstall()
1146
1147         # do some clean up to avoid lvm info leakage.  this sucks.
1148         for subdir in ("cache", "backup", "archive"):
1149             lvmdir = self._instroot + "/etc/lvm/" + subdir
1150             try:
1151                 for f in os.listdir(lvmdir):
1152                     os.unlink(lvmdir + "/" + f)
1153             except:
1154                 pass
1155
1156     def tpkinstall(self):
1157         if self.ks:
1158             tpk_pkgs = kickstart.get_tpkpackages(self.ks)
1159             tpk_repoList = kickstart.get_tpkrepos(self.ks)
1160             if tpk_repoList and tpk_pkgs:
1161                 tpk_dir = "/usr/apps/.preload-tpk"
1162                 fs.makedirs(self._instroot + "/usr/apps")
1163                 fs.makedirs(self._instroot + tpk_dir)
1164             for pkg in tpk_pkgs:
1165                 flag = 0
1166                 for tpk_repo in tpk_repoList:
1167                     if hasattr(tpk_repo,'baseurl') and tpk_repo.baseurl.startswith("file:"):
1168                         tpk_repourl = tpk_repo.baseurl.replace('file:','')
1169                         tpk_repourl = "/%s" % tpk_repourl.lstrip('/')
1170                         tpk_pkgpath = tpk_repourl + "/"+ pkg
1171                         if os.path.isfile(tpk_pkgpath):
1172                             shutil.copy(tpk_pkgpath,self._instroot + tpk_dir)
1173                             flag = 1
1174                             break
1175                     elif hasattr(tpk_repo,'baseurl'):
1176                         url = tpk_repo.baseurl.join(pkg)
1177                         filename = self._instroot+tpk_dir+"/"+pkg
1178                         if tpk_repo.baseurl.startswith("http:"):
1179                             import urllib
1180                             status = urllib.urlopen(url).code
1181                             if status == 200:
1182                                 filename = myurlgrab(url.full, filename, None)
1183                                 flag = 1
1184                                 break
1185                             elif status == 404 or status == None:
1186                                 continue
1187                         #url is ok, then download, url wrong, check other url.
1188                         elif tpk_repo.baseurl.startswith("https:") :
1189                             try:
1190                                 flag = 1
1191                                 filename = myurlgrab(url.full, filename, None)
1192                             except CreatorError:
1193                                 continue
1194                 if flag == 0:
1195                     raise CreatorError("Tpk package missing.")
1196
1197     def postinstall(self):
1198         pass
1199
1200     def _get_sign_scripts_env(self):
1201         """Return an environment dict for %post-umount scripts.
1202
1203         This is the hook where subclasses may specify some environment
1204         variables for %post-umount scripts by return a dict containing the
1205         desired environment.
1206         """
1207
1208         env = {}
1209
1210         # Directory path of images
1211         if self._imgdir:
1212             env['IMG_DIR_PATH'] = str(self._imgdir)
1213
1214         imgfiles = []
1215         imgpaths = []
1216         for item in self._instloops:
1217             imgfiles.append(item['name'])
1218             if self._imgdir:
1219                 imgpaths.append(os.path.join(self._imgdir, item['name']))
1220
1221         # Images file name
1222         env['IMG_FILES'] = ' '.join(imgfiles)
1223
1224         # Absolute path of images
1225         env['IMG_PATHS'] = ' '.join(imgpaths)
1226
1227         return env
1228
1229     def run_sign_scripts(self):
1230         if kickstart.get_sign_scripts(self.ks)==[]:
1231             return
1232         msger.info("Running sign scripts ...")
1233         if os.path.exists(self._instroot + "/tmp"):
1234             shutil.rmtree(self._instroot + "/tmp")
1235         os.mkdir (self._instroot + "/tmp", 0755)
1236         for s in kickstart.get_sign_scripts(self.ks):
1237             (fd, path) = tempfile.mkstemp(prefix = "ks-runscript-",
1238                                           dir = self._instroot + "/tmp")
1239             s.script = s.script.replace("\r", "")
1240             os.write(fd, s.script)
1241             if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1242                 os.write(fd, '\n')
1243                 os.write(fd, 'exit 0\n')
1244             os.close(fd)
1245             os.chmod(path, 0700)
1246
1247             for item in os.listdir(self._imgdir):
1248                 sub = os.path.splitext(item)[1]
1249                 if sub == ".img":
1250                     shutil.move(os.path.join(self._imgdir, item),
1251                                 os.path.join(self._instroot + "/tmp", item))
1252             oldoutdir = os.getcwd()
1253             os.chdir(self._instroot + "/tmp")
1254
1255             env = self._get_sign_scripts_env()
1256             #*.img files are moved to self._instroot + "/tmp" directory in running runscripts
1257             env['IMG_PATHS'] = env['IMG_PATHS'].replace(self._imgdir,self._instroot + "/tmp")
1258             try:
1259                 try:
1260                     p = subprocess.Popen([s.interp, path],
1261                                        env = env,
1262                                        stdout = subprocess.PIPE,
1263                                        stderr = subprocess.STDOUT)
1264                     while p.poll() == None:
1265                         msger.info(p.stdout.readline().strip())
1266                     if p.returncode != 0:
1267                         raise CreatorError("Failed to execute %%sign script "
1268                                            "with '%s'" % (s.interp))
1269                 except OSError, (err, msg):
1270                     raise CreatorError("Failed to execute %%sign script "
1271                                        "with '%s' : %s" % (s.interp, msg))
1272             finally:
1273                 os.chdir(oldoutdir)
1274                 os.unlink(path)
1275                 for item in os.listdir(self._instroot + "/tmp"):
1276                     shutil.move(os.path.join(self._instroot + "/tmp", item),
1277                                 os.path.join(self._imgdir, item))
1278
1279     def __run_post_scripts(self):
1280         msger.info("Running post scripts ...")
1281         if os.path.exists(self._instroot + "/tmp"):
1282             shutil.rmtree(self._instroot + "/tmp")
1283         os.mkdir (self._instroot + "/tmp", 0755)
1284         for s in kickstart.get_post_scripts(self.ks):
1285             (fd, path) = tempfile.mkstemp(prefix = "ks-postscript-",
1286                                           dir = self._instroot + "/tmp")
1287
1288             s.script = s.script.replace("\r", "")
1289             os.write(fd, s.script)
1290             if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1291                 os.write(fd, '\n')
1292                 os.write(fd, 'exit 0\n')
1293             os.close(fd)
1294             os.chmod(path, 0700)
1295
1296             env = self._get_post_scripts_env(s.inChroot)
1297             if 'PATH' not in env:
1298                 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1299
1300             if not s.inChroot:
1301                 preexec = None
1302                 script = path
1303             else:
1304                 preexec = self._chroot
1305                 script = "/tmp/" + os.path.basename(path)
1306
1307             start_time = time.time()
1308             try:
1309                 try:
1310                     p = subprocess.Popen([s.interp, script],
1311                                        preexec_fn = preexec,
1312                                        env = env,
1313                                        stdout = subprocess.PIPE,
1314                                        stderr = subprocess.STDOUT)
1315                     while p.poll() == None:
1316                         msger.info(p.stdout.readline().strip())
1317                         end_time = time.time()
1318                         if (end_time - start_time)/60 > MAX_RUN_TIME:
1319                             raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
1320                     if p.returncode != 0:
1321                         raise CreatorError("Failed to execute %%post script "
1322                                            "with '%s'" % (s.interp))
1323                 except OSError, (err, msg):
1324                     raise CreatorError("Failed to execute %%post script "
1325                                        "with '%s' : %s" % (s.interp, msg))
1326             finally:
1327                 os.unlink(path)
1328
1329     def __save_repo_keys(self, repodata):
1330         if not repodata:
1331             return None
1332
1333         gpgkeydir = "/etc/pki/rpm-gpg"
1334         fs.makedirs(self._instroot + gpgkeydir)
1335         for repo in repodata:
1336             if repo["repokey"]:
1337                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1338                 shutil.copy(repo["repokey"], self._instroot + repokey)
1339
1340     def configure(self, repodata = None):
1341         """Configure the system image according to the kickstart.
1342
1343         This method applies the (e.g. keyboard or network) configuration
1344         specified in the kickstart and executes the kickstart %post scripts.
1345
1346         If necessary, it also prepares the image to be bootable by e.g.
1347         creating an initrd and bootloader configuration.
1348
1349         """
1350         ksh = self.ks.handler
1351
1352         msger.info('Applying configurations ...')
1353         try:
1354             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1355             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1356             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1357             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1358             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1359             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1360             kickstart.UserConfig(self._instroot).apply(ksh.user)
1361             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1362             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1363             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1364             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1365             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1366             self.__save_repo_keys(repodata)
1367             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1368         except:
1369             msger.warning("Failed to apply configuration to image")
1370             raise
1371
1372         self._create_bootconfig()
1373         self.__run_post_scripts()
1374
1375     def launch_shell(self, launch):
1376         """Launch a shell in the install root.
1377
1378         This method is launches a bash shell chroot()ed in the install root;
1379         this can be useful for debugging.
1380
1381         """
1382         if launch:
1383             msger.info("Launching shell. Exit to continue.")
1384             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1385
1386     def do_genchecksum(self, image_name):
1387         if not self._genchecksum:
1388             return
1389
1390         md5sum = misc.get_md5sum(image_name)
1391         with open(image_name + ".md5sum", "w") as f:
1392             f.write("%s  %s" % (md5sum, os.path.basename(image_name)))
1393         self.outimage.append(image_name+".md5sum")
1394
1395     def remove_exclude_image(self):
1396         for item in self._instloops[:]:
1397             if item['exclude_image']:
1398                 msger.info("Removing %s in image." % item['name'])
1399                 imgfile = os.path.join(self._imgdir, item['name'])
1400                 try:
1401                     os.remove(imgfile)
1402                 except OSError as err:
1403                     if err.errno == errno.ENOENT:
1404                         pass
1405                 self._instloops.remove(item)
1406
1407     def create_cpio_image(self):
1408         for item in self._instloops:
1409             if item['cpioopts']:
1410                 msger.info("Create image by cpio.")
1411                 tmp_cpio = self.__builddir + "/tmp-cpio"
1412                 if not os.path.exists(tmp_cpio):
1413                     os.mkdir(tmp_cpio)
1414                 tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
1415                 try:
1416                     cpiocmd = fs.find_binary_path('cpio')
1417                     if cpiocmd:
1418                         oldoutdir = os.getcwd()
1419                         os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
1420                         # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1421                         runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
1422                         os.chdir(oldoutdir)
1423                 except OSError, (errno, msg):
1424                     raise CreatorError("Create image by cpio error: %s" % msg)
1425
1426     def copy_cpio_image(self):
1427         for item in self._instloops:
1428             if item['cpioopts']:
1429                 tmp_cpio = self.__builddir + "/tmp-cpio"
1430                 msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
1431                 try:
1432                     shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
1433                 except IOError:
1434                     raise CreatorError("Copy cpio image error")
1435                 os.remove(os.path.join(tmp_cpio, item['name']))
1436                 if not os.listdir(tmp_cpio):
1437                     shutil.rmtree(tmp_cpio, ignore_errors=True)
1438
1439     def package(self, destdir = "."):
1440         """Prepares the created image for final delivery.
1441
1442         In its simplest form, this method merely copies the install root to the
1443         supplied destination directory; other subclasses may choose to package
1444         the image by e.g. creating a bootable ISO containing the image and
1445         bootloader configuration.
1446
1447         destdir -- the directory into which the final image should be moved;
1448                    this defaults to the current directory.
1449
1450         """
1451         self.remove_exclude_image()
1452
1453         self._stage_final_image()
1454
1455         if not os.path.exists(destdir):
1456             fs.makedirs(destdir)
1457
1458         if self._recording_pkgs:
1459             self._save_recording_pkgs(destdir)
1460
1461         # For image formats with two or multiple image files, it will be
1462         # better to put them under a directory
1463         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1464             destdir = os.path.join(destdir, "%s-%s" \
1465                                             % (self.name, self.image_format))
1466             msger.debug("creating destination dir: %s" % destdir)
1467             fs.makedirs(destdir)
1468
1469         # Ensure all data is flushed to _outdir
1470         runner.quiet('sync')
1471
1472         misc.check_space_pre_cp(self._outdir, destdir)
1473         for f in os.listdir(self._outdir):
1474             shutil.move(os.path.join(self._outdir, f),
1475                         os.path.join(destdir, f))
1476             self.outimage.append(os.path.join(destdir, f))
1477             self.do_genchecksum(os.path.join(destdir, f))
1478
1479     def print_outimage_info(self):
1480         msg = "The new image can be found here:\n"
1481         self.outimage.sort()
1482         for file in self.outimage:
1483             msg += '  %s\n' % os.path.abspath(file)
1484
1485         msger.info(msg)
1486
1487     def check_depend_tools(self):
1488         for tool in self._dep_checks:
1489             fs.find_binary_path(tool)
1490
1491     def package_output(self, image_format, destdir = ".", package="none"):
1492         if not package or package == "none":
1493             return
1494
1495         destdir = os.path.abspath(os.path.expanduser(destdir))
1496         (pkg, comp) = os.path.splitext(package)
1497         if comp:
1498             comp=comp.lstrip(".")
1499
1500         if pkg == "tar":
1501             if comp:
1502                 dst = "%s/%s-%s.tar.%s" %\
1503                       (destdir, self.name, image_format, comp)
1504             else:
1505                 dst = "%s/%s-%s.tar" %\
1506                       (destdir, self.name, image_format)
1507
1508             msger.info("creating %s" % dst)
1509             tar = tarfile.open(dst, "w:" + comp)
1510
1511             for file in self.outimage:
1512                 msger.info("adding %s to %s" % (file, dst))
1513                 tar.add(file,
1514                         arcname=os.path.join("%s-%s" \
1515                                            % (self.name, image_format),
1516                                               os.path.basename(file)))
1517                 if os.path.isdir(file):
1518                     shutil.rmtree(file, ignore_errors = True)
1519                 else:
1520                     os.remove(file)
1521
1522             tar.close()
1523
1524             '''All the file in outimage has been packaged into tar.* file'''
1525             self.outimage = [dst]
1526
1527     def release_output(self, config, destdir, release):
1528         """ Create release directory and files
1529         """
1530
1531         def _rpath(fn):
1532             """ release path """
1533             return os.path.join(destdir, fn)
1534
1535         outimages = self.outimage
1536
1537         # new ks
1538         new_kspath = _rpath(self.name+'.ks')
1539         with open(config) as fr:
1540             with open(new_kspath, "w") as wf:
1541                 # When building a release we want to make sure the .ks
1542                 # file generates the same build even when --release not used.
1543                 wf.write(fr.read().replace("@BUILD_ID@", release))
1544         outimages.append(new_kspath)
1545
1546         # save log file, logfile is only available in creator attrs
1547         if hasattr(self, 'releaselog') and self.releaselog:
1548             outimages.append(self.logfile)
1549
1550         # rename iso and usbimg
1551         for f in os.listdir(destdir):
1552             if f.endswith(".iso"):
1553                 newf = f[:-4] + '.img'
1554             elif f.endswith(".usbimg"):
1555                 newf = f[:-7] + '.img'
1556             else:
1557                 continue
1558             os.rename(_rpath(f), _rpath(newf))
1559             outimages.append(_rpath(newf))
1560
1561         # generate MD5SUMS SHA1SUMS SHA256SUMS
1562         def generate_hashsum(hash_name, hash_method):
1563             with open(_rpath(hash_name), "w") as wf:
1564                 for f in os.listdir(destdir):
1565                     if f.endswith('SUMS'):
1566                         continue
1567
1568                     if os.path.isdir(os.path.join(destdir, f)):
1569                         continue
1570
1571                     hash_value = hash_method(_rpath(f))
1572                     # There needs to be two spaces between the sum and
1573                     # filepath to match the syntax with md5sum,sha1sum,
1574                     # sha256sum. This way also *sum -c *SUMS can be used.
1575                     wf.write("%s  %s\n" % (hash_value, f))
1576
1577             outimages.append("%s/%s" % (destdir, hash_name))
1578
1579         hash_dict = {
1580                      'MD5SUMS'    : misc.get_md5sum,
1581                      'SHA1SUMS'   : misc.get_sha1sum,
1582                      'SHA256SUMS' : misc.get_sha256sum
1583                     }
1584
1585         for k, v in hash_dict.items():
1586             generate_hashsum(k, v)
1587
1588         # Filter out the nonexist file
1589         for fp in outimages[:]:
1590             if not os.path.exists("%s" % fp):
1591                 outimages.remove(fp)
1592
1593     def copy_kernel(self):
1594         """ Copy kernel files to the outimage directory.
1595         NOTE: This needs to be called before unmounting the instroot.
1596         """
1597
1598         if not self._need_copy_kernel:
1599             return
1600
1601         if not os.path.exists(self.destdir):
1602             os.makedirs(self.destdir)
1603
1604         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1605             kernelfilename = "%s/%s-%s" % (self.destdir,
1606                                            self.name,
1607                                            os.path.basename(kernel))
1608             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1609                                                       kernelfilename))
1610             shutil.copy(kernel, kernelfilename)
1611             self.outimage.append(kernelfilename)
1612
1613     def copy_attachment(self):
1614         """ Subclass implement it to handle attachment files
1615         NOTE: This needs to be called before unmounting the instroot.
1616         """
1617         pass
1618
1619     def get_pkg_manager(self):
1620         return self.pkgmgr(target_arch = self.target_arch,
1621                            instroot = self._instroot,
1622                            cachedir = self.cachedir,
1623                            strict_mode = self.strict_mode)
1624
1625     def create_manifest(self):
1626         def get_pack_suffix():
1627             return '.' + self.pack_to.split('.', 1)[1]
1628
1629         if not os.path.exists(self.destdir):
1630             os.makedirs(self.destdir)
1631
1632         now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1633         manifest_dict = {'version': VERSION,
1634                          'created': now}
1635         if self.img_format:
1636             manifest_dict.update({'format': self.img_format})
1637
1638         if hasattr(self, 'logfile') and self.logfile:
1639             manifest_dict.update({'log_file': self.logfile})
1640
1641         if self.image_files:
1642             if self.pack_to:
1643                 self.image_files.update({'pack': get_pack_suffix()})
1644             manifest_dict.update({self.img_format: self.image_files})
1645
1646         msger.info('Creating manifest file...')
1647         manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1648         with open(manifest_file_path, 'w') as fest_file:
1649             json.dump(manifest_dict, fest_file, indent=4)
1650         self.outimage.append(manifest_file_path)