pty.py 4.14 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, FCNTL
11 12 13 14 15 16 17 18
import tty

STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2

CHILD = 0

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

Tim Peters's avatar
Tim Peters committed
23 24 25 26 27 28 29
    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
30

31
def master_open():
Tim Peters's avatar
Tim Peters committed
32 33 34
    """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."""
35

Tim Peters's avatar
Tim Peters committed
36 37 38 39 40 41 42 43
    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
44

Tim Peters's avatar
Tim Peters committed
45
    return _open_terminal()
46 47

def _open_terminal():
Tim Peters's avatar
Tim Peters committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    """Open pty master and return (master_fd, tty_name).
    SGI and generic BSD version, for when openpty() fails."""
    try:
        import sgi
    except ImportError:
        pass
    else:
        try:
            tty_name, master_fd = sgi._getpty(FCNTL.O_RDWR, 0666, 0)
        except IOError, msg:
            raise os.error, msg
        return master_fd, tty_name
    for x in 'pqrstuvwxyzPQRST':
        for y in '0123456789abcdef':
            pty_name = '/dev/pty' + x + y
            try:
                fd = os.open(pty_name, FCNTL.O_RDWR)
            except os.error:
                continue
            return (fd, '/dev/tty' + x + y)
    raise os.error, 'out of pty devices'
69 70

def slave_open(tty_name):
Tim Peters's avatar
Tim Peters committed
71 72 73 74
    """slave_open(tty_name) -> slave_fd
    Open the pty slave and acquire the controlling terminal, returning
    opened filedescriptor.
    Deprecated, use openpty() instead."""
75

Tim Peters's avatar
Tim Peters committed
76
    return os.open(tty_name, FCNTL.O_RDWR)
77 78

def fork():
Tim Peters's avatar
Tim Peters committed
79 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 110
    """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)

    # Parent and child process.
    return pid, master_fd
111

112
def _writen(fd, data):
Tim Peters's avatar
Tim Peters committed
113 114 115 116
    """Write all the data to a descriptor."""
    while data != '':
        n = os.write(fd, data)
        data = data[n:]
117

118
def _read(fd):
Tim Peters's avatar
Tim Peters committed
119 120
    """Default read function."""
    return os.read(fd, 1024)
121

122
def _copy(master_fd, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
123 124 125 126 127 128 129 130 131 132 133 134 135
    """Parent copy loop.
    Copies
            pty master -> standard output   (master_read)
            standard input -> pty master    (stdin_read)"""
    while 1:
        rfds, wfds, xfds = select(
                [master_fd, STDIN_FILENO], [], [])
        if master_fd in rfds:
            data = master_read(master_fd)
            os.write(STDOUT_FILENO, data)
        if STDIN_FILENO in rfds:
            data = stdin_read(STDIN_FILENO)
            _writen(master_fd, data)
136

137
def spawn(argv, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
138 139 140 141 142 143 144 145 146 147 148 149
    """Create a spawned process."""
    if type(argv) == type(''):
        argv = (argv,)
    pid, master_fd = fork()
    if pid == CHILD:
        apply(os.execlp, (argv[0],) + argv)
    mode = tty.tcgetattr(STDIN_FILENO)
    tty.setraw(STDIN_FILENO)
    try:
        _copy(master_fd, master_read, stdin_read)
    except:
        tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)