2121# Constants
2222from pty import (STDIN_FILENO , CHILD )
2323
24- from .util import which
24+ from .util import which , PtyProcessError
2525
2626_platform = sys .platform .lower ()
2727
@@ -62,11 +62,18 @@ def _make_eof_intr():
6262 # inherit EOF and INTR definitions from controlling process.
6363 try :
6464 from termios import VEOF , VINTR
65- try :
66- fd = sys .__stdin__ .fileno ()
67- except ValueError :
68- # ValueError: I/O operation on closed file
69- fd = sys .__stdout__ .fileno ()
65+ fd = None
66+ for name in 'stdin' , 'stdout' :
67+ stream = getattr (sys , '__%s__' % name , None )
68+ if stream is None or not hasattr (stream , 'fileno' ):
69+ continue
70+ try :
71+ fd = stream .fileno ()
72+ except ValueError :
73+ continue
74+ if fd is None :
75+ # no fd, raise ValueError to fallback on CEOF, CINTR
76+ raise ValueError ("No stream has a fileno" )
7077 intr = ord (termios .tcgetattr (fd )[6 ][VINTR ])
7178 eof = ord (termios .tcgetattr (fd )[6 ][VEOF ])
7279 except (ImportError , OSError , IOError , ValueError , termios .error ):
@@ -83,14 +90,11 @@ def _make_eof_intr():
8390 _INTR = _byte (intr )
8491 _EOF = _byte (eof )
8592
86- class PtyProcessError (Exception ):
87- """Generic error class for this package."""
88-
8993# setecho and setwinsize are pulled out here because on some platforms, we need
9094# to do this from the child before we exec()
9195
9296def _setecho (fd , state ):
93- errmsg = 'setecho() may not be called on this platform'
97+ errmsg = 'setecho() may not be called on this platform (it may still be possible to enable/disable echo when spawning the child process) '
9498
9599 try :
96100 attr = termios .tcgetattr (fd )
@@ -176,7 +180,7 @@ def __init__(self, pid, fd):
176180 @classmethod
177181 def spawn (
178182 cls , argv , cwd = None , env = None , echo = True , preexec_fn = None ,
179- dimensions = (24 , 80 )):
183+ dimensions = (24 , 80 ), pass_fds = () ):
180184 '''Start the given command in a child process in a pseudo terminal.
181185
182186 This does all the fork/exec type of stuff for a pty, and returns an
@@ -188,6 +192,10 @@ def spawn(
188192
189193 Dimensions of the psuedoterminal used for the subprocess can be
190194 specified as a tuple (rows, cols), or the default (24, 80) will be used.
195+
196+ By default, all file descriptors except 0, 1 and 2 are closed. This
197+ behavior can be overridden with pass_fds, a list of file descriptors to
198+ keep open between the parent and the child.
191199 '''
192200 # Note that it is difficult for this method to fail.
193201 # You cannot detect if the child process cannot start.
@@ -253,9 +261,14 @@ def spawn(
253261
254262 # Do not allow child to inherit open file descriptors from parent,
255263 # with the exception of the exec_err_pipe_write of the pipe
256- max_fd = resource .getrlimit (resource .RLIMIT_NOFILE )[0 ]
257- os .closerange (3 , exec_err_pipe_write )
258- os .closerange (exec_err_pipe_write + 1 , max_fd )
264+ # and pass_fds.
265+ # Impose ceiling on max_fd: AIX bugfix for users with unlimited
266+ # nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange()
267+ # occasionally raises out of range error
268+ max_fd = min (1048576 , resource .getrlimit (resource .RLIMIT_NOFILE )[0 ])
269+ spass_fds = sorted (set (pass_fds ) | {exec_err_pipe_write })
270+ for pair in zip ([2 ] + spass_fds , spass_fds + [max_fd ]):
271+ os .closerange (pair [0 ]+ 1 , pair [1 ])
259272
260273 if cwd is not None :
261274 os .chdir (cwd )
0 commit comments