def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
- interactive=False, timeout=None):
+ interactive=False, timeout=None, noclose_fds=None):
"""Execute a (shell) command.
The command should not read from its standard input, as it will be
@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
@rtype: L{RunResult}
@return: RunResult instance
@raise errors.ProgrammerError: if we call this when forks are disabled
try:
if output is None:
out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
- interactive, timeout)
+ interactive, timeout,
+ noclose_fds)
else:
timeout_action = _TIMEOUT_NONE
- status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
+ status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
out = err = ""
except OSError, err:
if err.errno == errno.ENOENT:
pass
-def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
+def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
_linger_timeout=constants.CHILD_LINGER_TIMEOUT):
"""Run a command and return its output.
@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
@rtype: tuple
@return: (out, err, status)
if interactive:
stderr = stdout = stdin = None
+ 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=True, env=env,
- cwd=cwd)
+ close_fds=close_fds, env=env,
+ cwd=cwd,
+ preexec_fn=preexec_fn)
out = StringIO()
err = StringIO()
return out, err, status, timeout_action
-def _RunCmdFile(cmd, env, via_shell, output, cwd):
+def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
"""Run a command and save its output to a file.
@type cmd: string or list
@param output: the filename in which to save the output
@type cwd: string
@param cwd: the working directory for the program
+ @type noclose_fds: list
+ @param noclose_fds: list of additional (fd >=3) file descriptors to leave
+ open for the child process
@rtype: int
@return: the exit status
"""
fh = open(output, "a")
+
+ if noclose_fds:
+ preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
+ close_fds = False
+ else:
+ preexec_fn = None
+ close_fds = True
+
try:
child = subprocess.Popen(cmd, shell=via_shell,
stderr=subprocess.STDOUT,
stdout=fh,
stdin=subprocess.PIPE,
- close_fds=True, env=env,
- cwd=cwd)
+ close_fds=close_fds, env=env,
+ cwd=cwd,
+ preexec_fn=preexec_fn)
child.stdin.close()
status = child.wait()
timeout = 0.2
(out, err, status, ta) = \
utils.process._RunCmdPipe(cmd, {}, False, "/", False,
- timeout, _linger_timeout=0.2)
+ timeout, None, _linger_timeout=0.2)
self.assert_(status < 0)
self.assertEqual(-status, signal.SIGKILL)
self.assertRaises(errors.ProgrammerError, utils.RunCmd, ["true"],
output="/dev/null", interactive=True)
+ def testNocloseFds(self):
+ """Test selective fd retention (noclose_fds)"""
+ temp = open(self.fname, "r+")
+ try:
+ temp.write("test")
+ temp.seek(0)
+ cmd = "read -u %d; echo $REPLY" % temp.fileno()
+ result = utils.RunCmd(["/bin/bash", "-c", cmd])
+ self.assertEqual(result.stdout.strip(), "")
+ temp.seek(0)
+ result = utils.RunCmd(["/bin/bash", "-c", cmd],
+ noclose_fds=[temp.fileno()])
+ self.assertEqual(result.stdout.strip(), "test")
+ finally:
+ temp.close()
+
class TestRunParts(testutils.GanetiTestCase):
"""Testing case for the RunParts function"""