From a4ccecf606d61375f18d3acbd416bbcc1f7a2a7c Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Tue, 11 Jan 2011 13:10:54 +0100
Subject: [PATCH] utils: Move process-related code into separate file

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 Makefile.am                                 |   2 +
 lib/ssh.py                                  |   6 +-
 lib/utils/__init__.py                       | 935 +------------------
 lib/utils/io.py                             |   2 +-
 lib/utils/process.py                        | 984 ++++++++++++++++++++
 test/ganeti.client.gnt_instance_unittest.py |   2 +-
 test/ganeti.utils.process_unittest.py       | 600 ++++++++++++
 test/ganeti.utils_unittest.py               | 551 -----------
 8 files changed, 1592 insertions(+), 1490 deletions(-)
 create mode 100644 lib/utils/process.py
 create mode 100755 test/ganeti.utils.process_unittest.py

diff --git a/Makefile.am b/Makefile.am
index 35c0a20c0..458545f6b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -220,6 +220,7 @@ utils_PYTHON = \
 	lib/utils/log.py \
 	lib/utils/mlock.py \
 	lib/utils/nodesetup.py \
+	lib/utils/process.py \
 	lib/utils/retry.py \
 	lib/utils/text.py \
 	lib/utils/wrapper.py \
@@ -495,6 +496,7 @@ python_tests = \
 	test/ganeti.utils.io_unittest.py \
 	test/ganeti.utils.mlock_unittest.py \
 	test/ganeti.utils.nodesetup_unittest.py \
+	test/ganeti.utils.process_unittest.py \
 	test/ganeti.utils.retry_unittest.py \
 	test/ganeti.utils.text_unittest.py \
 	test/ganeti.utils.wrapper_unittest.py \
diff --git a/lib/ssh.py b/lib/ssh.py
index 84ae69249..ee8aba6ee 100644
--- a/lib/ssh.py
+++ b/lib/ssh.py
@@ -106,7 +106,7 @@ class SshRunner:
     @param quiet: whether to enable -q to ssh
 
     @rtype: list
-    @return: the list of options ready to use in L{utils.RunCmd}
+    @return: the list of options ready to use in L{utils.process.RunCmd}
 
     """
     options = [
@@ -194,8 +194,8 @@ class SshRunner:
 
     Args: see SshRunner.BuildCmd.
 
-    @rtype: L{utils.RunResult}
-    @return: the result as from L{utils.RunCmd()}
+    @rtype: L{utils.process.RunResult}
+    @return: the result as from L{utils.process.RunCmd()}
 
     """
     return utils.RunCmd(self.BuildCmd(*args, **kwargs))
diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py
index 810924e7a..f31d52909 100644
--- a/lib/utils/__init__.py
+++ b/lib/utils/__init__.py
@@ -63,11 +63,9 @@ from ganeti.utils.filelock import * # pylint: disable-msg=W0401
 from ganeti.utils.io import * # pylint: disable-msg=W0401
 from ganeti.utils.x509 import * # pylint: disable-msg=W0401
 from ganeti.utils.nodesetup import * # pylint: disable-msg=W0401
+from ganeti.utils.process import * # pylint: disable-msg=W0401
 
 
-#: when set to True, L{RunCmd} is disabled
-_no_fork = False
-
 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
 
 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
@@ -75,620 +73,10 @@ _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
 UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
                      '[a-f0-9]{4}-[a-f0-9]{12}$')
 
-(_TIMEOUT_NONE,
- _TIMEOUT_TERM,
- _TIMEOUT_KILL) = range(3)
-
 #: Shell param checker regexp
 _SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
 
 
