Blame SOURCES/libvirt-qemu_hotplug-delay-sending-DEVICE_REMOVED-event-until-after-all-teardown.patch

0a7476
From ac1770917c5f6020ccb5b6247f3eeb9f50a67903 Mon Sep 17 00:00:00 2001
0a7476
Message-Id: <ac1770917c5f6020ccb5b6247f3eeb9f50a67903@dist-git>
0a7476
From: Laine Stump <laine@laine.org>
0a7476
Date: Thu, 11 Apr 2019 15:14:53 -0400
0a7476
Subject: [PATCH] qemu_hotplug: delay sending DEVICE_REMOVED event until after
0a7476
 *all* teardown
0a7476
0a7476
The VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED event is sent after qemu has
0a7476
responded to a device_del command with a DEVICE_DELETED event. Before
0a7476
queuing the event, *some* of the final teardown of the device's
0a7476
trappings in libvirt is done, but not *all* of it. As a result, an
0a7476
application may receive and process the DEVICE_REMOVED event before
0a7476
libvirt has really finished with it.
0a7476
0a7476
Usually this doesn't cause a problem, but it can - in the case of the
0a7476
bug report referenced below, vdsm is assigning a PCI device to a guest
0a7476
with managed='no', using livirt's virNodeDeviceDetachFlags() and
0a7476
virNodeDeviceReAttach() APIs. Immediately after receiving a
0a7476
DEVICE_REMOVED event from libvirt signalling that the device had been
0a7476
successfully unplugged, vdsm would cal virNodeDeviceReAttach() to
0a7476
unbind the device from vfio-pci and rebind it to the host driverm but
0a7476
because the event was received before libvirt had completely finished
0a7476
processing the removal, that device was still on the "activeDevs"
0a7476
list, and so virNodeDeviceReAttach() failed.
0a7476
0a7476
Experimentation with additional debug logs proved that libvirt would
0a7476
always end up dispatching the DEVICE_REMOVED event before it had
0a7476
removed the device from activeDevs (with a *much* greater difference
0a7476
with managed='yes', since in that case the re-binding of the device
0a7476
occurred after queuing the device).
0a7476
0a7476
Although the case of hostdev devices is the most extreme (since there
0a7476
is so much involved in tearing down the device), *all* device types
0a7476
suffer from the same problem - the DEVICE_REMOVED event is queued very
0a7476
early in the qemuDomainRemove*Device() function for all of them,
0a7476
resulting in a possibility of any application receiving the event
0a7476
before libvirt has really finished with the device.
0a7476
0a7476
The solution is to save the device's alias (which is the only piece of
0a7476
info from the device object that is needed for the event) at the
0a7476
beginning of processing the device removal, and then queue the event
0a7476
as a final act before returning. Since all of the
0a7476
qemuDomainRemove*Device() functions (except
0a7476
qemuDomainRemoveChrDevice()) are now called exclusively from
0a7476
qemuDomainRemoveDevice() (which selects which of the subordinates to
0a7476
call in a switch statement based on the type of device), the shortest
0a7476
route to a solution is to doing the saving of alias, and later
0a7476
queueing of the event, in the higher level qemuDomainRemoveDevice(),
0a7476
and just completely remove the event-related code from all the
0a7476
subordinate functions.
0a7476
0a7476
The single exception to this, as mentioned before, is
0a7476
qemuDomainRemoveChrDevice(), which is still called from somewhere
0a7476
other than qemuDomainRemoveDevice() (and has a separate arg used to
0a7476
trigger different behavior when the chr device has targetType ==
0a7476
GUESTFWD), so it must keep its original behavior intact, and must be
0a7476
treated differently by qemuDomainRemoveDevice() (similar to the way
0a7476
that qemuDomainDetachDeviceLive() treats chr and lease devices
0a7476
differently from all the others).
0a7476
0a7476
Signed-off-by: Laine Stump <laine@laine.org>
0a7476
ACKed-by: Peter Krempa <pkrempa@redhat.com>
0a7476
(cherry picked from commit 78b03a7770f1822458be3e0769538dfc92b34803)
0a7476
0a7476
Resolves: https://bugzilla.redhat.com/1658198
0a7476
Signed-off-by: Laine Stump <laine@redhat.com>
0a7476
0a7476
Conflicts:
0a7476
  src/qemu/qemu_hotplug.c:
