@@ -233,10 +233,8 @@ def _is_attached_to(self, vm):
233233
234234 for disk in xml_desc .findall ("devices/disk[source][@type='block']" ):
235235 backend_domain , port_id = _try_get_block_device_info (vm .app , disk )
236-
237236 if backend_domain .name != self .backend_domain .name :
238237 continue
239-
240238 if self .port_id == port_id :
241239 return True
242240 return False
@@ -307,6 +305,12 @@ def _try_get_block_device_info(app, disk):
307305
308306class BlockDeviceExtension (qubes .ext .Extension ):
309307
308+ def __init__ (self ):
309+ super ().__init__ ()
310+ # TODO: ben: https://github.com/QubesOS/qubes-core-admin/pull/819#discussion_r3537683559
311+ self ._attachments = {}
312+ self ._attachments_with_options = {}
313+
310314 @qubes .ext .handler ("domain-init" , "domain-load" )
311315 def on_domain_init_load (self , vm , event ):
312316 """Initialize watching for changes"""
@@ -327,6 +331,24 @@ def on_domain_init_load(self, vm, event):
327331 else :
328332 self .devices_cache [vm .name ] = {}
329333
334+ @qubes .ext .handler ("device-attach:block" )
335+ def on_device_block_attach (self , vm , event , device , options ):
336+ # pylint: disable=unused-argument
337+ self ._attachments [device .port_id ] = device .backend_domain
338+
339+ @qubes .ext .handler ("device-detach:block" )
340+ def on_device_block_detach (self , vm , event , port ):
341+ # pylint: disable=unused-argument
342+ if port in self ._attachments :
343+ self ._attachments .pop (port )
344+ if vm not in self ._attachments_with_options :
345+ return
346+ if port not in self ._attachments_with_options [vm ]:
347+ return
348+ for device , _options in self ._attachments_with_options [vm ].items ():
349+ if device .port_id == port :
350+ self ._attachments_with_options [vm ].pop (device )
351+
330352 @qubes .ext .handler ("domain-qdb-change:/qubes-block-devices" )
331353 def on_qdb_change (self , vm , event , path ):
332354 """A change in QubesDB means a change in a device list."""
@@ -338,8 +360,7 @@ def on_qdb_change(self, vm, event, path):
338360 )
339361 utils .device_list_change (self , current_devices , vm , path , BlockDevice )
340362
341- @staticmethod
342- def get_device_attachments (vm_ ):
363+ def get_device_attachments (self , vm_ ):
343364 result = {}
344365 for vm in vm_ .app .domains :
345366 if not vm .is_running () or isinstance (vm , RemoteVM ):
@@ -348,7 +369,10 @@ def get_device_attachments(vm_):
348369 if vm .app .vmm .offline_mode :
349370 return result
350371
351- # TODO: ben: cache
372+ if vm in self ._attachments :
373+ result = self ._attachments [vm ]
374+ continue
375+
352376 xml_desc = lxml .etree .fromstring (vm .libvirt_domain .XMLDesc ())
353377
354378 for disk in xml_desc .findall ("devices/disk[source][@type='block']" ):
@@ -359,6 +383,7 @@ def get_device_attachments(vm_):
359383 continue
360384
361385 result [port_id ] = vm
386+ self ._attachments [vm ] = result
362387 return result
363388
364389 @staticmethod
@@ -422,10 +447,14 @@ def on_device_list_attached(self, vm, event, **kwargs):
422447 if not vm .is_running ():
423448 return
424449
450+ if vm in self ._attachments_with_options :
451+ yield from self ._attachments_with_options [vm ]
452+ return
453+ self ._attachments_with_options [vm ] = []
454+
425455 system_disks = SYSTEM_DISKS
426456 if getattr (vm , "kernel" , None ):
427457 system_disks = SYSTEM_DISKS_DOM0_KERNEL
428- # TODO: ben: cache
429458 xml_desc = lxml .etree .fromstring (vm .libvirt_domain .XMLDesc ())
430459
431460 for disk in xml_desc .findall (
@@ -450,7 +479,9 @@ def on_device_list_attached(self, vm, event, **kwargs):
450479 if devtype != "disk" :
451480 options ["devtype" ] = devtype
452481
453- yield BlockDevice (Port (backend_domain , port_id , "block" )), options
482+ dev = BlockDevice (Port (backend_domain , port_id , "block" )), options
483+ yield dev
484+ self ._attachments_with_options [vm ].append (dev )
454485
455486 @staticmethod
456487 def _is_block (vm , block : str ) -> bool :
0 commit comments