Commit 0963d545 authored by René Nussbaumer's avatar René Nussbaumer
Browse files

Make it possible to invoke RunCmd in interactive mode



This is needed so we can run external scripts asking for password and such
to run interactive. Downside is that we can't get the output of the program
but we can still use the exit code.
Signed-off-by: default avatarRené Nussbaumer <rn@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent e8d61457
...@@ -158,7 +158,8 @@ def _BuildCmdEnvironment(env, reset): ...@@ -158,7 +158,8 @@ def _BuildCmdEnvironment(env, reset):
return cmd_env return cmd_env
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False): def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
interactive=False):
"""Execute a (shell) command. """Execute a (shell) command.
The command should not read from its standard input, as it will be The command should not read from its standard input, as it will be
...@@ -177,6 +178,9 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False): ...@@ -177,6 +178,9 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False):
directory for the command; the default will be / directory for the command; the default will be /
@type reset_env: boolean @type reset_env: boolean
@param reset_env: whether to reset or keep the default os environment @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
@rtype: L{RunResult} @rtype: L{RunResult}
@return: RunResult instance @return: RunResult instance
@raise errors.ProgrammerError: if we call this when forks are disabled @raise errors.ProgrammerError: if we call this when forks are disabled
...@@ -185,6 +189,10 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False): ...@@ -185,6 +189,10 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False):
if no_fork: if no_fork:
raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled") 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): if isinstance(cmd, basestring):
strcmd = cmd strcmd = cmd
shell = True shell = True
...@@ -202,7 +210,7 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False): ...@@ -202,7 +210,7 @@ def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False):
try: try:
if output is None: if output is None:
out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd) out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd, interactive)
else: else:
status = _RunCmdFile(cmd, cmd_env, shell, output, cwd) status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
out = err = "" out = err = ""
...@@ -418,7 +426,7 @@ def _StartDaemonChild(errpipe_read, errpipe_write, ...@@ -418,7 +426,7 @@ def _StartDaemonChild(errpipe_read, errpipe_write,
os._exit(1) # pylint: disable-msg=W0212 os._exit(1) # pylint: disable-msg=W0212
def _RunCmdPipe(cmd, env, via_shell, cwd): def _RunCmdPipe(cmd, env, via_shell, cwd, interactive):
"""Run a command and return its output. """Run a command and return its output.
@type cmd: string or list @type cmd: string or list
...@@ -429,46 +437,57 @@ def _RunCmdPipe(cmd, env, via_shell, cwd): ...@@ -429,46 +437,57 @@ def _RunCmdPipe(cmd, env, via_shell, cwd):
@param via_shell: if we should run via the shell @param via_shell: if we should run via the shell
@type cwd: string @type cwd: string
@param cwd: the working directory for the program @param cwd: the working directory for the program
@type interactive: boolean
@param interactive: Run command interactive (without piping)
@rtype: tuple @rtype: tuple
@return: (out, err, status) @return: (out, err, status)
""" """
poller = select.poll() 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, child = subprocess.Popen(cmd, shell=via_shell,
stderr=subprocess.PIPE, stderr=stderr,
stdout=subprocess.PIPE, stdout=stdout,
stdin=subprocess.PIPE, stdin=stdin,
close_fds=True, env=env, close_fds=True, env=env,
cwd=cwd) cwd=cwd)
child.stdin.close()
poller.register(child.stdout, select.POLLIN)
poller.register(child.stderr, select.POLLIN)
out = StringIO() out = StringIO()
err = StringIO() err = StringIO()
fdmap = { if not interactive:
child.stdout.fileno(): (out, child.stdout), child.stdin.close()
child.stderr.fileno(): (err, child.stderr), poller.register(child.stdout, select.POLLIN)
} poller.register(child.stderr, select.POLLIN)
for fd in fdmap: fdmap = {
SetNonblockFlag(fd, True) child.stdout.fileno(): (out, child.stdout),
child.stderr.fileno(): (err, child.stderr),
while fdmap: }
pollresult = RetryOnSignal(poller.poll) for fd in fdmap:
SetNonblockFlag(fd, True)
for fd, event in pollresult:
if event & select.POLLIN or event & select.POLLPRI: while fdmap:
data = fdmap[fd][1].read() pollresult = RetryOnSignal(poller.poll)
# no data from read signifies EOF (the same as POLLHUP)
if not data: 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) poller.unregister(fd)
del fdmap[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]
out = out.getvalue() out = out.getvalue()
err = err.getvalue() err = err.getvalue()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment