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