pty.py 4.65 KB
Newer Older
1
"""Pseudo terminal utilities."""
2 3

# Bugs: No signal handling.  Doesn't set slave termios and window size.
Tim Peters's avatar
Tim Peters committed
4 5 6
#       Only tested on Linux.
# See:  W. Richard Stevens. 1992.  Advanced Programming in the
#       UNIX Environment.  Chapter 19.
7 8 9
# Author: Steen Lumholt -- with additions by Guido.

from select import select
10
import os
11 12
import tty

13 14
__all__ = ["openpty","fork","spawn"]

15 16 17 18 19 20
STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2

CHILD = 0

21
def openpty():
Tim Peters's avatar
Tim Peters committed
22 23
    """openpty() -> (master_fd, slave_fd)
    Open a pty master/slave pair, using os.openpty() if possible."""
24

Tim Peters's avatar
Tim Peters committed
25 26 27 28 29 30 31
    try:
        return os.openpty()
    except (AttributeError, OSError):
        pass
    master_fd, slave_name = _open_terminal()
    slave_fd = slave_open(slave_name)
    return master_fd, slave_fd
32

33
def master_open():
Tim Peters's avatar
Tim Peters committed
34 35 36
    """master_open() -> (master_fd, slave_name)
    Open a pty master and return the fd, and the filename of the slave end.
    Deprecated, use openpty() instead."""
37

Tim Peters's avatar
Tim Peters committed
38 39 40 41 42 43 44 45
    try:
        master_fd, slave_fd = os.openpty()
    except (AttributeError, OSError):
        pass
    else:
        slave_name = os.ttyname(slave_fd)
        os.close(slave_fd)
        return master_fd, slave_name
46

Tim Peters's avatar
Tim Peters committed
47
    return _open_terminal()
48 49

def _open_terminal():
50
    """Open pty master and return (master_fd, tty_name)."""
Tim Peters's avatar
Tim Peters committed
51 52 53 54
    for x in 'pqrstuvwxyzPQRST':
        for y in '0123456789abcdef':
            pty_name = '/dev/pty' + x + y
            try:
55
                fd = os.open(pty_name, os.O_RDWR)
56
            except OSError:
Tim Peters's avatar
Tim Peters committed
57 58
                continue
            return (fd, '/dev/tty' + x + y)
59
    raise OSError('out of pty devices')
60 61

def slave_open(tty_name):
Tim Peters's avatar
Tim Peters committed
62 63 64 65
    """slave_open(tty_name) -> slave_fd
    Open the pty slave and acquire the controlling terminal, returning
    opened filedescriptor.
    Deprecated, use openpty() instead."""
66

67 68 69
    result = os.open(tty_name, os.O_RDWR)
    try:
        from fcntl import ioctl, I_PUSH
70
    except ImportError:
71 72
        return result
    try:
Tim Peters's avatar
Tim Peters committed
73 74
        ioctl(result, I_PUSH, "ptem")
        ioctl(result, I_PUSH, "ldterm")
75
    except OSError:
76 77
        pass
    return result
78 79

def fork():
Tim Peters's avatar
Tim Peters committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
    """fork() -> (pid, master_fd)
    Fork and make the child a session leader with a controlling terminal."""

    try:
        pid, fd = os.forkpty()
    except (AttributeError, OSError):
        pass
    else:
        if pid == CHILD:
            try:
                os.setsid()
            except OSError:
                # os.forkpty() already set us session leader
                pass
        return pid, fd

    master_fd, slave_fd = openpty()
    pid = os.fork()
    if pid == CHILD:
        # Establish a new session.
        os.setsid()
        os.close(master_fd)

        # Slave becomes stdin/stdout/stderr of child.
        os.dup2(slave_fd, STDIN_FILENO)
        os.dup2(slave_fd, STDOUT_FILENO)
        os.dup2(slave_fd, STDERR_FILENO)
        if (slave_fd > STDERR_FILENO):
            os.close (slave_fd)

110 111 112
        # Explicitly open the tty to make it become a controlling tty.
        tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
        os.close(tmp_fd)
113 114
    else:
        os.close(slave_fd)
115

Tim Peters's avatar
Tim Peters committed
116 117
    # Parent and child process.
    return pid, master_fd
118

119
def _writen(fd, data):
Tim Peters's avatar
Tim Peters committed
120
    """Write all the data to a descriptor."""
121
    while data:
Tim Peters's avatar
Tim Peters committed
122 123
        n = os.write(fd, data)
        data = data[n:]
124

125
def _read(fd):
Tim Peters's avatar
Tim Peters committed
126 127
    """Default read function."""
    return os.read(fd, 1024)
128

129
def _copy(master_fd, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
130 131 132 133
    """Parent copy loop.
    Copies
            pty master -> standard output   (master_read)
            standard input -> pty master    (stdin_read)"""
134 135 136
    fds = [master_fd, STDIN_FILENO]
    while True:
        rfds, wfds, xfds = select(fds, [], [])
Tim Peters's avatar
Tim Peters committed
137 138
        if master_fd in rfds:
            data = master_read(master_fd)
139 140 141 142
            if not data:  # Reached EOF.
                fds.remove(master_fd)
            else:
                os.write(STDOUT_FILENO, data)
Tim Peters's avatar
Tim Peters committed
143 144
        if STDIN_FILENO in rfds:
            data = stdin_read(STDIN_FILENO)
145 146 147 148
            if not data:
                fds.remove(STDIN_FILENO)
            else:
                _writen(master_fd, data)
149

150
def spawn(argv, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
151 152 153 154 155
    """Create a spawned process."""
    if type(argv) == type(''):
        argv = (argv,)
    pid, master_fd = fork()
    if pid == CHILD:
156
        os.execlp(argv[0], *argv)
157 158 159 160 161 162
    try:
        mode = tty.tcgetattr(STDIN_FILENO)
        tty.setraw(STDIN_FILENO)
        restore = 1
    except tty.error:    # This is the same as termios.error
        restore = 0
Tim Peters's avatar
Tim Peters committed
163 164
    try:
        _copy(master_fd, master_read, stdin_read)
165
    except OSError:
166 167
        if restore:
            tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
168 169

    os.close(master_fd)
170
    return os.waitpid(pid, 0)[1]