#
#
-# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011, 2012 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
from ganeti import errors
from ganeti import constants
+from ganeti import compat
from ganeti.utils import retry as utils_retry
from ganeti.utils import wrapper as utils_wrapper
"""Disables the use of fork(2).
"""
- global _no_fork # pylint: disable-msg=W0603
+ global _no_fork # pylint: disable=W0603
_no_fork = True
__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
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
- interactive=False, timeout=None, noclose_fds=None):
+ interactive=False, timeout=None, noclose_fds=None,
+ _postfork_fn=None):
"""Execute a (shell) command.
The command should not read from its standard input, as it 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
+ @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
@type noclose_fds: list
@param noclose_fds: list of additional (fd >=3) file descriptors to leave
open for the child process
+ @param _postfork_fn: Callback run after fork but before timeout (unittest)
@rtype: L{RunResult}
@return: RunResult instance
@raise errors.ProgrammerError: if we call this when forks are disabled
shell = False
if output:
- logging.debug("RunCmd %s, output file '%s'", strcmd, output)
+ logging.info("RunCmd %s, output file '%s'", strcmd, output)
else:
- logging.debug("RunCmd %s", strcmd)
+ logging.info("RunCmd %s", strcmd)
cmd_env = _BuildCmdEnvironment(env, reset_env)
if output is None:
out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
interactive, timeout,
- noclose_fds)
+ noclose_fds,
+ _postfork_fn=_postfork_fn)
else:
+ assert _postfork_fn is None, \
+ "_postfork_fn not supported if output provided"
timeout_action = _TIMEOUT_NONE
status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
out = err = ""
# 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:
- pass
+ output_close = False
elif output_file is not None:
# Open output file
try:
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):
output, output_fd, pidfile)
finally:
# Well, maybe child process failed
- os._exit(1) # pylint: disable-msg=W0212
+ os._exit(1) # pylint: disable=W0212
finally:
utils_wrapper.CloseFdNoError(errpipe_write)
pid = os.fork()
if pid != 0:
# Exit first child process
- os._exit(0) # pylint: disable-msg=W0212
+ os._exit(0) # pylint: disable=W0212
# Make sure pipe is closed on execv* (and thereby notifies
# original process)
os.execvp(args[0], args)
else:
os.execvpe(args[0], args, env)
- except: # pylint: disable-msg=W0702
+ except: # pylint: disable=W0702
try:
# Report errors to original process
WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
- except: # pylint: disable-msg=W0702
+ except: # pylint: disable=W0702
# Ignore errors in error handling
pass
- os._exit(1) # pylint: disable-msg=W0212
+ os._exit(1) # pylint: disable=W0212
def WriteErrorToFD(fd, err):
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
- _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
+ _linger_timeout=constants.CHILD_LINGER_TIMEOUT,
+ _postfork_fn=None):
"""Run a command and return its output.
@type cmd: string or list
@type noclose_fds: list
@param noclose_fds: list of additional (fd >=3) file descriptors to leave
open for the child process
+ @param _postfork_fn: Function run after fork but before timeout (unittest)
@rtype: tuple
@return: (out, err, status)
cwd=cwd,
preexec_fn=preexec_fn)
+ if _postfork_fn:
+ _postfork_fn(child.pid)
+
out = StringIO()
err = StringIO()
else:
try:
result = RunCmd([fname], env=env, reset_env=reset_env)
- except Exception, err: # pylint: disable-msg=W0703
+ except Exception, err: # pylint: disable=W0703
rr.append((relname, constants.RUNPARTS_ERR, str(err)))
else:
rr.append((relname, constants.RUNPARTS_RUN, result))
@type logfile: str
@param logfile: the logfile to which we should redirect stdout/stderr
- @rtype: int
- @return: the value zero
+ @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-msg=W0212
+ # pylint: disable=W0212
# yes, we really want os._exit
# TODO: do another attempt to merge Daemonize and StartDaemon, or at
rcode = 0
os._exit(rcode) # Exit parent of the first child.
- SetupDaemonFDs(logfile, None)
- return wpipe
+ reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
+
+ # Open logs for the first time
+ reopen_fn()
+
+ return (wpipe, reopen_fn)
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
# Call function
result = int(bool(fn(*args)))
assert result in (0, 1)
- except: # pylint: disable-msg=W0702
+ except: # pylint: disable=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
+ os._exit(result) # pylint: disable=W0212
# Parent process