qrun-in-vm 1.9 KB

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