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 fds = [master_fd, STDIN_FILENO] 146 while True: 147 rfds, wfds, xfds = select(fds, [], []) 148 if master_fd in rfds: 149 data = master_read(master_fd) 150 if not data: # Reached EOF. 151 fds.remove(master_fd) 152 else: 153 os.write(STDOUT_FILENO, data) 154 if STDIN_FILENO in rfds: 155 data = stdin_read(STDIN_FILENO) 156 if not data: 157 fds.remove(STDIN_FILENO) 158 else: 159 _writen(master_fd, data) 160 161 def spawn(argv, master_read=_read, stdin_read=_read): 162 """Create a spawned process.""" 163 if type(argv) == type(''): 164 argv = (argv,) 165 pid, master_fd = fork() 166 if pid == CHILD: 167 os.execlp(argv[0], *argv) 168 try: 169 mode = tty.tcgetattr(STDIN_FILENO) 170 tty.setraw(STDIN_FILENO) 171 restore = 1 172 except tty.error: # This is the same as termios.error 173 restore = 0 174 try: 175 _copy(master_fd, master_read, stdin_read) 176 except (IOError, OSError): 177 if restore: 178 tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) 179 180 os.close(master_fd) 181