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