pty.py 4.69 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 13 14 15 16 17 18

# Absurd:  import termios and then delete it.  This is to force an attempt
# to import pty to raise an ImportError on platforms that lack termios.
# Without this explicit import of termios here, some other module may
# import tty first, which in turn imports termios and dies with an
# ImportError then.  But since tty *does* exist across platforms, that
# leaves a damaged module object for tty in sys.modules, and the import
# of tty here then appears to work despite that the tty imported is junk.
Tim Peters's avatar
Tim Peters committed
19
import termios
20 21
del termios

22 23
import tty

24 25
__all__ = ["openpty","fork","spawn"]

26 27 28 29 30 31
STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2

CHILD = 0

32
def openpty():
Tim Peters's avatar
Tim Peters committed
33 34
    """openpty() -> (master_fd, slave_fd)
    Open a pty master/slave pair, using os.openpty() if possible."""
35

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

44
def master_open():
Tim Peters's avatar
Tim Peters committed
45 46 47
    """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."""
48

Tim Peters's avatar
Tim Peters committed
49 50 51 52 53 54 55 56
    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
57

Tim Peters's avatar
Tim Peters committed
58
    return _open_terminal()
59 60

def _open_terminal():
Tim Peters's avatar
Tim Peters committed
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:
69
            tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)
Tim Peters's avatar
Tim Peters committed
70 71 72 73 74 75 76
        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:
77
                fd = os.open(pty_name, os.O_RDWR)
Tim Peters's avatar
Tim Peters committed
78 79 80 81
            except os.error:
                continue
            return (fd, '/dev/tty' + x + y)
    raise os.error, 'out of pty devices'
82 83

def slave_open(tty_name):
Tim Peters's avatar
Tim Peters committed
84 85 86 87
    """slave_open(tty_name) -> slave_fd
    Open the pty slave and acquire the controlling terminal, returning
    opened filedescriptor.
    Deprecated, use openpty() instead."""
88

89
    return os.open(tty_name, os.O_RDWR)
90 91

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

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

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

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

150
def spawn(argv, master_read=_read, stdin_read=_read):
Tim Peters's avatar
Tim Peters committed
151 152 153 154 155 156 157 158 159 160
    """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)
161
    except IOError:
Tim Peters's avatar
Tim Peters committed
162
        tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)