diff --git a/qubesadmin/base.py b/qubesadmin/base.py index 05e30271..7657b750 100644 --- a/qubesadmin/base.py +++ b/qubesadmin/base.py @@ -69,13 +69,23 @@ def qubesd_call(self, dest, method, arg=None, payload=None, :param payload_stream: file-like object to read payload from :return: Data returned by qubesd (string) ''' + if not self.app: + raise NotImplementedError if dest is None: dest = self._method_dest + if ( + getattr(self, "_redirect_dispvm_calls", False) + and dest.startswith("@dispvm") + ): + if dest.startswith("@dispvm:"): + dest = dest[len("@dispvm:") :] + else: + dest = getattr(self.app, "default_dispvm", None) + if dest: + dest = dest.name # have the actual implementation at Qubes() instance - if self.app: - return self.app.qubesd_call(dest, method, arg, payload, - payload_stream) - raise NotImplementedError + return self.app.qubesd_call(dest, method, arg, payload, + payload_stream) @staticmethod def _parse_qubesd_response(response_data): diff --git a/qubesadmin/tests/tools/qvm_run.py b/qubesadmin/tests/tools/qvm_run.py index 9568fb06..54f41305 100644 --- a/qubesadmin/tests/tools/qvm_run.py +++ b/qubesadmin/tests/tools/qvm_run.py @@ -682,9 +682,6 @@ def test_008_dispvm_remote(self): self.assertAllCalled() def test_009_dispvm_remote_specific(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", "guivm", None) ] = b"0\x00default=True type=vm dom0" @@ -756,9 +753,6 @@ def test_010_dispvm_local(self): def test_011_dispvm_local_specific(self): self.app.qubesd_connection_type = "socket" - 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", "guivm", None) ] = b"0\x00default=False type=vm " @@ -1150,7 +1144,7 @@ def test_022_no_shell(self): def test_023_dispvm_no_shell(self): self.app.expected_calls[ ( - "@dispvm:test-vm", + "test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None, @@ -1277,7 +1271,7 @@ def test_026_no_shell_double_dashdash(self): def test_027_no_shell_dispvm(self): self.app.expected_calls[ ( - "@dispvm:test-vm", + "test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None, @@ -1308,7 +1302,7 @@ def test_027_no_shell_dispvm(self): def test_028_argparse_bug_workaround(self): self.app.expected_calls[ ( - "@dispvm:test-vm", + "test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None, @@ -1339,7 +1333,7 @@ def test_028_argparse_bug_workaround(self): def test_029_command_is_dashdash(self): self.app.expected_calls[ ( - "@dispvm:test-vm", + "test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None, @@ -1369,7 +1363,10 @@ def test_029_command_is_dashdash(self): def test_030_no_shell_dispvm(self): self.app.expected_calls[ - ("@dispvm", "admin.vm.feature.CheckWithTemplate", "vmexec", None) + ("dom0", "admin.property.Get", "default_dispvm", None) + ] = b"0\x00default=True type=vm test-vm" + self.app.expected_calls[ + ("test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None) ] = b"0\x001" ret = qubesadmin.tools.qvm_run.main( ["--no-gui", "--dispvm", "--", "test-vm", "command", "arg"], @@ -1395,7 +1392,10 @@ def test_030_no_shell_dispvm(self): def test_031_argparse_bug_workaround(self): self.app.expected_calls[ - ("@dispvm", "admin.vm.feature.CheckWithTemplate", "vmexec", None) + ("dom0", "admin.property.Get", "default_dispvm", None) + ] = b"0\x00default=True type=vm test-vm" + self.app.expected_calls[ + ("test-vm", "admin.vm.feature.CheckWithTemplate", "vmexec", None) ] = b"0\x001" ret = qubesadmin.tools.qvm_run.main( ["--no-gui", "--dispvm", "--", "test-vm", "command", "--"], diff --git a/qubesadmin/tools/qvm_run.py b/qubesadmin/tools/qvm_run.py index 2842011f..14be8dd8 100644 --- a/qubesadmin/tools/qvm_run.py +++ b/qubesadmin/tools/qvm_run.py @@ -165,9 +165,9 @@ action="store", nargs="?", const=True, - metavar="BASE_APPVM", - help="start a service in new Disposable VM; " - "optionally specify base AppVM for DispVM", + metavar="DISPOSABLE_TEMPLATE", + help="start a service in new disposable qube; optionally specify a " + "disposable template, else the system default will be used", ) parser.add_argument("VMNAME", nargs="?", action=qubesadmin.tools.VmNameAction) @@ -258,6 +258,9 @@ def run_command_single(args, vm): args.cmd = args.VMNAME args.VMNAME = None + if args.dispvm and args.gui is None: + args.gui = has_gui(vm) + use_exec = len(args.cmd_args) > 0 or args.no_shell copy_proc = None @@ -358,14 +361,9 @@ def main(args=None, app=None): if args.dispvm: if args.exclude: parser.error("Cannot use --exclude with --dispvm") - if args.gui is None: - args.gui = has_gui( - args.app.default_dispvm - if args.dispvm is True - else args.app.domains[args.dispvm] - ) dispvm = qubesadmin.vm.DispVM.from_appvm( - args.app, None if args.dispvm is True else args.dispvm + args.app, None if args.dispvm is True else args.dispvm, + redirect_dispvm_calls=True ) domains = [dispvm] elif args.all_domains: diff --git a/qubesadmin/vm/__init__.py b/qubesadmin/vm/__init__.py index 7543a358..796b1ee1 100644 --- a/qubesadmin/vm/__init__.py +++ b/qubesadmin/vm/__init__.py @@ -461,6 +461,10 @@ class DispVMWrapper(QubesVM): after service call ends. """ + def __init__(self, *args, **kwargs): + self._redirect_dispvm_calls = kwargs.pop("redirect_dispvm_calls", False) + super().__init__(*args, **kwargs) + def run_service(self, service, **kwargs): """Create disposable if absent and run service.""" if ( @@ -513,14 +517,21 @@ class DispVM(QubesVM): """Disposable VM""" @classmethod - def from_appvm(cls, app, appvm): + def from_appvm(cls, app, appvm, redirect_dispvm_calls=False): """Returns a wrapper for calling service in a new DispVM based on given - AppVM. If *appvm* is none, use default DispVM template""" + AppVM. If *appvm* is none, use default DispVM template + + If **redirect_dispvm_calls** is used, calls made before the disposable + is created will instead guess the disposable template to query for + features, properties etc. + """ if appvm: method_dest = "@dispvm:" + str(appvm) else: method_dest = "@dispvm" - wrapper = DispVMWrapper(app, method_dest) + wrapper = DispVMWrapper( + app, method_dest, redirect_dispvm_calls=redirect_dispvm_calls + ) return wrapper