Skip to content

Commit 3931cd4

Browse files
authored
Merge pull request #49 from eugpermar/master
Add pass_fds parameter to PtyProcess:spawn()
2 parents 5436e55 + f5a5163 commit 3931cd4

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

ptyprocess/ptyprocess.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def __init__(self, pid, fd):
178178
@classmethod
179179
def spawn(
180180
cls, argv, cwd=None, env=None, echo=True, preexec_fn=None,
181-
dimensions=(24, 80)):
181+
dimensions=(24, 80), pass_fds=()):
182182
'''Start the given command in a child process in a pseudo terminal.
183183
184184
This does all the fork/exec type of stuff for a pty, and returns an
@@ -190,6 +190,10 @@ def spawn(
190190
191191
Dimensions of the psuedoterminal used for the subprocess can be
192192
specified as a tuple (rows, cols), or the default (24, 80) will be used.
193+
194+
By default, all file descriptors except 0, 1 and 2 are closed. This
195+
behavior can be overridden with pass_fds, a list of file descriptors to
196+
keep open between the parent and the child.
193197
'''
194198
# Note that it is difficult for this method to fail.
195199
# You cannot detect if the child process cannot start.
@@ -255,12 +259,14 @@ def spawn(
255259

256260
# Do not allow child to inherit open file descriptors from parent,
257261
# with the exception of the exec_err_pipe_write of the pipe
262+
# and pass_fds.
258263
# Impose ceiling on max_fd: AIX bugfix for users with unlimited
259264
# nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange()
260265
# occasionally raises out of range error
261266
max_fd = min(1048576, resource.getrlimit(resource.RLIMIT_NOFILE)[0])
262-
os.closerange(3, exec_err_pipe_write)
263-
os.closerange(exec_err_pipe_write+1, max_fd)
267+
spass_fds = sorted(set(pass_fds) | {exec_err_pipe_write})
268+
for pair in zip([2] + spass_fds, spass_fds + [max_fd]):
269+
os.closerange(pair[0]+1, pair[1])
264270

265271
if cwd is not None:
266272
os.chdir(cwd)

tests/test_spawn.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import fcntl
12
import os
23
import time
34
import select
5+
import tempfile
46
import unittest
57
from ptyprocess.ptyprocess import which
68
from ptyprocess import PtyProcess, PtyProcessUnicode
@@ -111,3 +113,37 @@ def test_interactive_repl_unicode_noecho(self):
111113
@unittest.skipIf(which('bc') is None, "bc(1) not found on this server.")
112114
def test_interactive_repl_unicode_echo(self):
113115
self._interactive_repl_unicode(echo=True)
116+
117+
def test_pass_fds(self):
118+
with tempfile.NamedTemporaryFile() as temp_file:
119+
temp_file_fd = temp_file.fileno()
120+
temp_file_name = temp_file.name
121+
122+
# Temporary files are CLOEXEC by default
123+
fcntl.fcntl(temp_file_fd,
124+
fcntl.F_SETFD,
125+
fcntl.fcntl(temp_file_fd, fcntl.F_GETFD) &
126+
~fcntl.FD_CLOEXEC)
127+
128+
# You can write with pass_fds
129+
p = PtyProcess.spawn(['bash',
130+
'-c',
131+
'printf hello >&{}'.format(temp_file_fd)],
132+
echo=True,
133+
pass_fds=(temp_file_fd,))
134+
p.wait()
135+
assert p.status == 0
136+
137+
with open(temp_file_name, 'r') as temp_file_r:
138+
assert temp_file_r.read() == 'hello'
139+
140+
# You can't write without pass_fds
141+
p = PtyProcess.spawn(['bash',
142+
'-c',
143+
'printf bye >&{}'.format(temp_file_fd)],
144+
echo=True)
145+
p.wait()
146+
assert p.status != 0
147+
148+
with open(temp_file_name, 'r') as temp_file_r:
149+
assert temp_file_r.read() == 'hello'

0 commit comments

Comments
 (0)