1#!/usr/bin/env python3 2 3import errno 4import os 5import pty 6import select 7import signal 8import sys 9import termios 10 11STDIN = 0 12STDOUT = 1 13STDERR = 2 14 15 16def pipe(sfd, dfd): 17 try: 18 data = os.read(sfd, 256) 19 except OSError as e: 20 if e.errno != errno.EIO: 21 raise 22 return True # EOF 23 24 if not data: 25 return True # EOF 26 27 if dfd == STDOUT: 28 # Work around platform quirks. Some platforms echo ^D as \x04 29 # (AIX, BSDs) and some don't (Linux). 30 # 31 # The comparison against b' '[0] is because |data| is 32 # a string in python2 but a bytes object in python3. 33 filt = lambda c: c >= b' '[0] or c in b'\t\n\r\f' 34 data = filter(filt, data) 35 data = bytes(data) 36 37 while data: 38 try: 39 n = os.write(dfd, data) 40 except OSError as e: 41 if e.errno != errno.EIO: 42 raise 43 return True # EOF 44 data = data[n:] 45 46 47if __name__ == '__main__': 48 argv = sys.argv[1:] 49 50 # Make select() interruptable by SIGCHLD. 51 signal.signal(signal.SIGCHLD, lambda nr, _: None) 52 53 parent_fd, child_fd = pty.openpty() 54 assert parent_fd > STDIN 55 56 mode = termios.tcgetattr(child_fd) 57 # Don't translate \n to \r\n. 58 mode[1] = mode[1] & ~termios.ONLCR # oflag 59 # Disable ECHOCTL. It's a BSD-ism that echoes e.g. \x04 as ^D but it 60 # doesn't work on platforms like AIX and Linux. I checked Linux's tty 61 # driver and it's a no-op, the driver is just oblivious to the flag. 62 mode[3] = mode[3] & ~termios.ECHOCTL # lflag 63 termios.tcsetattr(child_fd, termios.TCSANOW, mode) 64 65 pid = os.fork() 66 if not pid: 67 os.setsid() 68 os.close(parent_fd) 69 70 # Ensure the pty is a controlling tty. 71 name = os.ttyname(child_fd) 72 fd = os.open(name, os.O_RDWR) 73 os.dup2(fd, child_fd) 74 os.close(fd) 75 76 os.dup2(child_fd, STDIN) 77 os.dup2(child_fd, STDOUT) 78 os.dup2(child_fd, STDERR) 79 80 if child_fd > STDERR: 81 os.close(child_fd) 82 83 os.execve(argv[0], argv, os.environ) 84 raise Exception('unreachable') 85 86 os.close(child_fd) 87 88 fds = [STDIN, parent_fd] 89 while fds: 90 try: 91 rfds, _, _ = select.select(fds, [], []) 92 except select.error as e: 93 if e[0] != errno.EINTR: 94 raise 95 if pid == os.waitpid(pid, os.WNOHANG)[0]: 96 break 97 98 if STDIN in rfds: 99 if pipe(STDIN, parent_fd): 100 fds.remove(STDIN) 101 102 if parent_fd in rfds: 103 if pipe(parent_fd, STDOUT): 104 break 105