diff --git a/qubesadmin/tests/tools/qvm_run.py b/qubesadmin/tests/tools/qvm_run.py
index c758747..c3d4cbd 100644
--- a/qubesadmin/tests/tools/qvm_run.py
+++ b/qubesadmin/tests/tools/qvm_run.py
@@ -17,7 +17,6 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see .
-
import io
import os
import unittest.mock
@@ -55,7 +54,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -91,14 +89,12 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
}),
('test-vm', 'qubes.VMShell', b'command; exit\n'),
('test-vm2', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -129,7 +125,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
'filter_esc': True,
- 'localcmd': None,
'stdout': None,
'stderr': None,
'user': None,
@@ -194,7 +189,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
'filter_esc': True,
- 'localcmd': None,
'stdout': None,
'stderr': None,
'user': None,
@@ -228,7 +222,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
'filter_esc': self.default_filter_esc(),
- 'localcmd': None,
'stdout': None,
'stderr': None,
'user': None,
@@ -265,7 +258,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
'filter_esc': False,
- 'localcmd': None,
'stdout': None,
'stderr': None,
'user': None,
@@ -276,7 +268,8 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
stdout.close()
self.assertAllCalled()
- def test_005_localcmd(self):
+ @unittest.mock.patch('subprocess.Popen')
+ def test_005_localcmd(self, mock_popen):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00test-vm class=AppVM state=Running\n'
@@ -286,6 +279,7 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
# self.app.expected_calls[
# ('test-vm', 'admin.vm.List', None, None)] = \
# b'0\x00test-vm class=AppVM state=Running\n'
+ mock_popen.return_value.wait.return_value = 0
ret = qubesadmin.tools.qvm_run.main(
['--no-gui', '--pass-io', '--localcmd', 'local-command',
'test-vm', 'command'],
@@ -293,13 +287,16 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
- 'localcmd': 'local-command',
- 'stdout': None,
+ 'stdout': subprocess.PIPE,
+ 'stdin': subprocess.PIPE,
'stderr': None,
'user': None,
}),
('test-vm', 'qubes.VMShell', b'command; exit\n')
])
+ mock_popen.assert_called_once_with('local-command',
+ # TODO: check if the right stdin/stdout objects are used
+ stdout=unittest.mock.ANY, stdin=unittest.mock.ANY, shell=True)
self.assertAllCalled()
def test_006_run_single_with_gui(self):
@@ -327,7 +324,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
}),
('test-vm', 'qubes.WaitForSession', b'user'),
('test-vm', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -358,7 +354,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
}),
('test-vm', 'qubes.WaitForSession', b'user'),
('test-vm', 'service.name', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -373,7 +368,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('$dispvm', 'test.service', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -388,7 +382,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('$dispvm:test-vm', 'test.service', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -412,7 +405,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('disp123', 'test.service', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -437,7 +429,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('disp123', 'test.service', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -468,7 +459,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -511,7 +501,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('disp123', 'qubes.VMShell+WaitForSession', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -540,7 +529,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('disp123', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
@@ -566,7 +554,6 @@ class TC_00_qvm_run(qubesadmin.tests.QubesTestCase):
self.assertEqual(ret, 0)
self.assertEqual(self.app.service_calls, [
('test-vm', 'qubes.VMShell', {
- 'localcmd': None,
'stdout': subprocess.DEVNULL,
'stderr': subprocess.DEVNULL,
'user': None,
diff --git a/qubesadmin/tools/qvm_run.py b/qubesadmin/tools/qvm_run.py
index f10b6c2..47ed33f 100644
--- a/qubesadmin/tools/qvm_run.py
+++ b/qubesadmin/tools/qvm_run.py
@@ -19,7 +19,6 @@
# with this program; if not, see .
''' qvm-run tool'''
-
import os
import subprocess
import sys
@@ -146,11 +145,15 @@ def main(args=None, app=None):
if not args.passio:
run_kwargs['stdout'] = subprocess.DEVNULL
run_kwargs['stderr'] = subprocess.DEVNULL
+ elif args.localcmd:
+ run_kwargs['stdin'] = subprocess.PIPE
+ run_kwargs['stdout'] = subprocess.PIPE
+ run_kwargs['stderr'] = None
else:
# connect process output to stdout/err directly if --pass-io is given
run_kwargs['stdout'] = None
run_kwargs['stderr'] = None
- if not args.localcmd and args.filter_esc:
+ if args.filter_esc:
run_kwargs['filter_esc'] = True
if isinstance(args.app, qubesadmin.app.QubesLocal) and \
@@ -204,21 +207,26 @@ def main(args=None, app=None):
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
wait_session.communicate(vm.default_user.encode())
if args.service:
- proc = vm.run_service(args.cmd,
- user=args.user,
- localcmd=args.localcmd,
- **run_kwargs)
+ service = args.cmd
else:
service = 'qubes.VMShell'
if args.gui and args.dispvm:
service += '+WaitForSession'
- proc = vm.run_service(service,
- user=args.user,
- localcmd=args.localcmd,
- **run_kwargs)
+ proc = vm.run_service(service,
+ user=args.user,
+ **run_kwargs)
+ if not args.service:
proc.stdin.write(vm.prepare_input_for_vmshell(args.cmd))
proc.stdin.flush()
- if args.passio and not args.localcmd:
+ if args.localcmd:
+ local_proc = subprocess.Popen(args.localcmd,
+ shell=True,
+ stdout=proc.stdin,
+ stdin=proc.stdout)
+ # stdin is closed below
+ proc.stdout.close()
+ procs.append(local_proc)
+ elif args.passio:
copy_proc = multiprocessing.Process(target=copy_stdin,
args=(proc.stdin,))
copy_proc.start()