• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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