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