--- cpython/Lib/pty.py 2020-08-07 19:08:16.285101146 -0500 +++ pty.py 2020-08-07 19:04:51.012271815 -0500 @@ -1,6 +1,6 @@ """Pseudo terminal utilities.""" -# Bugs: No signal handling. Doesn't set slave termios and window size. +# Bugs: No signal handling. Doesn't set slave termios. # Only tested on Linux. # See: W. Richard Stevens. 1992. Advanced Programming in the # UNIX Environment. Chapter 19. @@ -10,8 +10,13 @@ import os import sys import tty +import signal +import struct +import fcntl +import termios +import time -__all__ = ["openpty","fork","spawn"] +__all__ = ["openpty","fork","spawn","wspawn"] STDIN_FILENO = 0 STDOUT_FILENO = 1 @@ -170,3 +175,128 @@ os.close(master_fd) return os.waitpid(pid, 0)[1] + +# Author: Soumendra Ganguly. +SIGWINCH = signal.SIGWINCH + +def _login_pty(master_fd, slave_fd): + """Given a pty, makes the calling process a session leader, makes the pty slave + its controlling terminal, stdin, stdout, and stderr. Closes both pty ends.""" + # Establish a new session. + os.setsid() + os.close(master_fd) + + try: + fcntl.ioctl(slave_fd, termios.TIOCSCTTY) # Make the pty slave the controlling terminal. + except: + os.close(slave_fd) + raise + + # Slave becomes stdin/stdout/stderr. + 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) + +def _winresz(pty_slave): + """Resize window.""" + w = struct.pack('HHHH', 0, 0, 0, 0) + s = fcntl.ioctl(STDIN_FILENO, termios.TIOCGWINSZ, w) + fcntl.ioctl(pty_slave, termios.TIOCSWINSZ, s) + +def _create_hwinch(pty_slave): + """Creates SIGWINCH handler.""" + def _hwinch(signum, frame): + try: + _winresz(pty_slave) + except: + pass + return _hwinch + +def _cleanup(master_fd, slave_fd, old_hwinch, tty_mode): + """Performs cleanup in wspawn.""" + + # Close both pty ends. + os.close(master_fd) + os.close(slave_fd) + + # Restore original tty attributes. + if tty_mode != None: + tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, tty_mode) + + # Restore original SIGWINCH signal handler. + try: + signal.signal(SIGWINCH, old_hwinch) + except: + pass + +def _ekill(child_pid): + """Kill spawned process due to exception.""" + try: + os.kill(child_pid, signal.SIGTERM) + time.sleep(1) + os.kill(child_pid, signal.SIGKILL) + except: + pass + try: + os.waitpid(child_pid, 0) + except: + pass + +def _wcopy(master_fd, child_pid, master_read=_read, stdin_read=_read, timeout=0.01): + """Parent copy loop for wspawn.""" + fds = [master_fd, STDIN_FILENO] + ret = (0,0) + while True: + rfds, wfds, xfds = select(fds, [], [], timeout) + if ret == (0,0): + ret = os.waitpid(child_pid, os.WNOHANG) + elif len(rfds) == 0: + break; + if master_fd in rfds: + data = master_read(master_fd) + if not data: # Reached EOF. + fds.remove(master_fd) + else: + os.write(STDOUT_FILENO, data) + if STDIN_FILENO in rfds: + data = stdin_read(STDIN_FILENO) + if not data: + fds.remove(STDIN_FILENO) + else: + _writen(master_fd, data) + return ret + +def wspawn(argv, master_read=_read, stdin_read=_read, timeout=0.01): + """Create a spawned process with + terminal window resizing enabled.""" + if type(argv) == type(''): + argv = (argv,) + sys.audit('pty.wspawn', argv) + bk_hwinch = signal.getsignal(SIGWINCH) + master_fd, slave_fd = openpty() + try: + _winresz(slave_fd) + signal.signal(SIGWINCH, _create_hwinch(slave_fd)) + except: # User should handle exception and try spawn instead. + _cleanup(master_fd, slave_fd, bk_hwinch, None) + raise + pid = os.fork() + if pid == CHILD: + _login_pty(master_fd, slave_fd) + os.execlp(argv[0], *argv) + try: + mode = tty.tcgetattr(STDIN_FILENO) + tty.setraw(STDIN_FILENO) + except tty.error: # This is the same as termios.error + mode = None + try: + ret = _wcopy(master_fd, pid, master_read, stdin_read, timeout)[1] + except: + _ekill(pid) + _cleanup(master_fd, slave_fd, bk_hwinch, mode) + raise + + _cleanup(master_fd, slave_fd, bk_hwinch, mode) + return ret