pty.py 4.56 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():
Tim Peters's avatar
Tim Peters committed
50 51 52 53 54 55 56 57
    """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:
58
            tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)
Tim Peters's avatar
Tim Peters committed
59 60 61 62 63 64 65
        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:
66
                fd = os.open(pty_name, os.O_RDWR)
Tim Peters's avatar
Tim Peters committed
67 68 69 70
            except os.error:
                continue
            return (fd, '/dev/tty' + x + y)
    raise os.error, 'out of pty devices'
71 72

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

78 79 80 81 82 83
    result = os.open(tty_name, os.O_RDWR)
    try:
        from fcntl import ioctl, I_PUSH
    except ImportError:
        return result
    try:
Tim Peters's avatar
Tim Peters committed
84 85
        ioctl(result, I_PUSH, "ptem")
        ioctl(result, I_PUSH, "ldterm")
86 87 88
    except IOError:
        pass
    return result
89 90

def fork():
Tim Peters's avatar
Tim Peters committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    """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
123

124
def _writen(fd, data):
Tim Peters's avatar
Tim Peters committed
125 126 127 128
    """Write all the data to a descriptor."""
    while data != '':
        n = os.write(fd, data)
        data = data[n:]
129

130
def _read(fd):
Tim Peters's avatar
Tim Peters committed
131 132
    """Default read function."""
    return os.read(fd, 1024)
133

134
def _copy(master_fd, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
135 136 137 138 139 140 141 142 143 144 145 146 147
    """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)
148

149
def spawn(argv, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
150 151 152 153 154
    """Create a spawned process."""
    if type(argv) == type(''):
        argv = (argv,)
    pid, master_fd = fork()
    if pid == CHILD:
155
        os.execlp(argv[0], *argv)
156 157 158 159 160 161
    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
162 163
    try:
        _copy(master_fd, master_read, stdin_read)
164 165 166
    except (IOError, OSError):
        if restore:
            tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
167 168

    os.close(master_fd)