Home | History | Annotate | Download | only in Lib
      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     for x in 'pqrstuvwxyzPQRST':
     52         for y in '0123456789abcdef':
     53             pty_name = '/dev/pty' + x + y
     54             try:
     55                 fd = os.open(pty_name, os.O_RDWR)
     56             except OSError:
     57                 continue
     58             return (fd, '/dev/tty' + x + y)
     59     raise OSError('out of pty devices')
     60 
     61 def slave_open(tty_name):
     62     """slave_open(tty_name) -> slave_fd
     63     Open the pty slave and acquire the controlling terminal, returning
     64     opened filedescriptor.
     65     Deprecated, use openpty() instead."""
     66 
     67     result = os.open(tty_name, os.O_RDWR)
     68     try:
     69         from fcntl import ioctl, I_PUSH
     70     except ImportError:
     71         return result
     72     try:
     73         ioctl(result, I_PUSH, "ptem")
     74         ioctl(result, I_PUSH, "ldterm")
     75     except OSError:
     76         pass
     77     return result
     78 
     79 def fork():
     80     """fork() -> (pid, master_fd)
     81     Fork and make the child a session leader with a controlling terminal."""
     82 
     83     try:
     84         pid, fd = os.forkpty()
     85     except (AttributeError, OSError):
     86         pass
     87     else:
     88         if pid == CHILD:
     89             try:
     90                 os.setsid()
     91             except OSError:
     92                 # os.forkpty() already set us session leader
     93                 pass
     94         return pid, fd
     95 
     96     master_fd, slave_fd = openpty()
     97     pid = os.fork()
     98     if pid == CHILD:
     99         # Establish a new session.
    100         os.setsid()
    101         os.close(master_fd)
    102 
    103         # Slave becomes stdin/stdout/stderr of child.
    104         os.dup2(slave_fd, STDIN_FILENO)
    105         os.dup2(slave_fd, STDOUT_FILENO)
    106         os.dup2(slave_fd, STDERR_FILENO)
    107         if (slave_fd > STDERR_FILENO):
    108             os.close (slave_fd)
    109 
    110         # Explicitly open the tty to make it become a controlling tty.
    111         tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
    112         os.close(tmp_fd)
    113     else:
    114         os.close(slave_fd)
    115 
    116     # Parent and child process.
    117     return pid, master_fd
    118 
    119 def _writen(fd, data):
    120     """Write all the data to a descriptor."""
    121     while data:
    122         n = os.write(fd, data)
    123         data = data[n:]
    124 
    125 def _read(fd):
    126     """Default read function."""
    127     return os.read(fd, 1024)
    128 
    129 def _copy(master_fd, master_read=_read, stdin_read=_read):
    130     """Parent copy loop.
    131     Copies
    132             pty master -> standard output   (master_read)
    133             standard input -> pty master    (stdin_read)"""
    134     fds = [master_fd, STDIN_FILENO]
    135     while True:
    136         rfds, wfds, xfds = select(fds, [], [])
    137         if master_fd in rfds:
    138             data = master_read(master_fd)
    139             if not data:  # Reached EOF.
    140                 fds.remove(master_fd)
    141             else:
    142                 os.write(STDOUT_FILENO, data)
    143         if STDIN_FILENO in rfds:
    144             data = stdin_read(STDIN_FILENO)
    145             if not data:
    146                 fds.remove(STDIN_FILENO)
    147             else:
    148                 _writen(master_fd, data)
    149 
    150 def spawn(argv, master_read=_read, stdin_read=_read):
    151     """Create a spawned process."""
    152     if type(argv) == type(''):
    153         argv = (argv,)
    154     pid, master_fd = fork()
    155     if pid == CHILD:
    156         os.execlp(argv[0], *argv)
    157     try:
    158         mode = tty.tcgetattr(STDIN_FILENO)
    159         tty.setraw(STDIN_FILENO)
    160         restore = 1
    161     except tty.error:    # This is the same as termios.error
    162         restore = 0
    163     try:
    164         _copy(master_fd, master_read, stdin_read)
    165     except OSError:
    166         if restore:
    167             tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
    168 
    169     os.close(master_fd)
    170     return os.waitpid(pid, 0)[1]
    171