Home | History | Annotate | Download | only in python2.7
      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 
      9 from select import select
     10 import os
     11 import tty
     12 
     13 __all__ = ["openpty","fork","spawn"]
     14 
     15 STDIN_FILENO = 0
     16 STDOUT_FILENO = 1
     17 STDERR_FILENO = 2
     18 
     19 CHILD = 0
     20 
     21 def openpty():
     22     """openpty() -> (master_fd, slave_fd)
     23     Open a pty master/slave pair, using os.openpty() if possible."""
     24 
     25     try:
     26         return os.openpty()
     27     except (AttributeError, OSError):
     28         pass
     29     master_fd, slave_name = _open_terminal()
     30     slave_fd = slave_open(slave_name)
     31     return master_fd, slave_fd
     32 
     33 def master_open():
     34     """master_open() -> (master_fd, slave_name)
     35     Open a pty master and return the fd, and the filename of the slave end.
     36     Deprecated, use openpty() instead."""
     37 
     38     try:
     39         master_fd, slave_fd = os.openpty()
     40     except (AttributeError, OSError):
     41         pass
     42     else:
     43         slave_name = os.ttyname(slave_fd)
     44         os.close(slave_fd)
     45         return master_fd, slave_name
     46 
     47     return _open_terminal()
     48 
     49 def _open_terminal():
     50     """Open pty master and return (master_fd, tty_name).
     51     SGI and generic BSD version, for when openpty() fails."""
     52     try:
     53         import sgi
     54     except ImportError:
     55         pass
     56     else:
     57         try:
     58             tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)
     59         except IOError, msg:
     60             raise os.error, msg
     61         return master_fd, tty_name
     62     for x in 'pqrstuvwxyzPQRST':
     63         for y in '0123456789abcdef':
     64             pty_name = '/dev/pty' + x + y
     65             try:
     66                 fd = os.open(pty_name, os.O_RDWR)
     67             except os.error:
     68                 continue
     69             return (fd, '/dev/tty' + x + y)
     70     raise os.error, 'out of pty devices'
     71 
     72 def slave_open(tty_name):
     73     """slave_open(tty_name) -> slave_fd
     74     Open the pty slave and acquire the controlling terminal, returning
     75     opened filedescriptor.
     76     Deprecated, use openpty() instead."""
     77 
     78     result = os.open(tty_name, os.O_RDWR)
     79     try:
     80         from fcntl import ioctl, I_PUSH
     81     except ImportError:
     82         return result
     83     try:
     84         ioctl(result, I_PUSH, "ptem")
     85         ioctl(result, I_PUSH, "ldterm")
     86     except IOError:
     87         pass
     88     return result
     89 
     90 def fork():
     91     """fork() -> (pid, master_fd)
     92     Fork and make the child a session leader with a controlling terminal."""
     93 
     94     try:
     95         pid, fd = os.forkpty()
     96     except (AttributeError, OSError):
     97         pass
     98     else:
     99         if pid == CHILD:
    100             try:
    101                 os.setsid()
    102             except OSError:
    103                 # os.forkpty() already set us session leader
    104                 pass
    105         return pid, fd
    106 
    107     master_fd, slave_fd = openpty()
    108     pid = os.fork()
    109     if pid == CHILD:
    110         # Establish a new session.
    111         os.setsid()
    112         os.close(master_fd)
    113 
    114         # Slave becomes stdin/stdout/stderr of child.
    115         os.dup2(slave_fd, STDIN_FILENO)
    116         os.dup2(slave_fd, STDOUT_FILENO)
    117         os.dup2(slave_fd, STDERR_FILENO)
    118         if (slave_fd > STDERR_FILENO):
    119             os.close (slave_fd)
    120 
    121         # Explicitly open the tty to make it become a controlling tty.
    122         tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
    123         os.close(tmp_fd)
    124     else:
    125         os.close(slave_fd)
    126 
    127     # Parent and child process.
    128     return pid, master_fd
    129 
    130 def _writen(fd, data):
    131     """Write all the data to a descriptor."""
    132     while data != '':
    133         n = os.write(fd, data)
    134         data = data[n:]
    135 
    136 def _read(fd):
    137     """Default read function."""
    138     return os.read(fd, 1024)
    139 
    140 def _copy(master_fd, master_read=_read, stdin_read=_read):
    141     """Parent copy loop.
    142     Copies
    143             pty master -> standard output   (master_read)
    144             standard input -> pty master    (stdin_read)"""
    145     fds = [master_fd, STDIN_FILENO]
    146     while True:
    147         rfds, wfds, xfds = select(fds, [], [])
    148         if master_fd in rfds:
    149             data = master_read(master_fd)
    150             if not data:  # Reached EOF.
    151                 fds.remove(master_fd)
    152             else:
    153                 os.write(STDOUT_FILENO, data)
    154         if STDIN_FILENO in rfds:
    155             data = stdin_read(STDIN_FILENO)
    156             if not data:
    157                 fds.remove(STDIN_FILENO)
    158             else:
    159                 _writen(master_fd, data)
    160 
    161 def spawn(argv, master_read=_read, stdin_read=_read):
    162     """Create a spawned process."""
    163     if type(argv) == type(''):
    164         argv = (argv,)
    165     pid, master_fd = fork()
    166     if pid == CHILD:
    167         os.execlp(argv[0], *argv)
    168     try:
    169         mode = tty.tcgetattr(STDIN_FILENO)
    170         tty.setraw(STDIN_FILENO)
    171         restore = 1
    172     except tty.error:    # This is the same as termios.error
    173         restore = 0
    174     try:
    175         _copy(master_fd, master_read, stdin_read)
    176     except (IOError, OSError):
    177         if restore:
    178             tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
    179 
    180     os.close(master_fd)
    181