0a7476
    * some code around a removed event queuing had been
0a7476
      moved into a helper function upstream.
0a7476
0a7476
    * upstream patch used VIR_AUTOFREE, which isn't in 4.5.0
0a7476
0a7476
Signed-off-by: Laine Stump <laine@laine.org>
0a7476
Message-Id: <20190411191453.24055-42-laine@redhat.com>
0a7476
Acked-by: Michal Privoznik <mprivozn@redhat.com>
0a7476
---
0a7476
 src/qemu/qemu_hotplug.c | 145 +++++++++++++++++++---------------------
0a7476
 1 file changed, 70 insertions(+), 75 deletions(-)
0a7476
0a7476
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
0a7476
index ff88a827dd..103d3e59a7 100644
0a7476
--- a/src/qemu/qemu_hotplug.c
0a7476
+++ b/src/qemu/qemu_hotplug.c
0a7476
@@ -3905,7 +3905,6 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver,
0a7476
 {
0a7476
     qemuDomainStorageSourcePrivatePtr diskPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src);
0a7476
     virDomainDeviceDef dev;
0a7476
-    virObjectEventPtr event;
0a7476
     size_t i;
0a7476
     const char *src = virDomainDiskGetSource(disk);
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
@@ -3972,9 +3971,6 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver,
0a7476
 
0a7476
     virDomainAuditDisk(vm, disk->src, NULL, "detach", true);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, disk->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if (prManaged && !prUsed)
0a7476
         qemuProcessKillManagedPRDaemon(vm);
0a7476
 
0a7476
@@ -4003,19 +3999,14 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver,
0a7476
 
0a7476
 
0a7476
 static int