-def DisableFork():
-  """Disables the use of fork(2).
-
-  """
-  global _no_fork # pylint: disable-msg=W0603
-
-  _no_fork = True
-
-
-class RunResult(object):
-  """Holds the result of running external programs.
-
-  @type exit_code: int
-  @ivar exit_code: the exit code of the program, or None (if the program
-      didn't exit())
-  @type signal: int or None
-  @ivar signal: the signal that caused the program to finish, or None
-      (if the program wasn't terminated by a signal)
-  @type stdout: str
-  @ivar stdout: the standard output of the program
-  @type stderr: str
-  @ivar stderr: the standard error of the program
-  @type failed: boolean
-  @ivar failed: True in case the program was
-      terminated by a signal or exited with a non-zero exit code
-  @ivar fail_reason: a string detailing the termination reason
-
-  """
-  __slots__ = ["exit_code", "signal", "stdout", "stderr",
-               "failed", "fail_reason", "cmd"]
-
-
-  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
-               timeout):
-    self.cmd = cmd
-    self.exit_code = exit_code
-    self.signal = signal_
-    self.stdout = stdout
-    self.stderr = stderr
-    self.failed = (signal_ is not None or exit_code != 0)
-
-    fail_msgs = []
-    if self.signal is not None:
-      fail_msgs.append("terminated by signal %s" % self.signal)
-    elif self.exit_code is not None:
-      fail_msgs.append("exited with exit code %s" % self.exit_code)
-    else:
-      fail_msgs.append("unable to determine termination reason")
-
-    if timeout_action == _TIMEOUT_TERM:
-      fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
-    elif timeout_action == _TIMEOUT_KILL:
-      fail_msgs.append(("force termination after timeout of %.2f seconds"
-                        " and linger for another %.2f seconds") %
-                       (timeout, constants.CHILD_LINGER_TIMEOUT))
-
-    if fail_msgs and self.failed:
-      self.fail_reason = CommaJoin(fail_msgs)
-
-    if self.failed:
-      logging.debug("Command '%s' failed (%s); output: %s",
-                    self.cmd, self.fail_reason, self.output)
-
-  def _GetOutput(self):
-    """Returns the combined stdout and stderr for easier usage.
-
-    """
-    return self.stdout + self.stderr
-
-  output = property(_GetOutput, None, None, "Return full output")
-
-
-def _BuildCmdEnvironment(env, reset):
-  """Builds the environment for an external program.
-
-  """
-  if reset:
-    cmd_env = {}
-  else:
-    cmd_env = os.environ.copy()
-    cmd_env["LC_ALL"] = "C"
-
-  if env is not None:
-    cmd_env.update(env)
-
-  return cmd_env
-
-
-def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
-           interactive=False, timeout=None):
-  """Execute a (shell) command.
-
-  The command should not read from its standard input, as it will be
-  closed.
-
-  @type cmd: string or list
-  @param cmd: Command to run
-  @type env: dict
-  @param env: Additional environment variables
-  @type output: str
-  @param output: if desired, the output of the command can be
-      saved in a file instead of the RunResult instance; this
-      parameter denotes the file name (if not None)
-  @type cwd: string
-  @param cwd: if specified, will be used as the working
-      directory for the command; the default will be /
-  @type reset_env: boolean
-  @param reset_env: whether to reset or keep the default os environment
-  @type interactive: boolean
-  @param interactive: weather we pipe stdin, stdout and stderr
-                      (default behaviour) or run the command interactive
-  @type timeout: int
-  @param timeout: If not None, timeout in seconds until child process gets
-                  killed
-  @rtype: L{RunResult}
-  @return: RunResult instance
-  @raise errors.ProgrammerError: if we call this when forks are disabled
-
-  """
-  if _no_fork:
-    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
-
-  if output and interactive:
-    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
-                                 " not be provided at the same time")
-
-  if isinstance(cmd, basestring):
-    strcmd = cmd
-    shell = True
-  else:
-    cmd = [str(val) for val in cmd]
-    strcmd = ShellQuoteArgs(cmd)
-    shell = False
-
-  if output:
-    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
-  else:
-    logging.debug("RunCmd %s", strcmd)
-
-  cmd_env = _BuildCmdEnvironment(env, reset_env)
-
-  try:
-    if output is None:
-      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
-                                                     interactive, timeout)
-    else:
-      timeout_action = _TIMEOUT_NONE
-      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
-      out = err = ""
-  except OSError, err:
-    if err.errno == errno.ENOENT:
-      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
-                               (strcmd, err))
-    else:
-      raise
-
-  if status >= 0:
-    exitcode = status
-    signal_ = None
-  else:
-    exitcode = None
-    signal_ = -status
-
-  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
-
-
-def SetupDaemonEnv(cwd="/", umask=077):
-  """Setup a daemon's environment.
-
-  This should be called between the first and second fork, due to
-  setsid usage.
-
-  @param cwd: the directory to which to chdir
-  @param umask: the umask to setup
-
-  """
-  os.chdir(cwd)
-  os.umask(umask)
-  os.setsid()
-
-
-def SetupDaemonFDs(output_file, output_fd):
-  """Setups up a daemon's file descriptors.
-
-  @param output_file: if not None, the file to which to redirect
-      stdout/stderr
-  @param output_fd: if not None, the file descriptor for stdout/stderr
-
-  """
-  # check that at most one is defined
-  assert [output_file, output_fd].count(None) >= 1
-
-  # Open /dev/null (read-only, only for stdin)
-  devnull_fd = os.open(os.devnull, os.O_RDONLY)
-
-  if output_fd is not None:
-    pass
-  elif output_file is not None:
-    # Open output file
-    try:
-      output_fd = os.open(output_file,
-                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
-    except EnvironmentError, err:
-      raise Exception("Opening output file failed: %s" % err)
-  else:
-    output_fd = os.open(os.devnull, os.O_WRONLY)
-
-  # Redirect standard I/O
-  os.dup2(devnull_fd, 0)
-  os.dup2(output_fd, 1)
-  os.dup2(output_fd, 2)
-
-
-def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
-                pidfile=None):
-  """Start a daemon process after forking twice.
-
-  @type cmd: string or list
-  @param cmd: Command to run
-  @type env: dict
-  @param env: Additional environment variables
-  @type cwd: string
-  @param cwd: Working directory for the program
-  @type output: string
-  @param output: Path to file in which to save the output
-  @type output_fd: int
-  @param output_fd: File descriptor for output
-  @type pidfile: string
-  @param pidfile: Process ID file
-  @rtype: int
-  @return: Daemon process ID
-  @raise errors.ProgrammerError: if we call this when forks are disabled
-
-  """
-  if _no_fork:
-    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
-                                 " disabled")
-
-  if output and not (bool(output) ^ (output_fd is not None)):
-    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
-                                 " specified")
-
-  if isinstance(cmd, basestring):
-    cmd = ["/bin/sh", "-c", cmd]
-
-  strcmd = ShellQuoteArgs(cmd)
-
-  if output:
-    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
-  else:
-    logging.debug("StartDaemon %s", strcmd)
-
-  cmd_env = _BuildCmdEnvironment(env, False)
-
-  # Create pipe for sending PID back
-  (pidpipe_read, pidpipe_write) = os.pipe()
-  try:
-    try:
-      # Create pipe for sending error messages
-      (errpipe_read, errpipe_write) = os.pipe()
-      try:
-        try:
-          # First fork
-          pid = os.fork()
-          if pid == 0:
-            try:
-              # Child process, won't return
-              _StartDaemonChild(errpipe_read, errpipe_write,
-                                pidpipe_read, pidpipe_write,
-                                cmd, cmd_env, cwd,
-                                output, output_fd, pidfile)
-            finally:
-              # Well, maybe child process failed
-              os._exit(1) # pylint: disable-msg=W0212
-        finally:
-          CloseFdNoError(errpipe_write)
-
-        # Wait for daemon to be started (or an error message to
-        # arrive) and read up to 100 KB as an error message
-        errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
-      finally:
-        CloseFdNoError(errpipe_read)
-    finally:
-      CloseFdNoError(pidpipe_write)
-
-    # Read up to 128 bytes for PID
-    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
-  finally:
-    CloseFdNoError(pidpipe_read)
-
-  # Try to avoid zombies by waiting for child process
-  try:
-    os.waitpid(pid, 0)
-  except OSError:
-    pass
-
-  if errormsg:
-    raise errors.OpExecError("Error when starting daemon process: %r" %
-                             errormsg)
-
-  try:
-    return int(pidtext)
-  except (ValueError, TypeError), err:
-    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
-                             (pidtext, err))
-
-
-def _StartDaemonChild(errpipe_read, errpipe_write,
-                      pidpipe_read, pidpipe_write,
-                      args, env, cwd,
-                      output, fd_output, pidfile):
-  """Child process for starting daemon.
-
-  """
-  try:
-    # Close parent's side
-    CloseFdNoError(errpipe_read)
-    CloseFdNoError(pidpipe_read)
-
-    # First child process
-    SetupDaemonEnv()
-
-    # And fork for the second time
-    pid = os.fork()
-    if pid != 0:
-      # Exit first child process
-      os._exit(0) # pylint: disable-msg=W0212
-
-    # Make sure pipe is closed on execv* (and thereby notifies
-    # original process)
-    SetCloseOnExecFlag(errpipe_write, True)
-
-    # List of file descriptors to be left open
-    noclose_fds = [errpipe_write]
-
-    # Open PID file
-    if pidfile:
-      fd_pidfile = WritePidFile(pidfile)
-
-      # Keeping the file open to hold the lock
-      noclose_fds.append(fd_pidfile)
-
-      SetCloseOnExecFlag(fd_pidfile, False)
-    else:
-      fd_pidfile = None
-
-    SetupDaemonFDs(output, fd_output)
-
-    # Send daemon PID to parent
-    RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
-
-    # Close all file descriptors except stdio and error message pipe
-    CloseFDs(noclose_fds=noclose_fds)
-
-    # Change working directory
-    os.chdir(cwd)
-
-    if env is None:
-      os.execvp(args[0], args)
-    else:
-      os.execvpe(args[0], args, env)
-  except: # pylint: disable-msg=W0702
-    try:
-      # Report errors to original process
-      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
-    except: # pylint: disable-msg=W0702
-      # Ignore errors in error handling
-      pass
-
-  os._exit(1) # pylint: disable-msg=W0212
-
-
-def WriteErrorToFD(fd, err):
-  """Possibly write an error message to a fd.
-
-  @type fd: None or int (file descriptor)
-  @param fd: if not None, the error will be written to this fd
-  @param err: string, the error message
-
-  """
-  if fd is None:
-    return
-
-  if not err:
-    err = "<unknown error>"
-
-  RetryOnSignal(os.write, fd, err)
-
-
-def _CheckIfAlive(child):
-  """Raises L{RetryAgain} if child is still alive.
-
-  @raises RetryAgain: If child is still alive
-
-  """
-  if child.poll() is None:
-    raise RetryAgain()
-
-
-def _WaitForProcess(child, timeout):
-  """Waits for the child to terminate or until we reach timeout.
-
-  """
-  try:
-    Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
-  except RetryTimeout:
-    pass
-
-
-def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
-                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
-  """Run a command and return its output.
-
-  @type  cmd: string or list
-  @param cmd: Command to run
-  @type env: dict
-  @param env: The environment to use
-  @type via_shell: bool
-  @param via_shell: if we should run via the shell
-  @type cwd: string
-  @param cwd: the working directory for the program
-  @type interactive: boolean
-  @param interactive: Run command interactive (without piping)
-  @type timeout: int
-  @param timeout: Timeout after the programm gets terminated
-  @rtype: tuple
-  @return: (out, err, status)
-
-  """
-  poller = select.poll()
-
-  stderr = subprocess.PIPE
-  stdout = subprocess.PIPE
-  stdin = subprocess.PIPE
-
-  if interactive:
-    stderr = stdout = stdin = None
-
-  child = subprocess.Popen(cmd, shell=via_shell,
-                           stderr=stderr,
-                           stdout=stdout,
-                           stdin=stdin,
-                           close_fds=True, env=env,
-                           cwd=cwd)
-
-  out = StringIO()
-  err = StringIO()
-
-  linger_timeout = None
-
-  if timeout is None:
-    poll_timeout = None
-  else:
-    poll_timeout = RunningTimeout(timeout, True).Remaining
-
-  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
-                 (cmd, child.pid))
-  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
-                (cmd, child.pid))
-
-  timeout_action = _TIMEOUT_NONE
-
-  if not interactive:
-    child.stdin.close()
-    poller.register(child.stdout, select.POLLIN)
-    poller.register(child.stderr, select.POLLIN)
-    fdmap = {
-      child.stdout.fileno(): (out, child.stdout),
-      child.stderr.fileno(): (err, child.stderr),
-      }
-    for fd in fdmap:
-      SetNonblockFlag(fd, True)
-
-    while fdmap:
-      if poll_timeout:
-        pt = poll_timeout() * 1000
-        if pt < 0:
-          if linger_timeout is None:
-            logging.warning(msg_timeout)
-            if child.poll() is None:
-              timeout_action = _TIMEOUT_TERM
-              IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
-            linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
-          pt = linger_timeout() * 1000
-          if pt < 0:
-            break
-      else:
-        pt = None
-
-      pollresult = RetryOnSignal(poller.poll, pt)
-
-      for fd, event in pollresult:
-        if event & select.POLLIN or event & select.POLLPRI:
-          data = fdmap[fd][1].read()
-          # no data from read signifies EOF (the same as POLLHUP)
-          if not data:
-            poller.unregister(fd)
-            del fdmap[fd]
-            continue
-          fdmap[fd][0].write(data)
-        if (event & select.POLLNVAL or event & select.POLLHUP or
-            event & select.POLLERR):
-          poller.unregister(fd)
-          del fdmap[fd]
-
-  if timeout is not None:
-    assert callable(poll_timeout)
-
-    # We have no I/O left but it might still run
-    if child.poll() is None:
-      _WaitForProcess(child, poll_timeout())
-
-    # Terminate if still alive after timeout
-    if child.poll() is None:
-      if linger_timeout is None:
-        logging.warning(msg_timeout)
-        timeout_action = _TIMEOUT_TERM
-        IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
-        lt = _linger_timeout
-      else:
-        lt = linger_timeout()
-      _WaitForProcess(child, lt)
-
-    # Okay, still alive after timeout and linger timeout? Kill it!
-    if child.poll() is None:
-      timeout_action = _TIMEOUT_KILL
-      logging.warning(msg_linger)
-      IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
-
-  out = out.getvalue()
-  err = err.getvalue()
-
-  status = child.wait()
-  return out, err, status, timeout_action
-
-
-def _RunCmdFile(cmd, env, via_shell, output, cwd):
-  """Run a command and save its output to a file.
-
-  @type  cmd: string or list
-  @param cmd: Command to run
-  @type env: dict
-  @param env: The environment to use
-  @type via_shell: bool
-  @param via_shell: if we should run via the shell
-  @type output: str
-  @param output: the filename in which to save the output
-  @type cwd: string
-  @param cwd: the working directory for the program
-  @rtype: int
-  @return: the exit status
-
-  """
-  fh = open(output, "a")
-  try:
-    child = subprocess.Popen(cmd, shell=via_shell,
-                             stderr=subprocess.STDOUT,
-                             stdout=fh,
-                             stdin=subprocess.PIPE,
-                             close_fds=True, env=env,
-                             cwd=cwd)
-
-    child.stdin.close()
-    status = child.wait()
-  finally:
-    fh.close()
-  return status
-
-
-def RunParts(dir_name, env=None, reset_env=False):
-  """Run Scripts or programs in a directory
-
-  @type dir_name: string
-  @param dir_name: absolute path to a directory
-  @type env: dict
-  @param env: The environment to use
-  @type reset_env: boolean
-  @param reset_env: whether to reset or keep the default os environment
-  @rtype: list of tuples
-  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
-
-  """
-  rr = []
-
-  try:
-    dir_contents = ListVisibleFiles(dir_name)
-  except OSError, err:
-    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
-    return rr
-
-  for relname in sorted(dir_contents):
-    fname = PathJoin(dir_name, relname)
-    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
-            constants.EXT_PLUGIN_MASK.match(relname) is not None):
-      rr.append((relname, constants.RUNPARTS_SKIP, None))
-    else:
-      try:
-        result = RunCmd([fname], env=env, reset_env=reset_env)
-      except Exception, err: # pylint: disable-msg=W0703
-        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
-      else:
-        rr.append((relname, constants.RUNPARTS_RUN, result))
-
-  return rr
-
-
 def ForceDictType(target, key_types, allowed_values=None):
   """Force the values of a dict to have certain types.
 
@@ -758,136 +146,6 @@ def ForceDictType(target, key_types, allowed_values=None):
         raise errors.TypeEnforcementError(msg)
 
 
-def _GetProcStatusPath(pid):
-  """Returns the path for a PID's proc status file.
-
-  @type pid: int
-  @param pid: Process ID
-  @rtype: string
-
-  """
-  return "/proc/%d/status" % pid
-
-
-def IsProcessAlive(pid):
-  """Check if a given pid exists on the system.
-
-  @note: zombie status is not handled, so zombie processes
-      will be returned as alive
-  @type pid: int
-  @param pid: the process ID to check
-  @rtype: boolean
-  @return: True if the process exists
-
-  """
-  def _TryStat(name):
-    try:
-      os.stat(name)
-      return True
-    except EnvironmentError, err:
-      if err.errno in (errno.ENOENT, errno.ENOTDIR):
-        return False
-      elif err.errno == errno.EINVAL:
-        raise RetryAgain(err)
-      raise
-
-  assert isinstance(pid, int), "pid must be an integer"
-  if pid <= 0:
-    return False
-
-  # /proc in a multiprocessor environment can have strange behaviors.
-  # Retry the os.stat a few times until we get a good result.
-  try:
-    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
-                 args=[_GetProcStatusPath(pid)])
-  except RetryTimeout, err:
-    err.RaiseInner()
-
-
-def _ParseSigsetT(sigset):
-  """Parse a rendered sigset_t value.
-
-  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
-  function.
-
-  @type sigset: string
-  @param sigset: Rendered signal set from /proc/$pid/status
-  @rtype: set
-  @return: Set of all enabled signal numbers
-
-  """
-  result = set()
-
-  signum = 0
-  for ch in reversed(sigset):
-    chv = int(ch, 16)
-
-    # The following could be done in a loop, but it's easier to read and
-    # understand in the unrolled form
-    if chv & 1:
-      result.add(signum + 1)
-    if chv & 2:
-      result.add(signum + 2)
-    if chv & 4:
-      result.add(signum + 3)
-    if chv & 8:
-      result.add(signum + 4)
-
-    signum += 4
-
-  return result
-
-
-def _GetProcStatusField(pstatus, field):
-  """Retrieves a field from the contents of a proc status file.
-
-  @type pstatus: string
-  @param pstatus: Contents of /proc/$pid/status
-  @type field: string
-  @param field: Name of field whose value should be returned
-  @rtype: string
-
-  """
-  for line in pstatus.splitlines():
-    parts = line.split(":", 1)
-
-    if len(parts) < 2 or parts[0] != field:
-      continue
-
-    return parts[1].strip()
-
-  return None
-
-
-def IsProcessHandlingSignal(pid, signum, status_path=None):
-  """Checks whether a process is handling a signal.
-
-  @type pid: int
-  @param pid: Process ID
-  @type signum: int
-  @param signum: Signal number
-  @rtype: bool
-
-  """
-  if status_path is None:
-    status_path = _GetProcStatusPath(pid)
-
-  try:
-    proc_status = ReadFile(status_path)
-  except EnvironmentError, err:
-    # In at least one case, reading /proc/$pid/status failed with ESRCH.
-    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
-      return False
-    raise
-
-  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
-  if sigcgt is None:
-    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
-
-  # Now check whether signal is handled
-  return signum in _ParseSigsetT(sigcgt)
-
-
 def ValidateServiceName(name):
   """Validate the given service name.
 
@@ -1205,87 +463,6 @@ def WaitForFdCondition(fdobj, event, timeout):
   return result
 
 
