pty.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. """Pseudo terminal utilities."""
  2. # Bugs: No signal handling. Doesn't set slave termios and window size.
  3. # Only tested on Linux.
  4. # See: W. Richard Stevens. 1992. Advanced Programming in the
  5. # UNIX Environment. Chapter 19.
  6. # Author: Steen Lumholt -- with additions by Guido.
  7. from select import select
  8. import os
  9. import tty
  10. __all__ = ["openpty","fork","spawn"]
  11. STDIN_FILENO = 0
  12. STDOUT_FILENO = 1
  13. STDERR_FILENO = 2
  14. CHILD = 0
  15. def openpty():
  16. """openpty() -> (master_fd, slave_fd)
  17. Open a pty master/slave pair, using os.openpty() if possible."""
  18. try:
  19. return os.openpty()
  20. except (AttributeError, OSError):
  21. pass
  22. master_fd, slave_name = _open_terminal()
  23. slave_fd = slave_open(slave_name)
  24. return master_fd, slave_fd
  25. def master_open():
  26. """master_open() -> (master_fd, slave_name)
  27. Open a pty master and return the fd, and the filename of the slave end.
  28. Deprecated, use openpty() instead."""
  29. try:
  30. master_fd, slave_fd = os.openpty()
  31. except (AttributeError, OSError):
  32. pass
  33. else:
  34. slave_name = os.ttyname(slave_fd)
  35. os.close(slave_fd)
  36. return master_fd, slave_name
  37. return _open_terminal()
  38. def _open_terminal():
  39. """Open pty master and return (master_fd, tty_name).
  40. SGI and generic BSD version, for when openpty() fails."""
  41. try:
  42. import sgi
  43. except ImportError:
  44. pass
  45. else:
  46. try:
  47. tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)
  48. except IOError, msg:
  49. raise os.error, msg
  50. 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 os.error:
  57. continue
  58. return (fd, '/dev/tty' + x + y)
  59. raise os.error, 'out of pty devices'
  60. def slave_open(tty_name):
  61. """slave_open(tty_name) -> slave_fd
  62. Open the pty slave and acquire the controlling terminal, returning
  63. opened filedescriptor.
  64. Deprecated, use openpty() instead."""
  65. result = os.open(tty_name, os.O_RDWR)
  66. try:
  67. from fcntl import ioctl, I_PUSH
  68. except ImportError:
  69. return result
  70. try:
  71. ioctl(result, I_PUSH, "ptem")
  72. ioctl(result, I_PUSH, "ldterm")
  73. except IOError:
  74. pass
  75. return result
  76. def fork():
  77. """fork() -> (pid, master_fd)
  78. Fork and make the child a session leader with a controlling terminal."""
  79. try:
  80. pid, fd = os.forkpty()
  81. except (AttributeError, OSError):
  82. pass
  83. else:
  84. if pid == CHILD:
  85. try:
  86. os.setsid()
  87. except OSError:
  88. # os.forkpty() already set us session leader
  89. pass
  90. return pid, fd
  91. master_fd, slave_fd = openpty()
  92. pid = os.fork()
  93. if pid == CHILD:
  94. # Establish a new session.
  95. os.setsid()
  96. os.close(master_fd)
  97. # Slave becomes stdin/stdout/stderr of child.
  98. os.dup2(slave_fd, STDIN_FILENO)
  99. os.dup2(slave_fd, STDOUT_FILENO)
  100. os.dup2(slave_fd, STDERR_FILENO)
  101. if (slave_fd > STDERR_FILENO):
  102. os.close (slave_fd)
  103. # Parent and child process.
  104. return pid, master_fd
  105. def _writen(fd, data):
  106. """Write all the data to a descriptor."""
  107. while data != '':
  108. n = os.write(fd, data)
  109. data = data[n:]
  110. def _read(fd):
  111. """Default read function."""
  112. return os.read(fd, 1024)
  113. def _copy(master_fd, master_read=_read, stdin_read=_read):
  114. """Parent copy loop.
  115. Copies
  116. pty master -> standard output (master_read)
  117. standard input -> pty master (stdin_read)"""
  118. while 1:
  119. rfds, wfds, xfds = select(
  120. [master_fd, STDIN_FILENO], [], [])
  121. if master_fd in rfds:
  122. data = master_read(master_fd)
  123. os.write(STDOUT_FILENO, data)
  124. if STDIN_FILENO in rfds:
  125. data = stdin_read(STDIN_FILENO)
  126. _writen(master_fd, data)
  127. def spawn(argv, master_read=_read, stdin_read=_read):
  128. """Create a spawned process."""
  129. if type(argv) == type(''):
  130. argv = (argv,)
  131. pid, master_fd = fork()
  132. if pid == CHILD:
  133. os.execlp(argv[0], *argv)
  134. try:
  135. mode = tty.tcgetattr(STDIN_FILENO)
  136. tty.setraw(STDIN_FILENO)
  137. restore = 1
  138. except tty.error: # This is the same as termios.error
  139. restore = 0
  140. try:
  141. _copy(master_fd, master_read, stdin_read)
  142. except (IOError, OSError):
  143. if restore:
  144. tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
  145. os.close(master_fd)