fix the bug that modify the order of generating image by cpio.
[platform/upstream/mic.git] / mic / imager / baseimager.py
old mode 100644 (file)
new mode 100755 (executable)
index 83bdc01..e0340a1
@@ -26,13 +26,19 @@ import subprocess
 import re
 import tarfile
 import glob
+import json
+from datetime import datetime
 
 import rpm
-
+import time
 from mic import kickstart
-from mic import msger
+from mic import msger, __version__ as VERSION
 from mic.utils.errors import CreatorError, Abort
-from mic.utils import misc, rpmmisc, runner, fs_related as fs
+from mic.utils import misc, grabber, runner, fs_related as fs
+from mic.chroot import kill_proc_inchroot
+from mic.archive import get_archive_suffixes
+#post script max run time
+MAX_RUN_TIME = 120
 
 class BaseImageCreator(object):
     """Installs a system to a chroot directory.
@@ -47,12 +53,8 @@ class BaseImageCreator(object):
       imgcreate.ImageCreator(ks, "foo").create()
 
     """
-
-    # supported compression methods, and their corresponding cmd
-    zips = {
-        'gz': 'gzip',
-        'bz2': 'bzip2'
-    }
+    # Output image format
+    img_format = ''
 
     def __del__(self):
         self.cleanup()
