Exemple #1
0
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: tuple; (int, callable)
  @return: File descriptor of pipe(2) which must be closed to notify parent
    process and a callable to reopen log files

  """
    # pylint: disable=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.

    reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)

    # Open logs for the first time
    reopen_fn()

    return (wpipe, reopen_fn)
Exemple #2
0
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)
Exemple #3
0
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
                input_fd, postfork_fn=None,
                _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
  @type noclose_fds: list
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
                      open for the child process
  @type input_fd: C{file}-like object or numeric file descriptor
  @param input_fd: File descriptor for process' standard input
  @type postfork_fn: Callable receiving PID as parameter
  @param postfork_fn: Function run after fork but before timeout
  @rtype: tuple
  @return: (out, err, status)

  """
  poller = select.poll()

  if interactive:
    stderr = None
    stdout = None
  else:
    stderr = subprocess.PIPE
    stdout = subprocess.PIPE

  if input_fd:
    stdin = input_fd
  elif interactive:
    stdin = None
  else:
    stdin = subprocess.PIPE

  if noclose_fds:
    preexec_fn = lambda: CloseFDs(noclose_fds)
    close_fds = False
  else:
    preexec_fn = None
    close_fds = True

  child = subprocess.Popen(cmd, shell=via_shell,
                           stderr=stderr,
                           stdout=stdout,
                           stdin=stdin,
                           close_fds=close_fds, env=env,
                           cwd=cwd,
                           preexec_fn=preexec_fn)

  if postfork_fn:
    postfork_fn(child.pid)

  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

  # subprocess: "If the stdin argument is PIPE, this attribute is a file object
  # that provides input to the child process. Otherwise, it is None."
  assert (stdin == subprocess.PIPE) ^ (child.stdin is None), \
    "subprocess' stdin did not behave as documented"

  if not interactive:
    if child.stdin is not None:
      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
Exemple #4
0
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=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=W0702
    try:
      # Report errors to original process
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
    except: # pylint: disable=W0702
      # Ignore errors in error handling
      pass

  os._exit(1) # pylint: disable=W0212
Exemple #5
0
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=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))