|
1 """Pseudo terminal utilities.""" |
|
2 |
|
3 # Bugs: No signal handling. Doesn't set slave termios and window size. |
|
4 # Only tested on Linux. |
|
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 |
|
10 import os |
|
11 import tty |
|
12 |
|
13 __all__ = ["openpty","fork","spawn"] |
|
14 |
|
15 STDIN_FILENO = 0 |
|
16 STDOUT_FILENO = 1 |
|
17 STDERR_FILENO = 2 |
|
18 |
|
19 CHILD = 0 |
|
20 |
|
21 def openpty(): |
|
22 """openpty() -> (master_fd, slave_fd) |
|
23 Open a pty master/slave pair, using os.openpty() if possible.""" |
|
24 |
|
25 try: |
|
26 return os.openpty() |
|
27 except (AttributeError, OSError): |
|
28 pass |
|
29 master_fd, slave_name = _open_terminal() |
|
30 slave_fd = slave_open(slave_name) |
|
31 return master_fd, slave_fd |
|
32 |
|
33 def master_open(): |
|
34 """master_open() -> (master_fd, slave_name) |
|
35 Open a pty master and return the fd, and the filename of the slave end. |
|
36 Deprecated, use openpty() instead.""" |
|
37 |
|
38 try: |
|
39 master_fd, slave_fd = os.openpty() |
|
40 except (AttributeError, OSError): |
|
41 pass |
|
42 else: |
|
43 slave_name = os.ttyname(slave_fd) |
|
44 os.close(slave_fd) |
|
45 return master_fd, slave_name |
|
46 |
|
47 return _open_terminal() |
|
48 |
|
49 def _open_terminal(): |
|
50 """Open pty master and return (master_fd, tty_name). |
|
51 SGI and generic BSD version, for when openpty() fails.""" |
|
52 try: |
|
53 import sgi |
|
54 except ImportError: |
|
55 pass |
|
56 else: |
|
57 try: |
|
58 tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0) |
|
59 except IOError, msg: |
|
60 raise os.error, msg |
|
61 return master_fd, tty_name |
|
62 for x in 'pqrstuvwxyzPQRST': |
|
63 for y in '0123456789abcdef': |
|
64 pty_name = '/dev/pty' + x + y |
|
65 try: |
|
66 fd = os.open(pty_name, os.O_RDWR) |
|
67 except os.error: |
|
68 continue |
|
69 return (fd, '/dev/tty' + x + y) |
|
70 raise os.error, 'out of pty devices' |
|
71 |
|
72 def slave_open(tty_name): |
|
73 """slave_open(tty_name) -> slave_fd |
|
74 Open the pty slave and acquire the controlling terminal, returning |
|
75 opened filedescriptor. |
|
76 Deprecated, use openpty() instead.""" |
|
77 |
|
78 result = os.open(tty_name, os.O_RDWR) |
|
79 try: |
|
80 from fcntl import ioctl, I_PUSH |
|
81 except ImportError: |
|
82 return result |
|
83 try: |
|
84 ioctl(result, I_PUSH, "ptem") |
|
85 ioctl(result, I_PUSH, "ldterm") |
|
86 except IOError: |
|
87 pass |
|
88 return result |
|
89 |
|
90 def fork(): |
|
91 """fork() -> (pid, master_fd) |
|
92 Fork and make the child a session leader with a controlling terminal.""" |
|
93 |
|
94 try: |
|
95 pid, fd = os.forkpty() |
|
96 except (AttributeError, OSError): |
|
97 pass |
|
98 else: |
|
99 if pid == CHILD: |
|
100 try: |
|
101 os.setsid() |
|
102 except OSError: |
|
103 # os.forkpty() already set us session leader |
|
104 pass |
|
105 return pid, fd |
|
106 |
|
107 master_fd, slave_fd = openpty() |
|
108 pid = os.fork() |
|
109 if pid == CHILD: |
|
110 # Establish a new session. |
|
111 os.setsid() |
|
112 os.close(master_fd) |
|
113 |
|
114 # Slave becomes stdin/stdout/stderr of child. |
|
115 os.dup2(slave_fd, STDIN_FILENO) |
|
116 os.dup2(slave_fd, STDOUT_FILENO) |
|
117 os.dup2(slave_fd, STDERR_FILENO) |
|
118 if (slave_fd > STDERR_FILENO): |
|
119 os.close (slave_fd) |
|
120 |
|
121 # Explicitly open the tty to make it become a controlling tty. |
|
122 tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR) |
|
123 os.close(tmp_fd) |
|
124 else: |
|
125 os.close(slave_fd) |
|
126 |
|
127 # Parent and child process. |
|
128 return pid, master_fd |
|
129 |
|
130 def _writen(fd, data): |
|
131 """Write all the data to a descriptor.""" |
|
132 while data != '': |
|
133 n = os.write(fd, data) |
|
134 data = data[n:] |
|
135 |
|
136 def _read(fd): |
|
137 """Default read function.""" |
|
138 return os.read(fd, 1024) |
|
139 |
|
140 def _copy(master_fd, master_read=_read, stdin_read=_read): |
|
141 """Parent copy loop. |
|
142 Copies |
|
143 pty master -> standard output (master_read) |
|
144 standard input -> pty master (stdin_read)""" |
|
145 while 1: |
|
146 rfds, wfds, xfds = select( |
|
147 [master_fd, STDIN_FILENO], [], []) |
|
148 if master_fd in rfds: |
|
149 data = master_read(master_fd) |
|
150 os.write(STDOUT_FILENO, data) |
|
151 if STDIN_FILENO in rfds: |
|
152 data = stdin_read(STDIN_FILENO) |
|
153 _writen(master_fd, data) |
|
154 |
|
155 def spawn(argv, master_read=_read, stdin_read=_read): |
|
156 """Create a spawned process.""" |
|
157 if type(argv) == type(''): |
|
158 argv = (argv,) |
|
159 pid, master_fd = fork() |
|
160 if pid == CHILD: |
|
161 os.execlp(argv[0], *argv) |
|
162 try: |
|
163 mode = tty.tcgetattr(STDIN_FILENO) |
|
164 tty.setraw(STDIN_FILENO) |
|
165 restore = 1 |
|
166 except tty.error: # This is the same as termios.error |
|
167 restore = 0 |
|
168 try: |
|
169 _copy(master_fd, master_read, stdin_read) |
|
170 except (IOError, OSError): |
|
171 if restore: |
|
172 tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) |
|
173 |
|
174 os.close(master_fd) |