From ff1285bc3721e881996c1abed806f2832432e977 Mon Sep 17 00:00:00 2001 From: ahriman <177127055+RandyTheOtter@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:53:00 +0000 Subject: [PATCH 1/4] utils: extend dependency checking facilities --- qubesadmin/utils/__init__.py | 111 +++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/qubesadmin/utils/__init__.py b/qubesadmin/utils/__init__.py index 144141fc..6292a282 100644 --- a/qubesadmin/utils/__init__.py +++ b/qubesadmin/utils/__init__.py @@ -129,42 +129,58 @@ def updates_vms_status(qvm_collection: QubesBase) -> bool | None: def vm_dependencies( - app: QubesBase, reference_vm: QubesVM -) -> list[tuple[QubesVM | None, str]]: + app, + *domains, + global_properties:list=None, + vm_properties:list=None + ): """Helper function that returns a list of all the places a given VM is used - in. Output is a list of tuples (property_holder, property_name), with None - as property_holder for global properties + in. Output is a dictionary of: + { + supplier:[ + (dependant, property_name), + ... + ], + ... + } + + with qubesadmin.vm.QubesVM as supplier and dependant, and None as dependant + for global properties + + :param domains: domains to return dependencies for """ - result = [] - - global_properties = [ - "default_dispvm", - "default_netvm", - "default_guivm", - "default_audiovm", - "default_template", - "clockvm", - "updatevm", - "management_dispvm", - ] + result = {} + + if global_properties is None: + global_properties = [ + "default_dispvm", + "default_netvm", + "default_guivm", + "default_audiovm", + "default_template", + "clockvm", + "updatevm", + "management_dispvm", + ] for prop in global_properties: - if reference_vm == getattr(app, prop, None): - result.append((None, prop)) - - vm_properties = [ - "template", - "netvm", - "guivm", - "audiovm", - "default_dispvm", - "management_dispvm", - ] + if getattr(app, prop, None) in domains: + result[getattr(app,prop,None)] = [(None, prop)] + + if vm_properties is None: + vm_properties = [ + "template", + "netvm", + "guivm", + "audiovm", + "default_dispvm", + "management_dispvm", + ] for vm in app.domains: - if vm == reference_vm: - continue + #if vm == reference_vm: + # continue is_preload = getattr(vm, "is_preload", False) for prop in vm_properties: if not hasattr(vm, prop): @@ -174,7 +190,7 @@ def vm_dependencies( except qubesadmin.exc.QubesPropertyAccessError: is_prop_default = False if ( - reference_vm == getattr(vm, prop, None) + getattr(vm, prop, None) in domains and not is_prop_default and not ( is_preload @@ -185,11 +201,44 @@ def vm_dependencies( ) ) ): - result.append((vm, prop)) + result[getattr(vm, prop, None)].append((vm, prop)) return result +def is_independent(app,*domains): + """Return True if provided set of domains is independent i.e. has no + dependants external to it. For example, if these domains are running: + + sys-net + └─sys-firewall + └─disp1 + sys-net2 + └─sys-vpn + └─my_favourite_standalone + vault_cube_pictures + + is_independent(sys-net,sys-firewall) returns False and + is_independent(sys-net,sys-firewall,disp1) returns True. + + :param domains: set of domains to check (not object of type set, just + arguments). Domain is qubesadmin.vm.QubesVM + """ + + dependencies = vm_dependencies(app,*domains) + + for vm in dependencies: + for dependency in dependencies[vm]: + if ( + dependency[0] is not None + and dependency[0].get_power_state() == "Running" + and dependency[0] not in dependencies + ): + return False + + return True + + def encode_for_vmexec(args: Iterable[str]) -> str: """ Encode an argument list for qubes.VMExec call. From da31b40a54e84877744ddd6445537f6929e8e567 Mon Sep 17 00:00:00 2001 From: Randy <177127055+RandyTheOtter@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:28:08 +0000 Subject: [PATCH 2/4] utils: only check relevant properties --- qubesadmin/utils/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qubesadmin/utils/__init__.py b/qubesadmin/utils/__init__.py index 6292a282..91944390 100644 --- a/qubesadmin/utils/__init__.py +++ b/qubesadmin/utils/__init__.py @@ -225,7 +225,12 @@ def is_independent(app,*domains): arguments). Domain is qubesadmin.vm.QubesVM """ - dependencies = vm_dependencies(app,*domains) + dependencies = vm_dependencies( + app, + *domains, + global_properties=[], + vm_properties=['netvm','guivm','audiovm'] + ) for vm in dependencies: for dependency in dependencies[vm]: From ce7ba53d642ed4d2e7ad850e1935f05fbaf53d6c Mon Sep 17 00:00:00 2001 From: Randy <177127055+RandyTheOtter@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:45:15 +0000 Subject: [PATCH 3/4] handle missing keys --- qubesadmin/utils/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/qubesadmin/utils/__init__.py b/qubesadmin/utils/__init__.py index 91944390..97160390 100644 --- a/qubesadmin/utils/__init__.py +++ b/qubesadmin/utils/__init__.py @@ -166,7 +166,10 @@ def vm_dependencies( for prop in global_properties: if getattr(app, prop, None) in domains: - result[getattr(app,prop,None)] = [(None, prop)] + if getattr(app, prop, None) in result.keys(): + result[getattr(app,prop,None)].append((None, prop)) + else: + result[getattr(app,prop,None)] = [(None, prop)] if vm_properties is None: vm_properties = [ @@ -201,7 +204,11 @@ def vm_dependencies( ) ) ): - result[getattr(vm, prop, None)].append((vm, prop)) + if getattr(vm, prop, None) in result.keys(): + result[getattr(vm, prop, None)].append((vm, prop)) + else: + result[getattr(vm, prop, None)] = [(vm, prop)] + return result From ec6232416f989bfc7f82980d1988d54bd2aac519 Mon Sep 17 00:00:00 2001 From: ahriman <177127055+RandyTheOtter@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:41:17 +0000 Subject: [PATCH 4/4] fix missing result keys bug and use is_running --- qubesadmin/utils/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qubesadmin/utils/__init__.py b/qubesadmin/utils/__init__.py index 97160390..3eb4b831 100644 --- a/qubesadmin/utils/__init__.py +++ b/qubesadmin/utils/__init__.py @@ -151,6 +151,8 @@ def vm_dependencies( """ result = {} + for domain in domains: + result[domain] = [] if global_properties is None: global_properties = [ @@ -171,6 +173,7 @@ def vm_dependencies( else: result[getattr(app,prop,None)] = [(None, prop)] + if vm_properties is None: vm_properties = [ "template", @@ -208,8 +211,6 @@ def vm_dependencies( result[getattr(vm, prop, None)].append((vm, prop)) else: result[getattr(vm, prop, None)] = [(vm, prop)] - - return result @@ -243,7 +244,7 @@ def is_independent(app,*domains): for dependency in dependencies[vm]: if ( dependency[0] is not None - and dependency[0].get_power_state() == "Running" + and dependency[0].is_running() and dependency[0] not in dependencies ): return False