]> git.ipfire.org Git - pakfire.git/commitdiff
Rewrite code that forks subprocesses.
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 24 Oct 2012 19:18:51 +0000 (21:18 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 24 Oct 2012 19:18:51 +0000 (21:18 +0200)
There is nothing too new here, but the old code has
been cleaned up and been put into a shiny new class. :)

python/pakfire/actions.py
python/pakfire/builder.py
python/pakfire/chroot.py [deleted file]
python/pakfire/errors.py
python/pakfire/packages/lexer.py
python/pakfire/packages/make.py
python/pakfire/shell.py [new file with mode: 0644]

index b8d0fb3e0a638d85335325d73e32433c43604875..854198ca9e2e7277e65388f7c8e6f8014d44b0c5 100644 (file)
@@ -22,8 +22,8 @@
 import os
 import sys
 
-import chroot
 import packages
+import shell
 import util
 
 import logging
@@ -98,7 +98,7 @@ class Action(object):
                """
                return self.pakfire.repos.local
 
-       def do(self, cmd, **kwargs):
+       def execute(self, command, **kwargs):
                # If we are running in /, we do not need to chroot there.
                chroot_path = None
                if not self.pakfire.path == "/":
@@ -118,7 +118,6 @@ class Action(object):
 
                args = {
                        "cwd"         : cwd,
-                       "logger"      : log,
                        "personality" : self.pakfire.distro.personality,
                        "shell"       : False,
                        "timeout"     : SCRIPTLET_TIMEOUT,
@@ -128,11 +127,11 @@ class Action(object):
                args.update(kwargs)
 
                # You can never overwrite chrootPath.
-               args.update({
-                       "chrootPath"  : chroot_path,
-               })
+               args["chroot_path"] = chroot_path
 
-               return chroot.do(cmd, **args)
+               # Execute command.
+               shellenv = shell.ShellExecuteEnvironment(command, **args)
+               shellenv.execute()
 
 
 class ActionScript(Action):
@@ -248,9 +247,9 @@ class ActionScript(Action):
                command = [script_file_chroot] + self.args
 
                try:
-                       self.do(command)
+                       self.execute(command)
 
-               except Error, e:
+               except ShellEnvironmentError, e:
                        raise ActionError, _("The scriptlet returned an error:\n%s" % e)
 
                except commandTimeoutExpired:
@@ -398,7 +397,7 @@ class ActionInstall(Action):
                        ldconfig = os.path.join(self.pakfire.path, LDCONFIG[1:])
 
                        if os.path.exists(ldconfig) and os.access(ldconfig, os.X_OK):
-                               self.do(LDCONFIG)
+                               self.execute(LDCONFIG)
 
                        else:
                                log.debug("ldconfig is not present or not executable.")
index a766f166c037979985cf3eee43bae88f14b90c9b..ea74f7da1d441c0f28b2e33470994ebb30e54e74 100644 (file)
@@ -33,12 +33,12 @@ import uuid
 
 import base
 import cgroup
-import chroot
 import logger
 import packages
 import packages.file
 import packages.packager
 import repository
+import shell
 import util
 import _pakfire
 
@@ -204,6 +204,7 @@ class BuildEnviron(object):
                        "enable_ccache"   : True,
                        "enable_icecream" : False,
                        "sign_packages"   : False,
+                       "buildroot_tmpfs" : False,
                }
                #self.settings.update(settings)
 
@@ -271,6 +272,14 @@ class BuildEnviron(object):
                """
                return self.distro.arch
 
