Permalink
Newer
100644
187 lines (157 sloc)
5.09 KB
1
"""Pseudo terminal utilities."""
2
3
# Bugs: No signal handling. Doesn't set slave termios and window size.
4
# Only tested on Linux, FreeBSD, and macOS.
5
# See: W. Richard Stevens. 1992. Advanced Programming in the
6
# UNIX Environment. Chapter 19.
7
# Author: Steen Lumholt -- with additions by Guido.
8
9
from select import select
14
# names imported directly for test mocking purposes
15
from os import close, waitpid
16
from tty import setraw, tcgetattr, tcsetattr
17
18
__all__ = ["openpty", "fork", "spawn"]
20
STDIN_FILENO = 0
21
STDOUT_FILENO = 1
22
STDERR_FILENO = 2
23
24
CHILD = 0
25
27
"""openpty() -> (master_fd, slave_fd)
28
Open a pty master/slave pair, using os.openpty() if possible."""
30
try:
31
return os.openpty()
32
except (AttributeError, OSError):
33
pass
34
master_fd, slave_name = _open_terminal()
35
slave_fd = slave_open(slave_name)
36
return master_fd, slave_fd
39
"""master_open() -> (master_fd, slave_name)
40
Open a pty master and return the fd, and the filename of the slave end.
41
Deprecated, use openpty() instead."""
43
try:
44
master_fd, slave_fd = os.openpty()
45
except (AttributeError, OSError):
46
pass
47
else:
48
slave_name = os.ttyname(slave_fd)
49
os.close(slave_fd)
50
return master_fd, slave_name
55
"""Open pty master and return (master_fd, tty_name)."""
56
for x in 'pqrstuvwxyzPQRST':
57
for y in '0123456789abcdef':
58
pty_name = '/dev/pty' + x + y
59
try:
60
fd = os.open(pty_name, os.O_RDWR)
67
"""slave_open(tty_name) -> slave_fd
68
Open the pty slave and acquire the controlling terminal, returning
69
opened filedescriptor.
70
Deprecated, use openpty() instead."""
72
result = os.open(tty_name, os.O_RDWR)
73
try:
74
from fcntl import ioctl, I_PUSH
75
except ImportError:
76
return result
77
try:
78
ioctl(result, I_PUSH, "ptem")
79
ioctl(result, I_PUSH, "ldterm")
81
pass
82
return result
85
"""fork() -> (pid, master_fd)
86
Fork and make the child a session leader with a controlling terminal."""
87
88
try:
89
pid, fd = os.forkpty()
90
except (AttributeError, OSError):
91
pass
92
else:
93
if pid == CHILD:
94
try:
95
os.setsid()
96
except OSError:
97
# os.forkpty() already set us session leader
98
pass
99
return pid, fd
100
101
master_fd, slave_fd = openpty()
102
pid = os.fork()
103
if pid == CHILD:
104
# Establish a new session.
105
os.setsid()
106
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)
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)
131
"""Default read function."""
132
return os.read(fd, 1024)
134
def _copy(master_fd, master_read=_read, stdin_read=_read):
135
"""Parent copy loop.
136
Copies
137
pty master -> standard output (master_read)
138
standard input -> pty master (stdin_read)"""
139
fds = [master_fd, STDIN_FILENO]
140
while fds:
141
rfds, _wfds, _xfds = select(fds, [], [])
142
144
# Some OSes signal EOF by returning an empty byte string,
145
# some throw OSErrors.
146
try:
147
data = master_read(master_fd)
148
except OSError:
149
data = b""
150
if not data: # Reached EOF.
151
return # Assume the child process has exited and is
152
# unreachable, so we clean up.
153
else:
154
os.write(STDOUT_FILENO, data)
156
if STDIN_FILENO in rfds:
157
data = stdin_read(STDIN_FILENO)
158
if not data:
159
fds.remove(STDIN_FILENO)
160
else:
161
_writen(master_fd, data)
163
def spawn(argv, master_read=_read, stdin_read=_read):
165
if isinstance(argv, str):
167
sys.audit('pty.spawn', argv)
174
mode = tcgetattr(STDIN_FILENO)
175
setraw(STDIN_FILENO)
176
restore = True
177
except tty.error: # This is the same as termios.error
184
tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
186
close(master_fd)
187
return waitpid(pid, 0)[1]