pty.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. for x in 'pqrstuvwxyzPQRST':
  41. for y in '0123456789abcdef':
  42. pty_name = '/dev/pty' + x + y
  43. try:
  44. fd = os.open(pty_name, os.O_RDWR)
  45. except OSError:
  46. continue
  47. return (fd, '/dev/tty' + x + y)
  48. raise OSError('out of pty devices')
  49. def slave_open(tty_name):
  50. """slave_open(tty_name) -> slave_fd
  51. Open the pty slave and acquire the controlling terminal, returning
  52. opened filedescriptor.
  53. Deprecated, use openpty() instead."""
  54. result = os.open(tty_name, os.O_RDWR)
  55. try:
  56. from fcntl import ioctl, I_PUSH
  57. except ImportError:
  58. return result
  59. try:
  60. ioctl(result, I_PUSH, "ptem")
  61. ioctl(result, I_PUSH, "ldterm")
  62. except OSError:
  63. pass
  64. return result
  65. def fork():
  66. """fork() -> (pid, master_fd)
  67. Fork and make the child a session leader with a controlling terminal."""
  68. try:
  69. pid, fd = os.forkpty()
  70. except (AttributeError, OSError):
  71. pass
  72. else:
  73. if pid == CHILD:
  74. try:
  75. os.setsid()
  76. except OSError:
  77. # os.forkpty() already set us session leader
  78. pass
  79. return pid, fd
  80. master_fd, slave_fd = openpty()
  81. pid = os.fork()
  82. if pid == CHILD:
  83. # Establish a new session.
  84. os.setsid()
  85. os.close(master_fd)
  86. # Slave becomes stdin/stdout/stderr of child.
  87. os.dup2(slave_fd, STDIN_FILENO)
  88. os.dup2(slave_fd, STDOUT_FILENO)
  89. os.dup2(slave_fd, STDERR_FILENO)
  90. if (slave_fd > STDERR_FILENO):
  91. os.close (slave_fd)
  92. # Explicitly open the tty to make it become a controlling tty.
  93. tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
  94. os.close(tmp_fd)
  95. else:
  96. os.close(slave_fd)
  97. # Parent and child process.
  98. return pid, master_fd
  99. def _writen(fd, data):
  100. """Write all the data to a descriptor."""
  101. while data:
  102. n = os.write(fd, data)
  103. data = data[n:]
  104. def _read(fd):
  105. """Default read function."""
  106. return os.read(fd, 1024)
  107. def _copy(master_fd, master_read=_read, stdin_read=_read):
  108. """Parent copy loop.
  109. Copies
  110. pty master -> standard output (master_read)
  111. standard input -> pty master (stdin_read)"""
  112. fds = [master_fd, STDIN_FILENO]
  113. while True:
  114. rfds, wfds, xfds = select(fds, [], [])
  115. if master_fd in rfds:
  116. data = master_read(master_fd)
  117. if not data: # Reached EOF.
  118. fds.remove(master_fd)
  119. else:
  120. os.write(STDOUT_FILENO, data)
  121. if STDIN_FILENO in rfds:
  122. data = stdin_read(STDIN_FILENO)
  123. if not data:
  124. fds.remove(STDIN_FILENO)
  125. else:
  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 OSError:
  143. if restore:
  144. tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
  145. os.close(master_fd)
  146. return os.waitpid(pid, 0)[1]