@@ -69,6 +71,7 @@ class BaseImageCreator(object):
         """
 
         self.pkgmgr = pkgmgr
+        self.distro_name = ""
 
         self.__builddir = None
         self.__bindmounts = []
@@ -77,26 +80,30 @@ class BaseImageCreator(object):
         self.name = "target"
         self.tmpdir = "/var/tmp/mic"
         self.cachedir = "/var/tmp/mic/cache"
+        self.workdir = "/var/tmp/mic/build"
         self.destdir = "."
+        self.installerfw_prefix = "INSTALLERFW_"
         self.target_arch = "noarch"
+        self.strict_mode = False
         self._local_pkgs_path = None
+        self.pack_to = None
+        self.repourl = {}
+        self.multiple_partitions = False
 
         # If the kernel is save to the destdir when copy_kernel cmd is called.
         self._need_copy_kernel = False
-
-        # The compression method for disk image.
-        self._img_compression_method = None
+        # setup tmpfs tmpdir when enabletmpfs is True
+        self.enabletmpfs = False
 
         if createopts:
             # Mapping table for variables that have different names.
             optmap = {"pkgmgr" : "pkgmgr_name",
-                      "outdir" : "destdir",
                       "arch" : "target_arch",
                       "local_pkgs_path" : "_local_pkgs_path",
-                      "compress_disk_image" : "_img_compression_method",
                       "copy_kernel" : "_need_copy_kernel",
+                      "strict_mode" : "strict_mode",
                      }
-    
+
             # update setting from createopts
             for key in createopts.keys():
                 if key in optmap:
@@ -107,23 +114,21 @@ class BaseImageCreator(object):
 
             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
 
-            if 'release' in createopts and createopts['release']:
-                self.name += '-' + createopts['release']
-
-                if os.path.exists(self.destdir):
-                    if msger.ask("Image dir: %s already exists, cleanup and" \
-                                 "continue?" % self.destdir):
-                        shutil.rmtree(self.destdir, ignore_errors = True)
-                    else:
-                        raise Abort("Canceled")
+            if self.pack_to:
+                if '@NAME@' in self.pack_to:
+                    self.pack_to = self.pack_to.replace('@NAME@', self.name)
+                (tar, ext) = os.path.splitext(self.pack_to)
+                if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
+                    ext = ".tar" + ext
+                if ext not in get_archive_suffixes():
+                    self.pack_to += ".tar"
 
-                    # pending FEA: save log by default for --release
-
-        self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
+        self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
 
         # Output image file names
         self.outimage = []
-
+        # Output info related with manifest
+        self.image_files = {}
         # A flag to generate checksum
         self._genchecksum = False
 
@@ -144,31 +149,55 @@ class BaseImageCreator(object):
 
         # No ks provided when called by convertor, so skip the dependency check
         if self.ks:
-            # If we have btrfs partition we need to check that we have toosl for those
+            # If we have btrfs partition we need to check necessary tools
             for part in self.ks.handler.partition.partitions:
                 if part.fstype and part.fstype == "btrfs":
                     self._dep_checks.append("mkfs.btrfs")
                     break
-
-        if self.target_arch and self.target_arch.startswith("arm"):
-            for dep in self._dep_checks:
-                if dep == "extlinux":
-                    self._dep_checks.remove(dep)
-
-            if not os.path.exists("/usr/bin/qemu-arm") or \
-               not misc.is_statically_linked("/usr/bin/qemu-arm"):
-                self._dep_checks.append("qemu-arm-static")
-
-            if os.path.exists("/proc/sys/vm/vdso_enabled"):
-                vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
-                vdso_value = vdso_fh.read().strip()
-                vdso_fh.close()
-                if (int)(vdso_value) == 1:
-                    msger.warning("vdso is enabled on your host, which might "
-                        "cause problems with arm emulations.\n"
-                        "\tYou can disable vdso with following command before "
-                        "starting image build:\n"
-                        "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
+                if part.fstype == "cpio":
+                    part.fstype = "ext4"
+            if len(self.ks.handler.partition.partitions) > 1:
+                self.multiple_partitions = True
+
+        if self.target_arch:
+            if self.target_arch.startswith("arm"):
+                for dep in self._dep_checks:
+                    if dep == "extlinux":
+                        self._dep_checks.remove(dep)
+
+                if not os.path.exists("/usr/bin/qemu-arm") or \
+                   not misc.is_statically_linked("/usr/bin/qemu-arm"):
+                    self._dep_checks.append("qemu-arm-static")
+
+                if os.path.exists("/proc/sys/vm/vdso_enabled"):
+                    vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
+                    vdso_value = vdso_fh.read().strip()
+                    vdso_fh.close()
+                    if (int)(vdso_value) == 1:
+                        msger.warning("vdso is enabled on your host, which might "
+                            "cause problems with arm emulations.\n"
+                            "\tYou can disable vdso with following command before "
+                            "starting image build:\n"
+                            "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
+            elif self.target_arch == "mipsel":
+                for dep in self._dep_checks:
+                    if dep == "extlinux":
+                        self._dep_checks.remove(dep)
+
+                if not os.path.exists("/usr/bin/qemu-mipsel") or \
+                   not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
+                    self._dep_checks.append("qemu-mipsel-static")
+
+                if os.path.exists("/proc/sys/vm/vdso_enabled"):
+                    vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
+                    vdso_value = vdso_fh.read().strip()
+                    vdso_fh.close()
+                    if (int)(vdso_value) == 1:
+                        msger.warning("vdso is enabled on your host, which might "
+                            "cause problems with mipsel emulations.\n"
+                            "\tYou can disable vdso with following command before "
+                            "starting image build:\n"
+                            "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
 
         # make sure the specified tmpdir and cachedir exist
         if not os.path.exists(self.tmpdir):
@@ -176,13 +205,6 @@ class BaseImageCreator(object):
         if not os.path.exists(self.cachedir):
             os.makedirs(self.cachedir)
 
-        if self._img_compression_method != None and \
-           self._img_compression_method not in self.zips:
-            raise CreatorError("Given disk image compression method ('%s') is "
-                               "not valid. Valid values are: %s." \
-                               % (self._img_compression_method,
-                                  ', '.join(self.zips.keys())))
-
 
     #
     # Properties
@@ -289,16 +311,22 @@ class BaseImageCreator(object):
 
         if not os.path.exists(destdir):
             os.makedirs(destdir)
-        if 'name' in self._recording_pkgs :
+
+        content = None
+        if 'vcs' in self._recording_pkgs:
+            vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
+            content = '\n'.join(sorted(vcslst))
+        elif 'name' in self._recording_pkgs:
+            content = '\n'.join(pkgs)
+        if content:
             namefile = os.path.join(destdir, self.name + '.packages')
             f = open(namefile, "w")
-            content = '\n'.join(pkgs)
             f.write(content)
             f.close()
             self.outimage.append(namefile);
 
         # if 'content', save more details
-        if 'content' in self._recording_pkgs :
+        if 'content' in self._recording_pkgs:
             contfile = os.path.join(destdir, self.name + '.files')
             f = open(contfile, "w")
 
@@ -306,16 +334,9 @@ class BaseImageCreator(object):
                 content = pkg + '\n'
 
                 pkgcont = self._pkgs_content[pkg]
-                items = []
-                if pkgcont.has_key('dir'):
-                    items = map(lambda x:x+'/', pkgcont['dir'])
-                if pkgcont.has_key('file'):
-                    items.extend(pkgcont['file'])
-
-                if items:
-                    content += '    '
-                    content += '\n    '.join(items)
-                    content += '\n'
+                content += '    '
+                content += '\n    '.join(pkgcont)
+                content += '\n'
 
                 content += '\n'
                 f.write(content)
@@ -341,7 +362,7 @@ class BaseImageCreator(object):
                 f.write('\n')
 
             f.close()
-            self.outimage.append(licensefile);
+            self.outimage.append(licensefile)
 
     def _get_required_packages(self):
         """Return a list of required packages.
