2023-05-04 08:34:23 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2019-07-08 18:52:37 +00:00
|
|
|
import errno
|
|
|
|
import os
|
|
|
|
import pty
|
|
|
|
import select
|
|
|
|
import signal
|
|
|
|
import sys
|
|
|
|
import termios
|
|
|
|
|
|
|
|
STDIN = 0
|
|
|
|
STDOUT = 1
|
|
|
|
STDERR = 2
|
|
|
|
|
|
|
|
|
|
|
|
def pipe(sfd, dfd):
|
|
|
|
try:
|
|
|
|
data = os.read(sfd, 256)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno != errno.EIO:
|
|
|
|
raise
|
|
|
|
return True # EOF
|
|
|
|
|
|
|
|
if not data:
|
|
|
|
return True # EOF
|
|
|
|
|
|
|
|
if dfd == STDOUT:
|
|
|
|
# Work around platform quirks. Some platforms echo ^D as \x04
|
|
|
|
# (AIX, BSDs) and some don't (Linux).
|
2019-08-16 18:53:32 +00:00
|
|
|
#
|
|
|
|
# The comparison against b' '[0] is because |data| is
|
|
|
|
# a string in python2 but a bytes object in python3.
|
|
|
|
filt = lambda c: c >= b' '[0] or c in b'\t\n\r\f'
|
2019-07-08 18:52:37 +00:00
|
|
|
data = filter(filt, data)
|
2019-08-16 18:53:32 +00:00
|
|
|
data = bytes(data)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
|
|
|
while data:
|
|
|
|
try:
|
|
|
|
n = os.write(dfd, data)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno != errno.EIO:
|
|
|
|
raise
|
|
|
|
return True # EOF
|
|
|
|
data = data[n:]
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
argv = sys.argv[1:]
|
|
|
|
|
|
|
|
# Make select() interruptable by SIGCHLD.
|
|
|
|
signal.signal(signal.SIGCHLD, lambda nr, _: None)
|
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
parent_fd, child_fd = pty.openpty()
|
|
|
|
assert parent_fd > STDIN
|
2019-07-08 18:52:37 +00:00
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
mode = termios.tcgetattr(child_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
# Don't translate \n to \r\n.
|
|
|
|
mode[1] = mode[1] & ~termios.ONLCR # oflag
|
|
|
|
# Disable ECHOCTL. It's a BSD-ism that echoes e.g. \x04 as ^D but it
|
|
|
|
# doesn't work on platforms like AIX and Linux. I checked Linux's tty
|
|
|
|
# driver and it's a no-op, the driver is just oblivious to the flag.
|
|
|
|
mode[3] = mode[3] & ~termios.ECHOCTL # lflag
|
2019-07-14 22:14:32 +00:00
|
|
|
termios.tcsetattr(child_fd, termios.TCSANOW, mode)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
if not pid:
|
|
|
|
os.setsid()
|
2019-07-14 22:14:32 +00:00
|
|
|
os.close(parent_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
|
|
|
# Ensure the pty is a controlling tty.
|
2019-07-14 22:14:32 +00:00
|
|
|
name = os.ttyname(child_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
fd = os.open(name, os.O_RDWR)
|
2019-07-14 22:14:32 +00:00
|
|
|
os.dup2(fd, child_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
os.close(fd)
|
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
os.dup2(child_fd, STDIN)
|
|
|
|
os.dup2(child_fd, STDOUT)
|
|
|
|
os.dup2(child_fd, STDERR)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
if child_fd > STDERR:
|
|
|
|
os.close(child_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
|
|
|
os.execve(argv[0], argv, os.environ)
|
|
|
|
raise Exception('unreachable')
|
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
os.close(child_fd)
|
2019-07-08 18:52:37 +00:00
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
fds = [STDIN, parent_fd]
|
2019-07-08 18:52:37 +00:00
|
|
|
while fds:
|
|
|
|
try:
|
|
|
|
rfds, _, _ = select.select(fds, [], [])
|
|
|
|
except select.error as e:
|
|
|
|
if e[0] != errno.EINTR:
|
|
|
|
raise
|
|
|
|
if pid == os.waitpid(pid, os.WNOHANG)[0]:
|
|
|
|
break
|
|
|
|
|
|
|
|
if STDIN in rfds:
|
2019-07-14 22:14:32 +00:00
|
|
|
if pipe(STDIN, parent_fd):
|
2019-07-08 18:52:37 +00:00
|
|
|
fds.remove(STDIN)
|
|
|
|
|
2019-07-14 22:14:32 +00:00
|
|
|
if parent_fd in rfds:
|
|
|
|
if pipe(parent_fd, STDOUT):
|
2019-07-08 18:52:37 +00:00
|
|
|
break
|