diff --git a/doc/manpages/qvm-run.rst b/doc/manpages/qvm-run.rst index 199425a..c99aefb 100644 --- a/doc/manpages/qvm-run.rst +++ b/doc/manpages/qvm-run.rst @@ -63,6 +63,12 @@ Options Run the command without GUI forwarding enabled. Can be switched back with :option:`--gui`. +.. option:: --service + + Start RPC service instead of shell command. Specify name of the service in + place of *COMMAND* argument. You can also specify service argument, appending + it to the service name after `+` character. + .. option:: --colour-output=COLOUR, --color-output=COLOR Mark the qube output with given ANSI colour (ie. "31" for red). The exact diff --git a/qubesadmin/tests/tools/qvm_run.py b/qubesadmin/tests/tools/qvm_run.py index eff6feb..dcb40e4 100644 --- a/qubesadmin/tests/tools/qvm_run.py +++ b/qubesadmin/tests/tools/qvm_run.py @@ -262,3 +262,35 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase): ('test-vm', 'qubes.VMShell', b'command; exit\n') ]) self.assertAllCalled() + + def test_007_run_service_with_gui(self): + self.app.expected_calls[ + ('dom0', 'admin.vm.List', None, None)] = \ + b'0\x00test-vm class=AppVM state=Running\n' + self.app.expected_calls[ + ('test-vm', 'admin.vm.property.Get', 'default_user', None)] = \ + b'0\x00default=yes type=str user' + # self.app.expected_calls[ + # ('test-vm', 'admin.vm.List', None, None)] = \ + # b'0\x00test-vm class=AppVM state=Running\n' + ret = qubesadmin.tools.qvm_run.main( + ['--service', 'test-vm', 'service.name'], + app=self.app) + self.assertEqual(ret, 0) + # make sure we have the same instance below + self.assertEqual(self.app.service_calls, [ + ('test-vm', 'qubes.WaitForSession', { + 'stdout': subprocess.DEVNULL, + 'stderr': subprocess.DEVNULL, + }), + ('test-vm', 'qubes.WaitForSession', b'user'), + ('test-vm', 'service.name', { + 'filter_esc': True, + 'localcmd': None, + 'stdout': subprocess.DEVNULL, + 'stderr': subprocess.DEVNULL, + 'user': None, + }), + ('test-vm', 'service.name', b''), + ]) + self.assertAllCalled() diff --git a/qubesadmin/tools/qvm_run.py b/qubesadmin/tools/qvm_run.py index c401b8d..496db1f 100644 --- a/qubesadmin/tools/qvm_run.py +++ b/qubesadmin/tools/qvm_run.py @@ -86,6 +86,10 @@ parser.add_argument('--no-filter-escape-chars', help='do not filter terminal escape sequences; DANGEROUS when output is a' ' terminal emulator') +parser.add_argument('--service', + action='store_true', dest='service', + help='run a qrexec service (named by COMMAND) instead of shell command') + parser.add_argument('cmd', metavar='COMMAND', help='command to run') @@ -136,7 +140,7 @@ def main(args=None, app=None): run_kwargs['stderr'] = None if isinstance(args.app, qubesadmin.app.QubesLocal) and \ - not args.passio and not args.localcmd: + not args.passio and not args.localcmd and args.service: # wait=False works only in dom0; but it's still useful, to save on # simultaneous vchan connections run_kwargs['wait'] = False @@ -172,13 +176,20 @@ def main(args=None, app=None): if args.passio and not args.localcmd: loop = asyncio.new_event_loop() loop.add_signal_handler(signal.SIGCHLD, loop.stop) - proc = vm.run_service('qubes.VMShell', - user=args.user, - localcmd=args.localcmd, - filter_esc=args.filter_esc, - **run_kwargs) - proc.stdin.write(vm.prepare_input_for_vmshell(args.cmd)) - proc.stdin.flush() + if args.service: + proc = vm.run_service(args.cmd, + user=args.user, + localcmd=args.localcmd, + filter_esc=args.filter_esc, + **run_kwargs) + else: + proc = vm.run_service('qubes.VMShell', + user=args.user, + localcmd=args.localcmd, + filter_esc=args.filter_esc, + **run_kwargs) + proc.stdin.write(vm.prepare_input_for_vmshell(args.cmd)) + proc.stdin.flush() if args.passio and not args.localcmd: asyncio.ensure_future(loop.connect_read_pipe( functools.partial(DataCopyProtocol, proc.stdin,