@@ -414,6 +435,25 @@ class BaseImageCreator(object):
         s += "sysfs      /sys      sysfs   defaults         0 0\n"
         return s
 
+    def _set_part_env(self, pnum, prop, value):
+        """ This is a helper function which generates an environment variable
+        for a property "prop" with value "value" of a partition number "pnum".
+
+        The naming convention is:
+           * Variables start with INSTALLERFW_PART
+           * Then goes the partition number, the order is the same as
+             specified in the KS file
+           * Then goes the property name
+        """
+
+        if value == None:
+            value = ""
+        else:
+            value = str(value)
+
+        name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
+        return { name : value }
+
     def _get_post_scripts_env(self, in_chroot):
         """Return an environment dict for %post scripts.
 
@@ -421,13 +461,56 @@ class BaseImageCreator(object):
         variables for %post scripts by return a dict containing the desired
         environment.
 
-        By default, this returns an empty dict.
-
         in_chroot -- whether this %post script is to be executed chroot()ed
                      into _instroot.
-
         """
-        return {}
+
+        env = {}
+        pnum = 0
+
+        for p in kickstart.get_partitions(self.ks):
+            env.update(self._set_part_env(pnum, "SIZE", p.size))
+            env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
+            env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
+            env.update(self._set_part_env(pnum, "LABEL", p.label))
+            env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
+            env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
+            env.update(self._set_part_env(pnum, "ALIGN", p.align))
+            env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
+            env.update(self._set_part_env(pnum, "UUID", p.uuid))
+            env.update(self._set_part_env(pnum, "DEVNODE",
+                                          "/dev/%s%d" % (p.disk, pnum + 1)))
+            env.update(self._set_part_env(pnum, "DISK_DEVNODE",
+                                          "/dev/%s" % p.disk))
+            pnum += 1
+
+        # Count of paritions
+        env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
+
+        # Partition table format
+        ptable_format = self.ks.handler.bootloader.ptable
+        env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
+
+        # The kerned boot parameters
+        kernel_opts = self.ks.handler.bootloader.appendLine
+        env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
+
+        # Name of the image creation tool
+        env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
+
+        # The real current location of the mounted file-systems
+        if in_chroot:
+            mount_prefix = "/"
+        else:
+            mount_prefix = self._instroot
+        env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
+
+        # These are historical variables which lack the common name prefix
+        if not in_chroot:
+            env["INSTALL_ROOT"] = self._instroot
+            env["IMG_NAME"] = self._name
+
+        return env
 
     def __get_imgname(self):
         return self.name
@@ -601,7 +684,10 @@ class BaseImageCreator(object):
             return
 
         try:
