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