summaryrefslogtreecommitdiffstats
path: root/Lib/pty.py
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2021-08-12 12:36:04 (GMT)
committerGitHub <noreply@github.com>2021-08-12 12:36:04 (GMT)
commit5d444434ad4e1943a88c9d3aadd300fd0f05dab7 (patch)
treed287cc76adc529e32987d5d244fe55e0eb8fdded /Lib/pty.py
parent2666d702e49aa0e4975553bec2e92ca4a2dba4ea (diff)
downloadcpython-5d444434ad4e1943a88c9d3aadd300fd0f05dab7.zip
cpython-5d444434ad4e1943a88c9d3aadd300fd0f05dab7.tar.gz
cpython-5d444434ad4e1943a88c9d3aadd300fd0f05dab7.tar.bz2
bpo-26228: Fix pty EOF handling (GH-12049) (GH-27732)
On non-Linux POSIX platforms, like FreeBSD or macOS, the FD used to read a forked PTY may signal its exit not by raising an error but by sending empty data to the read syscall. This case wasn't handled, leading to hanging `pty.spawn` calls. Co-authored-by: Reilly Tucker Siemens <reilly@tuckersiemens.com> Co-authored-by: Ɓukasz Langa <lukasz@langa.pl> (cherry picked from commit 81ab8db235580317edcb0e559cd4c983f70883f5) Co-authored-by: Zephyr Shannon <geoffpshannon@gmail.com>
Diffstat (limited to 'Lib/pty.py')
-rw-r--r--Lib/pty.py45
1 files changed, 30 insertions, 15 deletions
diff --git a/Lib/pty.py b/Lib/pty.py
index a324320..43e974f 100644
--- a/Lib/pty.py
+++ b/Lib/pty.py
@@ -11,7 +11,11 @@ import os
import sys
import tty
-__all__ = ["openpty","fork","spawn"]
+# names imported directly for test mocking purposes
+from os import close, waitpid
+from tty import setraw, tcgetattr, tcsetattr
+
+__all__ = ["openpty", "fork", "spawn"]
STDIN_FILENO = 0
STDOUT_FILENO = 1
@@ -105,8 +109,8 @@ def fork():
os.dup2(slave_fd, STDIN_FILENO)
os.dup2(slave_fd, STDOUT_FILENO)
os.dup2(slave_fd, STDERR_FILENO)
- if (slave_fd > STDERR_FILENO):
- os.close (slave_fd)
+ if slave_fd > STDERR_FILENO:
+ os.close(slave_fd)
# Explicitly open the tty to make it become a controlling tty.
tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
@@ -133,14 +137,22 @@ def _copy(master_fd, master_read=_read, stdin_read=_read):
pty master -> standard output (master_read)
standard input -> pty master (stdin_read)"""
fds = [master_fd, STDIN_FILENO]
- while True:
- rfds, wfds, xfds = select(fds, [], [])
+ while fds:
+ rfds, _wfds, _xfds = select(fds, [], [])
+
if master_fd in rfds:
- data = master_read(master_fd)
+ # Some OSes signal EOF by returning an empty byte string,
+ # some throw OSErrors.
+ try:
+ data = master_read(master_fd)
+ except OSError:
+ data = b""
if not data: # Reached EOF.
- fds.remove(master_fd)
+ return # Assume the child process has exited and is
+ # unreachable, so we clean up.
else:
os.write(STDOUT_FILENO, data)
+
if STDIN_FILENO in rfds:
data = stdin_read(STDIN_FILENO)
if not data:
@@ -153,20 +165,23 @@ def spawn(argv, master_read=_read, stdin_read=_read):
if type(argv) == type(''):
argv = (argv,)
sys.audit('pty.spawn', argv)
+
pid, master_fd = fork()
if pid == CHILD:
os.execlp(argv[0], *argv)
+
try:
- mode = tty.tcgetattr(STDIN_FILENO)
- tty.setraw(STDIN_FILENO)
- restore = 1
+ mode = tcgetattr(STDIN_FILENO)
+ setraw(STDIN_FILENO)
+ restore = True
except tty.error: # This is the same as termios.error
- restore = 0
+ restore = False
+
try:
_copy(master_fd, master_read, stdin_read)
- except OSError:
+ finally:
if restore:
- tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
+ tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
- os.close(master_fd)
- return os.waitpid(pid, 0)[1]
+ close(master_fd)
+ return waitpid(pid, 0)[1]