-            self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
+            self.workdir = os.path.join(self.tmpdir, "build")
+            if not os.path.exists(self.workdir):
+                os.makedirs(self.workdir)
+            self.__builddir = tempfile.mkdtemp(dir = self.workdir,
                                                prefix = "imgcreate-")
         except OSError, (err, msg):
             raise CreatorError("Failed create build directory in %s: %s" %
@@ -620,7 +706,7 @@ class BaseImageCreator(object):
         return self.cachedir
 
     def __sanity_check(self):
-        """Ensure that the config we've been given is sane."""
+        """Ensure that the config we've been given is same."""
         if not (kickstart.get_packages(self.ks) or
                 kickstart.get_groups(self.ks)):
             raise CreatorError("No packages or groups specified")
@@ -631,9 +717,15 @@ class BaseImageCreator(object):
             raise CreatorError("No repositories specified")
 
     def __write_fstab(self):
-        fstab = open(self._instroot + "/etc/fstab", "w")
-        fstab.write(self._get_fstab())
-        fstab.close()
+        if kickstart.use_installerfw(self.ks, "fstab"):
+            # The fstab file will be generated by installer framework scripts
+            # instead.
+            return None
+        fstab_contents = self._get_fstab()
+        if fstab_contents:
+            fstab = open(self._instroot + "/etc/fstab", "w")
+            fstab.write(fstab_contents)
+            fstab.close()
 
     def __create_minimal_dev(self):
         """Create a minimal /dev so that we don't corrupt the host /dev"""
@@ -663,6 +755,18 @@ class BaseImageCreator(object):
 
         os.umask(origumask)
 
+    def __setup_tmpdir(self):
+        if not self.enabletmpfs:
+            return
+
+        runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
+
+    def __clean_tmpdir(self):
+        if not self.enabletmpfs:
+            return
+
+        runner.show('umount -l %s' % self.workdir)
+
     def mount(self, base_on = None, cachedir = None):
         """Setup the target filesystem in preparation for an install.
 
@@ -681,8 +785,12 @@ class BaseImageCreator(object):
                     multiple installs.
 
         """
+        self.__setup_tmpdir()
         self.__ensure_builddir()
 
+        # prevent popup dialog in Ubuntu(s)
+        misc.hide_loopdev_presentation()
+
         fs.makedirs(self._instroot)
         fs.makedirs(self._outdir)
 
@@ -697,7 +805,8 @@ class BaseImageCreator(object):
                   "/usr/bin"):
             fs.makedirs(self._instroot + d)
 
-        if self.target_arch and self.target_arch.startswith("arm"):
+        if self.target_arch and self.target_arch.startswith("arm") or \
+            self.target_arch == "aarch64" or self.target_arch == "mipsel" :
             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
                                                           self.target_arch)
 
@@ -708,7 +817,9 @@ class BaseImageCreator(object):
                           ("/proc", None),
                           ("/proc/sys/fs/binfmt_misc", None),
                           ("/dev/pts", None)]:
-            self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
+            self.__bindmounts.append(
+                    fs.BindChrootMount(
+                        f, self._instroot, dest))
 
         self._do_bindmounts()
 
@@ -756,6 +867,10 @@ class BaseImageCreator(object):
 
         self._unmount_instroot()
 
