Commit | Line | Data |
---|---|---|
86530b38 AT |
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 | # Parent and child process. | |
122 | return pid, master_fd | |
123 | ||
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 | ||
130 | def _read(fd): | |
131 | """Default read function.""" | |
132 | return os.read(fd, 1024) | |
133 | ||
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 | while 1: | |
140 | rfds, wfds, xfds = select( | |
141 | [master_fd, STDIN_FILENO], [], []) | |
142 | if master_fd in rfds: | |
143 | data = master_read(master_fd) | |
144 | os.write(STDOUT_FILENO, data) | |
145 | if STDIN_FILENO in rfds: | |
146 | data = stdin_read(STDIN_FILENO) | |
147 | _writen(master_fd, data) | |
148 | ||
149 | def spawn(argv, master_read=_read, stdin_read=_read): | |
150 | """Create a spawned process.""" | |
151 | if type(argv) == type(''): | |
152 | argv = (argv,) | |
153 | pid, master_fd = fork() | |
154 | if pid == CHILD: | |
155 | os.execlp(argv[0], *argv) | |
156 | try: | |
157 | mode = tty.tcgetattr(STDIN_FILENO) | |
158 | tty.setraw(STDIN_FILENO) | |
159 | restore = 1 | |
160 | except tty.error: # This is the same as termios.error | |
161 | restore = 0 | |
162 | try: | |
163 | _copy(master_fd, master_read, stdin_read) | |
164 | except (IOError, OSError): | |
165 | if restore: | |
166 | tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) | |
167 | ||
168 | os.close(master_fd) |