-def CloseFDs(noclose_fds=None):
-  """Close file descriptors.
-
-  This closes all file descriptors above 2 (i.e. except
-  stdin/out/err).
-
-  @type noclose_fds: list or None
-  @param noclose_fds: if given, it denotes a list of file descriptor
-      that should not be closed
-
-  """
-  # Default maximum for the number of available file descriptors.
-  if 'SC_OPEN_MAX' in os.sysconf_names:
-    try:
-      MAXFD = os.sysconf('SC_OPEN_MAX')
-      if MAXFD < 0:
-        MAXFD = 1024
-    except OSError:
-      MAXFD = 1024
-  else:
-    MAXFD = 1024
-  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
-  if (maxfd == resource.RLIM_INFINITY):
-    maxfd = MAXFD
-
-  # Iterate through and close all file descriptors (except the standard ones)
-  for fd in range(3, maxfd):
-    if noclose_fds and fd in noclose_fds:
-      continue
-    CloseFdNoError(fd)
-
-
-def Daemonize(logfile):
-  """Daemonize the current process.
-
-  This detaches the current process from the controlling terminal and
-  runs it in the background as a daemon.
-
-  @type logfile: str
-  @param logfile: the logfile to which we should redirect stdout/stderr
-  @rtype: int
-  @return: the value zero
-
-  """
-  # pylint: disable-msg=W0212
-  # yes, we really want os._exit
-
-  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
-  # least abstract the pipe functionality between them
-
-  # Create pipe for sending error messages
-  (rpipe, wpipe) = os.pipe()
-
-  # this might fail
-  pid = os.fork()
-  if (pid == 0):  # The first child.
-    SetupDaemonEnv()
-
-    # this might fail
-    pid = os.fork() # Fork a second child.
-    if (pid == 0):  # The second child.
-      CloseFdNoError(rpipe)
-    else:
-      # exit() or _exit()?  See below.
-      os._exit(0) # Exit parent (the first child) of the second child.
-  else:
-    CloseFdNoError(wpipe)
-    # Wait for daemon to be started (or an error message to
-    # arrive) and read up to 100 KB as an error message
-    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
-    if errormsg:
-      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
-      rcode = 1
-    else:
-      rcode = 0
-    os._exit(rcode) # Exit parent of the first child.
-
-  SetupDaemonFDs(logfile, None)
-  return wpipe
-
-
 def EnsureDaemon(name):
   """Check for and start daemon if not alive.
 
@@ -1312,69 +489,6 @@ def StopDaemon(name):
   return True
 
 
-def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
-                waitpid=False):
-  """Kill a process given by its pid.
-
-  @type pid: int
-  @param pid: The PID to terminate.
-  @type signal_: int
-  @param signal_: The signal to send, by default SIGTERM
-  @type timeout: int
-  @param timeout: The timeout after which, if the process is still alive,
-                  a SIGKILL will be sent. If not positive, no such checking
-                  will be done
-  @type waitpid: boolean
-  @param waitpid: If true, we should waitpid on this process after
-      sending signals, since it's our own child and otherwise it
-      would remain as zombie
-
-  """
-  def _helper(pid, signal_, wait):
-    """Simple helper to encapsulate the kill/waitpid sequence"""
-    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
-      try:
-        os.waitpid(pid, os.WNOHANG)
-      except OSError:
-        pass
-
-  if pid <= 0:
-    # kill with pid=0 == suicide
-    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
-
-  if not IsProcessAlive(pid):
-    return
-
-  _helper(pid, signal_, waitpid)
-
-  if timeout <= 0:
-    return
-
-  def _CheckProcess():
-    if not IsProcessAlive(pid):
-      return
-
-    try:
-      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
-    except OSError:
-      raise RetryAgain()
-
-    if result_pid > 0:
-      return
-
-    raise RetryAgain()
-
-  try:
-    # Wait up to $timeout seconds
-    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
-  except RetryTimeout:
-    pass
-
-  if IsProcessAlive(pid):
-    # Kill process if it's still alive
-    _helper(pid, signal.SIGKILL, waitpid)
-
-
 def CheckVolumeGroupSize(vglist, vgname, minsize):
   """Checks if the volume group list is valid.
 
@@ -1483,53 +597,6 @@ def GetMounts(filename=constants.PROC_MOUNTS):
   return data
 
 
-def RunInSeparateProcess(fn, *args):
-  """Runs a function in a separate process.
-
-  Note: Only boolean return values are supported.
-
-  @type fn: callable
-  @param fn: Function to be called
-  @rtype: bool
-  @return: Function's result
-
-  """
-  pid = os.fork()
-  if pid == 0:
-    # Child process
-    try:
-      # In case the function uses temporary files
-      ResetTempfileModule()
-
-      # Call function
-      result = int(bool(fn(*args)))
-      assert result in (0, 1)
-    except: # pylint: disable-msg=W0702
-      logging.exception("Error while calling function in separate process")
-      # 0 and 1 are reserved for the return value
-      result = 33
-
-    os._exit(result) # pylint: disable-msg=W0212
-
-  # Parent process
-
-  # Avoid zombies and check exit code
-  (_, status) = os.waitpid(pid, 0)
-
-  if os.WIFSIGNALED(status):
-    exitcode = None
-    signum = os.WTERMSIG(status)
-  else:
-    exitcode = os.WEXITSTATUS(status)
-    signum = None
-
-  if not (exitcode in (0, 1) and signum is None):
-    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
-                              (exitcode, signum))
-
-  return bool(exitcode)
-
-
 def SignalHandled(signums):
   """Signal Handled decoration.
 
diff --git a/lib/utils/io.py b/lib/utils/io.py
index eef7bb762..1e99d8ab6 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -569,7 +569,7 @@ def ReadPidFile(pidfile):
 def ReadLockedPidFile(path):
   """Reads a locked PID file.
 
-  This can be used together with L{utils.StartDaemon}.
+  This can be used together with L{utils.process.StartDaemon}.
 
   @type path: string
   @param path: Path to PID file