+        # reset settings of popup dialog in Ubuntu(s)
+        misc.unhide_loopdev_presentation()
+
+
     def cleanup(self):
         """Unmounts the target filesystem and deletes temporary files.
 
@@ -776,11 +891,15 @@ class BaseImageCreator(object):
         if not self.__builddir:
             return
 
+        kill_proc_inchroot(self._instroot)
+
         self.unmount()
 
         shutil.rmtree(self.__builddir, ignore_errors = True)
         self.__builddir = None
 
+        self.__clean_tmpdir()
+
     def __is_excluded_pkg(self, pkg):
         if pkg in self._excluded_pkgs:
             self._excluded_pkgs.remove(pkg)
@@ -842,18 +961,86 @@ class BaseImageCreator(object):
         for pkg in self._preinstall_pkgs:
             pkg_manager.preInstall(pkg)
 
-    def install(self, repo_urls = {}):
+    def __check_packages(self, pkg_manager):
+        for pkg in self.check_pkgs:
+            pkg_manager.checkPackage(pkg)
+
+    def __attachment_packages(self, pkg_manager):
+        if not self.ks:
+            return
+
+        self._attachment = []
+        for item in kickstart.get_attachment(self.ks):
+            if item.startswith('/'):
+                fpaths = os.path.join(self._instroot, item.lstrip('/'))
+                for fpath in glob.glob(fpaths):
+                    self._attachment.append(fpath)
+                continue
+
+            filelist = pkg_manager.getFilelist(item)
+            if filelist:
+                # found rpm in rootfs
+                for pfile in pkg_manager.getFilelist(item):
+                    fpath = os.path.join(self._instroot, pfile.lstrip('/'))
+                    self._attachment.append(fpath)
+                continue
+
+            # try to retrieve rpm file
+            (url, proxies) = pkg_manager.package_url(item)
+            if not url:
+                msger.warning("Can't get url from repo for %s" % item)
+                continue
+            fpath = os.path.join(self.cachedir, os.path.basename(url))
+            if not os.path.exists(fpath):
+                # download pkgs
+                try:
+                    fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
+                except CreatorError:
+                    raise
+
+            tmpdir = self._mkdtemp()
+            misc.extract_rpm(fpath, tmpdir)
+            for (root, dirs, files) in os.walk(tmpdir):
+                for fname in files:
+                    fpath = os.path.join(root, fname)
+                    self._attachment.append(fpath)
+
+    def install(self, repo_urls=None):
         """Install packages into the install root.
 
         This function installs the packages listed in the supplied kickstart
         into the install root. By default, the packages are installed from the
         repository URLs specified in the kickstart.
 
