Skip to content

Commit 75838e5

Browse files
authored
Update pty + tty + associated libraries - v3.13.10 (#6630)
* Update tty + added the test from v3.13.10 * Updated pty.py + test
1 parent 8977c39 commit 75838e5

File tree

5 files changed

+286
-140
lines changed

5 files changed

+286
-140
lines changed

Lib/pty.py

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def master_open():
4040
Open a pty master and return the fd, and the filename of the slave end.
4141
Deprecated, use openpty() instead."""
4242

43+
import warnings
44+
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
45+
4346
try:
4447
master_fd, slave_fd = os.openpty()
4548
except (AttributeError, OSError):
@@ -69,6 +72,9 @@ def slave_open(tty_name):
6972
opened filedescriptor.
7073
Deprecated, use openpty() instead."""
7174

75+
import warnings
76+
warnings.warn("Use pty.openpty() instead.", DeprecationWarning, stacklevel=2) # Remove API in 3.14
77+
7278
result = os.open(tty_name, os.O_RDWR)
7379
try:
7480
from fcntl import ioctl, I_PUSH
@@ -101,32 +107,14 @@ def fork():
101107
master_fd, slave_fd = openpty()
102108
pid = os.fork()
103109
if pid == CHILD:
104-
# Establish a new session.
105-
os.setsid()
106110
os.close(master_fd)
107-
108-
# Slave becomes stdin/stdout/stderr of child.
109-
os.dup2(slave_fd, STDIN_FILENO)
110-
os.dup2(slave_fd, STDOUT_FILENO)
111-
os.dup2(slave_fd, STDERR_FILENO)
112-
if slave_fd > STDERR_FILENO:
113-
os.close(slave_fd)
114-
115-
# Explicitly open the tty to make it become a controlling tty.
116-
tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
117-
os.close(tmp_fd)
111+
os.login_tty(slave_fd)
118112
else:
119113
os.close(slave_fd)
120114

121115
# Parent and child process.
122116
return pid, master_fd
123117

124-
def _writen(fd, data):
125-
"""Write all the data to a descriptor."""
126-
while data:
127-
n = os.write(fd, data)
128-
data = data[n:]
129-
130118
def _read(fd):
131119
"""Default read function."""
132120
return os.read(fd, 1024)
@@ -136,9 +124,42 @@ def _copy(master_fd, master_read=_read, stdin_read=_read):
136124
Copies
137125
pty master -> standard output (master_read)
138126
standard input -> pty master (stdin_read)"""
139-
fds = [master_fd, STDIN_FILENO]
140-
while fds:
141-
rfds, _wfds, _xfds = select(fds, [], [])
127+
if os.get_blocking(master_fd):
128+
# If we write more than tty/ndisc is willing to buffer, we may block
129+
# indefinitely. So we set master_fd to non-blocking temporarily during
130+
# the copy operation.
131+
os.set_blocking(master_fd, False)
132+
try:
133+
_copy(master_fd, master_read=master_read, stdin_read=stdin_read)
134+
finally:
135+
# restore blocking mode for backwards compatibility
136+
os.set_blocking(master_fd, True)
137+
return
138+
high_waterlevel = 4096
139+
stdin_avail = master_fd != STDIN_FILENO
140+
stdout_avail = master_fd != STDOUT_FILENO
141+
i_buf = b''
142+
o_buf = b''
143+
while 1:
144+
rfds = []
145+
wfds = []
146+
if stdin_avail and len(i_buf) < high_waterlevel:
147+
rfds.append(STDIN_FILENO)
148+
if stdout_avail and len(o_buf) < high_waterlevel:
149+
rfds.append(master_fd)
150+
if stdout_avail and len(o_buf) > 0:
151+
wfds.append(STDOUT_FILENO)
152+
if len(i_buf) > 0:
153+
wfds.append(master_fd)
154+
155+
rfds, wfds, _xfds = select(rfds, wfds, [])
156+
157+
if STDOUT_FILENO in wfds:
158+
try:
159+
n = os.write(STDOUT_FILENO, o_buf)
160+
o_buf = o_buf[n:]
161+
except OSError:
162+
stdout_avail = False
142163

143164
if master_fd in rfds:
144165
# Some OSes signal EOF by returning an empty byte string,
@@ -150,19 +171,22 @@ def _copy(master_fd, master_read=_read, stdin_read=_read):
150171
if not data: # Reached EOF.
151172
return # Assume the child process has exited and is
152173
# unreachable, so we clean up.
153-
else:
154-
os.write(STDOUT_FILENO, data)
174+
o_buf += data
175+
176+
if master_fd in wfds:
177+
n = os.write(master_fd, i_buf)
178+
i_buf = i_buf[n:]
155179

156-
if STDIN_FILENO in rfds:
180+
if stdin_avail and STDIN_FILENO in rfds:
157181
data = stdin_read(STDIN_FILENO)
158182
if not data:
159-
fds.remove(STDIN_FILENO)
183+
stdin_avail = False
160184
else:
161-
_writen(master_fd, data)
185+
i_buf += data
162186

163187
def spawn(argv, master_read=_read, stdin_read=_read):
164188
"""Create a spawned process."""
165-
if type(argv) == type(''):
189+
if isinstance(argv, str):
166190
argv = (argv,)
167191
sys.audit('pty.spawn', argv)
168192

Lib/test/test_builtin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,6 +2241,7 @@ def child(wpipe):
22412241
expected = terminal_input.decode(sys.stdin.encoding) # what else?
22422242
self.assertEqual(input_result, expected)
22432243

2244+
@unittest.expectedFailure # TODO: RUSTPYTHON
22442245
def test_input_tty(self):
22452246
# Test input() functionality when wired to a tty (the code path
22462247
# is different and invokes GNU readline if available).
@@ -2257,17 +2258,20 @@ def skip_if_readline(self):
22572258
self.skipTest("the readline module is loaded")
22582259

22592260
@unittest.skipUnless(hasattr(sys.stdin, 'detach'), 'TODO: RustPython: requires detach function in TextIOWrapper')
2261+
@unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux
22602262
def test_input_tty_non_ascii(self):
22612263
self.skip_if_readline()
22622264
# Check stdin/stdout encoding is used when invoking PyOS_Readline()
22632265
self.check_input_tty("prompté", b"quux\xe9", "utf-8")
22642266

22652267
@unittest.skipUnless(hasattr(sys.stdin, 'detach'), 'TODO: RustPython: requires detach function in TextIOWrapper')
2268+
@unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux
22662269
def test_input_tty_non_ascii_unicode_errors(self):
22672270
self.skip_if_readline()
22682271
# Check stdin/stdout error handler is used when invoking PyOS_Readline()
22692272
self.check_input_tty("prompté", b"quux\xe9", "ascii")
22702273

2274+
@unittest.expectedFailure # TODO: RUSTPYTHON AssertionError: got 0 lines in pipe but expected 2, child output was: quux
22712275
def test_input_no_stdout_fileno(self):
22722276
# Issue #24402: If stdin is the original terminal but stdout.fileno()
22732277
# fails, do not use the original stdout file descriptor

0 commit comments

Comments
 (0)