core-agent-linux/qubes-rpc/qrun-in-vm

46 lines
1.9 KiB
Plaintext
Raw Permalink Normal View History

#!/usr/bin/python3
# Send the command to the remote side, and then transfer stdin from local to
# remote and stdout from remote to local.
#
# The tricky part is delimiting the command from the stdin data. If we were
# implementing this from scratch, we'd probably use a null byte. However, we'd
# like to work with the existing qubes.VMShell service, whose implementation is
# simply "/bin/bash", so users don't have to maintain duplicate RPC policy. We
# take advantage of the fact that when bash is executing commands from a pipe,
# it reads one character at a time until it gets a newline that ends a command.
# So the initial qubes.VMShell bash process, which is executing commands from
# stdin, consumes exactly the line from the "write" below and then either
# completes the "exec" or exits. In no event does it touch the stdin data
# intended for the command.
import os
import subprocess
import sys
cmd = ' '.join(sys.argv[1:])
sys.stdout.write("exec bash -c '%s' || exit 127\n" % cmd.replace("'", "'\\''"))
sys.stdout.flush()
local_stdin = int(os.environ['SAVED_FD_0'])
local_stdout = int(os.environ['SAVED_FD_1'])
stdin_sender = subprocess.Popen(['cat'], stdin=local_stdin)
stdout_receiver = subprocess.Popen(['cat'], stdout=local_stdout)
# sys.std{in,out}.close() do not close the FDs, but they apparently stop Python
# from trying to close the FDs again on exit and generating an exception.
sys.stdin.close()
sys.stdout.close()
os.close(0)
# The really important step, so this process doesn't prevent qrexec-client-vm
# from seeing EOF on input.
os.close(1)
os.close(local_stdin)
os.close(local_stdout)
stdout_receiver.wait()
# With the current Qubes RPC implementation, the stdout receiver doesn't get EOF
# until the remote process has exited. At that point, we want to finish and not
# try to send more input. This is the same behavior ssh appears to have.
stdin_sender.terminate()
stdin_sender.wait()