-        repo_urls -- a dict which maps a repository name to a repository URL;
+        repo_urls -- a dict which maps a repository name to a repository;
                      if supplied, this causes any repository URLs specified in
                      the kickstart to be overridden.
 
         """
+        def checkScriptletError(dirname, suffix):
+            if os.path.exists(dirname):
+                list = os.listdir(dirname)
+                for line in list:
+                    filepath = os.path.join(dirname, line)
+                    if os.path.isfile(filepath) and 0 < line.find(suffix):
+                        return True
+                    else:
+                        continue
+             
+            return False
+
+        def showErrorInfo(filepath):
+            if os.path.isfile(filepath):
+                for line in open(filepath):
+                    msger.info("The error install package info: %s" % line)
+            else:
+                msger.info("%s is not found." % filepath)
+
+        def get_ssl_verify(ssl_verify=None):
+            if ssl_verify is not None:
+                return not ssl_verify.lower().strip() == 'no'
+            else:
+                return not self.ssl_verify.lower().strip() == 'no'
 
         # initialize pkg list to install
         if self.ks:
@@ -869,17 +1056,26 @@ class BaseImageCreator(object):
             self._excluded_pkgs = None
             self._required_groups = None
 
+        if not repo_urls:
+            repo_urls = self.extrarepos
+
         pkg_manager = self.get_pkg_manager()
         pkg_manager.setup()
 
-        for repo in kickstart.get_repos(self.ks, repo_urls):
+        if hasattr(self, 'install_pkgs') and self.install_pkgs:
+            if 'debuginfo' in self.install_pkgs:
+                pkg_manager.install_debuginfo = True
+
+        for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
             (name, baseurl, mirrorlist, inc, exc,
              proxy, proxy_username, proxy_password, debuginfo,
-             source, gpgkey, disable, ssl_verify, cost, priority) = repo
+             source, gpgkey, disable, ssl_verify, nocache,
+             cost, priority) = repo
 
+            ssl_verify = get_ssl_verify(ssl_verify)
             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
                         proxy_username, proxy_password, inc, exc, ssl_verify,
-                        cost, priority)
+                        nocache, cost, priority)
 
         if kickstart.exclude_docs(self.ks):
             rpm.addMacro("_excludedocs", "1")
@@ -889,28 +1085,44 @@ class BaseImageCreator(object):
             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
 
         try:
-            try:
-                self.__preinstall_packages(pkg_manager)
-                self.__select_packages(pkg_manager)
-                self.__select_groups(pkg_manager)
-                self.__deselect_packages(pkg_manager)
-                self.__localinst_packages(pkg_manager)
-
-                BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
-                checksize = self._root_fs_avail
-                if checksize:
-                    checksize -= BOOT_SAFEGUARD
-                if self.target_arch:
-                    pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
-                pkg_manager.runInstall(checksize)
-            except CreatorError, e:
-                raise
-        finally:
+            self.__preinstall_packages(pkg_manager)
+            self.__select_packages(pkg_manager)
+            self.__select_groups(pkg_manager)
+            self.__deselect_packages(pkg_manager)
+            self.__localinst_packages(pkg_manager)
+            self.__check_packages(pkg_manager)
+
+            BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
+            checksize = self._root_fs_avail
+            if checksize:
+                checksize -= BOOT_SAFEGUARD
+            if self.target_arch:
+                pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
+
+            # If we have multiple partitions, don't check diskspace when rpm run transaction
+            # because rpm check '/' partition only.
+            if self.multiple_partitions:
+                pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
+            pkg_manager.runInstall(checksize)
+        except CreatorError, e:
+            raise
+        except  KeyboardInterrupt:
+            raise
+        else:
             self._pkgs_content = pkg_manager.getAllContent()
             self._pkgs_license = pkg_manager.getPkgsLicense()
-
+            self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
+            self.__attachment_packages(pkg_manager)
+        finally:
             pkg_manager.close()
 
+        if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
+            showErrorInfo(self._instroot + "/tmp/.preload_install_error")
+            raise CreatorError('scriptlet errors occurred')
+            
+        # hook post install
+        self.postinstall()
+
         # do some clean up to avoid lvm info leakage.  this sucks.
         for subdir in ("cache", "backup", "archive"):
             lvmdir = self._instroot + "/etc/lvm/" + subdir
@@ -920,6 +1132,9 @@ class BaseImageCreator(object):
             except:
                 pass
 
+    def postinstall(self):
+        self.copy_attachment()
+
     def __run_post_scripts(self):
         msger.info("Running scripts ...")
         if os.path.exists(self._instroot + "/tmp"):
@@ -935,23 +1150,29 @@ class BaseImageCreator(object):
             os.chmod(path, 0700)
 
             env = self._get_post_scripts_env(s.inChroot)
+            if 'PATH' not in env:
+                env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
 
             if not s.inChroot:
-                env["INSTALL_ROOT"] = self._instroot
-                env["IMG_NAME"] = self._name
                 preexec = None
                 script = path
             else:
                 preexec = self._chroot
                 script = "/tmp/" + os.path.basename(path)
 
+            start_time = time.time()
             try:
                 try:
-                    subprocess.call([s.interp, script],
-                                    preexec_fn = preexec,
-                                    env = env,
-                                    stdout = sys.stdout,
-                                    stderr = sys.stderr)
+                    p = subprocess.Popen([s.interp, script],
+                                       preexec_fn = preexec,
+                                       env = env,
+                                       stdout = subprocess.PIPE,
+                                       stderr = subprocess.STDOUT)
+                    while p.poll() == None:
+                       msger.info(p.stdout.readline().strip())
+                        end_time = time.time()
+                        if (end_time - start_time)/60 > MAX_RUN_TIME:
+                            raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
                 except OSError, (err, msg):
                     raise CreatorError("Failed to execute %%post script "
                                        "with '%s' : %s" % (s.interp, msg))
@@ -996,7 +1217,7 @@ class BaseImageCreator(object):
             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
             self.__save_repo_keys(repodata)
-            kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
+            kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
         except:
             msger.warning("Failed to apply configuration to image")
             raise
@@ -1021,9 +1242,53 @@ class BaseImageCreator(object):
 
         md5sum = misc.get_md5sum(image_name)
         with open(image_name + ".md5sum", "w") as f:
-            f.write("%s %s" % (md5sum, os.path.basename(image_name)))
+            f.write("%s  %s" % (md5sum, os.path.basename(image_name)))
         self.outimage.append(image_name+".md5sum")
 
+    def remove_exclude_image(self):
+        for item in self._instloops[:]:
+            if item['exclude_image']:
+                msger.info("Removing %s in image." % item['name'])
+                imgfile = os.path.join(self._imgdir, item['name'])
+                try:
+                    os.remove(imgfile)
+                except OSError as err:
+                    if err.errno == errno.ENOENT:
+                        pass
+                self._instloops.remove(item)
+
+    def create_cpio_image(self):
+        for item in self._instloops:
+            if item['cpioopts']:
+                msger.info("Create image by cpio.")
+                tmp_cpio = self.__builddir + "/tmp-cpio"
+                if not os.path.exists(tmp_cpio):
+                    os.mkdir(tmp_cpio)
+                tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
+                try:
+                    cpiocmd = fs.find_binary_path('cpio')
+                    if cpiocmd:
+                        oldoutdir = os.getcwd()
+                        os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
+                        # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
+                        runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
+                        os.chdir(oldoutdir)
+                except OSError, (errno, msg):
+                    raise errors.CreatorError("Create image by cpio error: %s" % msg)
+
+    def copy_cpio_image(self):
+        for item in self._instloops:
+            if item['cpioopts']:
+                tmp_cpio = self.__builddir + "/tmp-cpio"
+                msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
+                try:
+                    shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
+                except IOError:
+                    raise errors.CreatorError("Copy cpio image error")
+                os.remove(os.path.join(tmp_cpio, item['name']))
+                if not os.listdir(tmp_cpio):
+                    shutil.rmtree(tmp_cpio, ignore_errors=True)
+
     def package(self, destdir = "."):
         """Prepares the created image for final delivery.
 
