Add run_service(..., autostart=False) argument

This allows to run a service but do not cause a qube to be started it
isn't already running. This is especially useful for background /
internal calls designed to service a running target VM - if VM is not
running, those do not make sense to be called in the first place.

Specifically, this will allow qvm-start-gui to avoid re-starting a
domain while calling qubes.NotifyMonitorLayout, when a VM is shutdown
shortly after its startup.
This commit is contained in:
Marek Marczykowski-Górecki 2019-09-21 03:57:35 +02:00
parent 1fcb031192
commit 98260ff148
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 48 additions and 8 deletions

View File

@ -487,7 +487,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
'class: qubesadmin.Qubes()') 'class: qubesadmin.Qubes()')
def run_service(self, dest, service, filter_esc=False, user=None, def run_service(self, dest, service, filter_esc=False, user=None,
localcmd=None, wait=True, **kwargs): localcmd=None, wait=True, autostart=True, **kwargs):
"""Run qrexec service in a given destination """Run qrexec service in a given destination
*kwargs* are passed verbatim to :py:meth:`subprocess.Popen`. *kwargs* are passed verbatim to :py:meth:`subprocess.Popen`.
@ -500,6 +500,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
:param str user: username to run service as :param str user: username to run service as
:param str localcmd: Command to connect stdin/stdout to :param str localcmd: Command to connect stdin/stdout to
:param bool wait: Wait service run :param bool wait: Wait service run
:param bool autostart: Automatically start the target VM
:rtype: subprocess.Popen :rtype: subprocess.Popen
""" """
raise NotImplementedError( raise NotImplementedError(
@ -576,7 +577,7 @@ class QubesLocal(QubesBase):
return self._parse_qubesd_response(return_data) return self._parse_qubesd_response(return_data)
def run_service(self, dest, service, filter_esc=False, user=None, def run_service(self, dest, service, filter_esc=False, user=None,
localcmd=None, wait=True, **kwargs): localcmd=None, wait=True, autostart=True, **kwargs):
"""Run qrexec service in a given destination """Run qrexec service in a given destination
:param str dest: Destination - may be a VM name or empty :param str dest: Destination - may be a VM name or empty
@ -594,10 +595,14 @@ class QubesLocal(QubesBase):
raise ValueError('Empty destination name allowed only from a VM') raise ValueError('Empty destination name allowed only from a VM')
if not wait and localcmd: if not wait and localcmd:
raise ValueError('wait=False incompatible with localcmd') raise ValueError('wait=False incompatible with localcmd')
try: if autostart:
self.qubesd_call(dest, 'admin.vm.Start') try:
except qubesadmin.exc.QubesVMNotHaltedError: self.qubesd_call(dest, 'admin.vm.Start')
pass except qubesadmin.exc.QubesVMNotHaltedError:
pass
elif not self.domains.get_blind(dest).is_running():
raise qubesadmin.exc.QubesVMNotRunningError(
'%s is not running', dest)
qrexec_opts = ['-d', dest] qrexec_opts = ['-d', dest]
if filter_esc: if filter_esc:
qrexec_opts.extend(['-t']) qrexec_opts.extend(['-t'])
@ -665,7 +670,7 @@ class QubesRemote(QubesBase):
return self._parse_qubesd_response(stdout) return self._parse_qubesd_response(stdout)
def run_service(self, dest, service, filter_esc=False, user=None, def run_service(self, dest, service, filter_esc=False, user=None,
localcmd=None, wait=True, **kwargs): localcmd=None, wait=True, autostart=True, **kwargs):
"""Run qrexec service in a given destination """Run qrexec service in a given destination
:param str dest: Destination - may be a VM name or empty :param str dest: Destination - may be a VM name or empty
@ -678,6 +683,9 @@ class QubesRemote(QubesBase):
:param bool wait: wait for process to finish :param bool wait: wait for process to finish
:rtype: subprocess.Popen :rtype: subprocess.Popen
""" """
if not autostart and not dest:
raise ValueError(
'autostart=False makes sense only with a defined target')
if user: if user:
raise ValueError( raise ValueError(
'non-default user not possible for calls from VM') 'non-default user not possible for calls from VM')
@ -686,8 +694,12 @@ class QubesRemote(QubesBase):
qrexec_opts = [] qrexec_opts = []
if filter_esc: if filter_esc:
qrexec_opts.extend(['-t']) qrexec_opts.extend(['-t'])
if filter_esc or os.isatty(sys.stderr.fileno()): if filter_esc or (
os.isatty(sys.stderr.fileno()) and 'stderr' not in kwargs):
qrexec_opts.extend(['-T']) qrexec_opts.extend(['-T'])
if not autostart and not self.domains.get_blind(dest).is_running():
raise qubesadmin.exc.QubesVMNotRunningError(
'%s is not running', dest)
if not wait: if not wait:
# qrexec-client-vm can only request service calls, which are # qrexec-client-vm can only request service calls, which are
# started using MSG_EXEC_CMDLINE qrexec protocol message; this # started using MSG_EXEC_CMDLINE qrexec protocol message; this

View File

@ -29,6 +29,7 @@ try:
import unittest.mock as mock import unittest.mock as mock
except ImportError: except ImportError:
import mock import mock
from mock import call
import tempfile import tempfile
@ -911,3 +912,30 @@ class TC_30_QubesRemote(unittest.TestCase):
'-T', '', 'service.name'], '-T', '', 'service.name'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
@mock.patch('os.isatty', lambda fd: fd == 2)
def test_014_run_service_no_autostart1(self):
self.set_proc_stdout( b'0\x00some-vm class=AppVM state=Running\n')
self.app.run_service('some-vm', 'service.name', autostart=False)
self.proc_mock.assert_has_calls([
call([qubesadmin.config.QREXEC_CLIENT_VM,
'some-vm', 'admin.vm.List'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE),
call().communicate(None),
call([qubesadmin.config.QREXEC_CLIENT_VM,
'-T', 'some-vm', 'service.name'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE),
])
@mock.patch('os.isatty', lambda fd: fd == 2)
def test_015_run_service_no_autostart2(self):
self.set_proc_stdout( b'0\x00some-vm class=AppVM state=Halted\n')
with self.assertRaises(qubesadmin.exc.QubesVMNotRunningError):
self.app.run_service('some-vm', 'service.name', autostart=False)
self.proc_mock.assert_called_once_with([
qubesadmin.config.QREXEC_CLIENT_VM,
'some-vm', 'admin.vm.List'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)