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))
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False, interactive=False, timeout=None, noclose_fds=None, input_fd=None, postfork_fn=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: whether 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 @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: Callback run after fork but before timeout @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 not (output is None or input_fd is None): # The current logic in "_RunCmdFile", which is used when output is defined, # does not support input files (not hard to implement, though) raise errors.ProgrammerError("Parameters 'output' and 'input_fd' can" " not be used 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.info("RunCmd %s, output file '%s'", strcmd, output) else: logging.info("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, noclose_fds, input_fd, postfork_fn=postfork_fn) else: if postfork_fn: raise errors.ProgrammerError("postfork_fn is not supported if output" " should be captured") assert input_fd is None timeout_action = _TIMEOUT_NONE status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds) out = err = "" except OSError, err: if err.errno == errno.ENOENT: raise errors.OpExecError("Can't execute '%s': not found (%s)" % (strcmd, err)) else: raise