@@ -1036,38 +1301,12 @@ class BaseImageCreator(object):
                    this defaults to the current directory.
 
         """
+        self.remove_exclude_image()
+
         self._stage_final_image()
 
         if not os.path.exists(destdir):
             fs.makedirs(destdir)
-        if self._img_compression_method:
-            if not self._img_name:
-                raise CreatorError("Image name not set.")
-
-            rc = None
-            img_location = os.path.join(self._outdir,self._img_name)
-            zipcmd = self.zips[self._img_compression_method]
-
-            # confirm the existing of zip command
-            fs.find_binary_path(zipcmd)
-
-            msger.info("Compressing %s with %s. Please wait ..." \
-                       % (img_location, zipcmd))
-            rc = runner.show([zipcmd, "-f", img_location])
-            if rc:
-                raise CreatorError("Failed to compress image %s with %s." \
-                            % (img_location, self._img_compression_method))
-
-            for bootimg in glob.glob(os.path.dirname(img_location) + \
-                                     "/*-boot.bin"):
-                msger.info("Compressing %s with %s. Please wait..." \
-                           % (bootimg, zipcmd))
-                rc = runner.show([zipcmd, "-f", bootimg])
-                if rc:
-                    raise CreatorError("Failed to compress image %s with "
-                                       "%s." \
-                                       % (bootimg,
-                                          self._img_compression_method))
 
         if self._recording_pkgs:
             self._save_recording_pkgs(destdir)
@@ -1113,9 +1352,12 @@ class BaseImageCreator(object):
 
         if pkg == "tar":
             if comp:
-                dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
+                dst = "%s/%s-%s.tar.%s" %\
+                      (destdir, self.name, image_format, comp)
             else:
-                dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
+                dst = "%s/%s-%s.tar" %\
+                      (destdir, self.name, image_format)
+
             msger.info("creating %s" % dst)
             tar = tarfile.open(dst, "w:" + comp)
 
@@ -1150,10 +1392,14 @@ class BaseImageCreator(object):
         with open(config) as fr:
             with open(new_kspath, "w") as wf:
                 # When building a release we want to make sure the .ks
-                # file generates the same build even when --release= is not used.
+                # file generates the same build even when --release not used.
                 wf.write(fr.read().replace("@BUILD_ID@", release))
         outimages.append(new_kspath)
 
+        # save log file, logfile is only available in creator attrs
+        if hasattr(self, 'releaselog') and self.releaselog:
+            outimages.append(self.logfile)
+
         # rename iso and usbimg
         for f in os.listdir(destdir):
             if f.endswith(".iso"):
@@ -1165,22 +1411,32 @@ class BaseImageCreator(object):
             os.rename(_rpath(f), _rpath(newf))
             outimages.append(_rpath(newf))
 
-        # generate MANIFEST
-        with open(_rpath("MANIFEST"), "w") as wf:
-            for f in os.listdir(destdir):
-                if f == "MANIFEST":
-                    continue
+        # generate MD5SUMS SHA1SUMS SHA256SUMS
+        def generate_hashsum(hash_name, hash_method):
+            with open(_rpath(hash_name), "w") as wf:
+                for f in os.listdir(destdir):
+                    if f.endswith('SUMS'):
+                        continue
 
-                if os.path.isdir(os.path.join(destdir, f)):
-                    continue
+                    if os.path.isdir(os.path.join(destdir, f)):
+                        continue
+
+                    hash_value = hash_method(_rpath(f))
+                    # There needs to be two spaces between the sum and
+                    # filepath to match the syntax with md5sum,sha1sum,
+                    # sha256sum. This way also *sum -c *SUMS can be used.
+                    wf.write("%s  %s\n" % (hash_value, f))
 
-                md5sum = misc.get_md5sum(_rpath(f))
-                # There needs to be two spaces between the sum and
-                # filepath to match the syntax with md5sum. 
-                # This way also md5sum -c MANIFEST can be used by users
-                wf.write("%s *%s\n" % (md5sum, f))
+            outimages.append("%s/%s" % (destdir, hash_name))
 
-        outimages.append("%s/MANIFEST" % destdir)
+        hash_dict = {
+                     'MD5SUMS'    : misc.get_md5sum,
+                     'SHA1SUMS'   : misc.get_sha1sum,
+                     'SHA256SUMS' : misc.get_sha256sum
+                    }
+
+        for k, v in hash_dict.items():
+            generate_hashsum(k, v)
 
         # Filter out the nonexist file
         for fp in outimages[:]:
@@ -1189,7 +1445,6 @@ class BaseImageCreator(object):
 
     def copy_kernel(self):
         """ Copy kernel files to the outimage directory.
