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)
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)
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) output_close = True if output_fd is not None: output_close = False 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, 0o600) except EnvironmentError as 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) if devnull_fd > 2: utils_wrapper.CloseFdNoError(devnull_fd) if output_close and output_fd > 2: utils_wrapper.CloseFdNoError(output_fd)
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
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))
# 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) if devnull_fd > 2: utils_wrapper.CloseFdNoError(devnull_fd) if output_close and output_fd > 2: utils_wrapper.CloseFdNoError(output_fd) 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