Skip to content
Snippets Groups Projects
Commit 26029388 authored by Per Cederqvist's avatar Per Cederqvist
Browse files

Some documentation added.

(stty_init): Change default value to False instead of None.
(__use): New function.
(_inherit_stty): New function.
(_stty_init): New function.
(_spawn): New arguments: inherit_stty, stty_init.  Only inherit
	stty settings if stdin is a tty.  If an exception occurs in
	the child, transfer it to the parent as a
	remote_exception.Remote object.
(Spawn.__init__): New optional arguments: inherit_stty, stty_init.
(spawn2): Pass all arguments to the Spawn constructor.
parent 051e1718
No related branches found
No related tags found
No related merge requests found
"""Spawn subprocesses via a pty.
The normal way to use this is by creating an instance of the Spawn
class. This will connect the stdin, stdout and stderr of the new
process to the pty. You can then use the send() method to send
data to the procces, and wait for input using the normal Expectable
API.
If you need to differentiate between stdout and stderr of the
proces, you can use the spawn2() function. This will create a
pipe and a pty. The pty will be connected to stdin and stdout of
the process, while the pipe will be connected to stderr. spawn2()
returns two expectable objects, representing the pty and the pipe.
The method of connecting stderr to a pipe is not perfect. Some
programs assumes that stderr is connected to the tty, and will not
work properly if it is connected to a pipe. For instance, if you
start csh on Solaris 9 this way, it will not have job control
features. And if you start bash, and try to run stty from bash, it
will fail (tested with bash-2.05 under Solaris 9 and Linux 2.4.22).
As long as the program you start isn't a shell or a program that
calls isatty(2) you should be fine, though.
inherit_stty
============
By default, the new pty will inherit the stty settings of stdin, if
stdin is a tty. You can disable this by setting the global module
variable inherit_stty to False. The setting of inherit_stty can
also be overriden by the optional argument inherit_stty to the
Spawn constructor or spawn2() function.
stty_init
=========
After possibly inheriting the stty settings (see inherit_stty
above) stty will be run on the new pty with the stty_init string as
an argument. stty_init is default False, which means that this
step is skipped. stty_init is also a global variable that can be
overridden by optional arguments to the Spawn constructor or
spawn2() function.
"""
import os import os
import sys
import pty import pty
import fcntl import fcntl
import pcl_expect import pcl_expect
import pcl_expect.remote_exception
__all__ = [ __all__ = [
"inherit_stty", "inherit_stty",
...@@ -11,37 +57,78 @@ __all__ = [ ...@@ -11,37 +57,78 @@ __all__ = [
"spawn2", "spawn2",
] ]
# When a new pty is created by spawn(), the terminal settings will be
# inherited from the settings of stdin of the current process.
# Setting inherit_stty to False will disable this inheritance. After
# the inheritance (if any), if stty_init is set to a string, stty will
# be run with that string as its argument.
inherit_stty = True inherit_stty = True
stty_init = None stty_init = False
def set_cloexec_flag(fd): def set_cloexec_flag(fd):
"""Set the FD_CLOEXEC flag on fd."""
old = fcntl.fcntl(fd, fcntl.F_GETFD) old = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC) fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
def _spawn(cmd, use_stderr_pipe): def _use(a, b):
if inherit_stty: """Return b if a is None, otherwise return a."""
if a is None:
return b
else:
return a
def _inherit_stty(override):
"""Return the inherit_stty setting to use."""
_use(override, inherit_stty)
def _stty_init(override):
"""Return the stty_init setting to use."""
_use(override, stty_init)
def _spawn(cmd, inherit_stty, stty_init, use_stderr_pipe):
"""Spawn a process running cmd.
cmd -- A list of strings to be used as the argv vector of
os.execvp. cmd[0] will also be used as the program to
run. This can also be a string that will be split on
space to produce this list. There is no way to quote
a space, so unless the string is a constant you should
normally not use that form.
inherit_stty -- True if the stty settings should be inherited
if stdin is a tty.
stty_init -- String to pass to stty to initialize the pty.
use_stderr_pipe -- True if stderr of the new process should be
connected to a separate pipe instead of to the pty.
"""
inherit = inherit_stty and os.isatty(sys.stdin.fileno())
if inherit:
f = os.popen("stty -g") f = os.popen("stty -g")
settings = f.read() settings = f.read()
f.close() f.close()
if isinstance(cmd, basestring): if isinstance(cmd, basestring):
cmd = cmd.split(" ") cmd = cmd.split(" ")
# Create a synchronization pipe, so that the parent doesn't
# continue execution until the pty has been initialized and the
# new process execed. If all goes well, the parent will receive
# an end-of-file on the pipe when the exec closes the pipe. If
# exec fails, the errno code will be sent via this pipe.
(r, w) = os.pipe() (r, w) = os.pipe()
set_cloexec_flag(w)
if use_stderr_pipe: if use_stderr_pipe:
(stderr_r, stderr_w) = os.pipe() (stderr_r, stderr_w) = os.pipe()
else: else:
stderr_r = None stderr_r = None
set_cloexec_flag(w)
pid, fd = pty.fork() pid, fd = pty.fork()
if pid == 0: if pid == 0:
try:
try: try:
os.close(r) os.close(r)
if inherit_stty: if inherit:
os.system("stty %s" % settings) os.system("stty %s" % settings)
if stty_init is not None: if stty_init is not None:
os.system("stty %s" % stty_init) os.system("stty %s" % stty_init)
...@@ -49,11 +136,10 @@ def _spawn(cmd, use_stderr_pipe): ...@@ -49,11 +136,10 @@ def _spawn(cmd, use_stderr_pipe):
os.close(stderr_r) os.close(stderr_r)
os.dup2(stderr_w, 2) os.dup2(stderr_w, 2)
os.close(stderr_w) os.close(stderr_w)
try:
os.execvp(cmd[0], cmd) os.execvp(cmd[0], cmd)
except OSError: except:
# FIXME: transfer failure reason. os.write(w, remote_exception.serialize())
os.write(w, "exec failed\n")
finally: finally:
os._exit(1) os._exit(1)
...@@ -61,17 +147,23 @@ def _spawn(cmd, use_stderr_pipe): ...@@ -61,17 +147,23 @@ def _spawn(cmd, use_stderr_pipe):
os.close(w) os.close(w)
if use_stderr_pipe: if use_stderr_pipe:
os.close(stderr_w) os.close(stderr_w)
res = os.read(r, 100) res = os.fdopen(r).read()
os.close(r)
if res != "": if res != "":
os.close(fd) os.close(fd)
os.waitpid(pid, 0) os.waitpid(pid, 0)
raise OSError("exec failed") remote_exception.re_raise(res)
return pid, fd, stderr_r return pid, fd, stderr_r
class Spawn(pcl_expect.Expectable): class Spawn(pcl_expect.Expectable):
def __init__(self, cmd, use_stderr_pipe = False): def __init__(self, cmd, inherit_stty = None, stty_init = None,
self.__child_pid, fd, stderr = _spawn(cmd, use_stderr_pipe) use_stderr_pipe = False):
self.__child_pid, fd, stderr = _spawn(
cmd,
_inherit_stty(inherit_stty),
_stty_init(stty_init),
use_stderr_pipe)
pcl_expect.Expectable.__init__(self, fd) pcl_expect.Expectable.__init__(self, fd)
if use_stderr_pipe: if use_stderr_pipe:
self.stderr = pcl_expect.Expectable(stderr) self.stderr = pcl_expect.Expectable(stderr)
...@@ -88,6 +180,6 @@ class Spawn(pcl_expect.Expectable): ...@@ -88,6 +180,6 @@ class Spawn(pcl_expect.Expectable):
self.stderr.close() self.stderr.close()
return os.waitpid(self.__child_pid, 0) return os.waitpid(self.__child_pid, 0)
def spawn2(cmd): def spawn2(cmd, *args, **kwargs):
proc = Spawn(cmd, True) proc = Spawn(cmd, use_stderr_pipe=True, *args, **kwargs)
return proc, proc.stderr return proc, proc.stderr
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment