Blob Blame History Raw
From d16f6dc87b5122c6d14ffb52a7276aef8f14049c Mon Sep 17 00:00:00 2001
Message-Id: <d16f6dc87b5122c6d14ffb52a7276aef8f14049c@dist-git>
From: Laine Stump <laine@laine.org>
Date: Thu, 11 Apr 2019 15:14:51 -0400
Subject: [PATCH] qemu_hotplug: consolidate all common detach code in
 qemuDomainDetachDeviceLive

Now that all the qemuDomainDetachPrep*() functions look nearly
identical at the end, we can put one copy of that identical code in
qemuDomainDetachDeviceLive() at the point after the individual prep
functions have been called, and remove the duplicated code from all
the prep functions. The code to locate the target "detach" device
based on the "match" device remains, as do all device-type-specific
validations.

Unfortunately there are a few things going on at once in this patch,
which makes it a bit more difficult to follow than the others; it was
just impossible to do the changes in stages and still have a
buildable/testable tree at each step.

The other changes of note:

* The individual prep functions no longer need their driver or async
  args, so those are removed, as are the local "ret" variables, since
  in all cases the functions just directly return -1 or 0.

* Some of the prep functions were checking for a valid alias and/or
  for attempts to detach a multifunction PCI device, but not all. In
  fact, both checks are valid (or at least harmless) for *all* device
  types, so they are removed from the prep functions, and done a
  single time in the common function.

  (any attempts to *create* an alias when there isn't one has been
  removed, since that is doomed to failure anyway; the only way the
  device wouldn't have an alias is if 1) the domain was created by
  calling virsh qemu-attach to attach an existing qemu process to
  libvirt, and 2) the qemu command that started said process used "old
  style" arguments for creating devices that didn't have any device
  ids. Even if we constructed a device id for one of these devices,
  qemu wouldn't recognize it in the device_del command anyway, so we
  may as well fail earlier with "device missing alias" rather than
  failing later with "couldn't delete device net0".)

* Only one type of device has shutdown code that must not be called
  until after *all* validation of the device is done (including
  checking for multifunction PCI and valid alias, which is done in the
  toplevel common code). For this reason, the Net function has been
  split in two, with the 2nd half (qemuDomainDetachShutdownNet())
  called from the common function, right before sending the delete
  command to qemu.

Signed-off-by: Laine Stump <laine@laine.org>
ACKed-by: Peter Krempa <pkrempa@redhat.com>
(cherry picked from commit dd60bd62d3ad60b564168f56b05f4c9354af4bd3)

Partially-Resolves: https://bugzilla.redhat.com/1658198
Signed-off-by: Laine Stump <laine@redhat.com>
Signed-off-by: Laine Stump <laine@laine.org>
Message-Id: <20190411191453.24055-40-laine@redhat.com>
Acked-by: Michal Privoznik <mprivozn@redhat.com>
---
 src/qemu/qemu_hotplug.c | 480 ++++++++++++----------------------------
 1 file changed, 142 insertions(+), 338 deletions(-)

diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 390fc36cf6..27d09d173b 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -4650,7 +4650,7 @@ qemuDomainRemoveRedirdevDevice(virQEMUDriverPtr driver,
 }
 
 
-static void ATTRIBUTE_UNUSED
+static void
 qemuDomainRemoveAuditDevice(virDomainObjPtr vm,
                             virDomainDeviceDefPtr detach,
                             bool success)
@@ -4902,15 +4902,12 @@ qemuFindDisk(virDomainDefPtr def, const char *dst)
 }
 
 static int
-qemuDomainDetachPrepDisk(virQEMUDriverPtr driver,
-                         virDomainObjPtr vm,
+qemuDomainDetachPrepDisk(virDomainObjPtr vm,
                          virDomainDiskDefPtr match,
-                         virDomainDiskDefPtr *detach,
-                         bool async)
+                         virDomainDiskDefPtr *detach)
 {
     virDomainDiskDefPtr disk;
     int idx;
-    int ret = -1;
 
     if ((idx = qemuFindDisk(vm->def, match->dst)) < 0) {
         virReportError(VIR_ERR_OPERATION_FAILED,
@@ -4962,34 +4959,7 @@ qemuDomainDetachPrepDisk(virQEMUDriverPtr driver,
     if (qemuDomainDiskBlockJobIsActive(disk))
         return -1;
 
-    if (disk->bus == VIR_DOMAIN_DISK_BUS_VIRTIO &&
-        qemuIsMultiFunctionDevice(vm->def, &disk->info)) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("cannot hot unplug multifunction PCI device: %s"),
-                       disk->dst);
-        return -1;
-    }
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &disk->info);
-
-    if (qemuDomainDeleteDevice(vm, disk->info.alias) < 0) {
-        if (virDomainObjIsActive(vm))
-            virDomainAuditDisk(vm, disk->src, NULL, "detach", false);
-        goto cleanup;
-    }
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveDiskDevice(driver, vm, disk);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
@@ -5054,13 +5024,11 @@ static bool qemuDomainControllerIsBusy(virDomainObjPtr vm,
 }
 
 static int
-qemuDomainDetachPrepController(virQEMUDriverPtr driver,
-                               virDomainObjPtr vm,
+qemuDomainDetachPrepController(virDomainObjPtr vm,
                                virDomainControllerDefPtr match,
-                               virDomainControllerDefPtr *detach,
-                               bool async)
+                               virDomainControllerDefPtr *detach)
 {
-    int idx, ret = -1;
+    int idx;
     virDomainControllerDefPtr controller = NULL;
 
     if (match->type != VIR_DOMAIN_CONTROLLER_TYPE_SCSI) {
@@ -5075,50 +5043,26 @@ qemuDomainDetachPrepController(virQEMUDriverPtr driver,
                        _("controller %s:%d not found"),
                        virDomainControllerTypeToString(match->type),
                        match->idx);
-        goto cleanup;
+        return -1;
     }
 
     *detach = controller = vm->def->controllers[idx];
 
-    if (qemuIsMultiFunctionDevice(vm->def, &controller->info)) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       "%s", _("cannot hot unplug multifunction PCI device"));
-        goto cleanup;
-    }
-
     if (qemuDomainControllerIsBusy(vm, controller)) {
         virReportError(VIR_ERR_OPERATION_FAILED, "%s",
                        _("device cannot be detached: device is busy"));
-        goto cleanup;
+        return -1;
     }
 
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &controller->info);
-
-    if (qemuDomainDeleteDevice(vm, controller->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveControllerDevice(driver, vm, controller);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 /* search for a hostdev matching dev and detach it */
 static int
-qemuDomainDetachPrepHostdev(virQEMUDriverPtr driver,
-                            virDomainObjPtr vm,
+qemuDomainDetachPrepHostdev(virDomainObjPtr vm,
                             virDomainHostdevDefPtr match,
-                            virDomainHostdevDefPtr *detach,
-                            bool async)
+                            virDomainHostdevDefPtr *detach)
 {
     virDomainHostdevSubsysPtr subsys = &match->source.subsys;
     virDomainHostdevSubsysUSBPtr usbsrc = &subsys->u.usb;
@@ -5127,7 +5071,6 @@ qemuDomainDetachPrepHostdev(virQEMUDriverPtr driver,
     virDomainHostdevSubsysMediatedDevPtr mdevsrc = &subsys->u.mdev;
     virDomainHostdevDefPtr hostdev = NULL;
     int idx;
-    int ret = -1;
 
     if (match->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
@@ -5190,54 +5133,15 @@ qemuDomainDetachPrepHostdev(virQEMUDriverPtr driver,
         return -1;
     }
 
-    if (qemuIsMultiFunctionDevice(vm->def, hostdev->info)) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("cannot hot unplug multifunction PCI device with guest address: "
-                         "%.4x:%.2x:%.2x.%.1x"),
-                       hostdev->info->addr.pci.domain, hostdev->info->addr.pci.bus,
-                       hostdev->info->addr.pci.slot, hostdev->info->addr.pci.function);
-        return -1;
-    }
-
-    if (!hostdev->info->alias) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       "%s", _("device cannot be detached without a device alias"));
-        return -1;
-    }
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, hostdev->info);
-
-    if (qemuDomainDeleteDevice(vm, hostdev->info->alias) < 0) {
-        if (virDomainObjIsActive(vm))
-            virDomainAuditHostdev(vm, hostdev, "detach", false);
-        goto cleanup;
-    }
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveHostDevice(driver, vm, hostdev);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-
-    return ret;
-
+    return 0;
 }
 
 
 static int
-qemuDomainDetachPrepShmem(virQEMUDriverPtr driver,
-                          virDomainObjPtr vm,
+qemuDomainDetachPrepShmem(virDomainObjPtr vm,
                           virDomainShmemDefPtr match,
-                          virDomainShmemDefPtr *detach,
-                          bool async)
+                          virDomainShmemDefPtr *detach)
 {
-    int ret = -1;
     ssize_t idx = -1;
     virDomainShmemDefPtr shmem = NULL;
 
@@ -5265,34 +5169,15 @@ qemuDomainDetachPrepShmem(virQEMUDriverPtr driver,
         return -1;
     }
 
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &shmem->info);
-
-    if (qemuDomainDeleteDevice(vm, shmem->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveShmemDevice(driver, vm, shmem);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
-qemuDomainDetachPrepWatchdog(virQEMUDriverPtr driver,
-                             virDomainObjPtr vm,
+qemuDomainDetachPrepWatchdog(virDomainObjPtr vm,
                              virDomainWatchdogDefPtr match,
-                             virDomainWatchdogDefPtr *detach,
-                             bool async)
+                             virDomainWatchdogDefPtr *detach)
 {
-    int ret = -1;
     virDomainWatchdogDefPtr watchdog;
 
     *detach = watchdog = vm->def->watchdog;
@@ -5323,34 +5208,15 @@ qemuDomainDetachPrepWatchdog(virQEMUDriverPtr driver,
         return -1;
     }
 
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &watchdog->info);
-
-    if (qemuDomainDeleteDevice(vm, watchdog->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveWatchdog(driver, vm, watchdog);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
-qemuDomainDetachPrepRedirdev(virQEMUDriverPtr driver,
-                             virDomainObjPtr vm,
+qemuDomainDetachPrepRedirdev(virDomainObjPtr vm,
                              virDomainRedirdevDefPtr match,
-                             virDomainRedirdevDefPtr *detach,
-                             bool async)
+                             virDomainRedirdevDefPtr *detach)
 {
-    int ret = -1;
     virDomainRedirdevDefPtr redirdev;
     ssize_t idx;
 
@@ -5362,59 +5228,41 @@ qemuDomainDetachPrepRedirdev(virQEMUDriverPtr driver,
 
     *detach = redirdev = vm->def->redirdevs[idx];
 
-    if (!redirdev->info.alias) {
-        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                       _("alias not set for redirdev device"));
-        return -1;
-    }
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &redirdev->info);
-
-    if (qemuDomainDeleteDevice(vm, redirdev->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveRedirdevDevice(driver, vm, redirdev);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
-qemuDomainDetachPrepNet(virQEMUDriverPtr driver,
-                        virDomainObjPtr vm,
+qemuDomainDetachPrepNet(virDomainObjPtr vm,
                         virDomainNetDefPtr match,
-                        virDomainNetDefPtr *detach,
-                        bool async)
+                        virDomainNetDefPtr *detach)
 {
-    int detachidx, ret = -1;
+    int detachidx;
     virDomainNetDefPtr net = NULL;
 
     if ((detachidx = virDomainNetFindIdx(vm->def, match)) < 0)
-        goto cleanup;
+        return -1;
 
     *detach = net = vm->def->nets[detachidx];
 
-    if (qemuIsMultiFunctionDevice(vm->def, &net->info)) {
-        virReportError(VIR_ERR_OPERATION_FAILED,
-                       _("cannot hot unplug multifunction PCI device: %s"),
-                       net->ifname);
-        goto cleanup;
-    }
+    return 0;
+}
 
-    if (!net->info.alias) {
-        if (qemuAssignDeviceNetAlias(vm->def, net, -1) < 0)
-            goto cleanup;
-    }
 
+static void
+qemuDomainDetachShutdownNet(virDomainNetDefPtr net)
+{
+/*
+ * These operations are in a separate function from
+ * qemuDomainDetachPrepNet() because they can't be done until after
+ * we've validated that this device really can be removed - in
+ * particular we need to check for multifunction PCI devices and
+ * presence of a device alias, which isn't done until *after* the
+ * return from qemuDomainDetachPrepNet(). Since we've already passed
+ * the "point of no return", we ignore any errors, and trudge ahead
+ * with shutting down and detaching the device even if there is an
+ * error in one of these functions.
+ */
     if (virDomainNetGetActualBandwidth(net) &&
         virNetDevSupportBandwidth(virDomainNetGetActualType(net)) &&
         virNetDevBandwidthClear(net->ifname) < 0)
@@ -5426,32 +5274,6 @@ qemuDomainDetachPrepNet(virQEMUDriverPtr driver,
      * the parent device offline)
      */
     ignore_value(qemuInterfaceStopDevice(net));
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &net->info);
-
-    if (qemuDomainDeleteDevice(vm, net->info.alias) < 0) {
-        if (virDomainObjIsActive(vm)) {
-            /* the audit message has a different format for hostdev network devices */
-            if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_HOSTDEV)
-                virDomainAuditHostdev(vm, virDomainNetGetActualHostdev(net), "detach", false);
-            else
-                virDomainAuditNet(vm, net, NULL, "detach", false);
-        }
-        goto cleanup;
-    }
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveNetDevice(driver, vm, net);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
 }
 
 
@@ -5514,15 +5336,12 @@ qemuDomainDetachDeviceChr(virQEMUDriverPtr driver,
 
 
 static int
-qemuDomainDetachPrepRNG(virQEMUDriverPtr driver,
-                        virDomainObjPtr vm,
+qemuDomainDetachPrepRNG(virDomainObjPtr vm,
                         virDomainRNGDefPtr match,
-                        virDomainRNGDefPtr *detach,
-                        bool async)
+                        virDomainRNGDefPtr *detach)
 {
     ssize_t idx;
     virDomainRNGDefPtr rng;
-    int ret = -1;
 
     if ((idx = virDomainRNGFind(vm->def, match)) < 0) {
         virReportError(VIR_ERR_DEVICE_MISSING,
@@ -5534,42 +5353,17 @@ qemuDomainDetachPrepRNG(virQEMUDriverPtr driver,
 
     *detach = rng = vm->def->rngs[idx];
 
-    if (!rng->info.alias) {
-        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                       _("alias not set for RNG device"));
-        return -1;
-    }
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &rng->info);
-
-    if (qemuDomainDeleteDevice(vm, rng->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveRNGDevice(driver, vm, rng);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
-qemuDomainDetachPrepMemory(virQEMUDriverPtr driver,
-                           virDomainObjPtr vm,
+qemuDomainDetachPrepMemory(virDomainObjPtr vm,
                            virDomainMemoryDefPtr match,
-                           virDomainMemoryDefPtr *detach,
-                           bool async)
+                           virDomainMemoryDefPtr *detach)
 {
     virDomainMemoryDefPtr mem;
     int idx;
-    int ret = -1;
 
     qemuDomainMemoryDeviceAlignSize(vm->def, match);
 
@@ -5583,40 +5377,16 @@ qemuDomainDetachPrepMemory(virQEMUDriverPtr driver,
 
     *detach = mem = vm->def->mems[idx];
 
-    if (!mem->info.alias) {
-        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                       _("alias for the memory device was not found"));
-        return -1;
-    }
-
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &mem->info);
-
-    if (qemuDomainDeleteDevice(vm, mem->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveMemoryDevice(driver, vm, mem);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
 qemuDomainDetachPrepInput(virDomainObjPtr vm,
                           virDomainInputDefPtr match,
-                          virDomainInputDefPtr *detach,
-                          bool async)
+                          virDomainInputDefPtr *detach)
 {
     virDomainInputDefPtr input;
-    int ret = -1;
     int idx;
 
     if ((idx = virDomainInputDefFind(vm->def, match)) < 0) {
@@ -5641,35 +5411,16 @@ qemuDomainDetachPrepInput(virDomainObjPtr vm,
         break;
     }
 
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &input->info);
-
-    if (qemuDomainDeleteDevice(vm, input->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveInputDevice(vm, input);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
 static int
 qemuDomainDetachPrepVsock(virDomainObjPtr vm,
                           virDomainVsockDefPtr match,
-                          virDomainVsockDefPtr *detach,
-                          bool async)
+                          virDomainVsockDefPtr *detach)
 {
     virDomainVsockDefPtr vsock;
-    int ret = -1;
-
 
     *detach = vsock = vm->def->vsock;
     if (!vsock ||
@@ -5679,23 +5430,7 @@ qemuDomainDetachPrepVsock(virDomainObjPtr vm,
         return -1;
     }
 
-    if (!async)
-        qemuDomainMarkDeviceForRemoval(vm, &vsock->info);
-
-    if (qemuDomainDeleteDevice(vm, vsock->info.alias) < 0)
-        goto cleanup;
-
-    if (async) {
-        ret = 0;
-    } else {
-        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
-            ret = qemuDomainRemoveVsockDevice(vm, vsock);
-    }
-
- cleanup:
-    if (!async)
-        qemuDomainResetDeviceRemoval(vm);
-    return ret;
+    return 0;
 }
 
 
@@ -5730,6 +5465,7 @@ qemuDomainDetachDeviceLive(virDomainObjPtr vm,
                            bool async)
 {
     virDomainDeviceDef detach = { .type = match->type };
+    virDomainDeviceInfoPtr info = NULL;
     int ret = -1;
 
     switch ((virDomainDeviceType)match->type) {
@@ -5752,68 +5488,68 @@ qemuDomainDetachDeviceLive(virDomainObjPtr vm,
          * assure it is okay to detach the device.
          */
     case VIR_DOMAIN_DEVICE_DISK:
-        if (qemuDomainDetachPrepDisk(driver, vm, match->data.disk,
-                                     &detach.data.disk, async) < 0) {
+        if (qemuDomainDetachPrepDisk(vm, match->data.disk,
+                                     &detach.data.disk) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_CONTROLLER:
-        if (qemuDomainDetachPrepController(driver, vm, match->data.controller,
-                                           &detach.data.controller, async) < 0) {
+        if (qemuDomainDetachPrepController(vm, match->data.controller,
+                                           &detach.data.controller) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_NET:
-        if (qemuDomainDetachPrepNet(driver, vm, match->data.net,
-                                    &detach.data.net, async) < 0) {
+        if (qemuDomainDetachPrepNet(vm, match->data.net,
+                                    &detach.data.net) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_HOSTDEV:
-        if (qemuDomainDetachPrepHostdev(driver, vm, match->data.hostdev,
-                                        &detach.data.hostdev, async) < 0) {
+        if (qemuDomainDetachPrepHostdev(vm, match->data.hostdev,
+                                        &detach.data.hostdev) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_RNG:
-        if (qemuDomainDetachPrepRNG(driver, vm, match->data.rng,
-                                    &detach.data.rng, async) < 0) {
+        if (qemuDomainDetachPrepRNG(vm, match->data.rng,
+                                    &detach.data.rng) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_MEMORY:
-        if (qemuDomainDetachPrepMemory(driver, vm, match->data.memory,
-                                       &detach.data.memory, async) < 0) {
+        if (qemuDomainDetachPrepMemory(vm, match->data.memory,
+                                       &detach.data.memory) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_SHMEM:
-        if (qemuDomainDetachPrepShmem(driver, vm, match->data.shmem,
-                                      &detach.data.shmem, async) < 0) {
+        if (qemuDomainDetachPrepShmem(vm, match->data.shmem,
+                                      &detach.data.shmem) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_WATCHDOG:
-        if (qemuDomainDetachPrepWatchdog(driver, vm, match->data.watchdog,
-                                         &detach.data.watchdog, async) < 0) {
+        if (qemuDomainDetachPrepWatchdog(vm, match->data.watchdog,
+                                         &detach.data.watchdog) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_INPUT:
         if (qemuDomainDetachPrepInput(vm, match->data.input,
-                                      &detach.data.input, async) < 0) {
+                                      &detach.data.input) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_REDIRDEV:
-        if (qemuDomainDetachPrepRedirdev(driver, vm, match->data.redirdev,
-                                         &detach.data.redirdev, async) < 0) {
+        if (qemuDomainDetachPrepRedirdev(vm, match->data.redirdev,
+                                         &detach.data.redirdev) < 0) {
             return -1;
         }
         break;
     case VIR_DOMAIN_DEVICE_VSOCK:
         if (qemuDomainDetachPrepVsock(vm, match->data.vsock,
-                                      &detach.data.vsock, async) < 0) {
+                                      &detach.data.vsock) < 0) {
             return -1;
         }
         break;
@@ -5837,7 +5573,75 @@ qemuDomainDetachDeviceLive(virDomainObjPtr vm,
         return -1;
     }
 
-    ret = 0;
+    /* "detach" now points to the actual device we want to detach */
+
+    if (!(info = virDomainDeviceGetInfo(&detach))) {
+        /*
+         * This should never happen, since all of the device types in
+         * the switch cases that end with a "break" instead of a
+         * return have a virDeviceInfo in them.
+         */
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("device of type '%s' has no device info"),
+                       virDomainDeviceTypeToString(detach.type));
+        return -1;
+    }
+
+
+    /* Make generic validation checks common to all device types */
+
+    if (!info->alias) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Cannot detach %s device with no alias"),
+                       virDomainDeviceTypeToString(detach.type));
+        return -1;
+    }
+
+    if (qemuIsMultiFunctionDevice(vm->def, info)) {
+        virReportError(VIR_ERR_OPERATION_FAILED,
+                       _("cannot hot unplug %s device with multifunction PCI guest address: "
+                         "%.4x:%.2x:%.2x.%.1x"),
+                       virDomainDeviceTypeToString(detach.type),
+                       info->addr.pci.domain, info->addr.pci.bus,
+                       info->addr.pci.slot, info->addr.pci.function);
+        return -1;
+    }
+
+
+    /*
+     * Do any device-specific shutdown that should be
+     * done after all validation checks, but before issuing the qemu
+     * command to delete the device. For now, the only type of device
+     * that has such shutdown needs is the net device.
+     */
+    if (detach.type == VIR_DOMAIN_DEVICE_NET)
+        qemuDomainDetachShutdownNet(detach.data.net);
+
+
+    /*
+     * Issue the qemu monitor command to delete the device (based on
+     * its alias), and optionally wait a short time in case the
+     * DEVICE_DELETED event arrives from qemu right away.
+     */
+    if (!async)
+        qemuDomainMarkDeviceForRemoval(vm, info);
+
+    if (qemuDomainDeleteDevice(vm, info->alias) < 0) {
+        if (virDomainObjIsActive(vm))
+            qemuDomainRemoveAuditDevice(vm, &detach, false);
+        goto cleanup;
+    }
+
+    if (async) {
+        ret = 0;
+    } else {
+        if ((ret = qemuDomainWaitForDeviceRemoval(vm)) == 1)
+            ret = qemuDomainRemoveDevice(driver, vm, &detach);
+    }
+
+ cleanup:
+    if (!async)
+        qemuDomainResetDeviceRemoval(vm);
 
     return ret;
 }
-- 
2.21.0