-        
         NOTE: This needs to be called before unmounting the instroot.
         """
 
@@ -1200,10 +1455,49 @@ class BaseImageCreator(object):
             os.makedirs(self.destdir)
 
         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
-            kernelfilename = "%s/%s-%s" % (self.destdir, self.name, os.path.basename(kernel))
-            msger.info('copy kernel file %s as %s' % (os.path.basename(kernel), kernelfilename))
+            kernelfilename = "%s/%s-%s" % (self.destdir,
+                                           self.name,
+                                           os.path.basename(kernel))
+            msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
+                                                      kernelfilename))
             shutil.copy(kernel, kernelfilename)
             self.outimage.append(kernelfilename)
 
+    def copy_attachment(self):
+        """ Subclass implement it to handle attachment files
+        NOTE: This needs to be called before unmounting the instroot.
+        """
+        pass
+
     def get_pkg_manager(self):
-        return self.pkgmgr(target_arch = self.target_arch, instroot = self._instroot, cachedir = self.cachedir)
+        return self.pkgmgr(target_arch = self.target_arch,
+                           instroot = self._instroot,
+                           cachedir = self.cachedir,
+                           strict_mode = self.strict_mode)
+
+    def create_manifest(self):
+        def get_pack_suffix():
+            return '.' + self.pack_to.split('.', 1)[1]
+
+        if not os.path.exists(self.destdir):
+            os.makedirs(self.destdir)
+
+        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        manifest_dict = {'version': VERSION,
+                         'created': now}
+        if self.img_format:
+            manifest_dict.update({'format': self.img_format})
+
+        if hasattr(self, 'logfile') and self.logfile:
+            manifest_dict.update({'log_file': self.logfile})
+
+        if self.image_files:
+            if self.pack_to:
+                self.image_files.update({'pack': get_pack_suffix()})
+            manifest_dict.update({self.img_format: self.image_files})
+
+        msger.info('Creating manifest file...')
+        manifest_file_path = os.path.join(self.destdir, 'manifest.json')
+        with open(manifest_file_path, 'w') as fest_file:
+            json.dump(manifest_dict, fest_file, indent=4)
+        self.outimage.append(manifest_file_path)