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