dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x
This commit is contained in:
commit
e6dcef9bf5
302 files changed
+75178
No files matched your search
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import base64
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import pty
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
|
||||
|
||||
def emit(value):
|
||||
sys.stdout.write(json.dumps(value, separators=(",", ":")) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def resize(master, cols, rows):
|
||||
fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
|
||||
|
||||
|
||||
def write_all(target, data):
|
||||
remaining = memoryview(data)
|
||||
while remaining:
|
||||
written = os.write(target, remaining)
|
||||
remaining = remaining[written:]
|
||||
|
||||
|
||||
def terminate(pid, requested_signal):
|
||||
try:
|
||||
os.killpg(pid, requested_signal)
|
||||
except ProcessLookupError:
|
||||
return
|
||||
except PermissionError:
|
||||
os.kill(pid, requested_signal)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
raise RuntimeError("usage: pty-helper.py <cwd> <cols> <rows>")
|
||||
cwd = sys.argv[1]
|
||||
cols = int(sys.argv[2])
|
||||
rows = int(sys.argv[3])
|
||||
pid, master = pty.fork()
|
||||
if pid == 0:
|
||||
os.chdir(cwd)
|
||||
os.umask(0o002)
|
||||
os.execvpe("/bin/bash", ["/bin/bash", "-i"], os.environ)
|
||||
def stop_child(received_signal, _frame):
|
||||
terminate(pid, signal.SIGTERM if received_signal in (signal.SIGINT, signal.SIGTERM) else received_signal)
|
||||
signal.signal(signal.SIGINT, stop_child)
|
||||
signal.signal(signal.SIGTERM, stop_child)
|
||||
resize(master, cols, rows)
|
||||
stdin_fd = sys.stdin.fileno()
|
||||
stdin_buffer = b""
|
||||
stdin_open = True
|
||||
while True:
|
||||
readable, _, _ = select.select([master] + ([stdin_fd] if stdin_open else []), [], [], 0.25)
|
||||
if master in readable:
|
||||
try:
|
||||
data = os.read(master, 16384)
|
||||
except OSError:
|
||||
data = b""
|
||||
if not data:
|
||||
break
|
||||
emit({"type": "output", "data": base64.b64encode(data).decode("ascii")})
|
||||
if stdin_open and stdin_fd in readable:
|
||||
data = os.read(stdin_fd, 65536)
|
||||
if not data:
|
||||
stdin_open = False
|
||||
terminate(pid, signal.SIGHUP)
|
||||
else:
|
||||
stdin_buffer += data
|
||||
while b"\n" in stdin_buffer:
|
||||
line, stdin_buffer = stdin_buffer.split(b"\n", 1)
|
||||
command = json.loads(line.decode("utf-8"))
|
||||
command_type = command.get("type")
|
||||
if command_type == "input":
|
||||
write_all(master, base64.b64decode(command["data"], validate=True))
|
||||
elif command_type == "resize":
|
||||
resize(master, int(command["cols"]), int(command["rows"]))
|
||||
elif command_type == "signal":
|
||||
terminate(pid, int(command["signal"]))
|
||||
else:
|
||||
raise RuntimeError("unknown PTY command")
|
||||
finished, status = os.waitpid(pid, os.WNOHANG)
|
||||
if finished == pid:
|
||||
if os.WIFEXITED(status):
|
||||
emit({"type": "exit", "code": os.WEXITSTATUS(status), "signal": None})
|
||||
else:
|
||||
emit({"type": "exit", "code": None, "signal": os.WTERMSIG(status)})
|
||||
os.close(master)
|
||||
return
|
||||
_, status = os.waitpid(pid, 0)
|
||||
if os.WIFEXITED(status):
|
||||
emit({"type": "exit", "code": os.WEXITSTATUS(status), "signal": None})
|
||||
else:
|
||||
emit({"type": "exit", "code": None, "signal": os.WTERMSIG(status)})
|
||||
os.close(master)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as error:
|
||||
emit({"type": "error", "message": str(error)})
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user