0a7476
-qemuDomainRemoveControllerDevice(virQEMUDriverPtr driver,
0a7476
-                                 virDomainObjPtr vm,
0a7476
+qemuDomainRemoveControllerDevice(virDomainObjPtr vm,
0a7476
                                  virDomainControllerDefPtr controller)
0a7476
 {
0a7476
-    virObjectEventPtr event;
0a7476
     size_t i;
0a7476
 
0a7476
     VIR_DEBUG("Removing controller %s from domain %p %s",
0a7476
               controller->info.alias, vm, vm->def->name);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, controller->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     for (i = 0; i < vm->def->ncontrollers; i++) {
0a7476
         if (vm->def->controllers[i] == controller) {
0a7476
             virDomainControllerRemove(vm->def, i);
0a7476
@@ -4037,7 +4028,6 @@ qemuDomainRemoveMemoryDevice(virQEMUDriverPtr driver,
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
     unsigned long long oldmem = virDomainDefGetMemoryTotal(vm->def);
0a7476
     unsigned long long newmem = oldmem - mem->size;
0a7476
-    virObjectEventPtr event;
0a7476
     char *backendAlias = NULL;
0a7476
     int rc;
0a7476
     int idx;
0a7476
@@ -4059,9 +4049,6 @@ qemuDomainRemoveMemoryDevice(virQEMUDriverPtr driver,
0a7476
     if (rc < 0)
0a7476
         return -1;
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, mem->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if ((idx = virDomainMemoryFindByDef(vm->def, mem)) >= 0)
0a7476
         virDomainMemoryRemove(vm->def, idx);
0a7476
 
0a7476
@@ -4141,7 +4128,6 @@ qemuDomainRemoveHostDevice(virQEMUDriverPtr driver,
0a7476
 {
0a7476
     virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
0a7476
     virDomainNetDefPtr net = NULL;
0a7476
-    virObjectEventPtr event;
0a7476
     size_t i;
0a7476
     int ret = -1;
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
@@ -4185,9 +4171,6 @@ qemuDomainRemoveHostDevice(virQEMUDriverPtr driver,
0a7476
             goto cleanup;
0a7476
     }
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, hostdev->info->alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if (hostdev->parent.type == VIR_DOMAIN_DEVICE_NET) {
0a7476
         net = hostdev->parent.data.net;
0a7476
 
0a7476
@@ -4266,7 +4249,6 @@ qemuDomainRemoveNetDevice(virQEMUDriverPtr driver,
0a7476
 {
0a7476
     virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
-    virObjectEventPtr event;
0a7476
     char *hostnet_name = NULL;
0a7476
     char *charDevAlias = NULL;
0a7476
     size_t i;
0a7476
@@ -4322,9 +4304,6 @@ qemuDomainRemoveNetDevice(virQEMUDriverPtr driver,
0a7476
 
0a7476
     virDomainAuditNet(vm, net, NULL, "detach", true);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, net->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     for (i = 0; i < vm->def->nnets; i++) {
0a7476
         if (vm->def->nets[i] == net) {
0a7476
             virDomainNetRemove(vm->def, i);
0a7476
@@ -4408,11 +4387,16 @@ qemuDomainRemoveChrDevice(virQEMUDriverPtr driver,
0a7476
     if (qemuDomainNamespaceTeardownChardev(vm, chr) < 0)
0a7476
         VIR_WARN("Unable to remove chr device from /dev");
0a7476
 
0a7476
+    qemuDomainReleaseDeviceAddress(vm, &chr->info, NULL);
0a7476
+    qemuDomainChrRemove(vm->def, chr);
0a7476
+
0a7476
+    /* The caller does not emit the event, so we must do it here. Note
0a7476
+     * that the event should be reported only after all backend
0a7476
+     * teardown is completed.
0a7476
+     */
0a7476
     event = virDomainEventDeviceRemovedNewFromObj(vm, chr->info.alias);
0a7476
     virObjectEventStateQueue(driver->domainEventState, event);
0a7476
 
0a7476
-    qemuDomainReleaseDeviceAddress(vm, &chr->info, NULL);
0a7476
-    qemuDomainChrRemove(vm->def, chr);
0a7476
     virDomainChrDefFree(chr);
0a7476
     ret = 0;
0a7476
 
0a7476
@@ -4427,7 +4411,6 @@ qemuDomainRemoveRNGDevice(virQEMUDriverPtr driver,
0a7476
                           virDomainObjPtr vm,
0a7476
                           virDomainRNGDefPtr rng)
0a7476
 {
0a7476
-    virObjectEventPtr event;
0a7476
     char *charAlias = NULL;
0a7476
     char *objAlias = NULL;
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
@@ -4469,9 +4452,6 @@ qemuDomainRemoveRNGDevice(virQEMUDriverPtr driver,
0a7476
     if (qemuDomainNamespaceTeardownRNG(vm, rng) < 0)
0a7476
         VIR_WARN("Unable to remove RNG device from /dev");
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, rng->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if ((idx = virDomainRNGFind(vm->def, rng)) >= 0)
0a7476
         virDomainRNGRemove(vm->def, idx);
0a7476
     qemuDomainReleaseDeviceAddress(vm, &rng->info, NULL);
0a7476
@@ -4496,7 +4476,6 @@ qemuDomainRemoveShmemDevice(virQEMUDriverPtr driver,
0a7476
     char *charAlias = NULL;
0a7476
     char *memAlias = NULL;
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
-    virObjectEventPtr event = NULL;
0a7476
 
0a7476
     VIR_DEBUG("Removing shmem device %s from domain %p %s",
0a7476
               shmem->info.alias, vm, vm->def->name);
0a7476
@@ -4524,9 +4503,6 @@ qemuDomainRemoveShmemDevice(virQEMUDriverPtr driver,
0a7476
     if (rc < 0)
0a7476
         goto cleanup;
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, shmem->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if ((idx = virDomainShmemDefFind(vm->def, shmem)) >= 0)
0a7476
         virDomainShmemDefRemove(vm->def, idx);
0a7476
     qemuDomainReleaseDeviceAddress(vm, &shmem->info, NULL);
0a7476
@@ -4542,17 +4518,12 @@ qemuDomainRemoveShmemDevice(virQEMUDriverPtr driver,
0a7476
 
0a7476
 
0a7476
 static int
0a7476
-qemuDomainRemoveWatchdog(virQEMUDriverPtr driver,
0a7476
-                         virDomainObjPtr vm,
0a7476
+qemuDomainRemoveWatchdog(virDomainObjPtr vm,
0a7476
                          virDomainWatchdogDefPtr watchdog)
0a7476
 {
0a7476
-    virObjectEventPtr event = NULL;
0a7476
-
0a7476
     VIR_DEBUG("Removing watchdog %s from domain %p %s",
0a7476
               watchdog->info.alias, vm, vm->def->name);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, watchdog->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
     qemuDomainReleaseDeviceAddress(vm, &watchdog->info, NULL);
0a7476
     virDomainWatchdogDefFree(vm->def->watchdog);
0a7476
     vm->def->watchdog = NULL;
0a7476
@@ -4564,16 +4535,11 @@ static int
0a7476
 qemuDomainRemoveInputDevice(virDomainObjPtr vm,
0a7476
                             virDomainInputDefPtr dev)
0a7476
 {
0a7476
-    qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
-    virQEMUDriverPtr driver = priv->driver;
0a7476
-    virObjectEventPtr event = NULL;
0a7476
     size_t i;
0a7476
 
0a7476
     VIR_DEBUG("Removing input device %s from domain %p %s",
0a7476
               dev->info.alias, vm, vm->def->name);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, dev->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
     for (i = 0; i < vm->def->ninputs; i++) {
0a7476
         if (vm->def->inputs[i] == dev)
0a7476
             break;
0a7476
@@ -4598,15 +4564,9 @@ static int
0a7476
 qemuDomainRemoveVsockDevice(virDomainObjPtr vm,
0a7476
                             virDomainVsockDefPtr dev)
0a7476
 {
0a7476
-    qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
-    virQEMUDriverPtr driver = priv->driver;
0a7476
-    virObjectEventPtr event = NULL;
0a7476
-
0a7476
     VIR_DEBUG("Removing vsock device %s from domain %p %s",
0a7476
               dev->info.alias, vm, vm->def->name);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, dev->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
     qemuDomainReleaseDeviceAddress(vm, &dev->info, NULL);
0a7476
     virDomainVsockDefFree(vm->def->vsock);
0a7476
     vm->def->vsock = NULL;
0a7476
@@ -4620,7 +4580,6 @@ qemuDomainRemoveRedirdevDevice(virQEMUDriverPtr driver,
0a7476
                                virDomainRedirdevDefPtr dev)
0a7476
 {
0a7476
     qemuDomainObjPrivatePtr priv = vm->privateData;
0a7476
-    virObjectEventPtr event;
0a7476
     char *charAlias = NULL;
0a7476
     ssize_t idx;
0a7476
     int ret = -1;
0a7476
@@ -4645,9 +4604,6 @@ qemuDomainRemoveRedirdevDevice(virQEMUDriverPtr driver,
0a7476
 
0a7476
     virDomainAuditRedirdev(vm, dev, "detach", true);
0a7476
 
0a7476
-    event = virDomainEventDeviceRemovedNewFromObj(vm, dev->info.alias);
0a7476
-    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
-
0a7476
     if ((idx = virDomainRedirdevDefFind(vm->def, dev)) >= 0)
0a7476
         virDomainRedirdevDefRemove(vm->def, idx);
0a7476
     qemuDomainReleaseDeviceAddress(vm, &dev->info, NULL);
0a7476
@@ -4730,50 +4686,81 @@ qemuDomainRemoveDevice(virQEMUDriverPtr driver,
0a7476
                        virDomainObjPtr vm,
0a7476
                        virDomainDeviceDefPtr dev)
0a7476
 {
0a7476
+    virDomainDeviceInfoPtr info;
0a7476
+    virObjectEventPtr event;
0a7476
+    char *alias = NULL;
0a7476
     int ret = -1;
0a7476
+
0a7476
+    /*
0a7476
+     * save the alias to use when sending a DEVICE_REMOVED event after
0a7476
+     * all other teardown is complete
0a7476
+     */
0a7476
+    if ((info = virDomainDeviceGetInfo(dev)) &&
0a7476
+        VIR_STRDUP(alias, info->alias) < 0) {
0a7476
+        goto cleanup;;
0a7476
+    }
0a7476
+    info = NULL;
0a7476
+
0a7476
     switch ((virDomainDeviceType)dev->type) {
0a7476
+    case VIR_DOMAIN_DEVICE_CHR:
0a7476
+        /* We must return directly after calling
0a7476
+         * qemuDomainRemoveChrDevice because it is called directly
0a7476
+         * from other places, so it must be completely self-contained
0a7476
+         * and can't take advantage of any common code at the end of
0a7476
+         * qemuDomainRemoveDevice().
0a7476
+         */
0a7476
+        ret = qemuDomainRemoveChrDevice(driver, vm, dev->data.chr, true);
0a7476
+        goto cleanup;
0a7476
+
0a7476
+        /*
0a7476
+         * all of the following qemuDomainRemove*Device() functions
0a7476
+         * are (and must be) only called from this function, so any
0a7476
+         * code that is common to them all can be pulled out and put
0a7476
+         * into this function.
0a7476
+         */
0a7476
     case VIR_DOMAIN_DEVICE_DISK:
0a7476
-        ret = qemuDomainRemoveDiskDevice(driver, vm, dev->data.disk);
0a7476
+        if (qemuDomainRemoveDiskDevice(driver, vm, dev->data.disk) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
     case VIR_DOMAIN_DEVICE_CONTROLLER:
0a7476
-        ret = qemuDomainRemoveControllerDevice(driver, vm, dev->data.controller);
0a7476
+        if (qemuDomainRemoveControllerDevice(vm, dev->data.controller) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
     case VIR_DOMAIN_DEVICE_NET:
0a7476
-        ret = qemuDomainRemoveNetDevice(driver, vm, dev->data.net);
0a7476
+        if (qemuDomainRemoveNetDevice(driver, vm, dev->data.net) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
     case VIR_DOMAIN_DEVICE_HOSTDEV:
0a7476
-        ret = qemuDomainRemoveHostDevice(driver, vm, dev->data.hostdev);
0a7476
-        break;
0a7476
-
0a7476
-    case VIR_DOMAIN_DEVICE_CHR:
0a7476
-        ret = qemuDomainRemoveChrDevice(driver, vm, dev->data.chr, true);
0a7476
+        if (qemuDomainRemoveHostDevice(driver, vm, dev->data.hostdev) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
     case VIR_DOMAIN_DEVICE_RNG:
0a7476
-        ret = qemuDomainRemoveRNGDevice(driver, vm, dev->data.rng);
0a7476
+        if (qemuDomainRemoveRNGDevice(driver, vm, dev->data.rng) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_MEMORY:
0a7476
-        ret = qemuDomainRemoveMemoryDevice(driver, vm, dev->data.memory);
0a7476
+        if (qemuDomainRemoveMemoryDevice(driver, vm, dev->data.memory) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_SHMEM:
0a7476
-        ret = qemuDomainRemoveShmemDevice(driver, vm, dev->data.shmem);
0a7476
+        if (qemuDomainRemoveShmemDevice(driver, vm, dev->data.shmem) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_INPUT:
0a7476
-        ret = qemuDomainRemoveInputDevice(vm, dev->data.input);
0a7476
+        if (qemuDomainRemoveInputDevice(vm, dev->data.input) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_REDIRDEV:
0a7476
-        ret = qemuDomainRemoveRedirdevDevice(driver, vm, dev->data.redirdev);
0a7476
+        if (qemuDomainRemoveRedirdevDevice(driver, vm, dev->data.redirdev) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_WATCHDOG:
0a7476
-        ret = qemuDomainRemoveWatchdog(driver, vm, dev->data.watchdog);
0a7476
+        if (qemuDomainRemoveWatchdog(vm, dev->data.watchdog) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
-
0a7476
     case VIR_DOMAIN_DEVICE_VSOCK:
0a7476
-        ret = qemuDomainRemoveVsockDevice(vm, dev->data.vsock);
0a7476
+        if (qemuDomainRemoveVsockDevice(vm, dev->data.vsock) < 0)
0a7476
+            goto cleanup;
0a7476
         break;
0a7476
 
0a7476
     case VIR_DOMAIN_DEVICE_NONE:
0a7476
@@ -4793,8 +4780,16 @@ qemuDomainRemoveDevice(virQEMUDriverPtr driver,
0a7476
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
0a7476
                        _("don't know how to remove a %s device"),
0a7476
                        virDomainDeviceTypeToString(dev->type));
0a7476
-        break;
0a7476
+        goto cleanup;
0a7476
     }
0a7476
+
0a7476
+    event = virDomainEventDeviceRemovedNewFromObj(vm, alias);
0a7476
+    virObjectEventStateQueue(driver->domainEventState, event);
0a7476
+
0a7476
+    ret = 0;
0a7476
+
0a7476
+ cleanup:
0a7476
+    VIR_FREE(alias);
0a7476
     return ret;
0a7476
 }
0a7476
 
0a7476
-- 
0a7476
2.21.0
0a7476