From 260293880a836f3d1b5b11d0c369555c90a41a07 Mon Sep 17 00:00:00 2001 From: Per Cederqvist <ceder@lysator.liu.se> Date: Sat, 25 Oct 2003 23:01:12 +0000 Subject: [PATCH] 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. --- pcl_expect/spawn.py | 148 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 28 deletions(-) diff --git a/pcl_expect/spawn.py b/pcl_expect/spawn.py index 8206a28..04057b3 100644 --- a/pcl_expect/spawn.py +++ b/pcl_expect/spawn.py @@ -1,8 +1,54 @@ +"""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 sys import pty import fcntl import pcl_expect +import pcl_expect.remote_exception __all__ = [ "inherit_stty", @@ -11,49 +57,89 @@ __all__ = [ "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 -stty_init = None +stty_init = False def set_cloexec_flag(fd): + """Set the FD_CLOEXEC flag on fd.""" old = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC) -def _spawn(cmd, use_stderr_pipe): - if inherit_stty: +def _use(a, b): + """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") settings = f.read() f.close() if isinstance(cmd, basestring): 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() + set_cloexec_flag(w) + if use_stderr_pipe: (stderr_r, stderr_w) = os.pipe() else: stderr_r = None - set_cloexec_flag(w) + pid, fd = pty.fork() if pid == 0: try: - os.close(r) - if inherit_stty: - os.system("stty %s" % settings) - if stty_init is not None: - os.system("stty %s" % stty_init) - if use_stderr_pipe: - os.close(stderr_r) - os.dup2(stderr_w, 2) - os.close(stderr_w) try: + os.close(r) + if inherit: + os.system("stty %s" % settings) + if stty_init is not None: + os.system("stty %s" % stty_init) + if use_stderr_pipe: + os.close(stderr_r) + os.dup2(stderr_w, 2) + os.close(stderr_w) + os.execvp(cmd[0], cmd) - except OSError: - # FIXME: transfer failure reason. - os.write(w, "exec failed\n") + except: + os.write(w, remote_exception.serialize()) finally: os._exit(1) @@ -61,17 +147,23 @@ def _spawn(cmd, use_stderr_pipe): os.close(w) if use_stderr_pipe: os.close(stderr_w) - res = os.read(r, 100) - os.close(r) + res = os.fdopen(r).read() if res != "": os.close(fd) os.waitpid(pid, 0) - raise OSError("exec failed") + remote_exception.re_raise(res) return pid, fd, stderr_r class Spawn(pcl_expect.Expectable): - def __init__(self, cmd, use_stderr_pipe = False): - self.__child_pid, fd, stderr = _spawn(cmd, use_stderr_pipe) + def __init__(self, cmd, inherit_stty = None, stty_init = None, + 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) if use_stderr_pipe: self.stderr = pcl_expect.Expectable(stderr) @@ -88,6 +180,6 @@ class Spawn(pcl_expect.Expectable): self.stderr.close() return os.waitpid(self.__child_pid, 0) -def spawn2(cmd): - proc = Spawn(cmd, True) +def spawn2(cmd, *args, **kwargs): + proc = Spawn(cmd, use_stderr_pipe=True, *args, **kwargs) return proc, proc.stderr -- GitLab