diff --git a/lib/utils/process.py b/lib/utils/process.py
new file mode 100644
index 000000000..5fbc8fe3f
--- /dev/null
+++ b/lib/utils/process.py
@@ -0,0 +1,984 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+"""Utility functions for processes.
+
+"""
+
+
+import os
+import sys
+import subprocess
+import errno
+import select
+import logging
+import signal
+import resource
+
+from cStringIO import StringIO
+
+from ganeti import errors
+from ganeti import constants
+
+from ganeti.utils import retry as utils_retry
+from ganeti.utils import wrapper as utils_wrapper
+from ganeti.utils import text as utils_text
+from ganeti.utils import io as utils_io
+from ganeti.utils import algo as utils_algo
+
+
+#: when set to True, L{RunCmd} is disabled
+_no_fork = False
+
+(_TIMEOUT_NONE,
+ _TIMEOUT_TERM,
+ _TIMEOUT_KILL) = range(3)
+
+
+def DisableFork():
+  """Disables the use of fork(2).
+
+  """
+  global _no_fork # pylint: disable-msg=W0603
+
+  _no_fork = True
+
+
+class RunResult(object):
+  """Holds the result of running external programs.
+
+  @type exit_code: int
+  @ivar exit_code: the exit code of the program, or None (if the program
+      didn't exit())
+  @type signal: int or None
+  @ivar signal: the signal that caused the program to finish, or None
+      (if the program wasn't terminated by a signal)
+  @type stdout: str
+  @ivar stdout: the standard output of the program
+  @type stderr: str
+  @ivar stderr: the standard error of the program
+  @type failed: boolean
+  @ivar failed: True in case the program was
+      terminated by a signal or exited with a non-zero exit code
+  @ivar fail_reason: a string detailing the termination reason
+
+  """
+  __slots__ = ["exit_code", "signal", "stdout", "stderr",
+               "failed", "fail_reason", "cmd"]
+
+
+  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
+               timeout):
+    self.cmd = cmd
+    self.exit_code = exit_code
+    self.signal = signal_
+    self.stdout = stdout
+    self.stderr = stderr
+    self.failed = (signal_ is not None or exit_code != 0)
+
+    fail_msgs = []
+    if self.signal is not None:
+      fail_msgs.append("terminated by signal %s" % self.signal)
+    elif self.exit_code is not None:
+      fail_msgs.append("exited with exit code %s" % self.exit_code)
+    else:
+      fail_msgs.append("unable to determine termination reason")
+
+    if timeout_action == _TIMEOUT_TERM:
+      fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
+    elif timeout_action == _TIMEOUT_KILL:
+      fail_msgs.append(("force termination after timeout of %.2f seconds"
+                        " and linger for another %.2f seconds") %
+                       (timeout, constants.CHILD_LINGER_TIMEOUT))
+
+    if fail_msgs and self.failed:
+      self.fail_reason = utils_text.CommaJoin(fail_msgs)
+
+    if self.failed:
+      logging.debug("Command '%s' failed (%s); output: %s",
+                    self.cmd, self.fail_reason, self.output)
+
+  def _GetOutput(self):
+    """Returns the combined stdout and stderr for easier usage.
+
+    """
+    return self.stdout + self.stderr
+
+  output = property(_GetOutput, None, None, "Return full output")
+
+
+def _BuildCmdEnvironment(env, reset):
+  """Builds the environment for an external program.
+
+  """
+  if reset:
+    cmd_env = {}
+  else:
+    cmd_env = os.environ.copy()
+    cmd_env["LC_ALL"] = "C"
+
+  if env is not None:
+    cmd_env.update(env)
+
+  return cmd_env
+
+
+def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
+           interactive=False, timeout=None):
+  """Execute a (shell) command.
+
+  The command should not read from its standard input, as it will be
+  closed.
+
+  @type cmd: string or list
+  @param cmd: Command to run
+  @type env: dict
+  @param env: Additional environment variables
+  @type output: str
+  @param output: if desired, the output of the command can be
+      saved in a file instead of the RunResult instance; this
+      parameter denotes the file name (if not None)
+  @type cwd: string
+  @param cwd: if specified, will be used as the working
+      directory for the command; the default will be /
+  @type reset_env: boolean
+  @param reset_env: whether to reset or keep the default os environment
+  @type interactive: boolean
+  @param interactive: weather we pipe stdin, stdout and stderr
+                      (default behaviour) or run the command interactive
+  @type timeout: int
+  @param timeout: If not None, timeout in seconds until child process gets
+                  killed
+  @rtype: L{RunResult}
+  @return: RunResult instance
+  @raise errors.ProgrammerError: if we call this when forks are disabled
+
+  """
+  if _no_fork:
+    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
+
+  if output and interactive:
+    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
+                                 " not be provided at the same time")
+
+  if isinstance(cmd, basestring):
+    strcmd = cmd
+    shell = True
+  else:
+    cmd = [str(val) for val in cmd]
+    strcmd = utils_text.ShellQuoteArgs(cmd)
+    shell = False
+
+  if output:
+    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
+  else:
+    logging.debug("RunCmd %s", strcmd)
+
+  cmd_env = _BuildCmdEnvironment(env, reset_env)
+
+  try:
+    if output is None:
+      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
+                                                     interactive, timeout)
+    else:
+      timeout_action = _TIMEOUT_NONE
+      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
+      out = err = ""
+  except OSError, err:
+    if err.errno == errno.ENOENT:
+      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
+                               (strcmd, err))
+    else:
+      raise
+
+  if status >= 0:
+    exitcode = status
+    signal_ = None
+  else:
+    exitcode = None
+    signal_ = -status
+
+  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
+
+
+def SetupDaemonEnv(cwd="/", umask=077):
+  """Setup a daemon's environment.
+
+  This should be called between the first and second fork, due to
+  setsid usage.
+
+  @param cwd: the directory to which to chdir
+  @param umask: the umask to setup
+
+  """
+  os.chdir(cwd)
+  os.umask(umask)
+  os.setsid()
+
+
+def SetupDaemonFDs(output_file, output_fd):
+  """Setups up a daemon's file descriptors.
+
+  @param output_file: if not None, the file to which to redirect
+      stdout/stderr
+  @param output_fd: if not None, the file descriptor for stdout/stderr
+
+  """
+  # check that at most one is defined
+  assert [output_file, output_fd].count(None) >= 1
+
+  # Open /dev/null (read-only, only for stdin)
+  devnull_fd = os.open(os.devnull, os.O_RDONLY)
+
+  if output_fd is not None:
+    pass
+  elif output_file is not None:
+    # Open output file
+    try:
+      output_fd = os.open(output_file,
+                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
+    except EnvironmentError, err:
+      raise Exception("Opening output file failed: %s" % err)
+  else:
+    output_fd = os.open(os.devnull, os.O_WRONLY)
+
+  # Redirect standard I/O
+  os.dup2(devnull_fd, 0)
+  os.dup2(output_fd, 1)
+  os.dup2(output_fd, 2)
+
+
+def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
+                pidfile=None):
+  """Start a daemon process after forking twice.
+
+  @type cmd: string or list
+  @param cmd: Command to run
+  @type env: dict
+  @param env: Additional environment variables
+  @type cwd: string
+  @param cwd: Working directory for the program
+  @type output: string
+  @param output: Path to file in which to save the output
+  @type output_fd: int
+  @param output_fd: File descriptor for output
+  @type pidfile: string
+  @param pidfile: Process ID file
+  @rtype: int
+  @return: Daemon process ID
+  @raise errors.ProgrammerError: if we call this when forks are disabled
+
+  """
+  if _no_fork:
+    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
+                                 " disabled")
+
+  if output and not (bool(output) ^ (output_fd is not None)):
+    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
+                                 " specified")
+
+  if isinstance(cmd, basestring):
+    cmd = ["/bin/sh", "-c", cmd]
+
+  strcmd = utils_text.ShellQuoteArgs(cmd)
+
+  if output:
+    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
+  else:
+    logging.debug("StartDaemon %s", strcmd)
+
+  cmd_env = _BuildCmdEnvironment(env, False)
+
+  # Create pipe for sending PID back
+  (pidpipe_read, pidpipe_write) = os.pipe()
+  try:
+    try:
+      # Create pipe for sending error messages
+      (errpipe_read, errpipe_write) = os.pipe()
+      try:
+        try:
+          # First fork
+          pid = os.fork()
+          if pid == 0:
+            try:
+              # Child process, won't return
+              _StartDaemonChild(errpipe_read, errpipe_write,
+                                pidpipe_read, pidpipe_write,
+                                cmd, cmd_env, cwd,
+                                output, output_fd, pidfile)
+            finally:
+              # Well, maybe child process failed
+              os._exit(1) # pylint: disable-msg=W0212
+        finally:
+          utils_wrapper.CloseFdNoError(errpipe_write)
+
+        # Wait for daemon to be started (or an error message to
+        # arrive) and read up to 100 KB as an error message
+        errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
+                                               100 * 1024)
+      finally:
+        utils_wrapper.CloseFdNoError(errpipe_read)
+    finally:
+      utils_wrapper.CloseFdNoError(pidpipe_write)
+
+    # Read up to 128 bytes for PID
+    pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
+  finally:
+    utils_wrapper.CloseFdNoError(pidpipe_read)
+
+  # Try to avoid zombies by waiting for child process
+  try:
+    os.waitpid(pid, 0)
+  except OSError:
+    pass
+
+  if errormsg:
+    raise errors.OpExecError("Error when starting daemon process: %r" %
+                             errormsg)
+
+  try:
+    return int(pidtext)
+  except (ValueError, TypeError), err:
+    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
+                             (pidtext, err))
+
+
+def _StartDaemonChild(errpipe_read, errpipe_write,
+                      pidpipe_read, pidpipe_write,
+                      args, env, cwd,
+                      output, fd_output, pidfile):
+  """Child process for starting daemon.
+
+  """
+  try:
+    # Close parent's side
+    utils_wrapper.CloseFdNoError(errpipe_read)
+    utils_wrapper.CloseFdNoError(pidpipe_read)
+
+    # First child process
+    SetupDaemonEnv()
+
+    # And fork for the second time
+    pid = os.fork()
+    if pid != 0:
+      # Exit first child process
+      os._exit(0) # pylint: disable-msg=W0212
+
+    # Make sure pipe is closed on execv* (and thereby notifies
+    # original process)
+    utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
+
+    # List of file descriptors to be left open
+    noclose_fds = [errpipe_write]
+
+    # Open PID file
+    if pidfile:
+      fd_pidfile = utils_io.WritePidFile(pidfile)
+
+      # Keeping the file open to hold the lock
+      noclose_fds.append(fd_pidfile)
+
+      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
+    else:
+      fd_pidfile = None
+
+    SetupDaemonFDs(output, fd_output)
+
+    # Send daemon PID to parent
+    utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
+
+    # Close all file descriptors except stdio and error message pipe
+    CloseFDs(noclose_fds=noclose_fds)
+
+    # Change working directory
+    os.chdir(cwd)
+
+    if env is None:
+      os.execvp(args[0], args)
+    else:
+      os.execvpe(args[0], args, env)
+  except: # pylint: disable-msg=W0702
+    try:
+      # Report errors to original process
+      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
+    except: # pylint: disable-msg=W0702
+      # Ignore errors in error handling
+      pass
+
+  os._exit(1) # pylint: disable-msg=W0212
+
+
+def WriteErrorToFD(fd, err):
+  """Possibly write an error message to a fd.
+
+  @type fd: None or int (file descriptor)
+  @param fd: if not None, the error will be written to this fd
+  @param err: string, the error message
+
+  """
+  if fd is None:
+    return
+
+  if not err:
+    err = "<unknown error>"
+
+  utils_wrapper.RetryOnSignal(os.write, fd, err)
+
+
+def _CheckIfAlive(child):
+  """Raises L{utils_retry.RetryAgain} if child is still alive.
+
+  @raises utils_retry.RetryAgain: If child is still alive
+
+  """
+  if child.poll() is None:
+    raise utils_retry.RetryAgain()
+
+
+def _WaitForProcess(child, timeout):
+  """Waits for the child to terminate or until we reach timeout.
+
+  """
+  try:
+    utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
+                      args=[child])
+  except utils_retry.RetryTimeout:
+    pass
+
+
+def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
+                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
+  """Run a command and return its output.
+
+  @type  cmd: string or list
+  @param cmd: Command to run
+  @type env: dict
+  @param env: The environment to use
+  @type via_shell: bool
+  @param via_shell: if we should run via the shell
+  @type cwd: string
+  @param cwd: the working directory for the program
+  @type interactive: boolean
+  @param interactive: Run command interactive (without piping)
+  @type timeout: int
+  @param timeout: Timeout after the programm gets terminated
+  @rtype: tuple
+  @return: (out, err, status)
+
+  """
+  poller = select.poll()
+
+  stderr = subprocess.PIPE
+  stdout = subprocess.PIPE
+  stdin = subprocess.PIPE
+
+  if interactive:
+    stderr = stdout = stdin = None
+
+  child = subprocess.Popen(cmd, shell=via_shell,
+                           stderr=stderr,
+                           stdout=stdout,
+                           stdin=stdin,
+                           close_fds=True, env=env,
+                           cwd=cwd)
+
+  out = StringIO()
+  err = StringIO()
+
+  linger_timeout = None
+
+  if timeout is None:
+    poll_timeout = None
+  else:
+    poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
+
+  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
+                 (cmd, child.pid))
+  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
+                (cmd, child.pid))
+
+  timeout_action = _TIMEOUT_NONE
+
+  if not interactive:
+    child.stdin.close()
+    poller.register(child.stdout, select.POLLIN)
+    poller.register(child.stderr, select.POLLIN)
+    fdmap = {
+      child.stdout.fileno(): (out, child.stdout),
+      child.stderr.fileno(): (err, child.stderr),
+      }
+    for fd in fdmap:
+      utils_wrapper.SetNonblockFlag(fd, True)
+
+    while fdmap:
+      if poll_timeout:
+        pt = poll_timeout() * 1000
+        if pt < 0:
+          if linger_timeout is None:
+            logging.warning(msg_timeout)
+            if child.poll() is None:
+              timeout_action = _TIMEOUT_TERM
+              utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
+                                                  signal.SIGTERM)
+            linger_timeout = \
+              utils_algo.RunningTimeout(_linger_timeout, True).Remaining
+          pt = linger_timeout() * 1000
+          if pt < 0:
+            break
+      else:
+        pt = None
+
+      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
+
+      for fd, event in pollresult:
+        if event & select.POLLIN or event & select.POLLPRI:
+          data = fdmap[fd][1].read()
+          # no data from read signifies EOF (the same as POLLHUP)
+          if not data:
+            poller.unregister(fd)
+            del fdmap[fd]
+            continue
+          fdmap[fd][0].write(data)
+        if (event & select.POLLNVAL or event & select.POLLHUP or
+            event & select.POLLERR):
+          poller.unregister(fd)
+          del fdmap[fd]
+
+  if timeout is not None:
+    assert callable(poll_timeout)
+
+    # We have no I/O left but it might still run
+    if child.poll() is None:
+      _WaitForProcess(child, poll_timeout())
+
+    # Terminate if still alive after timeout
+    if child.poll() is None:
+      if linger_timeout is None:
+        logging.warning(msg_timeout)
+        timeout_action = _TIMEOUT_TERM
+        utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
+        lt = _linger_timeout
+      else:
+        lt = linger_timeout()
+      _WaitForProcess(child, lt)
+
+    # Okay, still alive after timeout and linger timeout? Kill it!
+    if child.poll() is None:
+      timeout_action = _TIMEOUT_KILL
+      logging.warning(msg_linger)
+      utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
+
+  out = out.getvalue()
+  err = err.getvalue()
+
+  status = child.wait()
+  return out, err, status, timeout_action
+
+
+def _RunCmdFile(cmd, env, via_shell, output, cwd):
+  """Run a command and save its output to a file.
+
+  @type  cmd: string or list
+  @param cmd: Command to run
+  @type env: dict
+  @param env: The environment to use
+  @type via_shell: bool
+  @param via_shell: if we should run via the shell
+  @type output: str
+  @param output: the filename in which to save the output
+  @type cwd: string
+  @param cwd: the working directory for the program
+  @rtype: int
+  @return: the exit status
+
+  """
+  fh = open(output, "a")
+  try:
+    child = subprocess.Popen(cmd, shell=via_shell,
+                             stderr=subprocess.STDOUT,
+                             stdout=fh,
+                             stdin=subprocess.PIPE,
+                             close_fds=True, env=env,
+                             cwd=cwd)
+
+    child.stdin.close()
+    status = child.wait()
+  finally:
+    fh.close()
+  return status
+
+
+def RunParts(dir_name, env=None, reset_env=False):
+  """Run Scripts or programs in a directory
+
+  @type dir_name: string
+  @param dir_name: absolute path to a directory
+  @type env: dict
+  @param env: The environment to use
+  @type reset_env: boolean
+  @param reset_env: whether to reset or keep the default os environment
+  @rtype: list of tuples
+  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
+
+  """
+  rr = []
+
+  try:
+    dir_contents = utils_io.ListVisibleFiles(dir_name)
+  except OSError, err:
+    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
+    return rr
+
+  for relname in sorted(dir_contents):
+    fname = utils_io.PathJoin(dir_name, relname)
+    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
+            constants.EXT_PLUGIN_MASK.match(relname) is not None):
+      rr.append((relname, constants.RUNPARTS_SKIP, None))
+    else:
+      try:
+        result = RunCmd([fname], env=env, reset_env=reset_env)
+      except Exception, err: # pylint: disable-msg=W0703
+        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
+      else:
+        rr.append((relname, constants.RUNPARTS_RUN, result))
+
+  return rr
+
+
+def _GetProcStatusPath(pid):
+  """Returns the path for a PID's proc status file.
+
+  @type pid: int
+  @param pid: Process ID
+  @rtype: string
+
+  """
+  return "/proc/%d/status" % pid
+
+
+def IsProcessAlive(pid):
+  """Check if a given pid exists on the system.
+
+  @note: zombie status is not handled, so zombie processes
+      will be returned as alive
+  @type pid: int
+  @param pid: the process ID to check
+  @rtype: boolean
+  @return: True if the process exists
+
+  """
+  def _TryStat(name):
+    try:
+      os.stat(name)
+      return True
+    except EnvironmentError, err:
+      if err.errno in (errno.ENOENT, errno.ENOTDIR):
+        return False
+      elif err.errno == errno.EINVAL:
+        raise utils_retry.RetryAgain(err)
+      raise
+
+  assert isinstance(pid, int), "pid must be an integer"
+  if pid <= 0:
+    return False
+
+  # /proc in a multiprocessor environment can have strange behaviors.
+  # Retry the os.stat a few times until we get a good result.
+  try:
+    return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
+                             args=[_GetProcStatusPath(pid)])
+  except utils_retry.RetryTimeout, err:
+    err.RaiseInner()
+
+
+def _ParseSigsetT(sigset):
+  """Parse a rendered sigset_t value.
+
+  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
+  function.
+
+  @type sigset: string
+  @param sigset: Rendered signal set from /proc/$pid/status
+  @rtype: set
+  @return: Set of all enabled signal numbers
+
+  """
+  result = set()
+
+  signum = 0
+  for ch in reversed(sigset):
+    chv = int(ch, 16)
+
+    # The following could be done in a loop, but it's easier to read and
+    # understand in the unrolled form
+    if chv & 1:
+      result.add(signum + 1)
+    if chv & 2:
+      result.add(signum + 2)
+    if chv & 4:
+      result.add(signum + 3)
+    if chv & 8:
+      result.add(signum + 4)
+
+    signum += 4
+
+  return result
+
+
+def _GetProcStatusField(pstatus, field):
+  """Retrieves a field from the contents of a proc status file.
+
+  @type pstatus: string
+  @param pstatus: Contents of /proc/$pid/status
+  @type field: string
+  @param field: Name of field whose value should be returned
+  @rtype: string
+
+  """
+  for line in pstatus.splitlines():
+    parts = line.split(":", 1)
+
+    if len(parts) < 2 or parts[0] != field:
+      continue
+
+    return parts[1].strip()
+
+  return None
+
+
+def IsProcessHandlingSignal(pid, signum, status_path=None):
+  """Checks whether a process is handling a signal.
+
+  @type pid: int
+  @param pid: Process ID
+  @type signum: int
+  @param signum: Signal number
+  @rtype: bool
+
+  """
+  if status_path is None:
+    status_path = _GetProcStatusPath(pid)
+
+  try:
+    proc_status = utils_io.ReadFile(status_path)
+  except EnvironmentError, err:
+    # In at least one case, reading /proc/$pid/status failed with ESRCH.
+    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
+      return False
+    raise
+
+  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
+  if sigcgt is None:
+    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
+
+  # Now check whether signal is handled
+  return signum in _ParseSigsetT(sigcgt)
+
+
+def Daemonize(logfile):
+  """Daemonize the current process.
+
+  This detaches the current process from the controlling terminal and
+  runs it in the background as a daemon.
+
+  @type logfile: str
+  @param logfile: the logfile to which we should redirect stdout/stderr
+  @rtype: int
+  @return: the value zero
+
+  """
+  # pylint: disable-msg=W0212
+  # yes, we really want os._exit
+
+  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
+  # least abstract the pipe functionality between them
+
+  # Create pipe for sending error messages
+  (rpipe, wpipe) = os.pipe()
+
+  # this might fail
+  pid = os.fork()
+  if (pid == 0):  # The first child.
+    SetupDaemonEnv()
+
+    # this might fail
+    pid = os.fork() # Fork a second child.
+    if (pid == 0):  # The second child.
+      utils_wrapper.CloseFdNoError(rpipe)
+    else:
+      # exit() or _exit()?  See below.
+      os._exit(0) # Exit parent (the first child) of the second child.
+  else:
+    utils_wrapper.CloseFdNoError(wpipe)
+    # Wait for daemon to be started (or an error message to
+    # arrive) and read up to 100 KB as an error message
+    errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
+    if errormsg:
+      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
+      rcode = 1
+    else:
+      rcode = 0
+    os._exit(rcode) # Exit parent of the first child.
+
+  SetupDaemonFDs(logfile, None)
+  return wpipe
+
+
+def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
+                waitpid=False):
+  """Kill a process given by its pid.
+
+  @type pid: int
+  @param pid: The PID to terminate.
+  @type signal_: int
+  @param signal_: The signal to send, by default SIGTERM
+  @type timeout: int
+  @param timeout: The timeout after which, if the process is still alive,
+                  a SIGKILL will be sent. If not positive, no such checking
+                  will be done
+  @type waitpid: boolean
+  @param waitpid: If true, we should waitpid on this process after
+      sending signals, since it's our own child and otherwise it
+      would remain as zombie
+
+  """
+  def _helper(pid, signal_, wait):
+    """Simple helper to encapsulate the kill/waitpid sequence"""
+    if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
+      try:
+        os.waitpid(pid, os.WNOHANG)
+      except OSError:
+        pass
+
+  if pid <= 0:
+    # kill with pid=0 == suicide
+    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
+
+  if not IsProcessAlive(pid):
+    return
+
+  _helper(pid, signal_, waitpid)
+
+  if timeout <= 0:
+    return
+
+  def _CheckProcess():
+    if not IsProcessAlive(pid):
+      return
+
+    try:
+      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
+    except OSError:
+      raise utils_retry.RetryAgain()
+
+    if result_pid > 0:
+      return
+
+    raise utils_retry.RetryAgain()
+
+  try:
+    # Wait up to $timeout seconds
+    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
+  except utils_retry.RetryTimeout:
+    pass
+
+  if IsProcessAlive(pid):
+    # Kill process if it's still alive
+    _helper(pid, signal.SIGKILL, waitpid)
+
+
+def RunInSeparateProcess(fn, *args):
+  """Runs a function in a separate process.
+
+  Note: Only boolean return values are supported.
+
+  @type fn: callable
+  @param fn: Function to be called
+  @rtype: bool
+  @return: Function's result
+
+  """
+  pid = os.fork()
+  if pid == 0:
+    # Child process
+    try:
+      # In case the function uses temporary files
+      utils_wrapper.ResetTempfileModule()
+
+      # Call function
+      result = int(bool(fn(*args)))
+      assert result in (0, 1)
+    except: # pylint: disable-msg=W0702
+      logging.exception("Error while calling function in separate process")
+      # 0 and 1 are reserved for the return value
+      result = 33
+
+    os._exit(result) # pylint: disable-msg=W0212
+
+  # Parent process
+
+  # Avoid zombies and check exit code
+  (_, status) = os.waitpid(pid, 0)
+
+  if os.WIFSIGNALED(status):
+    exitcode = None
+    signum = os.WTERMSIG(status)
+  else:
+    exitcode = os.WEXITSTATUS(status)
+    signum = None
+
+  if not (exitcode in (0, 1) and signum is None):
+    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
+                              (exitcode, signum))
+
+  return bool(exitcode)
+
+
+def CloseFDs(noclose_fds=None):
+  """Close file descriptors.
+
+  This closes all file descriptors above 2 (i.e. except
+  stdin/out/err).
+
+  @type noclose_fds: list or None
+  @param noclose_fds: if given, it denotes a list of file descriptor
+      that should not be closed
+
+  """
+  # Default maximum for the number of available file descriptors.
+  if 'SC_OPEN_MAX' in os.sysconf_names:
+    try:
+      MAXFD = os.sysconf('SC_OPEN_MAX')
+      if MAXFD < 0:
+        MAXFD = 1024
+    except OSError:
+      MAXFD = 1024
+  else:
+    MAXFD = 1024
+
+  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+  if (maxfd == resource.RLIM_INFINITY):
+    maxfd = MAXFD
+
+  # Iterate through and close all file descriptors (except the standard ones)
+  for fd in range(3, maxfd):
+    if noclose_fds and fd in noclose_fds:
+      continue
+    utils_wrapper.CloseFdNoError(fd)
diff --git a/test/ganeti.client.gnt_instance_unittest.py b/test/ganeti.client.gnt_instance_unittest.py
index 6326963a7..cccbf8116 100755
--- a/test/ganeti.client.gnt_instance_unittest.py
+++ b/test/ganeti.client.gnt_instance_unittest.py
@@ -53,7 +53,7 @@ class TestConsole(unittest.TestCase):
     self.assertTrue(isinstance(cmd, list))
     self._cmds.append(cmd)
     return utils.RunResult(self._next_cmd_exitcode, None, "", "", "cmd",
-                           utils._TIMEOUT_NONE, 5)
+                           utils.process._TIMEOUT_NONE, 5)
 
   def testMessage(self):
     cons = objects.InstanceConsole(instance="inst98.example.com",
diff --git a/test/ganeti.utils.process_unittest.py b/test/ganeti.utils.process_unittest.py
new file mode 100755
index 000000000..6f4c15a41
--- /dev/null
+++ b/test/ganeti.utils.process_unittest.py
@@ -0,0 +1,600 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 Google Inc.
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing ganeti.utils.process"""
+
+import unittest
+import tempfile
+import shutil
+import os
+import stat
+import time
+import signal
+
+from ganeti import constants
+from ganeti import utils
+from ganeti import errors
+
+import testutils
+
+
+class TestIsProcessAlive(unittest.TestCase):
+  """Testing case for IsProcessAlive"""
+
+  def testExists(self):
+    mypid = os.getpid()
+    self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
+
+  def testNotExisting(self):
+    pid_non_existing = os.fork()
+    if pid_non_existing == 0:
+      os._exit(0)
+    elif pid_non_existing < 0:
+      raise SystemError("can't fork")
+    os.waitpid(pid_non_existing, 0)
+    self.assertFalse(utils.IsProcessAlive(pid_non_existing),
+                     "nonexisting process detected")
+
+
+class TestGetProcStatusPath(unittest.TestCase):
+  def test(self):
+    self.assert_("/1234/" in utils.process._GetProcStatusPath(1234))
+    self.assertNotEqual(utils.process._GetProcStatusPath(1),
+                        utils.process._GetProcStatusPath(2))
+
+
+class TestIsProcessHandlingSignal(unittest.TestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testParseSigsetT(self):
+    parse_sigset_t_fn = utils.process._ParseSigsetT
+    self.assertEqual(len(parse_sigset_t_fn("0")), 0)
+    self.assertEqual(parse_sigset_t_fn("1"), set([1]))
+    self.assertEqual(parse_sigset_t_fn("1000a"), set([2, 4, 17]))
+    self.assertEqual(parse_sigset_t_fn("810002"), set([2, 17, 24, ]))
+    self.assertEqual(parse_sigset_t_fn("0000000180000202"),
+                     set([2, 10, 32, 33]))
+    self.assertEqual(parse_sigset_t_fn("0000000180000002"),
+                     set([2, 32, 33]))
+    self.assertEqual(parse_sigset_t_fn("0000000188000002"),
+                     set([2, 28, 32, 33]))
+    self.assertEqual(parse_sigset_t_fn("000000004b813efb"),
+                     set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
+                          24, 25, 26, 28, 31]))
+    self.assertEqual(parse_sigset_t_fn("ffffff"), set(range(1, 25)))
+
+  def testGetProcStatusField(self):
+    for field in ["SigCgt", "Name", "FDSize"]:
+      for value in ["", "0", "cat", "  1234 KB"]:
+        pstatus = "\n".join([
+          "VmPeak: 999 kB",
+          "%s: %s" % (field, value),
+          "TracerPid: 0",
+          ])
+        result = utils.process._GetProcStatusField(pstatus, field)
+        self.assertEqual(result, value.strip())
+
+  def test(self):
+    sp = utils.PathJoin(self.tmpdir, "status")
+
+    utils.WriteFile(sp, data="\n".join([
+      "Name:   bash",
+      "State:  S (sleeping)",
+      "SleepAVG:       98%",
+      "Pid:    22250",
+      "PPid:   10858",
+      "TracerPid:      0",
+      "SigBlk: 0000000000010000",
+      "SigIgn: 0000000000384004",
+      "SigCgt: 000000004b813efb",
+      "CapEff: 0000000000000000",
+      ]))
+
+    self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
+
+  def testNoSigCgt(self):
+    sp = utils.PathJoin(self.tmpdir, "status")
+
+    utils.WriteFile(sp, data="\n".join([
+      "Name:   bash",
+      ]))
+
+    self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
+                      1234, 10, status_path=sp)
+
+  def testNoSuchFile(self):
+    sp = utils.PathJoin(self.tmpdir, "notexist")
+
+    self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
+
+  @staticmethod
+  def _TestRealProcess():
+    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
+    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
+      raise Exception("SIGUSR1 is handled when it should not be")
+
+    signal.signal(signal.SIGUSR1, lambda signum, frame: None)
+    if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
+      raise Exception("SIGUSR1 is not handled when it should be")
+
+    signal.signal(signal.SIGUSR1, signal.SIG_IGN)
+    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
+      raise Exception("SIGUSR1 is not handled when it should be")
+
+    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
+    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
+      raise Exception("SIGUSR1 is handled when it should not be")
+
+    return True
+
+  def testRealProcess(self):
+    self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
+
+
+class TestRunCmd(testutils.GanetiTestCase):
+  """Testing case for the RunCmd function"""
+
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+    self.magic = time.ctime() + " ganeti test"
+    self.fname = self._CreateTempFile()
+    self.fifo_tmpdir = tempfile.mkdtemp()
+    self.fifo_file = os.path.join(self.fifo_tmpdir, "ganeti_test_fifo")
+    os.mkfifo(self.fifo_file)
+
+  def tearDown(self):
+    shutil.rmtree(self.fifo_tmpdir)
+    testutils.GanetiTestCase.tearDown(self)
+
+  def testOk(self):
+    """Test successful exit code"""
+    result = utils.RunCmd("/bin/sh -c 'exit 0'")
+    self.assertEqual(result.exit_code, 0)
+    self.assertEqual(result.output, "")
+
+  def testFail(self):
+    """Test fail exit code"""
+    result = utils.RunCmd("/bin/sh -c 'exit 1'")
+    self.assertEqual(result.exit_code, 1)
+    self.assertEqual(result.output, "")
+
+  def testStdout(self):
+    """Test standard output"""
+    cmd = 'echo -n "%s"' % self.magic
+    result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
+    self.assertEqual(result.stdout, self.magic)
+    result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
+    self.assertEqual(result.output, "")
+    self.assertFileContent(self.fname, self.magic)
+
+  def testStderr(self):
+    """Test standard error"""
+    cmd = 'echo -n "%s"' % self.magic
+    result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
+    self.assertEqual(result.stderr, self.magic)
+    result = utils.RunCmd("/bin/sh -c '%s' 1>&2" % cmd, output=self.fname)
+    self.assertEqual(result.output, "")
+    self.assertFileContent(self.fname, self.magic)
+
+  def testCombined(self):
+    """Test combined output"""
+    cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
+    expected = "A" + self.magic + "B" + self.magic
+    result = utils.RunCmd("/bin/sh -c '%s'" % cmd)
+    self.assertEqual(result.output, expected)
+    result = utils.RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
+    self.assertEqual(result.output, "")
+    self.assertFileContent(self.fname, expected)
+
+  def testSignal(self):
+    """Test signal"""
+    result = utils.RunCmd(["python", "-c",
+                           "import os; os.kill(os.getpid(), 15)"])
+    self.assertEqual(result.signal, 15)
+    self.assertEqual(result.output, "")
+
+  def testTimeoutClean(self):
+    cmd = "trap 'exit 0' TERM; read < %s" % self.fifo_file
+    result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
+    self.assertEqual(result.exit_code, 0)
+
+  def testTimeoutKill(self):
+    cmd = ["/bin/sh", "-c", "trap '' TERM; read < %s" % self.fifo_file]
+    timeout = 0.2
+    (out, err, status, ta) = \
+      utils.process._RunCmdPipe(cmd, {}, False, "/", False,
+                                timeout, _linger_timeout=0.2)
+    self.assert_(status < 0)
+    self.assertEqual(-status, signal.SIGKILL)
+
+  def testTimeoutOutputAfterTerm(self):
+    cmd = "trap 'echo sigtermed; exit 1' TERM; read < %s" % self.fifo_file
+    result = utils.RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
+    self.assert_(result.failed)
+    self.assertEqual(result.stdout, "sigtermed\n")
+
+  def testListRun(self):
+    """Test list runs"""
+    result = utils.RunCmd(["true"])
+    self.assertEqual(result.signal, None)
+    self.assertEqual(result.exit_code, 0)
+    result = utils.RunCmd(["/bin/sh", "-c", "exit 1"])
+    self.assertEqual(result.signal, None)
+    self.assertEqual(result.exit_code, 1)
+    result = utils.RunCmd(["echo", "-n", self.magic])
+    self.assertEqual(result.signal, None)
+    self.assertEqual(result.exit_code, 0)
+    self.assertEqual(result.stdout, self.magic)
+
+  def testFileEmptyOutput(self):
+    """Test file output"""
+    result = utils.RunCmd(["true"], output=self.fname)
+    self.assertEqual(result.signal, None)
+    self.assertEqual(result.exit_code, 0)
+    self.assertFileContent(self.fname, "")
+
+  def testLang(self):
+    """Test locale environment"""
+    old_env = os.environ.copy()
+    try:
+      os.environ["LANG"] = "en_US.UTF-8"
+      os.environ["LC_ALL"] = "en_US.UTF-8"
+      result = utils.RunCmd(["locale"])
+      for line in result.output.splitlines():
+        key, value = line.split("=", 1)
+        # Ignore these variables, they're overridden by LC_ALL
+        if key == "LANG" or key == "LANGUAGE":
+          continue
+        self.failIf(value and value != "C" and value != '"C"',
+            "Variable %s is set to the invalid value '%s'" % (key, value))
+    finally:
+      os.environ = old_env
+
+  def testDefaultCwd(self):
+    """Test default working directory"""
+    self.failUnlessEqual(utils.RunCmd(["pwd"]).stdout.strip(), "/")
+
+  def testCwd(self):
+    """Test default working directory"""
+    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
+    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd="/tmp").stdout.strip(),
+                         "/tmp")
+    cwd = os.getcwd()
+    self.failUnlessEqual(utils.RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
+
+  def testResetEnv(self):
+    """Test environment reset functionality"""
+    self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True).stdout.strip(),
+                         "")
+    self.failUnlessEqual(utils.RunCmd(["env"], reset_env=True,
+                                      env={"FOO": "bar",}).stdout.strip(),
+                         "FOO=bar")
+
+  def testNoFork(self):
+    """Test that nofork raise an error"""
+    self.assertFalse(utils.process._no_fork)
+    utils.DisableFork()
+    try:
+      self.assertTrue(utils.process._no_fork)
+      self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"])
+    finally:
+      utils.process._no_fork = False
+    self.assertFalse(utils.process._no_fork)
+
+  def testWrongParams(self):
+    """Test wrong parameters"""
+    self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
+                      output="/dev/null", interactive=True)
+
+
+class TestRunParts(testutils.GanetiTestCase):
+  """Testing case for the RunParts function"""
+
+  def setUp(self):
+    self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
+
+  def tearDown(self):
+    shutil.rmtree(self.rundir)
+
+  def testEmpty(self):
+    """Test on an empty dir"""
+    self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True), [])
+
+  def testSkipWrongName(self):
+    """Test that wrong files are skipped"""
+    fname = os.path.join(self.rundir, "00test.dot")
+    utils.WriteFile(fname, data="")
+    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
+    relname = os.path.basename(fname)
+    self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
+                         [(relname, constants.RUNPARTS_SKIP, None)])
+
+  def testSkipNonExec(self):
+    """Test that non executable files are skipped"""
+    fname = os.path.join(self.rundir, "00test")
+    utils.WriteFile(fname, data="")
+    relname = os.path.basename(fname)
+    self.failUnlessEqual(utils.RunParts(self.rundir, reset_env=True),
+                         [(relname, constants.RUNPARTS_SKIP, None)])
+
+  def testError(self):
+    """Test error on a broken executable"""
+    fname = os.path.join(self.rundir, "00test")
+    utils.WriteFile(fname, data="")
+    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
+    (relname, status, error) = utils.RunParts(self.rundir, reset_env=True)[0]
+    self.failUnlessEqual(relname, os.path.basename(fname))
+    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
+    self.failUnless(error)
+
+  def testSorted(self):
+    """Test executions are sorted"""
+    files = []
+    files.append(os.path.join(self.rundir, "64test"))
+    files.append(os.path.join(self.rundir, "00test"))
+    files.append(os.path.join(self.rundir, "42test"))
+
+    for fname in files:
+      utils.WriteFile(fname, data="")
+
+    results = utils.RunParts(self.rundir, reset_env=True)
+
+    for fname in sorted(files):
+      self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
+
+  def testOk(self):
+    """Test correct execution"""
+    fname = os.path.join(self.rundir, "00test")
+    utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
+    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
+    (relname, status, runresult) = \
+      utils.RunParts(self.rundir, reset_env=True)[0]
+    self.failUnlessEqual(relname, os.path.basename(fname))
+    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
+    self.failUnlessEqual(runresult.stdout, "ciao")
+
+  def testRunFail(self):
+    """Test correct execution, with run failure"""
+    fname = os.path.join(self.rundir, "00test")
+    utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
+    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
+    (relname, status, runresult) = \
+      utils.RunParts(self.rundir, reset_env=True)[0]
+    self.failUnlessEqual(relname, os.path.basename(fname))
+    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
+    self.failUnlessEqual(runresult.exit_code, 1)
+    self.failUnless(runresult.failed)
+
+  def testRunMix(self):
+    files = []
+    files.append(os.path.join(self.rundir, "00test"))
+    files.append(os.path.join(self.rundir, "42test"))
+    files.append(os.path.join(self.rundir, "64test"))
+    files.append(os.path.join(self.rundir, "99test"))
+
+    files.sort()
+
+    # 1st has errors in execution
+    utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
+    os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
+
+    # 2nd is skipped
+    utils.WriteFile(files[1], data="")
+
+    # 3rd cannot execute properly
+    utils.WriteFile(files[2], data="")
+    os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
+
+    # 4th execs
+    utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
+    os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
+
+    results = utils.RunParts(self.rundir, reset_env=True)
+
+    (relname, status, runresult) = results[0]
+    self.failUnlessEqual(relname, os.path.basename(files[0]))
+    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
+    self.failUnlessEqual(runresult.exit_code, 1)
+    self.failUnless(runresult.failed)
+
+    (relname, status, runresult) = results[1]
+    self.failUnlessEqual(relname, os.path.basename(files[1]))
+    self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
+    self.failUnlessEqual(runresult, None)
+
+    (relname, status, runresult) = results[2]
+    self.failUnlessEqual(relname, os.path.basename(files[2]))
+    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
+    self.failUnless(runresult)
+
+    (relname, status, runresult) = results[3]
+    self.failUnlessEqual(relname, os.path.basename(files[3]))
+    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
+    self.failUnlessEqual(runresult.output, "ciao")
+    self.failUnlessEqual(runresult.exit_code, 0)
+    self.failUnless(not runresult.failed)
+
+  def testMissingDirectory(self):
+    nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
+    self.assertEqual(utils.RunParts(nosuchdir), [])
+
+
+class TestStartDaemon(testutils.GanetiTestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
+    self.tmpfile = os.path.join(self.tmpdir, "test")
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testShell(self):
+    utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
+    self._wait(self.tmpfile, 60.0, "Hello World")
+
+  def testShellOutput(self):
+    utils.StartDaemon("echo Hello World", output=self.tmpfile)
+    self._wait(self.tmpfile, 60.0, "Hello World")
+
+  def testNoShellNoOutput(self):
+    utils.StartDaemon(["pwd"])
+
+  def testNoShellNoOutputTouch(self):
+    testfile = os.path.join(self.tmpdir, "check")
+    self.failIf(os.path.exists(testfile))
+    utils.StartDaemon(["touch", testfile])
+    self._wait(testfile, 60.0, "")
+
+  def testNoShellOutput(self):
+    utils.StartDaemon(["pwd"], output=self.tmpfile)
+    self._wait(self.tmpfile, 60.0, "/")
+
+  def testNoShellOutputCwd(self):
+    utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
+    self._wait(self.tmpfile, 60.0, os.getcwd())
+
+  def testShellEnv(self):
+    utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
+                      env={ "GNT_TEST_VAR": "Hello World", })
+    self._wait(self.tmpfile, 60.0, "Hello World")
+
+  def testNoShellEnv(self):
+    utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
+                      env={ "GNT_TEST_VAR": "Hello World", })
+    self._wait(self.tmpfile, 60.0, "Hello World")
+
+  def testOutputFd(self):
+    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
+    try:
+      utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
+    finally:
+      os.close(fd)
+    self._wait(self.tmpfile, 60.0, os.getcwd())
+
+  def testPid(self):
+    pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
+    self._wait(self.tmpfile, 60.0, str(pid))
+
+  def testPidFile(self):
+    pidfile = os.path.join(self.tmpdir, "pid")
+    checkfile = os.path.join(self.tmpdir, "abort")
+
+    pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
+                            output=self.tmpfile)
+    try:
+      fd = os.open(pidfile, os.O_RDONLY)
+      try:
+        # Check file is locked
+        self.assertRaises(errors.LockError, utils.LockFile, fd)
+
+        pidtext = os.read(fd, 100)
+      finally:
+        os.close(fd)
+
+      self.assertEqual(int(pidtext.strip()), pid)
+
+      self.assert_(utils.IsProcessAlive(pid))
+    finally:
+      # No matter what happens, kill daemon
+      utils.KillProcess(pid, timeout=5.0, waitpid=False)
+      self.failIf(utils.IsProcessAlive(pid))
+
+    self.assertEqual(utils.ReadFile(self.tmpfile), "")
+
+  def _wait(self, path, timeout, expected):
+    # Due to the asynchronous nature of daemon processes, polling is necessary.
+    # A timeout makes sure the test doesn't hang forever.
+    def _CheckFile():
+      if not (os.path.isfile(path) and
+              utils.ReadFile(path).strip() == expected):
+        raise utils.RetryAgain()
+
+    try:
+      utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
+    except utils.RetryTimeout:
+      self.fail("Apparently the daemon didn't run in %s seconds and/or"
+                " didn't write the correct output" % timeout)
+
+  def testError(self):
+    self.assertRaises(errors.OpExecError, utils.StartDaemon,
+                      ["./does-NOT-EXIST/here/0123456789"])
+    self.assertRaises(errors.OpExecError, utils.StartDaemon,
+                      ["./does-NOT-EXIST/here/0123456789"],
+                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
+    self.assertRaises(errors.OpExecError, utils.StartDaemon,
+                      ["./does-NOT-EXIST/here/0123456789"],
+                      cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
+    self.assertRaises(errors.OpExecError, utils.StartDaemon,
+                      ["./does-NOT-EXIST/here/0123456789"],
+                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
+
+    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
+    try:
+      self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
+                        ["./does-NOT-EXIST/here/0123456789"],
+                        output=self.tmpfile, output_fd=fd)
+    finally:
+      os.close(fd)
+
+
+class RunInSeparateProcess(unittest.TestCase):
+  def test(self):
+    for exp in [True, False]:
+      def _child():
+        return exp
+
+      self.assertEqual(exp, utils.RunInSeparateProcess(_child))
+
+  def testArgs(self):
+    for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
+      def _child(carg1, carg2):
+        return carg1 == "Foo" and carg2 == arg
+
+      self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
+
+  def testPid(self):
+    parent_pid = os.getpid()
+
+    def _check():
+      return os.getpid() == parent_pid
+
+    self.failIf(utils.RunInSeparateProcess(_check))
+
+  def testSignal(self):
+    def _kill():
+      os.kill(os.getpid(), signal.SIGTERM)
+
+    self.assertRaises(errors.GenericError,
+                      utils.RunInSeparateProcess, _kill)
+
+  def testException(self):
+    def _exc():
+      raise errors.GenericError("This is a test")
+
+    self.assertRaises(errors.GenericError,
+                      utils.RunInSeparateProcess, _exc)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py
index 84a3130a6..661be9a02 100755
--- a/test/ganeti.utils_unittest.py
+++ b/test/ganeti.utils_unittest.py
@@ -48,519 +48,6 @@ from ganeti.utils import RunCmd, \
      RunParts
 
 
-class TestIsProcessAlive(unittest.TestCase):
-  """Testing case for IsProcessAlive"""
-
-  def testExists(self):
-    mypid = os.getpid()
-    self.assert_(utils.IsProcessAlive(mypid), "can't find myself running")
-
-  def testNotExisting(self):
-    pid_non_existing = os.fork()
-    if pid_non_existing == 0:
-      os._exit(0)
-    elif pid_non_existing < 0:
-      raise SystemError("can't fork")
-    os.waitpid(pid_non_existing, 0)
-    self.assertFalse(utils.IsProcessAlive(pid_non_existing),
-                     "nonexisting process detected")
-
-
-class TestGetProcStatusPath(unittest.TestCase):
-  def test(self):
-    self.assert_("/1234/" in utils._GetProcStatusPath(1234))
-    self.assertNotEqual(utils._GetProcStatusPath(1),
-                        utils._GetProcStatusPath(2))
-
-
-class TestIsProcessHandlingSignal(unittest.TestCase):
-  def setUp(self):
-    self.tmpdir = tempfile.mkdtemp()
-
-  def tearDown(self):
-    shutil.rmtree(self.tmpdir)
-
-  def testParseSigsetT(self):
-    self.assertEqual(len(utils._ParseSigsetT("0")), 0)
-    self.assertEqual(utils._ParseSigsetT("1"), set([1]))
-    self.assertEqual(utils._ParseSigsetT("1000a"), set([2, 4, 17]))
-    self.assertEqual(utils._ParseSigsetT("810002"), set([2, 17, 24, ]))
-    self.assertEqual(utils._ParseSigsetT("0000000180000202"),
-                     set([2, 10, 32, 33]))
-    self.assertEqual(utils._ParseSigsetT("0000000180000002"),
-                     set([2, 32, 33]))
-    self.assertEqual(utils._ParseSigsetT("0000000188000002"),
-                     set([2, 28, 32, 33]))
-    self.assertEqual(utils._ParseSigsetT("000000004b813efb"),
-                     set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
-                          24, 25, 26, 28, 31]))
-    self.assertEqual(utils._ParseSigsetT("ffffff"), set(range(1, 25)))
-
-  def testGetProcStatusField(self):
-    for field in ["SigCgt", "Name", "FDSize"]:
-      for value in ["", "0", "cat", "  1234 KB"]:
-        pstatus = "\n".join([
-          "VmPeak: 999 kB",
-          "%s: %s" % (field, value),
-          "TracerPid: 0",
-          ])
-        result = utils._GetProcStatusField(pstatus, field)
-        self.assertEqual(result, value.strip())
-
-  def test(self):
-    sp = utils.PathJoin(self.tmpdir, "status")
-
-    utils.WriteFile(sp, data="\n".join([
-      "Name:   bash",
-      "State:  S (sleeping)",
-      "SleepAVG:       98%",
-      "Pid:    22250",
-      "PPid:   10858",
-      "TracerPid:      0",
-      "SigBlk: 0000000000010000",
-      "SigIgn: 0000000000384004",
-      "SigCgt: 000000004b813efb",
-      "CapEff: 0000000000000000",
-      ]))
-
-    self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
-
-  def testNoSigCgt(self):
-    sp = utils.PathJoin(self.tmpdir, "status")
-
-    utils.WriteFile(sp, data="\n".join([
-      "Name:   bash",
-      ]))
-
-    self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
-                      1234, 10, status_path=sp)
-
-  def testNoSuchFile(self):
-    sp = utils.PathJoin(self.tmpdir, "notexist")
-
-    self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
-
-  @staticmethod
-  def _TestRealProcess():
-    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
-    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
-      raise Exception("SIGUSR1 is handled when it should not be")
-
-    signal.signal(signal.SIGUSR1, lambda signum, frame: None)
-    if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
-      raise Exception("SIGUSR1 is not handled when it should be")
-
-    signal.signal(signal.SIGUSR1, signal.SIG_IGN)
-    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
-      raise Exception("SIGUSR1 is not handled when it should be")
-
-    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
-    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
-      raise Exception("SIGUSR1 is handled when it should not be")
-
-    return True
-
-  def testRealProcess(self):
-    self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
-
-
-class TestRunCmd(testutils.GanetiTestCase):
-  """Testing case for the RunCmd function"""
-
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-    self.magic = time.ctime() + " ganeti test"
-    self.fname = self._CreateTempFile()
-    self.fifo_tmpdir = tempfile.mkdtemp()
-    self.fifo_file = os.path.join(self.fifo_tmpdir, "ganeti_test_fifo")
-    os.mkfifo(self.fifo_file)
-
-  def tearDown(self):
-    shutil.rmtree(self.fifo_tmpdir)
-    testutils.GanetiTestCase.tearDown(self)
-
-  def testOk(self):
-    """Test successful exit code"""
-    result = RunCmd("/bin/sh -c 'exit 0'")
-    self.assertEqual(result.exit_code, 0)
-    self.assertEqual(result.output, "")
-
-  def testFail(self):
-    """Test fail exit code"""
-    result = RunCmd("/bin/sh -c 'exit 1'")
-    self.assertEqual(result.exit_code, 1)
-    self.assertEqual(result.output, "")
-
-  def testStdout(self):
-    """Test standard output"""
-    cmd = 'echo -n "%s"' % self.magic
-    result = RunCmd("/bin/sh -c '%s'" % cmd)
-    self.assertEqual(result.stdout, self.magic)
-    result = RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
-    self.assertEqual(result.output, "")
-    self.assertFileContent(self.fname, self.magic)
-
-  def testStderr(self):
-    """Test standard error"""
-    cmd = 'echo -n "%s"' % self.magic
-    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd)
-    self.assertEqual(result.stderr, self.magic)
-    result = RunCmd("/bin/sh -c '%s' 1>&2" % cmd, output=self.fname)
-    self.assertEqual(result.output, "")
-    self.assertFileContent(self.fname, self.magic)
-
-  def testCombined(self):
-    """Test combined output"""
-    cmd = 'echo -n "A%s"; echo -n "B%s" 1>&2' % (self.magic, self.magic)
-    expected = "A" + self.magic + "B" + self.magic
-    result = RunCmd("/bin/sh -c '%s'" % cmd)
-    self.assertEqual(result.output, expected)
-    result = RunCmd("/bin/sh -c '%s'" % cmd, output=self.fname)
-    self.assertEqual(result.output, "")
-    self.assertFileContent(self.fname, expected)
-
-  def testSignal(self):
-    """Test signal"""
-    result = RunCmd(["python", "-c", "import os; os.kill(os.getpid(), 15)"])
-    self.assertEqual(result.signal, 15)
-    self.assertEqual(result.output, "")
-
-  def testTimeoutClean(self):
-    cmd = "trap 'exit 0' TERM; read < %s" % self.fifo_file
-    result = RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
-    self.assertEqual(result.exit_code, 0)
-
-  def testTimeoutKill(self):
-    cmd = ["/bin/sh", "-c", "trap '' TERM; read < %s" % self.fifo_file]
-    timeout = 0.2
-    out, err, status, ta = utils._RunCmdPipe(cmd, {}, False, "/", False,
-                                             timeout, _linger_timeout=0.2)
-    self.assert_(status < 0)
-    self.assertEqual(-status, signal.SIGKILL)
-
-  def testTimeoutOutputAfterTerm(self):
-    cmd = "trap 'echo sigtermed; exit 1' TERM; read < %s" % self.fifo_file
-    result = RunCmd(["/bin/sh", "-c", cmd], timeout=0.2)
-    self.assert_(result.failed)
-    self.assertEqual(result.stdout, "sigtermed\n")
-
-  def testListRun(self):
-    """Test list runs"""
-    result = RunCmd(["true"])
-    self.assertEqual(result.signal, None)
-    self.assertEqual(result.exit_code, 0)
-    result = RunCmd(["/bin/sh", "-c", "exit 1"])
-    self.assertEqual(result.signal, None)
-    self.assertEqual(result.exit_code, 1)
-    result = RunCmd(["echo", "-n", self.magic])
-    self.assertEqual(result.signal, None)
-    self.assertEqual(result.exit_code, 0)
-    self.assertEqual(result.stdout, self.magic)
-
-  def testFileEmptyOutput(self):
-    """Test file output"""
-    result = RunCmd(["true"], output=self.fname)
-    self.assertEqual(result.signal, None)
-    self.assertEqual(result.exit_code, 0)
-    self.assertFileContent(self.fname, "")
-
-  def testLang(self):
-    """Test locale environment"""
-    old_env = os.environ.copy()
-    try:
-      os.environ["LANG"] = "en_US.UTF-8"
-      os.environ["LC_ALL"] = "en_US.UTF-8"
-      result = RunCmd(["locale"])
-      for line in result.output.splitlines():
-        key, value = line.split("=", 1)
-        # Ignore these variables, they're overridden by LC_ALL
-        if key == "LANG" or key == "LANGUAGE":
-          continue
-        self.failIf(value and value != "C" and value != '"C"',
-            "Variable %s is set to the invalid value '%s'" % (key, value))
-    finally:
-      os.environ = old_env
-
-  def testDefaultCwd(self):
-    """Test default working directory"""
-    self.failUnlessEqual(RunCmd(["pwd"]).stdout.strip(), "/")
-
-  def testCwd(self):
-    """Test default working directory"""
-    self.failUnlessEqual(RunCmd(["pwd"], cwd="/").stdout.strip(), "/")
-    self.failUnlessEqual(RunCmd(["pwd"], cwd="/tmp").stdout.strip(), "/tmp")
-    cwd = os.getcwd()
-    self.failUnlessEqual(RunCmd(["pwd"], cwd=cwd).stdout.strip(), cwd)
-
-  def testResetEnv(self):
-    """Test environment reset functionality"""
-    self.failUnlessEqual(RunCmd(["env"], reset_env=True).stdout.strip(), "")
-    self.failUnlessEqual(RunCmd(["env"], reset_env=True,
-                                env={"FOO": "bar",}).stdout.strip(), "FOO=bar")
-
-  def testNoFork(self):
-    """Test that nofork raise an error"""
-    self.assertFalse(utils._no_fork)
-    utils.DisableFork()
-    try:
-      self.assertTrue(utils._no_fork)
-      self.assertRaises(errors.ProgrammerError, RunCmd, ["true"])
-    finally:
-      utils._no_fork = False
-
-  def testWrongParams(self):
-    """Test wrong parameters"""
-    self.assertRaises(errors.ProgrammerError, RunCmd, ["true"],
-                      output="/dev/null", interactive=True)
-
-
-class TestRunParts(testutils.GanetiTestCase):
-  """Testing case for the RunParts function"""
-
-  def setUp(self):
-    self.rundir = tempfile.mkdtemp(prefix="ganeti-test", suffix=".tmp")
-
-  def tearDown(self):
-    shutil.rmtree(self.rundir)
-
-  def testEmpty(self):
-    """Test on an empty dir"""
-    self.failUnlessEqual(RunParts(self.rundir, reset_env=True), [])
-
-  def testSkipWrongName(self):
-    """Test that wrong files are skipped"""
-    fname = os.path.join(self.rundir, "00test.dot")
-    utils.WriteFile(fname, data="")
-    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
-    relname = os.path.basename(fname)
-    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
-                         [(relname, constants.RUNPARTS_SKIP, None)])
-
-  def testSkipNonExec(self):
-    """Test that non executable files are skipped"""
-    fname = os.path.join(self.rundir, "00test")
-    utils.WriteFile(fname, data="")
-    relname = os.path.basename(fname)
-    self.failUnlessEqual(RunParts(self.rundir, reset_env=True),
-                         [(relname, constants.RUNPARTS_SKIP, None)])
-
-  def testError(self):
-    """Test error on a broken executable"""
-    fname = os.path.join(self.rundir, "00test")
-    utils.WriteFile(fname, data="")
-    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
-    (relname, status, error) = RunParts(self.rundir, reset_env=True)[0]
-    self.failUnlessEqual(relname, os.path.basename(fname))
-    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
-    self.failUnless(error)
-
-  def testSorted(self):
-    """Test executions are sorted"""
-    files = []
-    files.append(os.path.join(self.rundir, "64test"))
-    files.append(os.path.join(self.rundir, "00test"))
-    files.append(os.path.join(self.rundir, "42test"))
-
-    for fname in files:
-      utils.WriteFile(fname, data="")
-
-    results = RunParts(self.rundir, reset_env=True)
-
-    for fname in sorted(files):
-      self.failUnlessEqual(os.path.basename(fname), results.pop(0)[0])
-
-  def testOk(self):
-    """Test correct execution"""
-    fname = os.path.join(self.rundir, "00test")
-    utils.WriteFile(fname, data="#!/bin/sh\n\necho -n ciao")
-    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
-    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
-    self.failUnlessEqual(relname, os.path.basename(fname))
-    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
-    self.failUnlessEqual(runresult.stdout, "ciao")
-
-  def testRunFail(self):
-    """Test correct execution, with run failure"""
-    fname = os.path.join(self.rundir, "00test")
-    utils.WriteFile(fname, data="#!/bin/sh\n\nexit 1")
-    os.chmod(fname, stat.S_IREAD | stat.S_IEXEC)
-    (relname, status, runresult) = RunParts(self.rundir, reset_env=True)[0]
-    self.failUnlessEqual(relname, os.path.basename(fname))
-    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
-    self.failUnlessEqual(runresult.exit_code, 1)
-    self.failUnless(runresult.failed)
-
-  def testRunMix(self):
-    files = []
-    files.append(os.path.join(self.rundir, "00test"))
-    files.append(os.path.join(self.rundir, "42test"))
-    files.append(os.path.join(self.rundir, "64test"))
-    files.append(os.path.join(self.rundir, "99test"))
-
-    files.sort()
-
-    # 1st has errors in execution
-    utils.WriteFile(files[0], data="#!/bin/sh\n\nexit 1")
-    os.chmod(files[0], stat.S_IREAD | stat.S_IEXEC)
-
-    # 2nd is skipped
-    utils.WriteFile(files[1], data="")
-
-    # 3rd cannot execute properly
-    utils.WriteFile(files[2], data="")
-    os.chmod(files[2], stat.S_IREAD | stat.S_IEXEC)
-
-    # 4th execs
-    utils.WriteFile(files[3], data="#!/bin/sh\n\necho -n ciao")
-    os.chmod(files[3], stat.S_IREAD | stat.S_IEXEC)
-
-    results = RunParts(self.rundir, reset_env=True)
-
-    (relname, status, runresult) = results[0]
-    self.failUnlessEqual(relname, os.path.basename(files[0]))
-    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
-    self.failUnlessEqual(runresult.exit_code, 1)
-    self.failUnless(runresult.failed)
-
-    (relname, status, runresult) = results[1]
-    self.failUnlessEqual(relname, os.path.basename(files[1]))
-    self.failUnlessEqual(status, constants.RUNPARTS_SKIP)
-    self.failUnlessEqual(runresult, None)
-
-    (relname, status, runresult) = results[2]
-    self.failUnlessEqual(relname, os.path.basename(files[2]))
-    self.failUnlessEqual(status, constants.RUNPARTS_ERR)
-    self.failUnless(runresult)
-
-    (relname, status, runresult) = results[3]
-    self.failUnlessEqual(relname, os.path.basename(files[3]))
-    self.failUnlessEqual(status, constants.RUNPARTS_RUN)
-    self.failUnlessEqual(runresult.output, "ciao")
-    self.failUnlessEqual(runresult.exit_code, 0)
-    self.failUnless(not runresult.failed)
-
-  def testMissingDirectory(self):
-    nosuchdir = utils.PathJoin(self.rundir, "no/such/directory")
-    self.assertEqual(RunParts(nosuchdir), [])
-
-
-class TestStartDaemon(testutils.GanetiTestCase):
-  def setUp(self):
-    self.tmpdir = tempfile.mkdtemp(prefix="ganeti-test")
-    self.tmpfile = os.path.join(self.tmpdir, "test")
-
-  def tearDown(self):
-    shutil.rmtree(self.tmpdir)
-
-  def testShell(self):
-    utils.StartDaemon("echo Hello World > %s" % self.tmpfile)
-    self._wait(self.tmpfile, 60.0, "Hello World")
-
-  def testShellOutput(self):
-    utils.StartDaemon("echo Hello World", output=self.tmpfile)
-    self._wait(self.tmpfile, 60.0, "Hello World")
-
-  def testNoShellNoOutput(self):
-    utils.StartDaemon(["pwd"])
-
-  def testNoShellNoOutputTouch(self):
-    testfile = os.path.join(self.tmpdir, "check")
-    self.failIf(os.path.exists(testfile))
-    utils.StartDaemon(["touch", testfile])
-    self._wait(testfile, 60.0, "")
-
-  def testNoShellOutput(self):
-    utils.StartDaemon(["pwd"], output=self.tmpfile)
-    self._wait(self.tmpfile, 60.0, "/")
-
-  def testNoShellOutputCwd(self):
-    utils.StartDaemon(["pwd"], output=self.tmpfile, cwd=os.getcwd())
-    self._wait(self.tmpfile, 60.0, os.getcwd())
-
-  def testShellEnv(self):
-    utils.StartDaemon("echo \"$GNT_TEST_VAR\"", output=self.tmpfile,
-                      env={ "GNT_TEST_VAR": "Hello World", })
-    self._wait(self.tmpfile, 60.0, "Hello World")
-
-  def testNoShellEnv(self):
-    utils.StartDaemon(["printenv", "GNT_TEST_VAR"], output=self.tmpfile,
-                      env={ "GNT_TEST_VAR": "Hello World", })
-    self._wait(self.tmpfile, 60.0, "Hello World")
-
-  def testOutputFd(self):
-    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
-    try:
-      utils.StartDaemon(["pwd"], output_fd=fd, cwd=os.getcwd())
-    finally:
-      os.close(fd)
-    self._wait(self.tmpfile, 60.0, os.getcwd())
-
-  def testPid(self):
-    pid = utils.StartDaemon("echo $$ > %s" % self.tmpfile)
-    self._wait(self.tmpfile, 60.0, str(pid))
-
-  def testPidFile(self):
-    pidfile = os.path.join(self.tmpdir, "pid")
-    checkfile = os.path.join(self.tmpdir, "abort")
-
-    pid = utils.StartDaemon("while sleep 5; do :; done", pidfile=pidfile,
-                            output=self.tmpfile)
-    try:
-      fd = os.open(pidfile, os.O_RDONLY)
-      try:
-        # Check file is locked
-        self.assertRaises(errors.LockError, utils.LockFile, fd)
-
-        pidtext = os.read(fd, 100)
-      finally:
-        os.close(fd)
-
-      self.assertEqual(int(pidtext.strip()), pid)
-
-      self.assert_(utils.IsProcessAlive(pid))
-    finally:
-      # No matter what happens, kill daemon
-      utils.KillProcess(pid, timeout=5.0, waitpid=False)
-      self.failIf(utils.IsProcessAlive(pid))
-
-    self.assertEqual(utils.ReadFile(self.tmpfile), "")
-
-  def _wait(self, path, timeout, expected):
-    # Due to the asynchronous nature of daemon processes, polling is necessary.
-    # A timeout makes sure the test doesn't hang forever.
-    def _CheckFile():
-      if not (os.path.isfile(path) and
-              utils.ReadFile(path).strip() == expected):
-        raise utils.RetryAgain()
-
-    try:
-      utils.Retry(_CheckFile, (0.01, 1.5, 1.0), timeout)
-    except utils.RetryTimeout:
-      self.fail("Apparently the daemon didn't run in %s seconds and/or"
-                " didn't write the correct output" % timeout)
-
-  def testError(self):
-    self.assertRaises(errors.OpExecError, utils.StartDaemon,
-                      ["./does-NOT-EXIST/here/0123456789"])
-    self.assertRaises(errors.OpExecError, utils.StartDaemon,
-                      ["./does-NOT-EXIST/here/0123456789"],
-                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
-    self.assertRaises(errors.OpExecError, utils.StartDaemon,
-                      ["./does-NOT-EXIST/here/0123456789"],
-                      cwd=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
-    self.assertRaises(errors.OpExecError, utils.StartDaemon,
-                      ["./does-NOT-EXIST/here/0123456789"],
-                      output=os.path.join(self.tmpdir, "DIR/NOT/EXIST"))
-
-    fd = os.open(self.tmpfile, os.O_WRONLY | os.O_CREAT)
-    try:
-      self.assertRaises(errors.ProgrammerError, utils.StartDaemon,
-                        ["./does-NOT-EXIST/here/0123456789"],
-                        output=self.tmpfile, output_fd=fd)
-    finally:
-      os.close(fd)
-
-
 class TestParseCpuMask(unittest.TestCase):
   """Test case for the ParseCpuMask function."""
 
@@ -714,44 +201,6 @@ class TestForceDictType(unittest.TestCase):
                       {"b": "hello"}, {"b": "no-such-type"})
 
 
-class RunInSeparateProcess(unittest.TestCase):
-  def test(self):
-    for exp in [True, False]:
-      def _child():
-        return exp
-
-      self.assertEqual(exp, utils.RunInSeparateProcess(_child))
-
-  def testArgs(self):
-    for arg in [0, 1, 999, "Hello World", (1, 2, 3)]:
-      def _child(carg1, carg2):
-        return carg1 == "Foo" and carg2 == arg
-
-      self.assert_(utils.RunInSeparateProcess(_child, "Foo", arg))
-
-  def testPid(self):
-    parent_pid = os.getpid()
-
-    def _check():
-      return os.getpid() == parent_pid
-
-    self.failIf(utils.RunInSeparateProcess(_check))
-
-  def testSignal(self):
-    def _kill():
-      os.kill(os.getpid(), signal.SIGTERM)
-
-    self.assertRaises(errors.GenericError,
-                      utils.RunInSeparateProcess, _kill)
-
-  def testException(self):
-    def _exc():
-      raise errors.GenericError("This is a test")
-
-    self.assertRaises(errors.GenericError,
-                      utils.RunInSeparateProcess, _exc)
-
-
 class TestValidateServiceName(unittest.TestCase):
   def testValid(self):
     testnames = [
-- 
GitLab