+       @property
+       def personality(self):
+               """
+                       Gets the personality from the distribution configuration.
+               """
+               if self.distro:
+                       return self.distro.personality
+
        @property
        def info(self):
                return {
@@ -528,9 +537,7 @@ class BuildEnviron(object):
                        if not os.path.exists(mountpoint):
                                os.makedirs(mountpoint)
 
-                       cmd = "mount -n -t %s %s %s %s" % \
-                               (fs, options, src, mountpoint)
-                       chroot.do(cmd, shell=True)
+                       self.execute_root("mount -n -t %s %s %s %s" % (fs, options, src, mountpoint), shell=True)
 
        def _umountall(self):
                self.log.debug("Umounting environment")
@@ -543,16 +550,20 @@ class BuildEnviron(object):
                for dest in mountpoints:
                        mountpoint = self.chrootPath(dest)
 
-                       chroot.do("umount -n %s" % mountpoint, raiseExc=0, shell=True)
+                       try:
+                               self.execute_root("umount -n %s" % mountpoint, shell=True)
+                       except ShellEnvironmentError:
+                               pass
 
        @property
        def mountpoints(self):
                mountpoints = []
 
-               # Make root as a tmpfs.
-               #mountpoints += [
-               #       ("pakfire_root", "/", "tmpfs", "defaults"),
-               #]
+               # Make root as a tmpfs if enabled.
+               if self.settings.get("buildroot_tmpfs"):
+                       mountpoints += [
+                               ("pakfire_root", "/", "tmpfs", "defaults"),
+                       ]
 
                mountpoints += [
                        # src, dest, fs, options
@@ -669,9 +680,14 @@ class BuildEnviron(object):
                        f.write("\n".join(conf))
                        f.close()
 
-       def do(self, command, shell=True, personality=None, logger=None, *args, **kwargs):
-               ret = None
+       @property
+       def pkg_makefile(self):
+               return os.path.join(self.build_dir, "%s.%s" % (self.pkg.name, MAKEFILE_EXTENSION))
 
+       def execute(self, command, logger=None, **kwargs):
+               """
+                       Executes the given command in the build chroot.
+               """
                # Environment variables
                env = self.environ
 
@@ -682,42 +698,42 @@ class BuildEnviron(object):
                for k, v in sorted(env.items()):
                        self.log.debug("  %s=%s" % (k, v))
 
-               # Update personality it none was set
-               if not personality:
-                       personality = self.distro.personality
-
                # Make every shell to a login shell because we set a lot of
                # environment things there.
-               if shell:
-                       command = ["bash", "--login", "-c", command]
-
-               if not kwargs.has_key("chrootPath"):
-                       kwargs["chrootPath"] = self.chrootPath()
-
-               if not kwargs.has_key("cgroup"):
-                       kwargs["cgroup"] = self.cgroup
-
-               ret = chroot.do(
-                       command,
-                       personality=personality,
-                       shell=False,
-                       env=env,
-                       logger=logger,
-                       *args,
-                       **kwargs
-               )
+               command = ["bash", "--login", "-c", command]
+
+               args = {
+                       "chroot_path" : self.chrootPath(),
+                       "cgroup"      : self.cgroup,
+                       "env"         : env,
+                       "logger"      : logger,
+                       "personality" : self.personality,
+                       "shell"       : False,
+               }
+               args.update(kwargs)
 
-               return ret
+               # Run the shit.
+               shellenv = shell.ShellExecuteEnvironment(command, **args)
+               shellenv.execute()
+
+               return shellenv
+
+       def execute_root(self, command, **kwargs):
+               """
+                       Executes the given command outside the build chroot.
+               """
+               shellenv = shell.ShellExecuteEnvironment(command, **kwargs)
+               shellenv.execute()
+
+               return shellenv
 
        def build(self, install_test=True, prepare=False):
                if not self.pkg:
                        raise BuildError, _("You cannot run a build when no package was given.")
 
                # Search for the package file in build_dir and raise BuildError if it is not present.
-               pkgfile = os.path.join(self.build_dir, "%s.%s" % (self.pkg.name, MAKEFILE_EXTENSION))
-               if not os.path.exists(pkgfile):
-                       raise BuildError, _("Could not find makefile in build root: %s") % pkgfile
-               pkgfile = "/%s" % os.path.relpath(pkgfile, self.chrootPath())
+               if not os.path.exists(self.pkg_makefile):
+                       raise BuildError, _("Could not find makefile in build root: %s") % self.pkg_makefile
 
                # Write pakfire configuration into the chroot.
                self.write_config()
@@ -727,41 +743,49 @@ class BuildEnviron(object):
                        "/usr/lib/pakfire/builder",
                        "--offline",
                        "build",
-                       pkgfile,
+                       "/%s" % os.path.relpath(self.pkg_makefile, self.chrootPath()),
                        "--arch", self.arch,
                        "--nodeps",
                        "--resultdir=/result",
                ]
+               build_command = " ".join(build_command)
 
+               # Check if only the preparation stage should be run.
                if prepare:
                        build_command.append("--prepare")
 
+               error = False
                try:
-                       self.do(" ".join(build_command), logger=self.log)
+                       self.execute(build_command, logger=self.log)
 
                        # Perform the install test after the actual build.
                        if install_test and not prepare:
                                self.install_test()
 
-               except Error:
+               except ShellEnvironmentError:
+                       error = True
+                       self.log.error(_("Build failed"))
+
+               # Catch all other errors.
+               except:
+                       error = True
                        self.log.error(_("Build failed."), exc_info=True)
 
-                       raise BuildError, _("The build command failed. See logfile for details.")
+               else:
+                       # Don't sign packages in prepare mode.
+                       if prepare:
+                               return
 
-               # Don't sign packages in prepare mode.
-               if prepare:
-                       return
+                       # Sign all built packages with the host key (if available).
+                       self.sign_packages()
 
-               # Sign all built packages with the host key (if available).
-               if self.settings.get("sign_packages"):
-                       host_key = self.keyring.get_host_key_id()
-                       assert host_key
+                       # Dump package information.
+                       self.dump()
 
-                       # Do the signing...
-                       self.sign(host_key)
+                       return
 
-               # Dump package information.
-               self.dump()
+               # End here in case of an error.
+               raise BuildError, _("The build command failed. See logfile for details.")
 
        def install_test(self):
                self.log.info(_("Running installation test..."))
@@ -801,8 +825,14 @@ class BuildEnviron(object):
                shell = os.system(command)
                return os.WEXITSTATUS(shell)
 
-       def sign(self, keyfp):
-               assert self.keyring.get_key(keyfp), "Key for signing does not exist"
+       def sign_packages(self, keyfp=None):
+               # Do nothing if signing is not requested.
+               if not self.settings.get("sign_packages"):
+                       return
+
+               # Get key, that should be used for signing.
+               if not keyfp:
+                       keyfp = self.keyring.get_host_key_id()
 
                # Find all files to process.
                files = self.find_result_packages()
@@ -982,42 +1012,33 @@ class Builder(object):
 
                return environ
 
-       def do(self, command, shell=True, *args, **kwargs):
-               try:
-                       logger = kwargs["logger"]
-               except KeyError:
+       def execute(self, command, logger=None, **kwargs):
+               if logger is None:
                        logger = logging.getLogger("pakfire")
-                       kwargs["logger"] = logger
-
-               # Environment variables
-               log.debug("Environment:")
-               for k, v in sorted(self.environ.items()):
-                       log.debug("  %s=%s" % (k, v))
-
-               # Update personality it none was set
-               if not kwargs.has_key("personality"):
-                       kwargs["personality"] = self.distro.personality
-
-               if not kwargs.has_key("cwd"):
-                       kwargs["cwd"] = "/%s" % LOCAL_TMP_PATH
 
                # Make every shell to a login shell because we set a lot of
                # environment things there.
-               if shell:
-                       command = ["bash", "--login", "-c", command]
-                       kwargs["shell"] = False
-
-               kwargs["env"] = self.environ
+               command = ["bash", "--login", "-c", command]
+
+               args = {
+                       "cwd"         : "/%s" % LOCAL_TMP_PATH,
+                       "env"         : self.environ,
+                       "logger"      : logger,
+                       "personality" : self.distro.personality,
+                       "shell"       : False,
+               }
+               args.update(kwargs)
 
                try:
-                       return chroot.do(command, *args, **kwargs)
-               except Error:
-                       if not logger:
-                               logger = logging.getLogger("pakfire")
+                       shellenv = shell.ShellExecuteEnvironment(command, **args)
+                       shellenv.execute()
 
+               except ShellEnvironmentError:
                        logger.error("Command exited with an error: %s" % command)
                        raise
 
+               return shellenv
+
        def run_script(self, script, *args):
                if not script.startswith("/"):
                        script = os.path.join(SCRIPT_DIR, script)
@@ -1031,15 +1052,24 @@ class Builder(object):
 
                # Returns the output of the command, but the output won't get
                # logged.
-               return self.do(cmd, returnOutput=True, logger=None)
+               exe = self.execute(cmd, record_output=True, log_output=False)
+
+               # Return the output of the command.
+               if exe.exitcode == 0:
+                       return exe.output
 
        def create_icecream_toolchain(self):
                try:
-                       out = self.do("icecc --build-native 2>/dev/null", returnOutput=True, cwd="/tmp")
-               except Error:
+                       exe = self.execute(
+                               "icecc --build-native 2>/dev/null",
+                               record_output=True, record_stderr=False,
+                               log_output=False, log_errors=False,
+                               cwd="/tmp",
+                       )
+               except ShellEnvironmentError:
                        return
 
-               for line in out.splitlines():
+               for line in exe.output.splitlines():
                        m = re.match(r"^creating ([a-z0-9]+\.tar\.gz)", line)
                        if m:
                                self._environ["ICECC_VERSION"] = "/tmp/%s" % m.group(1)
@@ -1107,7 +1137,7 @@ class Builder(object):
                log.info(_("Running stage %s:") % stage)
 
                try:
-                       self.do(buildscript, shell=False)
+                       self.execute(buildscript)
 
                finally:
                        # Remove the buildscript.
@@ -1119,15 +1149,15 @@ class Builder(object):
                keep_libs = keep_libs.split()
 
                try:
-                       self.do("%s/remove-static-libs %s %s" % \
+                       self.execute("%s/remove-static-libs %s %s" % \
                                (SCRIPT_DIR, self.buildroot, " ".join(keep_libs)))
-               except Error, e:
+               except ShellEnvironmentError, e:
                        log.warning(_("Could not remove static libraries: %s") % e)
 
        def post_compress_man_pages(self):
                try:
-                       self.do("%s/compress-man-pages %s" % (SCRIPT_DIR, self.buildroot))
-               except Error, e:
+                       self.execute("%s/compress-man-pages %s" % (SCRIPT_DIR, self.buildroot))
+               except ShellEnvironmentError, e:
                        log.warning(_("Compressing man pages did not complete successfully."))
 
        def post_extract_debuginfo(self):
@@ -1146,8 +1176,8 @@ class Builder(object):
                args += options.split()
 
                try:
-                       self.do("%s/extract-debuginfo %s %s" % (SCRIPT_DIR, " ".join(args), self.pkg.buildroot))
-               except Error, e:
+                       self.execute("%s/extract-debuginfo %s %s" % (SCRIPT_DIR, " ".join(args), self.pkg.buildroot))
+               except ShellEnvironmentError, e:
                        log.error(_("Extracting debuginfo did not complete with success. Aborting build."))
                        raise
 
diff --git a/python/pakfire/chroot.py b/python/pakfire/chroot.py
deleted file mode 100644 (file)
index c08bbdb..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/usr/bin/python
-###############################################################################
-#                                                                             #
-# Pakfire - The IPFire package management system                              #
-# Copyright (C) 2011 Pakfire development team                                 #
-#                                                                             #
-# This program is free software: you can redistribute it and/or modify        #
-# it under the terms of the GNU General Public License as published by        #
-# the Free Software Foundation, either version 3 of the License, or           #
-# (at your option) any later version.                                         #
-#                                                                             #
-# This program is distributed in the hope that it will be useful,             #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
-# GNU General Public License for more details.                                #
-#                                                                             #
-# You should have received a copy of the GNU General Public License           #
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
-#                                                                             #
-###############################################################################
-
-import fcntl
-import os
-import select
-import subprocess
-import time
-
-from _pakfire import PERSONALITY_LINUX, PERSONALITY_LINUX32
-
-import pakfire.util as util
-from errors import *
-
-def logOutput(fds, logger, returnOutput=1, start=0, timeout=0):
-       output=""
-       done = 0
-
-       # set all fds to nonblocking
-       for fd in fds:
-               flags = fcntl.fcntl(fd, fcntl.F_GETFL)
-               if not fd.closed:
-                       fcntl.fcntl(fd, fcntl.F_SETFL, flags| os.O_NONBLOCK)
-
-       tail = ""
-       while not done:
-               if (time.time() - start) > timeout and timeout != 0:
-                       done = 1
-                       break
-
-               i_rdy, o_rdy, e_rdy = select.select(fds,[],[],1)
-
-               for s in i_rdy:
-                       # slurp as much input as is ready
-                       input = s.read()
-
-                       if input == "":
-                               done = 1
-                               break
-
-                       if logger is not None:
-                               lines = input.split("\n")
-                               if tail:
-                                       lines[0] = tail + lines[0]
-
-                               # we may not have all of the last line
-                               tail = lines.pop()
-
-                               for line in lines:
-                                       logger.info(line)
-
-                               for h in logger.handlers:
-                                       h.flush()
-
-                       if returnOutput:
-                               output += input
-
-       if tail and logger is not None:
-               logger.info(tail)
-
-       return output
-
-
-def do(command, shell=False, chrootPath=None, cwd=None, timeout=0, raiseExc=True, returnOutput=0, logstderr=1, personality=None, logger=None, env=None, cgroup=None, *args, **kargs):
-       # Save the output of command
-       output = ""
-
-       # Save time when command was started
-       start = time.time()
-
-       # Create preexecution thingy for command
-       preexec = ChildPreExec(personality, chrootPath, cwd)
-
-       if logger:
-               logger.debug("Executing command: %s in %s" % (command, chrootPath or "/"))
-
-       child = None
-
-       if logstderr:
-               stderr = subprocess.PIPE
-       else:
-               stderr = open("/dev/null", "w")
-
-       try:
-               # Create new child process
-               child = subprocess.Popen(
-                       command,
-                       shell=shell,
-                       bufsize=0, close_fds=True, 
-                       stdin=open("/dev/null", "r"), 
-                       stdout=subprocess.PIPE,
-                       stderr=stderr,
-                       preexec_fn = preexec,
-                       env=env
-               )
-
-               # If cgroup is given, attach the subprocess.
-               if cgroup:
-                       cgroup.attach_task(child.pid)
-
-               # use select() to poll for output so we dont block
-               fds = [child.stdout,]
-               if logstderr:
-                       fds.append(child.stderr)
-
-               output = logOutput(fds, logger, returnOutput, start, timeout)
-
-       except:
-               # kill children if they aren't done
-               if child and child.returncode is None:
-                       os.killpg(child.pid, 9)
-               try:
-                       if child:
-                               os.waitpid(child.pid, 0)
-               except:
-                       pass
-               raise
-
-       # wait until child is done, kill it if it passes timeout
-       niceExit=1
-       while child.poll() is None:
-               if (time.time() - start) > timeout and timeout != 0:
-                       niceExit = 0
-                       os.killpg(child.pid, 15)
-               if (time.time() - start) > (timeout+1) and timeout != 0:
-                       niceExit = 0
-                       os.killpg(child.pid, 9)
-
-       if not niceExit:
-               raise commandTimeoutExpired, ("Timeout(%s) expired for command:\n # %s\n%s" % (timeout, command, output))
-
-       if logger:
-               logger.debug("Child returncode was: %s" % str(child.returncode))
-
-       if raiseExc and child.returncode:
-               if returnOutput:
-                       raise Error, ("Command failed: \n # %s\n%s" % (command, output), child.returncode)
-               else:
-                       raise Error, ("Command failed. See logs for output.\n # %s" % (command,), child.returncode)
-
-       return output
-
-class ChildPreExec(object):
-       def __init__(self, personality, chrootPath, cwd):
-               self._personality = personality
-               self.chrootPath  = chrootPath
-               self.cwd = cwd
-
-       @property
-       def personality(self):
-               """
-                       Return personality value if supported.
-                       Otherwise return None.
-               """
-               personality_defs = {
-                       "linux64": PERSONALITY_LINUX,
-                       "linux32": PERSONALITY_LINUX32,
-               }
-
-               try:
-                       return personality_defs[self._personality]
-               except KeyError:
-                       pass
-
-       def __call__(self, *args, **kargs):
-               # Set a new process group
-               os.setpgrp()
-
-               # Set new personality if we got one.
-               if self.personality:
-                       util.personality(self.personality)
-
-               # Change into new root.
-               if self.chrootPath:
-                       os.chdir(self.chrootPath)
-                       os.chroot(self.chrootPath)
-
-               # Change to cwd.
-               if self.cwd:
-                       if not os.path.exists(self.cwd):
-                               os.makedirs(self.cwd)
-
-                       os.chdir(self.cwd)
index 3af257d531be3513ddd1d067f40c8d8890a280fb..3ac7b3c1f1ff7ee334af06248b258e84e8519754 100644 (file)
@@ -86,6 +86,10 @@ class PakfireContainerError(Error):
        message = _("Running pakfire-build in a pakfire container?")
 
 
+class ShellEnvironmentError(Error):
+       pass
+
+
 class SignatureError(Error):
        pass
 
index 38445d833dfd128abbf74e1b5bc528c595c9e292..c31ffd69bcccacbc00f6bd896ffa6bc0dba3bdc3 100644 (file)
@@ -6,7 +6,7 @@ import re
 from pakfire.constants import *
 from pakfire.i18n import _
 
-import pakfire.chroot
+import pakfire.shell
 
 import logging
 #log = logging.getLogger("pakfire.lexer")
@@ -237,17 +237,20 @@ class Lexer(object):
                        return
 
                # Do we need to chroot and change personality?
+               shellenv = pakfire.shell.ShellExecuteEnvironment(command,
+                       shell=True, record_output=True, log_output=False, record_stderr=False)
+
                try:
-                       output = pakfire.chroot.do(command, shell=True, returnOutput=1, logstderr=False)
+                       shellenv.execute()
 
-               except Error:
+               except ShellEnvironmentError:
                        return
 
                # Strip newline.
-               if output:
-                       output = output.rstrip("\n")
+               if shellenv.output:
+                       return shellenv.output.rstrip("\n")
 
-               return output
+               return shellenv.output
 
        def get_var(self, key, default=None, raw=False):
                definitions = {}
index 764cb9cea417efffe025c3ac1a853e29cf988bd0..37d9c5e5c5a55f443fa48e59a9099800742cfd5d 100644 (file)
@@ -36,7 +36,6 @@ import packager
 import logging
 log = logging.getLogger("pakfire")
 
-import pakfire.chroot as chroot
 import pakfire.downloader as downloader
 import pakfire.util as util
 
diff --git a/python/pakfire/shell.py b/python/pakfire/shell.py
new file mode 100644 (file)
index 0000000..e1f7600
--- /dev/null
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2012 Pakfire development team                                 #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import fcntl
+import os
+import select
+import subprocess
+import time
+
+from _pakfire import PERSONALITY_LINUX, PERSONALITY_LINUX32
+
+from pakfire.i18n import _
+import pakfire.util as util
+from errors import *
+
+class ShellExecuteEnvironment(object):
+       def __init__(self, command, cwd=None, chroot_path=None, personality=None, shell=False, timeout=0, env=None,
+                       cgroup=None, logger=None, log_output=True, log_errors=True, record_output=False, record_stdout=True, record_stderr=True):
+               # The given command that should be executed.
+               self.command = command
+
+               # Change into current working dir.
+               self.cwd = cwd
+
+               # Chroot into this directory.
+               self.chroot_path = chroot_path
+
+               # The logger where all the output goes.
+               self.logger = logger
+
+               # Set timeout.
+               self.timeout = timeout
+
+               # Personality.
+               self.personality = personality
+
+               # Shell.
+               self.shell = shell
+               self.env = env
+
+               # cgroup to which the newly created process should be attached.
+               self.cgroup = cgroup
+
+               # Timestamp, when execution has been started and ended.
+               self.time_start = None
+               self.time_end = None
+
+               # Output, that has to be returned.
+               self.output = ""
+               self.record_output = record_output
+               self.record_stdout = record_stdout
+               self.record_stderr = record_stderr
+
+               # Log the output and errors?
+               self.log_errors = log_errors
+               self.log_output = log_output
+
+               # Exit code of command.
+               self.exitcode = None
+
+       def execute(self):
+               # Save start time.
+               self.time_start = time.time()
+
+               if self.logger:
+                       self.logger.debug(_("Executing command: %s in %s") % (self.command, self.chroot_path or "/"))
+
+               child = None
+               try:
+                       # Create new child process
+                       child = self.create_subprocess()
+
+                       # Record the output.
+                       self.tee_log(child)
+               except:
+                       # In case there has been an error, kill children if they aren't done
+                       if child and child.returncode is None:
+                               os.killpg(child.pid, 9)
+
+                       try:
+                               if child:
+                                       os.waitpid(child.pid, 0)
+                       except:
+                               pass
+
+                       # Raise original exception.
+                       raise
+
+               finally:
+                       # Save end time.
+                       self.time_end = time.time()
+
+               # wait until child is done, kill it if it passes timeout
+               nice_exit = True
+               while child.poll() is None:
+                       if self.timeout_has_been_exceeded():
+                               nice_exit = False
+                               os.killpg(child.pid, 15)
+
+                       if self.timeout_has_been_exceeded(3):
+                               nice_exit = False
+                               os.killpg(child.pid, 9)
+
+               if not nice_exit:
+                       raise commandTimeoutExpired, (_("Command exceeded timeout (%(timeout)d): %(command)s") % (self.timeout, self.command))
+
+               # Save exitcode.
+               self.exitcode = child.returncode
+
+               if self.logger:
+                       self.logger.debug(_("Child returncode was: %s") % self.exitcode)
+
+               if self.exitcode and self.log_errors:
+                       raise ShellEnvironmentError, (_("Command failed: %s") % self.command, self.exitcode)
+
+               return self.exitcode
+
+       def create_subprocess(self):
+               # Create preexecution thingy for command
+               preexec_fn = ChildPreExec(self.personality, self.chroot_path, self.cwd)
+
+               kwargs = {
+                       "bufsize"    : 0,
+                       "close_fds"  : True,
+                       "env"        : self.env,
+                       "preexec_fn" : preexec_fn,
+                       "shell"      : self.shell,
+               }
+
+               # File descriptors.
+               stdin = open("/dev/null", "r")
+
+               if self.record_stdout:
+                       stdout = subprocess.PIPE
+               else:
+                       stdout = open("/dev/null", "w")
+
+               if self.record_stderr:
+                       stderr = subprocess.PIPE
+               else:
+                       stderr = open("/dev/null", "w")
+
+               kwargs.update({
+                       "stdin"  : stdin,
+                       "stdout" : stdout,
+                       "stderr" : stderr,
+               })
+
+               child = subprocess.Popen(self.command, **kwargs)
+
+               # If cgroup is given, attach the subprocess.
+               if self.cgroup:
+                       self.cgroup.attach_task(child.pid)
+
+               return child
+
+       def timeout_has_been_exceeded(self, offset=0):
+               """
+                       Returns true when the command has been running
+                       for more than 'timeout' seconds.
+               """
+               # If no timeout has been configured, it can never be exceeded.
+               if not self.timeout:
+                       return False
+
+               # Check if the command has already been started.
+               if not self.time_start:
+                       return False
+
+               return (time.time() - self.time_start - offset) > self.timeout
+
+       def tee_log(self, child):
+               fds = []
+
+               if self.record_stdout:
+                       fds.append(child.stdout)
+
+               if self.record_stderr:
+                       fds.append(child.stderr)
+
+               # Set all file descriptors as non-blocking.
+               for fd in fds:
+                       # Skip already closed file descriptors.
+                       if fd.closed:
+                               continue
+
+                       flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+                       fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+               done = False
+               tail = ""
+               while not done:
+                       # Check if timeout has been hit.
+                       if self.timeout_has_been_exceeded():
+                               done = True
+                               break
+
+                       # Start the select() call.
+                       i_rdy, o_rdy, e_rdy = select.select(fds, [], [], 1)
+
+                       # Process output.
+                       for s in i_rdy:
+                               # Read as much data as possible.
+                               input = s.read()
+
+                               if input == "":
+                                       done = True
+                                       break
+
+                               if self.record_output:
+                                       self.output += input
+
+                               if self.log_output and self.logger:
+                                       lines = input.split("\n")
+                                       if tail:
+                                               lines[0] = tail + lines[0]
+
+                                       # We may not have got all the characters of the last line.
+                                       tail = lines.pop()
+
+                                       for line in lines:
+                                               self.logger.info(line)
+
+                                       # Flush all handlers of the logger.
+                                       for h in self.logger.handlers:
+                                               h.flush()
+
+               # Log the rest of the last line.
+               if tail and self.log_output and self.logger:
+                       self.logger.info(tail)
+
+
+class ChildPreExec(object):
+       def __init__(self, personality, chroot_path, cwd):
+               self._personality = personality
+               self.chroot_path  = chroot_path
+               self.cwd = cwd
+
+       @property
+       def personality(self):
+               """
+                       Return personality value if supported.
+                       Otherwise return None.
+               """
+               personality_defs = {
+                       "linux64": PERSONALITY_LINUX,
+                       "linux32": PERSONALITY_LINUX32,
+               }
+
+               try:
+                       return personality_defs[self._personality]
+               except KeyError:
+                       pass
+
+       def __call__(self, *args, **kargs):
+               # Set a new process group
+               os.setpgrp()
+
+               # Set new personality if we got one.
+               if self.personality:
+                       util.personality(self.personality)
+
+               # Change into new root.
+               if self.chroot_path:
+                       os.chdir(self.chroot_path)
+                       os.chroot(self.chroot_path)
+
+               # Change to cwd.
+               if self.cwd:
+                       if not os.path.exists(self.cwd):
+                               os.makedirs(self.cwd)
+
+                       os.chdir(self.cwd)