[PATCH 5/5] qemu: Extract snapshot related code to a separate file

Peter Krempa pkrempa at redhat.com
Thu Aug 6 09:55:16 UTC 2020


We've dumped all the snapshot helpers and related code into
qemu_driver.c. It accounted for ~10% of overal size of qemu_driver.c.

Separate the code to qemu_snapshot.c/h.

Signed-off-by: Peter Krempa <pkrempa at redhat.com>
---
 po/POTFILES.in           |    1 +
 src/qemu/meson.build     |    1 +
 src/qemu/qemu_driver.c   | 2487 +++-----------------------------------
 src/qemu/qemu_snapshot.c | 2266 ++++++++++++++++++++++++++++++++++
 src/qemu/qemu_snapshot.h |   55 +
 5 files changed, 2475 insertions(+), 2335 deletions(-)
 create mode 100644 src/qemu/qemu_snapshot.c
 create mode 100644 src/qemu/qemu_snapshot.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6f47371b01..3d6c20c55f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -172,6 +172,7 @@
 @SRCDIR at src/qemu/qemu_qapi.c
 @SRCDIR at src/qemu/qemu_saveimage.c
 @SRCDIR at src/qemu/qemu_slirp.c
+ at SRCDIR@src/qemu/qemu_snapshot.c
 @SRCDIR at src/qemu/qemu_tpm.c
 @SRCDIR at src/qemu/qemu_validate.c
 @SRCDIR at src/qemu/qemu_vhost_user.c
diff --git a/src/qemu/meson.build b/src/qemu/meson.build
index 7d5249978a..85d020465f 100644
--- a/src/qemu/meson.build
+++ b/src/qemu/meson.build
@@ -31,6 +31,7 @@ qemu_driver_sources = [
   'qemu_qapi.c',
   'qemu_saveimage.c',
   'qemu_security.c',
+  'qemu_snapshot.c',
   'qemu_slirp.c',
   'qemu_tpm.c',
   'qemu_validate.c',
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index a6b8c79168..bc2879dee4 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -52,6 +52,7 @@
 #include "qemu_backup.h"
 #include "qemu_namespace.h"
 #include "qemu_saveimage.h"
+#include "qemu_snapshot.h"

 #include "virerror.h"
 #include "virlog.h"
@@ -171,29 +172,6 @@ qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot)
 }


-/* Looks up snapshot object from VM and name */
-static virDomainMomentObjPtr
-qemuSnapObjFromName(virDomainObjPtr vm,
-                    const char *name)
-{
-    virDomainMomentObjPtr snap = NULL;
-    snap = virDomainSnapshotFindByName(vm->snapshots, name);
-    if (!snap)
-        virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
-                       _("no domain snapshot with matching name '%s'"),
-                       name);
-
-    return snap;
-}
-
-
-/* Looks up snapshot object from VM and snapshotPtr */
-static virDomainMomentObjPtr
-qemuSnapObjFromSnapshot(virDomainObjPtr vm,
-                        virDomainSnapshotPtr snapshot)
-{
-    return qemuSnapObjFromName(vm, snapshot->name);
-}


 static int
@@ -2218,20 +2196,6 @@ qemuDomainReset(virDomainPtr dom, unsigned int flags)
 }


-/* Count how many snapshots in a set are external snapshots.  */
-static int
-qemuDomainSnapshotCountExternal(void *payload,
-                                const void *name G_GNUC_UNUSED,
-                                void *data)
-{
-    virDomainMomentObjPtr snap = payload;
-    int *count = data;
-
-    if (virDomainSnapshotIsExternal(snap))
-        (*count)++;
-    return 0;
-}
-
 static int
 qemuDomainDestroyFlags(virDomainPtr dom,
                        unsigned int flags)
@@ -13353,1820 +13317,230 @@ qemuDomainMigrateStartPostCopy(virDomainPtr dom,
 }


-/* Return -1 if request is not sent to agent due to misconfig, -2 if request
- * is sent but failed, and number of frozen filesystems on success. If -2 is
- * returned, FSThaw should be called revert the quiesced status. */
-static int
-qemuDomainSnapshotFSFreeze(virDomainObjPtr vm,
-                           const char **mountpoints,
-                           unsigned int nmountpoints)
+static virDomainSnapshotPtr
+qemuDomainSnapshotCreateXML(virDomainPtr domain,
+                            const char *xmlDesc,
+                            unsigned int flags)
 {
-    qemuAgentPtr agent;
-    int frozen;
+    virDomainObjPtr vm = NULL;
+    virDomainSnapshotPtr snapshot = NULL;

-    if (!qemuDomainAgentAvailable(vm, true))
-        return -1;
+    if (!(vm = qemuDomainObjFromDomain(domain)))
+        goto cleanup;

-    agent = qemuDomainObjEnterAgent(vm);
-    frozen = qemuAgentFSFreeze(agent, mountpoints, nmountpoints);
-    qemuDomainObjExitAgent(vm, agent);
-    return frozen < 0 ? -2 : frozen;
+    if (virDomainSnapshotCreateXMLEnsureACL(domain->conn, vm->def, flags) < 0)
+        goto cleanup;
+
+    snapshot = qemuSnapshotCreateXML(domain, vm, xmlDesc, flags);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return snapshot;
 }


-/* Return -1 on error, otherwise number of thawed filesystems. */
 static int
-qemuDomainSnapshotFSThaw(virDomainObjPtr vm,
-                         bool report)
+qemuDomainSnapshotListNames(virDomainPtr domain,
+                            char **names,
+                            int nameslen,
+                            unsigned int flags)
 {
-    qemuAgentPtr agent;
-    int thawed;
-    virErrorPtr err = NULL;
+    virDomainObjPtr vm = NULL;
+    int n = -1;
+
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);

-    if (!qemuDomainAgentAvailable(vm, report))
+    if (!(vm = qemuDomainObjFromDomain(domain)))
         return -1;

-    agent = qemuDomainObjEnterAgent(vm);
-    if (!report)
-        virErrorPreserveLast(&err);
-    thawed = qemuAgentFSThaw(agent);
-    qemuDomainObjExitAgent(vm, agent);
+    if (virDomainSnapshotListNamesEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;

-    virErrorRestore(&err);
+    n = virDomainSnapshotObjListGetNames(vm->snapshots, NULL, names, nameslen,
+                                         flags);

-    return thawed;
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return n;
 }


-/* The domain is expected to be locked and inactive. */
 static int
-qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         virDomainMomentObjPtr snap)
+qemuDomainSnapshotNum(virDomainPtr domain,
+                      unsigned int flags)
 {
-    return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false);
-}
-
+    virDomainObjPtr vm = NULL;
+    int n = -1;

-/* The domain is expected to be locked and inactive. */
-static int
-qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         virDomainMomentObjPtr snap,
-                                         bool reuse)
-{
-    size_t i;
-    virDomainSnapshotDiskDefPtr snapdisk;
-    virDomainDiskDefPtr defdisk;
-    virCommandPtr cmd = NULL;
-    const char *qemuImgPath;
-    virBitmapPtr created = NULL;
-    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
-    int ret = -1;
-    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
-    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);

-    if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
-        goto cleanup;
+    if (!(vm = qemuDomainObjFromDomain(domain)))
+        return -1;

-    if (!(created = virBitmapNew(snapdef->ndisks)))
+    if (virDomainSnapshotNumEnsureACL(domain->conn, vm->def) < 0)
         goto cleanup;

-    /* If reuse is true, then qemuDomainSnapshotPrepare already
-     * ensured that the new files exist, and it was up to the user to
-     * create them correctly.  */
-    for (i = 0; i < snapdef->ndisks && !reuse; i++) {
-        snapdisk = &(snapdef->disks[i]);
-        defdisk = snapdef->parent.dom->disks[snapdisk->idx];
-        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
-            continue;
-
-        if (!snapdisk->src->format)
-            snapdisk->src->format = VIR_STORAGE_FILE_QCOW2;
-
-        if (qemuDomainStorageSourceValidateDepth(defdisk->src, 1, defdisk->dst) < 0)
-            goto cleanup;
+    n = virDomainSnapshotObjListNum(vm->snapshots, NULL, flags);

-        /* creates cmd line args: qemu-img create -f qcow2 -o */
-        if (!(cmd = virCommandNewArgList(qemuImgPath,
-                                         "create",
-                                         "-f",
-                                         virStorageFileFormatTypeToString(snapdisk->src->format),
-                                         "-o",
-                                         NULL)))
-            goto cleanup;
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return n;
+}

-        /* adds cmd line arg: backing_fmt=format,backing_file=/path/to/backing/file */
-        virBufferAsprintf(&buf, "backing_fmt=%s,backing_file=",
-                          virStorageFileFormatTypeToString(defdisk->src->format));
-        virQEMUBuildBufferEscapeComma(&buf, defdisk->src->path);
-        virCommandAddArgBuffer(cmd, &buf);

-        /* adds cmd line args: /path/to/target/file */
-        virQEMUBuildBufferEscapeComma(&buf, snapdisk->src->path);
-        virCommandAddArgBuffer(cmd, &buf);
+static int
+qemuDomainListAllSnapshots(virDomainPtr domain,
+                           virDomainSnapshotPtr **snaps,
+                           unsigned int flags)
+{
+    virDomainObjPtr vm = NULL;
+    int n = -1;

-        /* If the target does not exist, we're going to create it possibly */
-        if (!virFileExists(snapdisk->src->path))
-            ignore_value(virBitmapSetBit(created, i));
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);

-        if (virCommandRun(cmd, NULL) < 0)
-            goto cleanup;
+    if (!(vm = qemuDomainObjFromDomain(domain)))
+        return -1;

-        virCommandFree(cmd);
-        cmd = NULL;
-    }
+    if (virDomainListAllSnapshotsEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;

-    /* update disk definitions */
-    for (i = 0; i < snapdef->ndisks; i++) {
-        g_autoptr(virStorageSource) newsrc = NULL;
+    n = virDomainListSnapshots(vm->snapshots, NULL, domain, snaps, flags);

-        snapdisk = &(snapdef->disks[i]);
-        defdisk = vm->def->disks[snapdisk->idx];
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return n;
+}

-        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
-            continue;

-        if (!(newsrc = virStorageSourceCopy(snapdisk->src, false)))
-            goto cleanup;
+static int
+qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot,
+                                    char **names,
+                                    int nameslen,
+                                    unsigned int flags)
+{
+    virDomainObjPtr vm = NULL;
+    virDomainMomentObjPtr snap = NULL;
+    int n = -1;

-        if (virStorageSourceInitChainElement(newsrc, defdisk->src, false) < 0)
-            goto cleanup;
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);

-        if (!reuse &&
-            virStorageSourceHasBacking(defdisk->src)) {
-            defdisk->src->readonly = true;
-            newsrc->backingStore = g_steal_pointer(&defdisk->src);
-        } else {
-            virObjectUnref(defdisk->src);
-        }
+    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
+        return -1;

-        defdisk->src = g_steal_pointer(&newsrc);
-    }
+    if (virDomainSnapshotListChildrenNamesEnsureACL(snapshot->domain->conn, vm->def) < 0)
+        goto cleanup;

-    if (virDomainDefSave(vm->def, driver->xmlopt, cfg->configDir) < 0)
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
         goto cleanup;

-    ret = 0;
+    n = virDomainSnapshotObjListGetNames(vm->snapshots, snap, names, nameslen,
+                                         flags);

  cleanup:
-    virCommandFree(cmd);
-
-    /* unlink images if creation has failed */
-    if (ret < 0 && created) {
-        ssize_t bit = -1;
-        while ((bit = virBitmapNextSetBit(created, bit)) >= 0) {
-            snapdisk = &(snapdef->disks[bit]);
-            if (unlink(snapdisk->src->path) < 0)
-                VIR_WARN("Failed to remove snapshot image '%s'",
-                         snapdisk->src->path);
-        }
-    }
-    virBitmapFree(created);
-
-    return ret;
+    virDomainObjEndAPI(&vm);
+    return n;
 }


-/* The domain is expected to be locked and active. */
 static int
-qemuDomainSnapshotCreateActiveInternal(virQEMUDriverPtr driver,
-                                       virDomainObjPtr vm,
-                                       virDomainMomentObjPtr snap,
-                                       unsigned int flags)
+qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot,
+                              unsigned int flags)
 {
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    virObjectEventPtr event = NULL;
-    bool resume = false;
-    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
-    int ret = -1;
-
-    if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0))
-        goto cleanup;
-
-    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
-        /* savevm monitor command pauses the domain emitting an event which
-         * confuses libvirt since it's not notified when qemu resumes the
-         * domain. Thus we stop and start CPUs ourselves.
-         */
-        if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE,
-                                QEMU_ASYNC_JOB_SNAPSHOT) < 0)
-            goto cleanup;
+    virDomainObjPtr vm = NULL;
+    virDomainMomentObjPtr snap = NULL;
+    int n = -1;

-        resume = true;
-        if (!virDomainObjIsActive(vm)) {
-            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                           _("guest unexpectedly quit"));
-            goto cleanup;
-        }
-    }
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);

-    if (qemuDomainObjEnterMonitorAsync(driver, vm,
-                                       QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
-        resume = false;
-        goto cleanup;
-    }
+    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
+        return -1;

-    ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name);
-    if (qemuDomainObjExitMonitor(driver, vm) < 0)
-        ret = -1;
-    if (ret < 0)
+    if (virDomainSnapshotNumChildrenEnsureACL(snapshot->domain->conn, vm->def) < 0)
         goto cleanup;

-    if (!(snapdef->cookie = (virObjectPtr) qemuDomainSaveCookieNew(vm)))
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
         goto cleanup;

-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
-        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
-                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
-        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
-                        QEMU_ASYNC_JOB_SNAPSHOT, 0);
-        virDomainAuditStop(vm, "from-snapshot");
-        resume = false;
-    }
+    n = virDomainSnapshotObjListNum(vm->snapshots, snap, flags);

  cleanup:
-    if (resume && virDomainObjIsActive(vm) &&
-        qemuProcessStartCPUs(driver, vm,
-                             VIR_DOMAIN_RUNNING_UNPAUSED,
-                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
-        event = virDomainEventLifecycleNewFromObj(vm,
-                                         VIR_DOMAIN_EVENT_SUSPENDED,
-                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
-        if (virGetLastErrorCode() == VIR_ERR_OK) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("resuming after snapshot failed"));
-        }
-    }
-
-    virObjectEventStateQueue(driver->domainEventState, event);
-
-    return ret;
+    virDomainObjEndAPI(&vm);
+    return n;
 }


 static int
-qemuDomainSnapshotPrepareDiskShared(virDomainSnapshotDiskDefPtr snapdisk,
-                                    virDomainDiskDefPtr domdisk)
+qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot,
+                                  virDomainSnapshotPtr **snaps,
+                                  unsigned int flags)
 {
-    if (!domdisk->src->shared || domdisk->src->readonly)
-        return 0;
+    virDomainObjPtr vm = NULL;
+    virDomainMomentObjPtr snap = NULL;
+    int n = -1;

-    if (!qemuBlockStorageSourceSupportsConcurrentAccess(snapdisk->src)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                       _("shared access for disk '%s' requires use of "
-                         "supported storage format"), domdisk->dst);
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
+                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
+                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
+
+    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
         return -1;
-    }

-    return 0;
+    if (virDomainSnapshotListAllChildrenEnsureACL(snapshot->domain->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
+        goto cleanup;
+
+    n = virDomainListSnapshots(vm->snapshots, snap, snapshot->domain, snaps,
+                               flags);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return n;
 }


-static int
-qemuDomainSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDefPtr snapdisk,
-                                              virDomainDiskDefPtr domdisk)
+static virDomainSnapshotPtr
+qemuDomainSnapshotLookupByName(virDomainPtr domain,
+                               const char *name,
+                               unsigned int flags)
 {
-    int domDiskType = virStorageSourceGetActualType(domdisk->src);
-    int snapDiskType = virStorageSourceGetActualType(snapdisk->src);
-
-    switch ((virStorageType)domDiskType) {
-    case VIR_STORAGE_TYPE_BLOCK:
-    case VIR_STORAGE_TYPE_FILE:
-        break;
+    virDomainObjPtr vm;
+    virDomainMomentObjPtr snap = NULL;
+    virDomainSnapshotPtr snapshot = NULL;

-    case VIR_STORAGE_TYPE_NETWORK:
-        switch ((virStorageNetProtocol) domdisk->src->protocol) {
-        case VIR_STORAGE_NET_PROTOCOL_NONE:
-        case VIR_STORAGE_NET_PROTOCOL_NBD:
-        case VIR_STORAGE_NET_PROTOCOL_RBD:
-        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
-        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
-        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
-        case VIR_STORAGE_NET_PROTOCOL_HTTP:
-        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
-        case VIR_STORAGE_NET_PROTOCOL_FTP:
-        case VIR_STORAGE_NET_PROTOCOL_FTPS:
-        case VIR_STORAGE_NET_PROTOCOL_TFTP:
-        case VIR_STORAGE_NET_PROTOCOL_SSH:
-        case VIR_STORAGE_NET_PROTOCOL_VXHS:
-        case VIR_STORAGE_NET_PROTOCOL_LAST:
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("external inactive snapshots are not supported on "
-                             "'network' disks using '%s' protocol"),
-                           virStorageNetProtocolTypeToString(domdisk->src->protocol));
-            return -1;
-        }
-        break;
+    virCheckFlags(0, NULL);

-    case VIR_STORAGE_TYPE_DIR:
-    case VIR_STORAGE_TYPE_VOLUME:
-    case VIR_STORAGE_TYPE_NVME:
-    case VIR_STORAGE_TYPE_NONE:
-    case VIR_STORAGE_TYPE_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("external inactive snapshots are not supported on "
-                         "'%s' disks"), virStorageTypeToString(domDiskType));
-        return -1;
-    }
+    if (!(vm = qemuDomainObjFromDomain(domain)))
+        return NULL;

-    switch ((virStorageType)snapDiskType) {
-    case VIR_STORAGE_TYPE_BLOCK:
-    case VIR_STORAGE_TYPE_FILE:
-        break;
+    if (virDomainSnapshotLookupByNameEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;

-    case VIR_STORAGE_TYPE_NETWORK:
-    case VIR_STORAGE_TYPE_DIR:
-    case VIR_STORAGE_TYPE_VOLUME:
-    case VIR_STORAGE_TYPE_NVME:
-    case VIR_STORAGE_TYPE_NONE:
-    case VIR_STORAGE_TYPE_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("external inactive snapshots are not supported on "
-                         "'%s' disks"), virStorageTypeToString(snapDiskType));
-        return -1;
-    }
+    if (!(snap = qemuSnapObjFromName(vm, name)))
+        goto cleanup;

-    if (qemuDomainSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
-        return -1;
+    snapshot = virGetDomainSnapshot(domain, snap->def->name);

-    return 0;
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return snapshot;
 }


 static int
-qemuDomainSnapshotPrepareDiskExternalActive(virDomainObjPtr vm,
-                                            virDomainSnapshotDiskDefPtr snapdisk,
-                                            virDomainDiskDefPtr domdisk,
-                                            bool blockdev)
-{
-    int actualType = virStorageSourceGetActualType(snapdisk->src);
-
-    if (domdisk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("external active snapshots are not supported on scsi "
-                         "passthrough devices"));
-        return -1;
-    }
-
-    if (!qemuDomainDiskBlockJobIsSupported(vm, domdisk))
-        return -1;
-
-    switch ((virStorageType)actualType) {
-    case VIR_STORAGE_TYPE_BLOCK:
-    case VIR_STORAGE_TYPE_FILE:
-        break;
-
-    case VIR_STORAGE_TYPE_NETWORK:
-        /* defer all of the checking to either qemu or libvirt's blockdev code */
-        if (blockdev)
-            break;
-
-        switch ((virStorageNetProtocol) snapdisk->src->protocol) {
-        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
-            break;
-
-        case VIR_STORAGE_NET_PROTOCOL_NONE:
-        case VIR_STORAGE_NET_PROTOCOL_NBD:
-        case VIR_STORAGE_NET_PROTOCOL_RBD:
-        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
-        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
-        case VIR_STORAGE_NET_PROTOCOL_HTTP:
-        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
-        case VIR_STORAGE_NET_PROTOCOL_FTP:
-        case VIR_STORAGE_NET_PROTOCOL_FTPS:
-        case VIR_STORAGE_NET_PROTOCOL_TFTP:
-        case VIR_STORAGE_NET_PROTOCOL_SSH:
-        case VIR_STORAGE_NET_PROTOCOL_VXHS:
-        case VIR_STORAGE_NET_PROTOCOL_LAST:
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("external active snapshots are not supported on "
-                             "'network' disks using '%s' protocol"),
-                           virStorageNetProtocolTypeToString(snapdisk->src->protocol));
-            return -1;
-
-        }
-        break;
-
-    case VIR_STORAGE_TYPE_DIR:
-    case VIR_STORAGE_TYPE_VOLUME:
-    case VIR_STORAGE_TYPE_NVME:
-    case VIR_STORAGE_TYPE_NONE:
-    case VIR_STORAGE_TYPE_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("external active snapshots are not supported on "
-                         "'%s' disks"), virStorageTypeToString(actualType));
-        return -1;
-    }
-
-    if (qemuDomainSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
-        return -1;
-
-    return 0;
-}
-
-
-static int
-qemuDomainSnapshotPrepareDiskExternal(virDomainObjPtr vm,
-                                      virDomainDiskDefPtr disk,
-                                      virDomainSnapshotDiskDefPtr snapdisk,
-                                      bool active,
-                                      bool reuse,
-                                      bool blockdev)
-{
-    struct stat st;
-    int err;
-    int rc;
-
-    if (disk->src->readonly && !(reuse || blockdev)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                       _("external snapshot for readonly disk %s "
-                         "is not supported"), disk->dst);
-        return -1;
-    }
-
-    if (qemuTranslateSnapshotDiskSourcePool(snapdisk) < 0)
-        return -1;
-
-    if (!active) {
-        if (virDomainDiskTranslateSourcePool(disk) < 0)
-            return -1;
-
-        if (qemuDomainSnapshotPrepareDiskExternalInactive(snapdisk, disk) < 0)
-            return -1;
-    } else {
-        if (qemuDomainSnapshotPrepareDiskExternalActive(vm, snapdisk, disk, blockdev) < 0)
-            return -1;
-    }
-
-    if (virStorageSourceIsLocalStorage(snapdisk->src)) {
-        if (virStorageFileInit(snapdisk->src) < 0)
-            return -1;
-
-        rc = virStorageFileStat(snapdisk->src, &st);
-        err = errno;
-
-        virStorageFileDeinit(snapdisk->src);
-
-        if (rc < 0) {
-            if (err != ENOENT) {
-                virReportSystemError(err,
-                                     _("unable to stat for disk %s: %s"),
-                                     snapdisk->name, snapdisk->src->path);
-                return -1;
-            } else if (reuse) {
-                virReportSystemError(err,
-                                     _("missing existing file for disk %s: %s"),
-                                     snapdisk->name, snapdisk->src->path);
-                return -1;
-            }
-        } else if (!S_ISBLK(st.st_mode) && st.st_size && !reuse) {
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                           _("external snapshot file for disk %s already "
-                             "exists and is not a block device: %s"),
-                           snapdisk->name, snapdisk->src->path);
-            return -1;
-        }
-    }
-
-    return 0;
-}
-
-
-static int
-qemuDomainSnapshotPrepareDiskInternal(virDomainDiskDefPtr disk,
-                                      bool active)
-{
-    int actualType;
-
-    /* active disks are handled by qemu itself so no need to worry about those */
-    if (active)
-        return 0;
-
-    if (virDomainDiskTranslateSourcePool(disk) < 0)
-        return -1;
-
-    actualType = virStorageSourceGetActualType(disk->src);
-
-    switch ((virStorageType)actualType) {
-    case VIR_STORAGE_TYPE_BLOCK:
-    case VIR_STORAGE_TYPE_FILE:
-        return 0;
-
-    case VIR_STORAGE_TYPE_NETWORK:
-        switch ((virStorageNetProtocol) disk->src->protocol) {
-        case VIR_STORAGE_NET_PROTOCOL_NONE:
-        case VIR_STORAGE_NET_PROTOCOL_NBD:
-        case VIR_STORAGE_NET_PROTOCOL_RBD:
-        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
-        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
-        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
-        case VIR_STORAGE_NET_PROTOCOL_HTTP:
-        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
-        case VIR_STORAGE_NET_PROTOCOL_FTP:
-        case VIR_STORAGE_NET_PROTOCOL_FTPS:
-        case VIR_STORAGE_NET_PROTOCOL_TFTP:
-        case VIR_STORAGE_NET_PROTOCOL_SSH:
-        case VIR_STORAGE_NET_PROTOCOL_VXHS:
-        case VIR_STORAGE_NET_PROTOCOL_LAST:
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("internal inactive snapshots are not supported on "
-                             "'network' disks using '%s' protocol"),
-                           virStorageNetProtocolTypeToString(disk->src->protocol));
-            return -1;
-        }
-        break;
-
-    case VIR_STORAGE_TYPE_DIR:
-    case VIR_STORAGE_TYPE_VOLUME:
-    case VIR_STORAGE_TYPE_NVME:
-    case VIR_STORAGE_TYPE_NONE:
-    case VIR_STORAGE_TYPE_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("internal inactive snapshots are not supported on "
-                         "'%s' disks"), virStorageTypeToString(actualType));
-        return -1;
-    }
-
-    return 0;
-}
-
-
-static int
-qemuDomainSnapshotPrepare(virDomainObjPtr vm,
-                          virDomainSnapshotDefPtr def,
-                          unsigned int *flags)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
-    size_t i;
-    bool active = virDomainObjIsActive(vm);
-    bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
-    bool found_internal = false;
-    bool forbid_internal = false;
-    int external = 0;
-
-    for (i = 0; i < def->ndisks; i++) {
-        virDomainSnapshotDiskDefPtr disk = &def->disks[i];
-        virDomainDiskDefPtr dom_disk = vm->def->disks[i];
-
-        if (disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NONE &&
-            qemuDomainDiskBlockJobIsActive(dom_disk))
-            return -1;
-
-        switch ((virDomainSnapshotLocation) disk->snapshot) {
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL:
-            found_internal = true;
-
-            if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && active) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("active qemu domains require external disk "
-                                 "snapshots; disk %s requested internal"),
-                               disk->name);
-                return -1;
-            }
-
-            if (qemuDomainSnapshotPrepareDiskInternal(dom_disk,
-                                                      active) < 0)
-                return -1;
-
-            if (dom_disk->src->format > 0 &&
-                dom_disk->src->format != VIR_STORAGE_FILE_QCOW2) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("internal snapshot for disk %s unsupported "
-                                 "for storage type %s"),
-                               disk->name,
-                               virStorageFileFormatTypeToString(dom_disk->src->format));
-                return -1;
-            }
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL:
-            if (!disk->src->format) {
-                disk->src->format = VIR_STORAGE_FILE_QCOW2;
-            } else if (disk->src->format != VIR_STORAGE_FILE_QCOW2 &&
-                       disk->src->format != VIR_STORAGE_FILE_QED) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("external snapshot format for disk %s "
-                                 "is unsupported: %s"),
-                               disk->name,
-                               virStorageFileFormatTypeToString(disk->src->format));
-                return -1;
-            }
-
-            if (qemuDomainSnapshotPrepareDiskExternal(vm, dom_disk, disk,
-                                                      active, reuse, blockdev) < 0)
-                return -1;
-
-            external++;
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE:
-            /* Remember seeing a disk that has snapshot disabled */
-            if (!virStorageSourceIsEmpty(dom_disk->src) &&
-                !dom_disk->src->readonly)
-                forbid_internal = true;
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT:
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_LAST:
-            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                           _("unexpected code path"));
-            return -1;
-        }
-    }
-
-    if (!found_internal && !external &&
-        def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("nothing selected for snapshot"));
-        return -1;
-    }
-
-    /* internal snapshot requires a disk image to store the memory image to, and
-     * also disks can't be excluded from an internal snapshot */
-    if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && !found_internal) ||
-        (found_internal && forbid_internal)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("internal and full system snapshots require all "
-                         "disks to be selected for snapshot"));
-        return -1;
-    }
-
-    /* disk snapshot requires at least one disk */
-    if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("disk-only snapshots require at least "
-                         "one disk to be selected for snapshot"));
-        return -1;
-    }
-
-    /* For now, we don't allow mixing internal and external disks.
-     * XXX technically, we could mix internal and external disks for
-     * offline snapshots */
-    if ((found_internal && external) ||
-         (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && external) ||
-         (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL && found_internal)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("mixing internal and external targets for a snapshot "
-                         "is not yet supported"));
-        return -1;
-    }
-
-    /* internal snapshots + pflash based loader have the following problems:
-     * - if the variable store is raw, the snapshot fails
-     * - allowing a qcow2 image as the varstore would make it eligible to receive
-     *   the vmstate dump, which would make it huge
-     * - offline snapshot would not snapshot the varstore at all
-     *
-     * Avoid the issues by forbidding internal snapshot with pflash completely.
-     */
-    if (found_internal &&
-        virDomainDefHasOldStyleUEFI(vm->def)) {
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("internal snapshots of a VM with pflash based "
-                         "firmware are not supported"));
-        return -1;
-    }
-
-    /* Alter flags to let later users know what we learned.  */
-    if (external && !active)
-        *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
-
-    return 0;
-}
-
-
-struct _qemuDomainSnapshotDiskData {
-    virStorageSourcePtr src;
-    bool initialized; /* @src was initialized in the storage driver */
-    bool created; /* @src was created by the snapshot code */
-    bool prepared; /* @src was prepared using qemuDomainStorageSourceAccessAllow */
-    virDomainDiskDefPtr disk;
-    char *relPath; /* relative path component to fill into original disk */
-    qemuBlockStorageSourceChainDataPtr crdata;
-    bool blockdevadded;
-
-    virStorageSourcePtr persistsrc;
-    virDomainDiskDefPtr persistdisk;
-};
-
-typedef struct _qemuDomainSnapshotDiskData qemuDomainSnapshotDiskData;
-typedef qemuDomainSnapshotDiskData *qemuDomainSnapshotDiskDataPtr;
-
-
-static void
-qemuDomainSnapshotDiskCleanup(qemuDomainSnapshotDiskDataPtr data,
-                              size_t ndata,
-                              virQEMUDriverPtr driver,
-                              virDomainObjPtr vm,
-                              qemuDomainAsyncJob asyncJob)
-{
-    virErrorPtr orig_err;
-    size_t i;
-
-    if (!data)
-        return;
-
-    virErrorPreserveLast(&orig_err);
-
-    for (i = 0; i < ndata; i++) {
-        /* on success of the snapshot the 'src' and 'persistsrc' properties will
-         * be set to NULL by qemuDomainSnapshotDiskUpdateSource */
-        if (data[i].src) {
-            if (data[i].blockdevadded) {
-                if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) == 0) {
-
-                    qemuBlockStorageSourceAttachRollback(qemuDomainGetMonitor(vm),
-                                                         data[i].crdata->srcdata[0]);
-                    ignore_value(qemuDomainObjExitMonitor(driver, vm));
-                }
-            }
-
-            if (data[i].created &&
-                virStorageFileUnlink(data[i].src) < 0) {
-                VIR_WARN("Unable to remove just-created %s",
-                         NULLSTR(data[i].src->path));
-            }
-
-            if (data[i].initialized)
-                virStorageFileDeinit(data[i].src);
-
-            if (data[i].prepared)
-                qemuDomainStorageSourceAccessRevoke(driver, vm, data[i].src);
-
-            virObjectUnref(data[i].src);
-        }
-        virObjectUnref(data[i].persistsrc);
-        VIR_FREE(data[i].relPath);
-        qemuBlockStorageSourceChainDataFree(data[i].crdata);
-    }
-
-    VIR_FREE(data);
-    virErrorRestore(&orig_err);
-}
-
-
-/**
- * qemuDomainSnapshotDiskBitmapsPropagate:
- *
- * This function propagates any active persistent bitmap present in the original
- * image into the new snapshot. This is necessary to keep tracking the changed
- * blocks in the active bitmaps as the backing file will become read-only.
- * We leave the original bitmap active as in cases when the overlay is
- * discarded (snapshot revert with abandoning the history) everything works as
- * expected.
- */
-static int
-qemuDomainSnapshotDiskBitmapsPropagate(qemuDomainSnapshotDiskDataPtr dd,
-                                       virJSONValuePtr actions,
-                                       virHashTablePtr blockNamedNodeData)
-{
-    qemuBlockNamedNodeDataPtr entry;
-    size_t i;
-
-    if (!(entry = virHashLookup(blockNamedNodeData, dd->disk->src->nodeformat)))
-        return 0;
-
-    for (i = 0; i < entry->nbitmaps; i++) {
-        qemuBlockNamedNodeDataBitmapPtr bitmap = entry->bitmaps[i];
-
-        /* we don't care about temporary, inconsistent, or disabled bitmaps */
-        if (!bitmap->persistent || !bitmap->recording || bitmap->inconsistent)
-            continue;
-
-        if (qemuMonitorTransactionBitmapAdd(actions, dd->src->nodeformat,
-                                            bitmap->name, true, false,
-                                            bitmap->granularity) < 0)
-            return -1;
-    }
-
-    return 0;
-}
-
-
-static int
-qemuDomainSnapshotDiskPrepareOneBlockdev(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         qemuDomainSnapshotDiskDataPtr dd,
-                                         virQEMUDriverConfigPtr cfg,
-                                         bool reuse,
-                                         virHashTablePtr blockNamedNodeData,
-                                         qemuDomainAsyncJob asyncJob)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    g_autoptr(virStorageSource) terminator = NULL;
-    int rc;
-
-    /* create a terminator for the snapshot disks so that qemu does not try
-     * to open them at first */
-    if (!(terminator = virStorageSourceNew()))
-        return -1;
-
-    if (qemuDomainPrepareStorageSourceBlockdev(dd->disk, dd->src,
-                                               priv, cfg) < 0)
-        return -1;
-
-    if (!(dd->crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(dd->src,
-                                                                           terminator,
-                                                                           priv->qemuCaps)))
-        return -1;
-
-    if (reuse) {
-        if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
-            return -1;
-
-        rc = qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm),
-                                               dd->crdata->srcdata[0]);
-
-        if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
-            return -1;
-    } else {
-        if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData,
-                                                   dd->src, dd->disk->src) < 0)
-            return -1;
-
-        if (qemuBlockStorageSourceCreate(vm, dd->src, dd->disk->src,
-                                         NULL, dd->crdata->srcdata[0],
-                                         asyncJob) < 0)
-            return -1;
-    }
-
-    dd->blockdevadded = true;
-    return 0;
-}
-
-
-static int
-qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver,
-                                 virDomainObjPtr vm,
-                                 virQEMUDriverConfigPtr cfg,
-                                 virDomainDiskDefPtr disk,
-                                 virDomainSnapshotDiskDefPtr snapdisk,
-                                 qemuDomainSnapshotDiskDataPtr dd,
-                                 virHashTablePtr blockNamedNodeData,
-                                 bool reuse,
-                                 bool blockdev,
-                                 qemuDomainAsyncJob asyncJob,
-                                 virJSONValuePtr actions)
-{
-    virDomainDiskDefPtr persistdisk;
-    bool supportsCreate;
-    bool updateRelativeBacking = false;
-
-    dd->disk = disk;
-
-    if (qemuDomainStorageSourceValidateDepth(disk->src, 1, disk->dst) < 0)
-        return -1;
-
-    if (!(dd->src = virStorageSourceCopy(snapdisk->src, false)))
-        return -1;
-
-    if (virStorageSourceInitChainElement(dd->src, dd->disk->src, false) < 0)
-        return -1;
-
-    /* modify disk in persistent definition only when the source is the same */
-    if (vm->newDef &&
-        (persistdisk = virDomainDiskByTarget(vm->newDef, dd->disk->dst)) &&
-        virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) {
-
-        dd->persistdisk = persistdisk;
-
-        if (!(dd->persistsrc = virStorageSourceCopy(dd->src, false)))
-            return -1;
-
-        if (virStorageSourceInitChainElement(dd->persistsrc,
-                                             dd->persistdisk->src, false) < 0)
-            return -1;
-    }
-
-    supportsCreate = virStorageFileSupportsCreate(dd->src);
-
-    /* relative backing store paths need to be updated so that relative
-     * block commit still works. With blockdev we must update it when doing
-     * commit anyways so it's skipped here */
-    if (!blockdev &&
-        virStorageFileSupportsBackingChainTraversal(dd->src))
-        updateRelativeBacking = true;
-
-    if (supportsCreate || updateRelativeBacking) {
-        if (qemuDomainStorageFileInit(driver, vm, dd->src, NULL) < 0)
-            return -1;
-
-        dd->initialized = true;
-
-        if (reuse) {
-            if (updateRelativeBacking) {
-                g_autofree char *backingStoreStr = NULL;
-
-                if (virStorageFileGetBackingStoreStr(dd->src, &backingStoreStr) < 0)
-                    return -1;
-                if (backingStoreStr != NULL) {
-                    if (virStorageIsRelative(backingStoreStr))
-                        dd->relPath = g_steal_pointer(&backingStoreStr);
-                }
-            }
-        } else {
-            /* pre-create the image file so that we can label it before handing it to qemu */
-            if (supportsCreate && dd->src->type != VIR_STORAGE_TYPE_BLOCK) {
-                if (virStorageFileCreate(dd->src) < 0) {
-                    virReportSystemError(errno, _("failed to create image file '%s'"),
-                                         NULLSTR(dd->src->path));
-                    return -1;
-                }
-                dd->created = true;
-            }
-        }
-    }
-
-    /* set correct security, cgroup and locking options on the new image */
-    if (qemuDomainStorageSourceAccessAllow(driver, vm, dd->src,
-                                           false, true, true) < 0)
-        return -1;
-
-    dd->prepared = true;
-
-    if (blockdev) {
-        if (qemuDomainSnapshotDiskPrepareOneBlockdev(driver, vm, dd, cfg, reuse,
-                                                     blockNamedNodeData, asyncJob) < 0)
-            return -1;
-
-        if (qemuDomainSnapshotDiskBitmapsPropagate(dd, actions, blockNamedNodeData) < 0)
-            return -1;
-
-        if (qemuBlockSnapshotAddBlockdev(actions, dd->disk, dd->src) < 0)
-            return -1;
-    } else {
-        if (qemuBlockSnapshotAddLegacy(actions, dd->disk, dd->src, reuse) < 0)
-            return -1;
-    }
-
-    return 0;
-}
-
-
-/**
- * qemuDomainSnapshotDiskPrepare:
- *
- * Collects and prepares a list of structures that hold information about disks
- * that are selected for the snapshot.
- */
-static int
-qemuDomainSnapshotDiskPrepare(virQEMUDriverPtr driver,
-                              virDomainObjPtr vm,
-                              virDomainMomentObjPtr snap,
-                              virQEMUDriverConfigPtr cfg,
-                              bool reuse,
-                              bool blockdev,
-                              virHashTablePtr blockNamedNodeData,
-                              qemuDomainAsyncJob asyncJob,
-                              qemuDomainSnapshotDiskDataPtr *rdata,
-                              size_t *rndata,
-                              virJSONValuePtr actions)
-{
-    size_t i;
-    qemuDomainSnapshotDiskDataPtr data;
-    size_t ndata = 0;
-    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
-    int ret = -1;
-
-    if (VIR_ALLOC_N(data, snapdef->ndisks) < 0)
-        return -1;
-
-    for (i = 0; i < snapdef->ndisks; i++) {
-        if (snapdef->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
-            continue;
-
-        if (qemuDomainSnapshotDiskPrepareOne(driver, vm, cfg, vm->def->disks[i],
-                                             snapdef->disks + i,
-                                             data + ndata++,
-                                             blockNamedNodeData,
-                                             reuse, blockdev,
-                                             asyncJob,
-                                             actions) < 0)
-            goto cleanup;
-    }
-
-    *rdata = g_steal_pointer(&data);
-    *rndata = ndata;
-    ret = 0;
-
- cleanup:
-    qemuDomainSnapshotDiskCleanup(data, ndata, driver, vm, asyncJob);
-    return ret;
-}
-
-
-static void
-qemuDomainSnapshotDiskUpdateSourceRenumber(virStorageSourcePtr src)
-{
-    virStorageSourcePtr next;
-    unsigned int idx = 1;
-
-    for (next = src->backingStore; virStorageSourceIsBacking(next); next = next->backingStore)
-        next->id = idx++;
-}
-
-
-/**
- * qemuDomainSnapshotDiskUpdateSource:
- * @driver: QEMU driver
- * @vm: domain object
- * @dd: snapshot disk data object
- * @blockdev: -blockdev is in use for the VM
- *
- * Updates disk definition after a successful snapshot.
- */
-static void
-qemuDomainSnapshotDiskUpdateSource(virQEMUDriverPtr driver,
-                                   virDomainObjPtr vm,
-                                   qemuDomainSnapshotDiskDataPtr dd,
-                                   bool blockdev)
-{
-    /* storage driver access won'd be needed */
-    if (dd->initialized)
-        virStorageFileDeinit(dd->src);
-
-    if (qemuSecurityMoveImageMetadata(driver, vm, dd->disk->src, dd->src) < 0)
-        VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name);
-
-    /* unlock the write lock on the original image as qemu will no longer write to it */
-    virDomainLockImageDetach(driver->lockManager, vm, dd->disk->src);
-
-    /* unlock also the new image if the VM is paused to follow the locking semantics */
-    if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING)
-        virDomainLockImageDetach(driver->lockManager, vm, dd->src);
-
-    /* the old disk image is now readonly */
-    dd->disk->src->readonly = true;
-
-    dd->disk->src->relPath = g_steal_pointer(&dd->relPath);
-    dd->src->backingStore = g_steal_pointer(&dd->disk->src);
-    dd->disk->src = g_steal_pointer(&dd->src);
-
-    /* fix numbering of disks */
-    if (!blockdev)
-        qemuDomainSnapshotDiskUpdateSourceRenumber(dd->disk->src);
-
-    if (dd->persistdisk) {
-        dd->persistdisk->src->readonly = true;
-        dd->persistsrc->backingStore = g_steal_pointer(&dd->persistdisk->src);
-        dd->persistdisk->src = g_steal_pointer(&dd->persistsrc);
-    }
-}
-
-
-/* The domain is expected to be locked and active. */
-static int
-qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver,
-                                   virDomainObjPtr vm,
-                                   virDomainMomentObjPtr snap,
-                                   virHashTablePtr blockNamedNodeData,
-                                   unsigned int flags,
-                                   virQEMUDriverConfigPtr cfg,
-                                   qemuDomainAsyncJob asyncJob)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    g_autoptr(virJSONValue) actions = NULL;
-    int rc;
-    int ret = -1;
-    size_t i;
-    bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
-    qemuDomainSnapshotDiskDataPtr diskdata = NULL;
-    size_t ndiskdata = 0;
-    bool blockdev =  virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
-
-    if (virDomainObjCheckActive(vm) < 0)
-        return -1;
-
-    actions = virJSONValueNewArray();
-
-    /* prepare a list of objects to use in the vm definition so that we don't
-     * have to roll back later */
-    if (qemuDomainSnapshotDiskPrepare(driver, vm, snap, cfg, reuse, blockdev,
-                                      blockNamedNodeData, asyncJob,
-                                      &diskdata, &ndiskdata, actions) < 0)
-        goto cleanup;
-
-    /* check whether there's anything to do */
-    if (ndiskdata == 0) {
-        ret = 0;
-        goto cleanup;
-    }
-
-    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
-        goto cleanup;
-
-    rc = qemuMonitorTransaction(priv->mon, &actions);
-
-    if (qemuDomainObjExitMonitor(driver, vm) < 0)
-        rc = -1;
-
-    for (i = 0; i < ndiskdata; i++) {
-        qemuDomainSnapshotDiskDataPtr dd = &diskdata[i];
-
-        virDomainAuditDisk(vm, dd->disk->src, dd->src, "snapshot", rc >= 0);
-
-        if (rc == 0)
-            qemuDomainSnapshotDiskUpdateSource(driver, vm, dd, blockdev);
-    }
-
-    if (rc < 0)
-        goto cleanup;
-
-    if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0 ||
-        (vm->newDef && virDomainDefSave(vm->newDef, driver->xmlopt,
-                                        cfg->configDir) < 0))
-        goto cleanup;
-
-    ret = 0;
-
- cleanup:
-    qemuDomainSnapshotDiskCleanup(diskdata, ndiskdata, driver, vm, asyncJob);
-    return ret;
-}
-
-
-static int
-qemuDomainSnapshotCreateActiveExternal(virQEMUDriverPtr driver,
-                                       virDomainObjPtr vm,
-                                       virDomainMomentObjPtr snap,
-                                       virQEMUDriverConfigPtr cfg,
-                                       unsigned int flags)
-{
-    virObjectEventPtr event;
-    bool resume = false;
-    int ret = -1;
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    g_autofree char *xml = NULL;
-    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
-    bool memory = snapdef->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-    bool memory_unlink = false;
-    int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */
-    bool pmsuspended = false;
-    int compressed;
-    g_autoptr(virCommand) compressor = NULL;
-    virQEMUSaveDataPtr data = NULL;
-    g_autoptr(virHashTable) blockNamedNodeData = NULL;
-
-    /* If quiesce was requested, then issue a freeze command, and a
-     * counterpart thaw command when it is actually sent to agent.
-     * The command will fail if the guest is paused or the guest agent
-     * is not running, or is already quiesced.  */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) {
-        int freeze;
-
-        if (qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) < 0)
-            goto cleanup;
-
-        if (virDomainObjCheckActive(vm) < 0) {
-            qemuDomainObjEndAgentJob(vm);
-            goto cleanup;
-        }
-
-        freeze = qemuDomainSnapshotFSFreeze(vm, NULL, 0);
-        qemuDomainObjEndAgentJob(vm);
-
-        if (freeze < 0) {
-            /* the helper reported the error */
-            if (freeze == -2)
-                thaw = -1; /* the command is sent but agent failed */
-            goto cleanup;
-        }
-        thaw = 1;
-    }
-
-    /* We need to track what state the guest is in, since taking the
-     * snapshot may alter that state and we must restore it later.  */
-    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PMSUSPENDED) {
-        pmsuspended = true;
-    } else if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
-        /* For full system external snapshots (those with memory), the guest
-         * must pause (either by libvirt up front, or by qemu after
-         * _LIVE converges). */
-        if (memory)
-            resume = true;
-
-        if (memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) {
-            if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
-                                    QEMU_ASYNC_JOB_SNAPSHOT) < 0)
-                goto cleanup;
-
-            if (!virDomainObjIsActive(vm)) {
-                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                               _("guest unexpectedly quit"));
-                goto cleanup;
-            }
-
-            resume = true;
-        }
-    }
-
-    /* We need to collect reply from 'query-named-block-nodes' prior to the
-     * migration step as qemu deactivates bitmaps after migration so the result
-     * would be wrong */
-    if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
-        !(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_SNAPSHOT)))
-        goto cleanup;
-
-    /* do the memory snapshot if necessary */
-    if (memory) {
-        /* check if migration is possible */
-        if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0))
-            goto cleanup;
-
-        priv->job.current->statsType = QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP;
-
-        /* allow the migration job to be cancelled or the domain to be paused */
-        qemuDomainObjSetAsyncJobMask(vm, (QEMU_JOB_DEFAULT_MASK |
-                                          JOB_MASK(QEMU_JOB_SUSPEND) |
-                                          JOB_MASK(QEMU_JOB_MIGRATION_OP)));
-
-        if ((compressed = qemuSaveImageGetCompressionProgram(cfg->snapshotImageFormat,
-                                                             &compressor,
-                                                             "snapshot", false)) < 0)
-            goto cleanup;
-
-        if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
-                                            vm->def, priv->origCPU,
-                                            true, true)) ||
-            !(snapdef->cookie = (virObjectPtr) qemuDomainSaveCookieNew(vm)))
-            goto cleanup;
-
-        if (!(data = virQEMUSaveDataNew(xml,
-                                        (qemuDomainSaveCookiePtr) snapdef->cookie,
-                                        resume, compressed, driver->xmlopt)))
-            goto cleanup;
-        xml = NULL;
-
-        if ((ret = qemuSaveImageCreate(driver, vm, snapdef->file, data,
-                                      compressor, 0,
-                                      QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
-            goto cleanup;
-
-        /* the memory image was created, remove it on errors */
-        memory_unlink = true;
-
-        /* forbid any further manipulation */
-        qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_DEFAULT_MASK);
-    }
-
-    /* the domain is now paused if a memory snapshot was requested */
-
-    if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap,
-                                                  blockNamedNodeData, flags, cfg,
-                                                  QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
-        goto cleanup;
-
-    /* the snapshot is complete now */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
-        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
-                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
-        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
-                        QEMU_ASYNC_JOB_SNAPSHOT, 0);
-        virDomainAuditStop(vm, "from-snapshot");
-        resume = false;
-        thaw = 0;
-        virObjectEventStateQueue(driver->domainEventState, event);
-    } else if (memory && pmsuspended) {
-        /* qemu 1.3 is unable to save a domain in pm-suspended (S3)
-         * state; so we must emit an event stating that it was
-         * converted to paused.  */
-        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
-                             VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
-        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED,
-                                         VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT);
-        virObjectEventStateQueue(driver->domainEventState, event);
-    }
-
-    ret = 0;
-
- cleanup:
-    if (resume && virDomainObjIsActive(vm) &&
-        qemuProcessStartCPUs(driver, vm,
-                             VIR_DOMAIN_RUNNING_UNPAUSED,
-                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
-        event = virDomainEventLifecycleNewFromObj(vm,
-                                         VIR_DOMAIN_EVENT_SUSPENDED,
-                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
-        virObjectEventStateQueue(driver->domainEventState, event);
-        if (virGetLastErrorCode() == VIR_ERR_OK) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("resuming after snapshot failed"));
-        }
-
-        ret = -1;
-    }
-
-    if (thaw != 0 &&
-        qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) >= 0 &&
-        virDomainObjIsActive(vm)) {
-        if (qemuDomainSnapshotFSThaw(vm, ret == 0 && thaw > 0) < 0) {
-            /* helper reported the error, if it was needed */
-            if (thaw > 0)
-                ret = -1;
-        }
-
-        qemuDomainObjEndAgentJob(vm);
-    }
-
-    virQEMUSaveDataFree(data);
-    if (memory_unlink && ret < 0)
-        unlink(snapdef->file);
-
-    return ret;
-}
-
-
-static virDomainSnapshotPtr
-qemuDomainSnapshotCreateXML(virDomainPtr domain,
-                            const char *xmlDesc,
-                            unsigned int flags)
-{
-    virQEMUDriverPtr driver = domain->conn->privateData;
-    virDomainObjPtr vm = NULL;
-    g_autofree char *xml = NULL;
-    virDomainMomentObjPtr snap = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    virDomainMomentObjPtr current = NULL;
-    bool update_current = true;
-    bool redefine = flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
-    unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS;
-    int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
-    bool align_match = true;
-    g_autoptr(virQEMUDriverConfig) cfg = NULL;
-    qemuDomainObjPrivatePtr priv;
-    virDomainSnapshotState state;
-    g_autoptr(virDomainSnapshotDef) def = NULL;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_HALT |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_LIVE |
-                  VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE, NULL);
-
-    VIR_REQUIRE_FLAG_RET(VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE,
-                         VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY,
-                         NULL);
-    VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_SNAPSHOT_CREATE_LIVE,
-                            VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE,
-                            NULL);
-
-    if ((redefine && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) ||
-        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA))
-        update_current = false;
-    if (redefine)
-        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE;
-
-    if (!(vm = qemuDomainObjFromDomain(domain)))
-        goto cleanup;
-
-    priv = vm->privateData;
-    cfg = virQEMUDriverGetConfig(driver);
-
-    if (virDomainSnapshotCreateXMLEnsureACL(domain->conn, vm->def, flags) < 0)
-        goto cleanup;
-
-    if (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0)
-        goto cleanup;
-
-    if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
-        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
-                       _("cannot halt after transient domain snapshot"));
-        goto cleanup;
-    }
-    if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) ||
-        !virDomainObjIsActive(vm))
-        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE;
-
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE)
-        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE;
-
-    if (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->xmlopt,
-                                                priv->qemuCaps, NULL, parse_flags)))
-        goto cleanup;
-
-    /* reject snapshot names containing slashes or starting with dot as
-     * snapshot definitions are saved in files named by the snapshot name */
-    if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
-        if (strchr(def->parent.name, '/')) {
-            virReportError(VIR_ERR_XML_DETAIL,
-                           _("invalid snapshot name '%s': "
-                             "name can't contain '/'"),
-                           def->parent.name);
-            goto cleanup;
-        }
-
-        if (def->parent.name[0] == '.') {
-            virReportError(VIR_ERR_XML_DETAIL,
-                           _("invalid snapshot name '%s': "
-                             "name can't start with '.'"),
-                           def->parent.name);
-            goto cleanup;
-        }
-    }
-
-    /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE &&
-        (!virDomainObjIsActive(vm) ||
-         def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)) {
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("live snapshot creation is supported only "
-                         "during full system snapshots"));
-        goto cleanup;
-    }
-
-    /* allow snapshots only in certain states */
-    state = redefine ? def->state : vm->state.state;
-    switch (state) {
-        /* valid states */
-    case VIR_DOMAIN_SNAPSHOT_RUNNING:
-    case VIR_DOMAIN_SNAPSHOT_PAUSED:
-    case VIR_DOMAIN_SNAPSHOT_SHUTDOWN:
-    case VIR_DOMAIN_SNAPSHOT_SHUTOFF:
-    case VIR_DOMAIN_SNAPSHOT_CRASHED:
-        break;
-
-    case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT:
-        if (!redefine) {
-            virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"),
-                           virDomainSnapshotStateTypeToString(state));
-            goto cleanup;
-        }
-        break;
-
-    case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED:
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("qemu doesn't support taking snapshots of "
-                         "PMSUSPENDED guests"));
-        goto cleanup;
-
-        /* invalid states */
-    case VIR_DOMAIN_SNAPSHOT_NOSTATE:
-    case VIR_DOMAIN_SNAPSHOT_BLOCKED: /* invalid state, unused in qemu */
-    case VIR_DOMAIN_SNAPSHOT_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"),
-                       virDomainSnapshotStateTypeToString(state));
-        goto cleanup;
-    }
-
-    /* We are going to modify the domain below. Internal snapshots would use
-     * a regular job, so we need to set the job mask to disallow query as
-     * 'savevm' blocks the monitor. External snapshot will then modify the
-     * job mask appropriately. */
-    if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_SNAPSHOT,
-                                   VIR_DOMAIN_JOB_OPERATION_SNAPSHOT, flags) < 0)
-        goto cleanup;
-
-    qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE);
-
-    if (redefine) {
-        if (virDomainSnapshotRedefinePrep(vm, &def, &snap,
-                                          driver->xmlopt,
-                                          flags) < 0)
-            goto endjob;
-    } else {
-        /* Easiest way to clone inactive portion of vm->def is via
-         * conversion in and back out of xml.  */
-        if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
-                                            vm->def, priv->origCPU,
-                                            true, true)) ||
-            !(def->parent.dom = virDomainDefParseString(xml, driver->xmlopt,
-                                                        priv->qemuCaps,
-                                                        VIR_DOMAIN_DEF_PARSE_INACTIVE |
-                                                        VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
-            goto endjob;
-
-        if (vm->newDef) {
-            def->parent.inactiveDom = virDomainDefCopy(vm->newDef,
-                                                       driver->xmlopt, priv->qemuCaps, true);
-            if (!def->parent.inactiveDom)
-                goto endjob;
-        }
-
-        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
-            align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-            align_match = false;
-            if (virDomainObjIsActive(vm))
-                def->state = VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT;
-            else
-                def->state = VIR_DOMAIN_SNAPSHOT_SHUTOFF;
-            def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE;
-        } else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-            def->state = virDomainObjGetState(vm, NULL);
-            align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-            align_match = false;
-        } else {
-            def->state = virDomainObjGetState(vm, NULL);
-
-            if (virDomainObjIsActive(vm) &&
-                def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) {
-                virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                               _("internal snapshot of a running VM "
-                                 "must include the memory state"));
-                goto endjob;
-            }
-
-            def->memory = (def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF ?
-                           VIR_DOMAIN_SNAPSHOT_LOCATION_NONE :
-                           VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL);
-        }
-        if (virDomainSnapshotAlignDisks(def, align_location,
-                                        align_match) < 0 ||
-            qemuDomainSnapshotPrepare(vm, def, &flags) < 0)
-            goto endjob;
-    }
-
-    if (!snap) {
-        if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def)))
-            goto endjob;
-
-        def = NULL;
-    }
-
-    current = virDomainSnapshotGetCurrent(vm->snapshots);
-    if (current) {
-        if (!redefine)
-            snap->def->parent_name = g_strdup(current->def->name);
-    }
-
-    /* actually do the snapshot */
-    if (redefine) {
-        /* XXX Should we validate that the redefined snapshot even
-         * makes sense, such as checking that qemu-img recognizes the
-         * snapshot name in at least one of the domain's disks?  */
-    } else if (virDomainObjIsActive(vm)) {
-        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY ||
-            virDomainSnapshotObjGetDef(snap)->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-            /* external full system or disk snapshot */
-            if (qemuDomainSnapshotCreateActiveExternal(driver,
-                                                       vm, snap, cfg, flags) < 0)
-                goto endjob;
-        } else {
-            /* internal full system */
-            if (qemuDomainSnapshotCreateActiveInternal(driver,
-                                                       vm, snap, flags) < 0)
-                goto endjob;
-        }
-    } else {
-        /* inactive; qemuDomainSnapshotPrepare guaranteed that we
-         * aren't mixing internal and external, and altered flags to
-         * contain DISK_ONLY if there is an external disk.  */
-        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
-            bool reuse = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT);
-
-            if (qemuDomainSnapshotCreateInactiveExternal(driver, vm, snap,
-                                                         reuse) < 0)
-                goto endjob;
-        } else {
-            if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap) < 0)
-                goto endjob;
-        }
-    }
-
-    /* If we fail after this point, there's not a whole lot we can
-     * do; we've successfully taken the snapshot, and we are now running
-     * on it, so we have to go forward the best we can
-     */
-    snapshot = virGetDomainSnapshot(domain, snap->def->name);
-
- endjob:
-    if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
-        if (update_current)
-            virDomainSnapshotSetCurrent(vm->snapshots, snap);
-        if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                            driver->xmlopt,
-                                            cfg->snapshotDir) < 0) {
-            /* if writing of metadata fails, error out rather than trying
-             * to silently carry on without completing the snapshot */
-            virObjectUnref(snapshot);
-            snapshot = NULL;
-            virReportError(VIR_ERR_INTERNAL_ERROR,
-                           _("unable to save metadata for snapshot %s"),
-                           snap->def->name);
-            virDomainSnapshotObjListRemove(vm->snapshots, snap);
-        } else {
-            virDomainSnapshotLinkParent(vm->snapshots, snap);
-        }
-    } else if (snap) {
-        virDomainSnapshotObjListRemove(vm->snapshots, snap);
-    }
-
-    qemuDomainObjEndAsyncJob(driver, vm);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return snapshot;
-}
-
-
-static int
-qemuDomainSnapshotListNames(virDomainPtr domain,
-                            char **names,
-                            int nameslen,
-                            unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomainObjFromDomain(domain)))
-        return -1;
-
-    if (virDomainSnapshotListNamesEnsureACL(domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    n = virDomainSnapshotObjListGetNames(vm->snapshots, NULL, names, nameslen,
-                                         flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static int
-qemuDomainSnapshotNum(virDomainPtr domain,
-                      unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomainObjFromDomain(domain)))
-        return -1;
-
-    if (virDomainSnapshotNumEnsureACL(domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    n = virDomainSnapshotObjListNum(vm->snapshots, NULL, flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static int
-qemuDomainListAllSnapshots(virDomainPtr domain,
-                           virDomainSnapshotPtr **snaps,
-                           unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomainObjFromDomain(domain)))
-        return -1;
-
-    if (virDomainListAllSnapshotsEnsureACL(domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    n = virDomainListSnapshots(vm->snapshots, NULL, domain, snaps, flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static int
-qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot,
-                                    char **names,
-                                    int nameslen,
-                                    unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    virDomainMomentObjPtr snap = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
-        return -1;
-
-    if (virDomainSnapshotListChildrenNamesEnsureACL(snapshot->domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto cleanup;
-
-    n = virDomainSnapshotObjListGetNames(vm->snapshots, snap, names, nameslen,
-                                         flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static int
-qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot,
-                              unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    virDomainMomentObjPtr snap = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
-        return -1;
-
-    if (virDomainSnapshotNumChildrenEnsureACL(snapshot->domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto cleanup;
-
-    n = virDomainSnapshotObjListNum(vm->snapshots, snap, flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static int
-qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot,
-                                  virDomainSnapshotPtr **snaps,
-                                  unsigned int flags)
-{
-    virDomainObjPtr vm = NULL;
-    virDomainMomentObjPtr snap = NULL;
-    int n = -1;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
-                  VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL |
-                  VIR_DOMAIN_SNAPSHOT_FILTERS_ALL, -1);
-
-    if (!(vm = qemuDomObjFromSnapshot(snapshot)))
-        return -1;
-
-    if (virDomainSnapshotListAllChildrenEnsureACL(snapshot->domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto cleanup;
-
-    n = virDomainListSnapshots(vm->snapshots, snap, snapshot->domain, snaps,
-                               flags);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return n;
-}
-
-
-static virDomainSnapshotPtr
-qemuDomainSnapshotLookupByName(virDomainPtr domain,
-                               const char *name,
-                               unsigned int flags)
-{
-    virDomainObjPtr vm;
-    virDomainMomentObjPtr snap = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-
-    virCheckFlags(0, NULL);
-
-    if (!(vm = qemuDomainObjFromDomain(domain)))
-        return NULL;
-
-    if (virDomainSnapshotLookupByNameEnsureACL(domain->conn, vm->def) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromName(vm, name)))
-        goto cleanup;
-
-    snapshot = virGetDomainSnapshot(domain, snap->def->name);
-
- cleanup:
-    virDomainObjEndAPI(&vm);
-    return snapshot;
-}
-
-
-static int
-qemuDomainHasCurrentSnapshot(virDomainPtr domain,
-                             unsigned int flags)
+qemuDomainHasCurrentSnapshot(virDomainPtr domain,
+                             unsigned int flags)
 {
     virDomainObjPtr vm;
     int ret = -1;
@@ -15342,601 +13716,44 @@ qemuDomainSnapshotHasMetadata(virDomainSnapshotPtr snapshot,
 }


-/* The domain is expected to be locked and inactive. */
-static int
-qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver,
-                                 virDomainObjPtr vm,
-                                 virDomainMomentObjPtr snap)
-{
-    /* Try all disks, but report failure if we skipped any.  */
-    int ret = qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", true);
-    return ret > 0 ? -1 : ret;
-}
-
-
 static int
 qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
                            unsigned int flags)
 {
-    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
     virDomainObjPtr vm = NULL;
     int ret = -1;
-    virDomainMomentObjPtr snap = NULL;
-    virDomainSnapshotDefPtr snapdef;
-    virObjectEventPtr event = NULL;
-    virObjectEventPtr event2 = NULL;
-    int detail;
-    qemuDomainObjPrivatePtr priv;
-    int rc;
-    virDomainDefPtr config = NULL;
-    virDomainDefPtr inactiveConfig = NULL;
-    g_autoptr(virQEMUDriverConfig) cfg = NULL;
-    bool was_stopped = false;
-    qemuDomainSaveCookiePtr cookie;
-    virCPUDefPtr origCPU = NULL;
-    unsigned int start_flags = VIR_QEMU_PROCESS_START_GEN_VMID;
-    qemuDomainAsyncJob jobType = QEMU_ASYNC_JOB_START;
-    bool defined = false;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
-                  VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
-                  VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1);
-
-    /* We have the following transitions, which create the following events:
-     * 1. inactive -> inactive: none
-     * 2. inactive -> running:  EVENT_STARTED
-     * 3. inactive -> paused:   EVENT_STARTED, EVENT_PAUSED
-     * 4. running  -> inactive: EVENT_STOPPED
-     * 5. running  -> running:  none
-     * 6. running  -> paused:   EVENT_PAUSED
-     * 7. paused   -> inactive: EVENT_STOPPED
-     * 8. paused   -> running:  EVENT_RESUMED
-     * 9. paused   -> paused:   none
-     * Also, several transitions occur even if we fail partway through,
-     * and use of FORCE can cause multiple transitions.
-     */

     virNWFilterReadLockFilterUpdates();

     if (!(vm = qemuDomObjFromSnapshot(snapshot)))
         goto cleanup;

-    priv = vm->privateData;
-    cfg = virQEMUDriverGetConfig(driver);
-
     if (virDomainRevertToSnapshotEnsureACL(snapshot->domain->conn, vm->def) < 0)
         goto cleanup;

-    if (qemuDomainHasBlockjob(vm, false)) {
-        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
-                       _("domain has active block job"));
-        goto cleanup;
-    }
-
-    if (qemuProcessBeginJob(driver, vm,
-                            VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT,
-                            flags) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto endjob;
-    snapdef = virDomainSnapshotObjGetDef(snap);
-
-    if (!vm->persistent &&
-        snapdef->state != VIR_DOMAIN_SNAPSHOT_RUNNING &&
-        snapdef->state != VIR_DOMAIN_SNAPSHOT_PAUSED &&
-        (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
-                  VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) {
-        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
-                       _("transient domain needs to request run or pause "
-                         "to revert to inactive snapshot"));
-        goto endjob;
-    }
-
-    if (virDomainSnapshotIsExternal(snap)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("revert to external snapshot not supported yet"));
-        goto endjob;
-    }
-
-    if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
-        if (!snap->def->dom) {
-            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
-                           _("snapshot '%s' lacks domain '%s' rollback info"),
-                           snap->def->name, vm->def->name);
-            goto endjob;
-        }
-        if (virDomainObjIsActive(vm) &&
-            !(snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
-              snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) &&
-            (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
-                      VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
-            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
-                           _("must respawn qemu to start inactive snapshot"));
-            goto endjob;
-        }
-        if (vm->hasManagedSave &&
-            !(snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
-              snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED)) {
-            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
-                           _("snapshot without memory state, removal of "
-                             "existing managed saved state strongly "
-                             "recommended to avoid corruption"));
-            goto endjob;
-        }
-    }
-
-    if (snap->def->dom) {
-        config = virDomainDefCopy(snap->def->dom,
-                                  driver->xmlopt, priv->qemuCaps, true);
-        if (!config)
-            goto endjob;
-    }
-
-    if (snap->def->inactiveDom) {
-        inactiveConfig = virDomainDefCopy(snap->def->inactiveDom,
-                                          driver->xmlopt, priv->qemuCaps, true);
-        if (!inactiveConfig)
-            goto endjob;
-    } else {
-        /* Inactive domain definition is missing:
-         * - either this is an old active snapshot and we need to copy the
-         *   active definition as an inactive one
-         * - or this is an inactive snapshot which means config contains the
-         *   inactive definition.
-         */
-        if (snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
-            snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) {
-            inactiveConfig = virDomainDefCopy(snap->def->dom,
-                                              driver->xmlopt, priv->qemuCaps, true);
-            if (!inactiveConfig)
-                goto endjob;
-        } else {
-            inactiveConfig = g_steal_pointer(&config);
-        }
-    }
-
-    cookie = (qemuDomainSaveCookiePtr) snapdef->cookie;
-
-    switch ((virDomainSnapshotState) snapdef->state) {
-    case VIR_DOMAIN_SNAPSHOT_RUNNING:
-    case VIR_DOMAIN_SNAPSHOT_PAUSED:
-        start_flags |= VIR_QEMU_PROCESS_START_PAUSED;
-
-        /* Transitions 2, 3, 5, 6, 8, 9 */
-        /* When using the loadvm monitor command, qemu does not know
-         * whether to pause or run the reverted domain, and just stays
-         * in the same state as before the monitor command, whether
-         * that is paused or running.  We always pause before loadvm,
-         * to have finer control.  */
-        if (virDomainObjIsActive(vm)) {
-            /* Transitions 5, 6, 8, 9 */
-            /* Check for ABI compatibility. We need to do this check against
-             * the migratable XML or it will always fail otherwise */
-            if (config) {
-                bool compatible;
-
-                /* Replace the CPU in config and put the original one in priv
-                 * once we're done. When we have the updated CPU def in the
-                 * cookie, we don't want to replace the CPU in migratable def
-                 * when doing ABI checks to make sure the current CPU exactly
-                 * matches the one used at the time the snapshot was taken.
-                 */
-                if (cookie && cookie->cpu && config->cpu) {
-                    origCPU = config->cpu;
-                    if (!(config->cpu = virCPUDefCopy(cookie->cpu)))
-                        goto endjob;
-
-                    compatible = qemuDomainDefCheckABIStability(driver,
-                                                                priv->qemuCaps,
-                                                                vm->def,
-                                                                config);
-                } else {
-                    compatible = qemuDomainCheckABIStability(driver, vm, config);
-                }
-
-                /* If using VM GenID, there is no way currently to change
-                 * the genid for the running guest, so set an error,
-                 * mark as incompatible, and don't allow change of genid
-                 * if the revert force flag would start the guest again. */
-                if (compatible && config->genidRequested) {
-                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                                   _("domain genid update requires restart"));
-                    compatible = false;
-                    start_flags &= ~VIR_QEMU_PROCESS_START_GEN_VMID;
-                }
-
-                if (!compatible) {
-                    virErrorPtr err = virGetLastError();
-
-                    if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
-                        /* Re-spawn error using correct category. */
-                        if (err->code == VIR_ERR_CONFIG_UNSUPPORTED)
-                            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
-                                           err->str2);
-                        goto endjob;
-                    }
-                    virResetError(err);
-                    qemuProcessStop(driver, vm,
-                                    VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
-                                    QEMU_ASYNC_JOB_START, 0);
-                    virDomainAuditStop(vm, "from-snapshot");
-                    detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
-                    event = virDomainEventLifecycleNewFromObj(vm,
-                                                     VIR_DOMAIN_EVENT_STOPPED,
-                                                     detail);
-                    virObjectEventStateQueue(driver->domainEventState, event);
-                    /* Start after stop won't be an async start job, so
-                     * reset to none */
-                    jobType = QEMU_ASYNC_JOB_NONE;
-                    goto load;
-                }
-            }
-
-            if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
-                /* Transitions 5, 6 */
-                if (qemuProcessStopCPUs(driver, vm,
-                                        VIR_DOMAIN_PAUSED_FROM_SNAPSHOT,
-                                        QEMU_ASYNC_JOB_START) < 0)
-                    goto endjob;
-                if (!virDomainObjIsActive(vm)) {
-                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                                   _("guest unexpectedly quit"));
-                    goto endjob;
-                }
-            }
-
-            if (qemuDomainObjEnterMonitorAsync(driver, vm,
-                                               QEMU_ASYNC_JOB_START) < 0)
-                goto endjob;
-            rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name);
-            if (qemuDomainObjExitMonitor(driver, vm) < 0)
-                goto endjob;
-            if (rc < 0) {
-                /* XXX resume domain if it was running before the
-                 * failed loadvm attempt? */
-                goto endjob;
-            }
-            if (config) {
-                virCPUDefFree(priv->origCPU);
-                priv->origCPU = g_steal_pointer(&origCPU);
-            }
-
-            if (cookie && !cookie->slirpHelper)
-                priv->disableSlirp = true;
-
-            if (inactiveConfig) {
-                virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
-                inactiveConfig = NULL;
-                defined = true;
-            }
-        } else {
-            /* Transitions 2, 3 */
-        load:
-            was_stopped = true;
-
-            if (inactiveConfig) {
-                virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
-                inactiveConfig = NULL;
-                defined = true;
-            }
-
-            if (config) {
-                virDomainObjAssignDef(vm, config, true, NULL);
-                config = NULL;
-            }
-
-            /* No cookie means libvirt which saved the domain was too old to
-             * mess up the CPU definitions.
-             */
-            if (cookie &&
-                qemuDomainFixupCPUs(vm, &cookie->cpu) < 0)
-                goto cleanup;
-
-            rc = qemuProcessStart(snapshot->domain->conn, driver, vm,
-                                  cookie ? cookie->cpu : NULL,
-                                  jobType, NULL, -1, NULL, snap,
-                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
-                                  start_flags);
-            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
-            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-            event = virDomainEventLifecycleNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STARTED,
-                                             detail);
-            if (rc < 0)
-                goto endjob;
-        }
-
-        /* Touch up domain state.  */
-        if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) &&
-            (snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED ||
-             (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
-            /* Transitions 3, 6, 9 */
-            virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
-                                 VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
-            if (was_stopped) {
-                /* Transition 3, use event as-is and add event2 */
-                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
-                event2 = virDomainEventLifecycleNewFromObj(vm,
-                                                  VIR_DOMAIN_EVENT_SUSPENDED,
-                                                  detail);
-            } /* else transition 6 and 9 use event as-is */
-        } else {
-            /* Transitions 2, 5, 8 */
-            if (!virDomainObjIsActive(vm)) {
-                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                               _("guest unexpectedly quit"));
-                goto endjob;
-            }
-            rc = qemuProcessStartCPUs(driver, vm,
-                                      VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
-                                      jobType);
-            if (rc < 0)
-                goto endjob;
-            virObjectUnref(event);
-            event = NULL;
-            if (was_stopped) {
-                /* Transition 2 */
-                detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-                event = virDomainEventLifecycleNewFromObj(vm,
-                                                 VIR_DOMAIN_EVENT_STARTED,
-                                                 detail);
-            }
-        }
-        break;
-
-    case VIR_DOMAIN_SNAPSHOT_SHUTDOWN:
-    case VIR_DOMAIN_SNAPSHOT_SHUTOFF:
-    case VIR_DOMAIN_SNAPSHOT_CRASHED:
-        /* Transitions 1, 4, 7 */
-        /* Newer qemu -loadvm refuses to revert to the state of a snapshot
-         * created by qemu-img snapshot -c.  If the domain is running, we
-         * must take it offline; then do the revert using qemu-img.
-         */
-
-        if (virDomainObjIsActive(vm)) {
-            /* Transitions 4, 7 */
-            qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
-                            QEMU_ASYNC_JOB_START, 0);
-            virDomainAuditStop(vm, "from-snapshot");
-            detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
-            event = virDomainEventLifecycleNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STOPPED,
-                                             detail);
-        }
-
-        if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) {
-            qemuDomainRemoveInactive(driver, vm);
-            qemuProcessEndJob(driver, vm);
-            goto cleanup;
-        }
-
-        if (inactiveConfig) {
-            virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
-            inactiveConfig = NULL;
-            defined = true;
-        }
-
-        if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
-                     VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {
-            /* Flush first event, now do transition 2 or 3 */
-            bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0;
-
-            start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;
-
-            virObjectEventStateQueue(driver->domainEventState, event);
-            rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL,
-                                  QEMU_ASYNC_JOB_START, NULL, -1, NULL, NULL,
-                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
-                                  start_flags);
-            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
-            if (rc < 0) {
-                qemuDomainRemoveInactive(driver, vm);
-                qemuProcessEndJob(driver, vm);
-                goto cleanup;
-            }
-            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-            event = virDomainEventLifecycleNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STARTED,
-                                             detail);
-            if (paused) {
-                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
-                event2 = virDomainEventLifecycleNewFromObj(vm,
-                                                  VIR_DOMAIN_EVENT_SUSPENDED,
-                                                  detail);
-            }
-        }
-        break;
-
-    case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED:
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("qemu doesn't support reversion of snapshot taken in "
-                         "PMSUSPENDED state"));
-        goto endjob;
-
-    case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT:
-        /* Rejected earlier as an external snapshot */
-    case VIR_DOMAIN_SNAPSHOT_NOSTATE:
-    case VIR_DOMAIN_SNAPSHOT_BLOCKED:
-    case VIR_DOMAIN_SNAPSHOT_LAST:
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("Invalid target domain state '%s'. Refusing "
-                         "snapshot reversion"),
-                       virDomainSnapshotStateTypeToString(snapdef->state));
-        goto endjob;
-    }
-
-    ret = 0;
-
- endjob:
-    qemuProcessEndJob(driver, vm);
+    ret = qemuSnapshotRevert(vm, snapshot, flags);

  cleanup:
-    if (ret == 0) {
-        virDomainSnapshotSetCurrent(vm->snapshots, snap);
-        if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                            driver->xmlopt,
-                                            cfg->snapshotDir) < 0) {
-            virDomainSnapshotSetCurrent(vm->snapshots, NULL);
-            ret = -1;
-        }
-    }
-    if (ret == 0 && defined && vm->persistent &&
-        !(ret = virDomainDefSave(vm->newDef ? vm->newDef : vm->def,
-                                 driver->xmlopt, cfg->configDir))) {
-        detail = VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT;
-        virObjectEventStateQueue(driver->domainEventState,
-            virDomainEventLifecycleNewFromObj(vm,
-                                              VIR_DOMAIN_EVENT_DEFINED,
-                                              detail));
-    }
-    virObjectEventStateQueue(driver->domainEventState, event);
-    virObjectEventStateQueue(driver->domainEventState, event2);
     virDomainObjEndAPI(&vm);
     virNWFilterUnlockFilterUpdates();
-    virCPUDefFree(origCPU);
-    virDomainDefFree(config);
-    virDomainDefFree(inactiveConfig);
-
     return ret;
 }


-typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
-typedef virQEMUMomentReparent *virQEMUMomentReparentPtr;
-struct _virQEMUMomentReparent {
-    const char *dir;
-    virDomainMomentObjPtr parent;
-    virDomainObjPtr vm;
-    virDomainXMLOptionPtr xmlopt;
-    int err;
-    int (*writeMetadata)(virDomainObjPtr, virDomainMomentObjPtr,
-                         virDomainXMLOptionPtr, const char *);
-};
-
-
-static int
-qemuDomainMomentReparentChildren(void *payload,
-                                 const void *name G_GNUC_UNUSED,
-                                 void *data)
-{
-    virDomainMomentObjPtr moment = payload;
-    virQEMUMomentReparentPtr rep = data;
-
-    if (rep->err < 0)
-        return 0;
-
-    VIR_FREE(moment->def->parent_name);
-
-    if (rep->parent->def)
-        moment->def->parent_name = g_strdup(rep->parent->def->name);
-
-    rep->err = rep->writeMetadata(rep->vm, moment, rep->xmlopt,
-                                  rep->dir);
-    return 0;
-}
-
-
 static int
 qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
                          unsigned int flags)
 {
-    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
     virDomainObjPtr vm = NULL;
     int ret = -1;
-    virDomainMomentObjPtr snap = NULL;
-    virQEMUMomentRemove rem;
-    virQEMUMomentReparent rep;
-    bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
-    int external = 0;
-    g_autoptr(virQEMUDriverConfig) cfg = NULL;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
-                  VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
-                  VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1);

     if (!(vm = qemuDomObjFromSnapshot(snapshot)))
         return -1;

-    cfg = virQEMUDriverGetConfig(driver);
-
     if (virDomainSnapshotDeleteEnsureACL(snapshot->domain->conn, vm->def) < 0)
         goto cleanup;

-    if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
-        goto cleanup;
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto endjob;
-
-    if (!metadata_only) {
-        if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) &&
-            virDomainSnapshotIsExternal(snap))
-            external++;
-        if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
-                     VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))
-            virDomainMomentForEachDescendant(snap,
-                                             qemuDomainSnapshotCountExternal,
-                                             &external);
-        if (external) {
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                           _("deletion of %d external disk snapshots not "
-                             "supported yet"), external);
-            goto endjob;
-        }
-    }
-
-    if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
-                 VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
-        rem.driver = driver;
-        rem.vm = vm;
-        rem.metadata_only = metadata_only;
-        rem.err = 0;
-        rem.current = virDomainSnapshotGetCurrent(vm->snapshots);
-        rem.found = false;
-        rem.momentDiscard = qemuDomainSnapshotDiscard;
-        virDomainMomentForEachDescendant(snap, qemuDomainMomentDiscardAll,
-                                         &rem);
-        if (rem.err < 0)
-            goto endjob;
-        if (rem.found) {
-            virDomainSnapshotSetCurrent(vm->snapshots, snap);
-            if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
-                if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                                    driver->xmlopt,
-                                                    cfg->snapshotDir) < 0) {
-                    virReportError(VIR_ERR_INTERNAL_ERROR,
-                                   _("failed to set snapshot '%s' as current"),
-                                   snap->def->name);
-                    virDomainSnapshotSetCurrent(vm->snapshots, NULL);
-                    goto endjob;
-                }
-            }
-        }
-    } else if (snap->nchildren) {
-        rep.dir = cfg->snapshotDir;
-        rep.parent = snap->parent;
-        rep.vm = vm;
-        rep.err = 0;
-        rep.xmlopt = driver->xmlopt;
-        rep.writeMetadata = qemuDomainSnapshotWriteMetadata;
-        virDomainMomentForEachChild(snap,
-                                    qemuDomainMomentReparentChildren,
-                                    &rep);
-        if (rep.err < 0)
-            goto endjob;
-        virDomainMomentMoveChildren(snap, snap->parent);
-    }
-
-    if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
-        virDomainMomentDropChildren(snap);
-        ret = 0;
-    } else {
-        ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only);
-    }
-
- endjob:
-    qemuDomainObjEndJob(driver, vm);
+    ret = qemuSnapshotDelete(vm, snapshot, flags);

  cleanup:
     virDomainObjEndAPI(&vm);
@@ -19593,7 +17410,7 @@ qemuDomainFSFreeze(virDomainPtr dom,
     if (virDomainObjCheckActive(vm) < 0)
         goto endjob;

-    ret = qemuDomainSnapshotFSFreeze(vm, mountpoints, nmountpoints);
+    ret = qemuSnapshotFSFreeze(vm, mountpoints, nmountpoints);

  endjob:
     qemuDomainObjEndAgentJob(vm);
@@ -19634,7 +17451,7 @@ qemuDomainFSThaw(virDomainPtr dom,
     if (virDomainObjCheckActive(vm) < 0)
         goto endjob;

-    ret = qemuDomainSnapshotFSThaw(vm, true);
+    ret = qemuSnapshotFSThaw(vm, true);

  endjob:
     qemuDomainObjEndAgentJob(vm);
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
new file mode 100644
index 0000000000..1e8ea80b22
--- /dev/null
+++ b/src/qemu/qemu_snapshot.c
@@ -0,0 +1,2266 @@
+/*
+ * qemu_snapshot.c: snapshot related implementation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "qemu_snapshot.h"
+
+#include "qemu_monitor.h"
+#include "qemu_domain.h"
+#include "qemu_block.h"
+#include "qemu_process.h"
+#include "qemu_migration.h"
+#include "qemu_command.h"
+#include "qemu_security.h"
+#include "qemu_saveimage.h"
+
+#include "virerror.h"
+#include "virlog.h"
+#include "datatypes.h"
+#include "viralloc.h"
+#include "domain_conf.h"
+#include "domain_audit.h"
+#include "locking/domain_lock.h"
+#include "libvirt_internal.h"
+#include "virxml.h"
+#include "virstring.h"
+#include "virdomainsnapshotobjlist.h"
+#include "virqemu.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_snapshot");
+
+
+/* Looks up snapshot object from VM and name */
+virDomainMomentObjPtr
+qemuSnapObjFromName(virDomainObjPtr vm,
+                    const char *name)
+{
+    virDomainMomentObjPtr snap = NULL;
+    snap = virDomainSnapshotFindByName(vm->snapshots, name);
+    if (!snap)
+        virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
+                       _("no domain snapshot with matching name '%s'"),
+                       name);
+
+    return snap;
+}
+
+
+/* Looks up snapshot object from VM and snapshotPtr */
+virDomainMomentObjPtr
+qemuSnapObjFromSnapshot(virDomainObjPtr vm,
+                        virDomainSnapshotPtr snapshot)
+{
+    return qemuSnapObjFromName(vm, snapshot->name);
+}
+
+
+/* Count how many snapshots in a set are external snapshots.  */
+static int
+qemuSnapshotCountExternal(void *payload,
+                          const void *name G_GNUC_UNUSED,
+                          void *data)
+{
+    virDomainMomentObjPtr snap = payload;
+    int *count = data;
+
+    if (virDomainSnapshotIsExternal(snap))
+        (*count)++;
+    return 0;
+}
+
+
+/* Return -1 if request is not sent to agent due to misconfig, -2 if request
+ * is sent but failed, and number of frozen filesystems on success. If -2 is
+ * returned, FSThaw should be called revert the quiesced status. */
+int
+qemuSnapshotFSFreeze(virDomainObjPtr vm,
+                     const char **mountpoints,
+                     unsigned int nmountpoints)
+{
+    qemuAgentPtr agent;
+    int frozen;
+
+    if (!qemuDomainAgentAvailable(vm, true))
+        return -1;
+
+    agent = qemuDomainObjEnterAgent(vm);
+    frozen = qemuAgentFSFreeze(agent, mountpoints, nmountpoints);
+    qemuDomainObjExitAgent(vm, agent);
+    return frozen < 0 ? -2 : frozen;
+}
+
+
+/* Return -1 on error, otherwise number of thawed filesystems. */
+int
+qemuSnapshotFSThaw(virDomainObjPtr vm,
+                   bool report)
+{
+    qemuAgentPtr agent;
+    int thawed;
+    virErrorPtr err = NULL;
+
+    if (!qemuDomainAgentAvailable(vm, report))
+        return -1;
+
+    agent = qemuDomainObjEnterAgent(vm);
+    if (!report)
+        virErrorPreserveLast(&err);
+    thawed = qemuAgentFSThaw(agent);
+    qemuDomainObjExitAgent(vm, agent);
+
+    virErrorRestore(&err);
+
+    return thawed;
+}
+
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuSnapshotCreateInactiveInternal(virQEMUDriverPtr driver,
+                                   virDomainObjPtr vm,
+                                   virDomainMomentObjPtr snap)
+{
+    return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false);
+}
+
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuSnapshotCreateInactiveExternal(virQEMUDriverPtr driver,
+                                   virDomainObjPtr vm,
+                                   virDomainMomentObjPtr snap,
+                                   bool reuse)
+{
+    size_t i;
+    virDomainSnapshotDiskDefPtr snapdisk;
+    virDomainDiskDefPtr defdisk;
+    virCommandPtr cmd = NULL;
+    const char *qemuImgPath;
+    virBitmapPtr created = NULL;
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+    int ret = -1;
+    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
+
+    if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
+        goto cleanup;
+
+    if (!(created = virBitmapNew(snapdef->ndisks)))
+        goto cleanup;
+
+    /* If reuse is true, then qemuSnapshotPrepare already
+     * ensured that the new files exist, and it was up to the user to
+     * create them correctly.  */
+    for (i = 0; i < snapdef->ndisks && !reuse; i++) {
+        snapdisk = &(snapdef->disks[i]);
+        defdisk = snapdef->parent.dom->disks[snapdisk->idx];
+        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
+            continue;
+
+        if (!snapdisk->src->format)
+            snapdisk->src->format = VIR_STORAGE_FILE_QCOW2;
+
+        if (qemuDomainStorageSourceValidateDepth(defdisk->src, 1, defdisk->dst) < 0)
+            goto cleanup;
+
+        /* creates cmd line args: qemu-img create -f qcow2 -o */
+        if (!(cmd = virCommandNewArgList(qemuImgPath,
+                                         "create",
+                                         "-f",
+                                         virStorageFileFormatTypeToString(snapdisk->src->format),
+                                         "-o",
+                                         NULL)))
+            goto cleanup;
+
+        /* adds cmd line arg: backing_fmt=format,backing_file=/path/to/backing/file */
+        virBufferAsprintf(&buf, "backing_fmt=%s,backing_file=",
+                          virStorageFileFormatTypeToString(defdisk->src->format));
+        virQEMUBuildBufferEscapeComma(&buf, defdisk->src->path);
+        virCommandAddArgBuffer(cmd, &buf);
+
+        /* adds cmd line args: /path/to/target/file */
+        virQEMUBuildBufferEscapeComma(&buf, snapdisk->src->path);
+        virCommandAddArgBuffer(cmd, &buf);
+
+        /* If the target does not exist, we're going to create it possibly */
+        if (!virFileExists(snapdisk->src->path))
+            ignore_value(virBitmapSetBit(created, i));
+
+        if (virCommandRun(cmd, NULL) < 0)
+            goto cleanup;
+
+        virCommandFree(cmd);
+        cmd = NULL;
+    }
+
+    /* update disk definitions */
+    for (i = 0; i < snapdef->ndisks; i++) {
+        g_autoptr(virStorageSource) newsrc = NULL;
+
+        snapdisk = &(snapdef->disks[i]);
+        defdisk = vm->def->disks[snapdisk->idx];
+
+        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
+            continue;
+
+        if (!(newsrc = virStorageSourceCopy(snapdisk->src, false)))
+            goto cleanup;
+
+        if (virStorageSourceInitChainElement(newsrc, defdisk->src, false) < 0)
+            goto cleanup;
+
+        if (!reuse &&
+            virStorageSourceHasBacking(defdisk->src)) {
+            defdisk->src->readonly = true;
+            newsrc->backingStore = g_steal_pointer(&defdisk->src);
+        } else {
+            virObjectUnref(defdisk->src);
+        }
+
+        defdisk->src = g_steal_pointer(&newsrc);
+    }
+
+    if (virDomainDefSave(vm->def, driver->xmlopt, cfg->configDir) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virCommandFree(cmd);
+
+    /* unlink images if creation has failed */
+    if (ret < 0 && created) {
+        ssize_t bit = -1;
+        while ((bit = virBitmapNextSetBit(created, bit)) >= 0) {
+            snapdisk = &(snapdef->disks[bit]);
+            if (unlink(snapdisk->src->path) < 0)
+                VIR_WARN("Failed to remove snapshot image '%s'",
+                         snapdisk->src->path);
+        }
+    }
+    virBitmapFree(created);
+
+    return ret;
+}
+
+
+/* The domain is expected to be locked and active. */
+static int
+qemuSnapshotCreateActiveInternal(virQEMUDriverPtr driver,
+                                 virDomainObjPtr vm,
+                                 virDomainMomentObjPtr snap,
+                                 unsigned int flags)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virObjectEventPtr event = NULL;
+    bool resume = false;
+    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
+    int ret = -1;
+
+    if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0))
+        goto cleanup;
+
+    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+        /* savevm monitor command pauses the domain emitting an event which
+         * confuses libvirt since it's not notified when qemu resumes the
+         * domain. Thus we stop and start CPUs ourselves.
+         */
+        if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE,
+                                QEMU_ASYNC_JOB_SNAPSHOT) < 0)
+            goto cleanup;
+
+        resume = true;
+        if (!virDomainObjIsActive(vm)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("guest unexpectedly quit"));
+            goto cleanup;
+        }
+    }
+
+    if (qemuDomainObjEnterMonitorAsync(driver, vm,
+                                       QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
+        resume = false;
+        goto cleanup;
+    }
+
+    ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name);
+    if (qemuDomainObjExitMonitor(driver, vm) < 0)
+        ret = -1;
+    if (ret < 0)
+        goto cleanup;
+
+    if (!(snapdef->cookie = (virObjectPtr) qemuDomainSaveCookieNew(vm)))
+        goto cleanup;
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
+        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
+        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
+                        QEMU_ASYNC_JOB_SNAPSHOT, 0);
+        virDomainAuditStop(vm, "from-snapshot");
+        resume = false;
+    }
+
+ cleanup:
+    if (resume && virDomainObjIsActive(vm) &&
+        qemuProcessStartCPUs(driver, vm,
+                             VIR_DOMAIN_RUNNING_UNPAUSED,
+                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
+        event = virDomainEventLifecycleNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
+        if (virGetLastErrorCode() == VIR_ERR_OK) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("resuming after snapshot failed"));
+        }
+    }
+
+    virObjectEventStateQueue(driver->domainEventState, event);
+
+    return ret;
+}
+
+
+static int
+qemuSnapshotPrepareDiskShared(virDomainSnapshotDiskDefPtr snapdisk,
+                              virDomainDiskDefPtr domdisk)
+{
+    if (!domdisk->src->shared || domdisk->src->readonly)
+        return 0;
+
+    if (!qemuBlockStorageSourceSupportsConcurrentAccess(snapdisk->src)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("shared access for disk '%s' requires use of "
+                         "supported storage format"), domdisk->dst);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDefPtr snapdisk,
+                                        virDomainDiskDefPtr domdisk)
+{
+    int domDiskType = virStorageSourceGetActualType(domdisk->src);
+    int snapDiskType = virStorageSourceGetActualType(snapdisk->src);
+
+    switch ((virStorageType)domDiskType) {
+    case VIR_STORAGE_TYPE_BLOCK:
+    case VIR_STORAGE_TYPE_FILE:
+        break;
+
+    case VIR_STORAGE_TYPE_NETWORK:
+        switch ((virStorageNetProtocol) domdisk->src->protocol) {
+        case VIR_STORAGE_NET_PROTOCOL_NONE:
+        case VIR_STORAGE_NET_PROTOCOL_NBD:
+        case VIR_STORAGE_NET_PROTOCOL_RBD:
+        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+        case VIR_STORAGE_NET_PROTOCOL_HTTP:
+        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+        case VIR_STORAGE_NET_PROTOCOL_FTP:
+        case VIR_STORAGE_NET_PROTOCOL_FTPS:
+        case VIR_STORAGE_NET_PROTOCOL_TFTP:
+        case VIR_STORAGE_NET_PROTOCOL_SSH:
+        case VIR_STORAGE_NET_PROTOCOL_VXHS:
+        case VIR_STORAGE_NET_PROTOCOL_LAST:
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("external inactive snapshots are not supported on "
+                             "'network' disks using '%s' protocol"),
+                           virStorageNetProtocolTypeToString(domdisk->src->protocol));
+            return -1;
+        }
+        break;
+
+    case VIR_STORAGE_TYPE_DIR:
+    case VIR_STORAGE_TYPE_VOLUME:
+    case VIR_STORAGE_TYPE_NVME:
+    case VIR_STORAGE_TYPE_NONE:
+    case VIR_STORAGE_TYPE_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("external inactive snapshots are not supported on "
+                         "'%s' disks"), virStorageTypeToString(domDiskType));
+        return -1;
+    }
+
+    switch ((virStorageType)snapDiskType) {
+    case VIR_STORAGE_TYPE_BLOCK:
+    case VIR_STORAGE_TYPE_FILE:
+        break;
+
+    case VIR_STORAGE_TYPE_NETWORK:
+    case VIR_STORAGE_TYPE_DIR:
+    case VIR_STORAGE_TYPE_VOLUME:
+    case VIR_STORAGE_TYPE_NVME:
+    case VIR_STORAGE_TYPE_NONE:
+    case VIR_STORAGE_TYPE_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("external inactive snapshots are not supported on "
+                         "'%s' disks"), virStorageTypeToString(snapDiskType));
+        return -1;
+    }
+
+    if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotPrepareDiskExternalActive(virDomainObjPtr vm,
+                                      virDomainSnapshotDiskDefPtr snapdisk,
+                                      virDomainDiskDefPtr domdisk,
+                                      bool blockdev)
+{
+    int actualType = virStorageSourceGetActualType(snapdisk->src);
+
+    if (domdisk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("external active snapshots are not supported on scsi "
+                         "passthrough devices"));
+        return -1;
+    }
+
+    if (!qemuDomainDiskBlockJobIsSupported(vm, domdisk))
+        return -1;
+
+    switch ((virStorageType)actualType) {
+    case VIR_STORAGE_TYPE_BLOCK:
+    case VIR_STORAGE_TYPE_FILE:
+        break;
+
+    case VIR_STORAGE_TYPE_NETWORK:
+        /* defer all of the checking to either qemu or libvirt's blockdev code */
+        if (blockdev)
+            break;
+
+        switch ((virStorageNetProtocol) snapdisk->src->protocol) {
+        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+            break;
+
+        case VIR_STORAGE_NET_PROTOCOL_NONE:
+        case VIR_STORAGE_NET_PROTOCOL_NBD:
+        case VIR_STORAGE_NET_PROTOCOL_RBD:
+        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+        case VIR_STORAGE_NET_PROTOCOL_HTTP:
+        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+        case VIR_STORAGE_NET_PROTOCOL_FTP:
+        case VIR_STORAGE_NET_PROTOCOL_FTPS:
+        case VIR_STORAGE_NET_PROTOCOL_TFTP:
+        case VIR_STORAGE_NET_PROTOCOL_SSH:
+        case VIR_STORAGE_NET_PROTOCOL_VXHS:
+        case VIR_STORAGE_NET_PROTOCOL_LAST:
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("external active snapshots are not supported on "
+                             "'network' disks using '%s' protocol"),
+                           virStorageNetProtocolTypeToString(snapdisk->src->protocol));
+            return -1;
+
+        }
+        break;
+
+    case VIR_STORAGE_TYPE_DIR:
+    case VIR_STORAGE_TYPE_VOLUME:
+    case VIR_STORAGE_TYPE_NVME:
+    case VIR_STORAGE_TYPE_NONE:
+    case VIR_STORAGE_TYPE_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("external active snapshots are not supported on "
+                         "'%s' disks"), virStorageTypeToString(actualType));
+        return -1;
+    }
+
+    if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotPrepareDiskExternal(virDomainObjPtr vm,
+                                virDomainDiskDefPtr disk,
+                                virDomainSnapshotDiskDefPtr snapdisk,
+                                bool active,
+                                bool reuse,
+                                bool blockdev)
+{
+    struct stat st;
+    int err;
+    int rc;
+
+    if (disk->src->readonly && !(reuse || blockdev)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("external snapshot for readonly disk %s "
+                         "is not supported"), disk->dst);
+        return -1;
+    }
+
+    if (qemuTranslateSnapshotDiskSourcePool(snapdisk) < 0)
+        return -1;
+
+    if (!active) {
+        if (virDomainDiskTranslateSourcePool(disk) < 0)
+            return -1;
+
+        if (qemuSnapshotPrepareDiskExternalInactive(snapdisk, disk) < 0)
+            return -1;
+    } else {
+        if (qemuSnapshotPrepareDiskExternalActive(vm, snapdisk, disk, blockdev) < 0)
+            return -1;
+    }
+
+    if (virStorageSourceIsLocalStorage(snapdisk->src)) {
+        if (virStorageFileInit(snapdisk->src) < 0)
+            return -1;
+
+        rc = virStorageFileStat(snapdisk->src, &st);
+        err = errno;
+
+        virStorageFileDeinit(snapdisk->src);
+
+        if (rc < 0) {
+            if (err != ENOENT) {
+                virReportSystemError(err,
+                                     _("unable to stat for disk %s: %s"),
+                                     snapdisk->name, snapdisk->src->path);
+                return -1;
+            } else if (reuse) {
+                virReportSystemError(err,
+                                     _("missing existing file for disk %s: %s"),
+                                     snapdisk->name, snapdisk->src->path);
+                return -1;
+            }
+        } else if (!S_ISBLK(st.st_mode) && st.st_size && !reuse) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("external snapshot file for disk %s already "
+                             "exists and is not a block device: %s"),
+                           snapdisk->name, snapdisk->src->path);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotPrepareDiskInternal(virDomainDiskDefPtr disk,
+                                bool active)
+{
+    int actualType;
+
+    /* active disks are handled by qemu itself so no need to worry about those */
+    if (active)
+        return 0;
+
+    if (virDomainDiskTranslateSourcePool(disk) < 0)
+        return -1;
+
+    actualType = virStorageSourceGetActualType(disk->src);
+
+    switch ((virStorageType)actualType) {
+    case VIR_STORAGE_TYPE_BLOCK:
+    case VIR_STORAGE_TYPE_FILE:
+        return 0;
+
+    case VIR_STORAGE_TYPE_NETWORK:
+        switch ((virStorageNetProtocol) disk->src->protocol) {
+        case VIR_STORAGE_NET_PROTOCOL_NONE:
+        case VIR_STORAGE_NET_PROTOCOL_NBD:
+        case VIR_STORAGE_NET_PROTOCOL_RBD:
+        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+        case VIR_STORAGE_NET_PROTOCOL_HTTP:
+        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+        case VIR_STORAGE_NET_PROTOCOL_FTP:
+        case VIR_STORAGE_NET_PROTOCOL_FTPS:
+        case VIR_STORAGE_NET_PROTOCOL_TFTP:
+        case VIR_STORAGE_NET_PROTOCOL_SSH:
+        case VIR_STORAGE_NET_PROTOCOL_VXHS:
+        case VIR_STORAGE_NET_PROTOCOL_LAST:
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("internal inactive snapshots are not supported on "
+                             "'network' disks using '%s' protocol"),
+                           virStorageNetProtocolTypeToString(disk->src->protocol));
+            return -1;
+        }
+        break;
+
+    case VIR_STORAGE_TYPE_DIR:
+    case VIR_STORAGE_TYPE_VOLUME:
+    case VIR_STORAGE_TYPE_NVME:
+    case VIR_STORAGE_TYPE_NONE:
+    case VIR_STORAGE_TYPE_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("internal inactive snapshots are not supported on "
+                         "'%s' disks"), virStorageTypeToString(actualType));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotPrepare(virDomainObjPtr vm,
+                    virDomainSnapshotDefPtr def,
+                    unsigned int *flags)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
+    size_t i;
+    bool active = virDomainObjIsActive(vm);
+    bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
+    bool found_internal = false;
+    bool forbid_internal = false;
+    int external = 0;
+
+    for (i = 0; i < def->ndisks; i++) {
+        virDomainSnapshotDiskDefPtr disk = &def->disks[i];
+        virDomainDiskDefPtr dom_disk = vm->def->disks[i];
+
+        if (disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NONE &&
+            qemuDomainDiskBlockJobIsActive(dom_disk))
+            return -1;
+
+        switch ((virDomainSnapshotLocation) disk->snapshot) {
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL:
+            found_internal = true;
+
+            if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && active) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("active qemu domains require external disk "
+                                 "snapshots; disk %s requested internal"),
+                               disk->name);
+                return -1;
+            }
+
+            if (qemuSnapshotPrepareDiskInternal(dom_disk,
+                                                active) < 0)
+                return -1;
+
+            if (dom_disk->src->format > 0 &&
+                dom_disk->src->format != VIR_STORAGE_FILE_QCOW2) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("internal snapshot for disk %s unsupported "
+                                 "for storage type %s"),
+                               disk->name,
+                               virStorageFileFormatTypeToString(dom_disk->src->format));
+                return -1;
+            }
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL:
+            if (!disk->src->format) {
+                disk->src->format = VIR_STORAGE_FILE_QCOW2;
+            } else if (disk->src->format != VIR_STORAGE_FILE_QCOW2 &&
+                       disk->src->format != VIR_STORAGE_FILE_QED) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("external snapshot format for disk %s "
+                                 "is unsupported: %s"),
+                               disk->name,
+                               virStorageFileFormatTypeToString(disk->src->format));
+                return -1;
+            }
+
+            if (qemuSnapshotPrepareDiskExternal(vm, dom_disk, disk,
+                                                active, reuse, blockdev) < 0)
+                return -1;
+
+            external++;
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE:
+            /* Remember seeing a disk that has snapshot disabled */
+            if (!virStorageSourceIsEmpty(dom_disk->src) &&
+                !dom_disk->src->readonly)
+                forbid_internal = true;
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT:
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_LAST:
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("unexpected code path"));
+            return -1;
+        }
+    }
+
+    if (!found_internal && !external &&
+        def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("nothing selected for snapshot"));
+        return -1;
+    }
+
+    /* internal snapshot requires a disk image to store the memory image to, and
+     * also disks can't be excluded from an internal snapshot */
+    if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && !found_internal) ||
+        (found_internal && forbid_internal)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("internal and full system snapshots require all "
+                         "disks to be selected for snapshot"));
+        return -1;
+    }
+
+    /* disk snapshot requires at least one disk */
+    if (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT && !external) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("disk-only snapshots require at least "
+                         "one disk to be selected for snapshot"));
+        return -1;
+    }
+
+    /* For now, we don't allow mixing internal and external disks.
+     * XXX technically, we could mix internal and external disks for
+     * offline snapshots */
+    if ((found_internal && external) ||
+         (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL && external) ||
+         (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL && found_internal)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("mixing internal and external targets for a snapshot "
+                         "is not yet supported"));
+        return -1;
+    }
+
+    /* internal snapshots + pflash based loader have the following problems:
+     * - if the variable store is raw, the snapshot fails
+     * - allowing a qcow2 image as the varstore would make it eligible to receive
+     *   the vmstate dump, which would make it huge
+     * - offline snapshot would not snapshot the varstore at all
+     *
+     * Avoid the issues by forbidding internal snapshot with pflash completely.
+     */
+    if (found_internal &&
+        virDomainDefHasOldStyleUEFI(vm->def)) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("internal snapshots of a VM with pflash based "
+                         "firmware are not supported"));
+        return -1;
+    }
+
+    /* Alter flags to let later users know what we learned.  */
+    if (external && !active)
+        *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
+
+    return 0;
+}
+
+
+struct _qemuSnapshotDiskData {
+    virStorageSourcePtr src;
+    bool initialized; /* @src was initialized in the storage driver */
+    bool created; /* @src was created by the snapshot code */
+    bool prepared; /* @src was prepared using qemuDomainStorageSourceAccessAllow */
+    virDomainDiskDefPtr disk;
+    char *relPath; /* relative path component to fill into original disk */
+    qemuBlockStorageSourceChainDataPtr crdata;
+    bool blockdevadded;
+
+    virStorageSourcePtr persistsrc;
+    virDomainDiskDefPtr persistdisk;
+};
+
+typedef struct _qemuSnapshotDiskData qemuSnapshotDiskData;
+typedef qemuSnapshotDiskData *qemuSnapshotDiskDataPtr;
+
+
+static void
+qemuSnapshotDiskCleanup(qemuSnapshotDiskDataPtr data,
+                        size_t ndata,
+                        virQEMUDriverPtr driver,
+                        virDomainObjPtr vm,
+                        qemuDomainAsyncJob asyncJob)
+{
+    virErrorPtr orig_err;
+    size_t i;
+
+    if (!data)
+        return;
+
+    virErrorPreserveLast(&orig_err);
+
+    for (i = 0; i < ndata; i++) {
+        /* on success of the snapshot the 'src' and 'persistsrc' properties will
+         * be set to NULL by qemuSnapshotDiskUpdateSource */
+        if (data[i].src) {
+            if (data[i].blockdevadded) {
+                if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) == 0) {
+
+                    qemuBlockStorageSourceAttachRollback(qemuDomainGetMonitor(vm),
+                                                         data[i].crdata->srcdata[0]);
+                    ignore_value(qemuDomainObjExitMonitor(driver, vm));
+                }
+            }
+
+            if (data[i].created &&
+                virStorageFileUnlink(data[i].src) < 0) {
+                VIR_WARN("Unable to remove just-created %s",
+                         NULLSTR(data[i].src->path));
+            }
+
+            if (data[i].initialized)
+                virStorageFileDeinit(data[i].src);
+
+            if (data[i].prepared)
+                qemuDomainStorageSourceAccessRevoke(driver, vm, data[i].src);
+
+            virObjectUnref(data[i].src);
+        }
+        virObjectUnref(data[i].persistsrc);
+        VIR_FREE(data[i].relPath);
+        qemuBlockStorageSourceChainDataFree(data[i].crdata);
+    }
+
+    VIR_FREE(data);
+    virErrorRestore(&orig_err);
+}
+
+
+/**
+ * qemuSnapshotDiskBitmapsPropagate:
+ *
+ * This function propagates any active persistent bitmap present in the original
+ * image into the new snapshot. This is necessary to keep tracking the changed
+ * blocks in the active bitmaps as the backing file will become read-only.
+ * We leave the original bitmap active as in cases when the overlay is
+ * discarded (snapshot revert with abandoning the history) everything works as
+ * expected.
+ */
+static int
+qemuSnapshotDiskBitmapsPropagate(qemuSnapshotDiskDataPtr dd,
+                                 virJSONValuePtr actions,
+                                 virHashTablePtr blockNamedNodeData)
+{
+    qemuBlockNamedNodeDataPtr entry;
+    size_t i;
+
+    if (!(entry = virHashLookup(blockNamedNodeData, dd->disk->src->nodeformat)))
+        return 0;
+
+    for (i = 0; i < entry->nbitmaps; i++) {
+        qemuBlockNamedNodeDataBitmapPtr bitmap = entry->bitmaps[i];
+
+        /* we don't care about temporary, inconsistent, or disabled bitmaps */
+        if (!bitmap->persistent || !bitmap->recording || bitmap->inconsistent)
+            continue;
+
+        if (qemuMonitorTransactionBitmapAdd(actions, dd->src->nodeformat,
+                                            bitmap->name, true, false,
+                                            bitmap->granularity) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+qemuSnapshotDiskPrepareOneBlockdev(virQEMUDriverPtr driver,
+                                   virDomainObjPtr vm,
+                                   qemuSnapshotDiskDataPtr dd,
+                                   virQEMUDriverConfigPtr cfg,
+                                   bool reuse,
+                                   virHashTablePtr blockNamedNodeData,
+                                   qemuDomainAsyncJob asyncJob)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    g_autoptr(virStorageSource) terminator = NULL;
+    int rc;
+
+    /* create a terminator for the snapshot disks so that qemu does not try
+     * to open them at first */
+    if (!(terminator = virStorageSourceNew()))
+        return -1;
+
+    if (qemuDomainPrepareStorageSourceBlockdev(dd->disk, dd->src,
+                                               priv, cfg) < 0)
+        return -1;
+
+    if (!(dd->crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(dd->src,
+                                                                           terminator,
+                                                                           priv->qemuCaps)))
+        return -1;
+
+    if (reuse) {
+        if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
+            return -1;
+
+        rc = qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm),
+                                               dd->crdata->srcdata[0]);
+
+        if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
+            return -1;
+    } else {
+        if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData,
+                                                   dd->src, dd->disk->src) < 0)
+            return -1;
+
+        if (qemuBlockStorageSourceCreate(vm, dd->src, dd->disk->src,
+                                         NULL, dd->crdata->srcdata[0],
+                                         asyncJob) < 0)
+            return -1;
+    }
+
+    dd->blockdevadded = true;
+    return 0;
+}
+
+
+static int
+qemuSnapshotDiskPrepareOne(virQEMUDriverPtr driver,
+                           virDomainObjPtr vm,
+                           virQEMUDriverConfigPtr cfg,
+                           virDomainDiskDefPtr disk,
+                           virDomainSnapshotDiskDefPtr snapdisk,
+                           qemuSnapshotDiskDataPtr dd,
+                           virHashTablePtr blockNamedNodeData,
+                           bool reuse,
+                           bool blockdev,
+                           qemuDomainAsyncJob asyncJob,
+                           virJSONValuePtr actions)
+{
+    virDomainDiskDefPtr persistdisk;
+    bool supportsCreate;
+    bool updateRelativeBacking = false;
+
+    dd->disk = disk;
+
+    if (qemuDomainStorageSourceValidateDepth(disk->src, 1, disk->dst) < 0)
+        return -1;
+
+    if (!(dd->src = virStorageSourceCopy(snapdisk->src, false)))
+        return -1;
+
+    if (virStorageSourceInitChainElement(dd->src, dd->disk->src, false) < 0)
+        return -1;
+
+    /* modify disk in persistent definition only when the source is the same */
+    if (vm->newDef &&
+        (persistdisk = virDomainDiskByTarget(vm->newDef, dd->disk->dst)) &&
+        virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) {
+
+        dd->persistdisk = persistdisk;
+
+        if (!(dd->persistsrc = virStorageSourceCopy(dd->src, false)))
+            return -1;
+
+        if (virStorageSourceInitChainElement(dd->persistsrc,
+                                             dd->persistdisk->src, false) < 0)
+            return -1;
+    }
+
+    supportsCreate = virStorageFileSupportsCreate(dd->src);
+
+    /* relative backing store paths need to be updated so that relative
+     * block commit still works. With blockdev we must update it when doing
+     * commit anyways so it's skipped here */
+    if (!blockdev &&
+        virStorageFileSupportsBackingChainTraversal(dd->src))
+        updateRelativeBacking = true;
+
+    if (supportsCreate || updateRelativeBacking) {
+        if (qemuDomainStorageFileInit(driver, vm, dd->src, NULL) < 0)
+            return -1;
+
+        dd->initialized = true;
+
+        if (reuse) {
+            if (updateRelativeBacking) {
+                g_autofree char *backingStoreStr = NULL;
+
+                if (virStorageFileGetBackingStoreStr(dd->src, &backingStoreStr) < 0)
+                    return -1;
+                if (backingStoreStr != NULL) {
+                    if (virStorageIsRelative(backingStoreStr))
+                        dd->relPath = g_steal_pointer(&backingStoreStr);
+                }
+            }
+        } else {
+            /* pre-create the image file so that we can label it before handing it to qemu */
+            if (supportsCreate && dd->src->type != VIR_STORAGE_TYPE_BLOCK) {
+                if (virStorageFileCreate(dd->src) < 0) {
+                    virReportSystemError(errno, _("failed to create image file '%s'"),
+                                         NULLSTR(dd->src->path));
+                    return -1;
+                }
+                dd->created = true;
+            }
+        }
+    }
+
+    /* set correct security, cgroup and locking options on the new image */
+    if (qemuDomainStorageSourceAccessAllow(driver, vm, dd->src,
+                                           false, true, true) < 0)
+        return -1;
+
+    dd->prepared = true;
+
+    if (blockdev) {
+        if (qemuSnapshotDiskPrepareOneBlockdev(driver, vm, dd, cfg, reuse,
+                                               blockNamedNodeData, asyncJob) < 0)
+            return -1;
+
+        if (qemuSnapshotDiskBitmapsPropagate(dd, actions, blockNamedNodeData) < 0)
+            return -1;
+
+        if (qemuBlockSnapshotAddBlockdev(actions, dd->disk, dd->src) < 0)
+            return -1;
+    } else {
+        if (qemuBlockSnapshotAddLegacy(actions, dd->disk, dd->src, reuse) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+/**
+ * qemuSnapshotDiskPrepare:
+ *
+ * Collects and prepares a list of structures that hold information about disks
+ * that are selected for the snapshot.
+ */
+static int
+qemuSnapshotDiskPrepare(virQEMUDriverPtr driver,
+                        virDomainObjPtr vm,
+                        virDomainMomentObjPtr snap,
+                        virQEMUDriverConfigPtr cfg,
+                        bool reuse,
+                        bool blockdev,
+                        virHashTablePtr blockNamedNodeData,
+                        qemuDomainAsyncJob asyncJob,
+                        qemuSnapshotDiskDataPtr *rdata,
+                        size_t *rndata,
+                        virJSONValuePtr actions)
+{
+    size_t i;
+    qemuSnapshotDiskDataPtr data;
+    size_t ndata = 0;
+    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
+    int ret = -1;
+
+    if (VIR_ALLOC_N(data, snapdef->ndisks) < 0)
+        return -1;
+
+    for (i = 0; i < snapdef->ndisks; i++) {
+        if (snapdef->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
+            continue;
+
+        if (qemuSnapshotDiskPrepareOne(driver, vm, cfg, vm->def->disks[i],
+                                       snapdef->disks + i,
+                                       data + ndata++,
+                                       blockNamedNodeData,
+                                       reuse, blockdev,
+                                       asyncJob,
+                                       actions) < 0)
+            goto cleanup;
+    }
+
+    *rdata = g_steal_pointer(&data);
+    *rndata = ndata;
+    ret = 0;
+
+ cleanup:
+    qemuSnapshotDiskCleanup(data, ndata, driver, vm, asyncJob);
+    return ret;
+}
+
+
+static void
+qemuSnapshotDiskUpdateSourceRenumber(virStorageSourcePtr src)
+{
+    virStorageSourcePtr next;
+    unsigned int idx = 1;
+
+    for (next = src->backingStore; virStorageSourceIsBacking(next); next = next->backingStore)
+        next->id = idx++;
+}
+
+
+/**
+ * qemuSnapshotDiskUpdateSource:
+ * @driver: QEMU driver
+ * @vm: domain object
+ * @dd: snapshot disk data object
+ * @blockdev: -blockdev is in use for the VM
+ *
+ * Updates disk definition after a successful snapshot.
+ */
+static void
+qemuSnapshotDiskUpdateSource(virQEMUDriverPtr driver,
+                             virDomainObjPtr vm,
+                             qemuSnapshotDiskDataPtr dd,
+                             bool blockdev)
+{
+    /* storage driver access won'd be needed */
+    if (dd->initialized)
+        virStorageFileDeinit(dd->src);
+
+    if (qemuSecurityMoveImageMetadata(driver, vm, dd->disk->src, dd->src) < 0)
+        VIR_WARN("Unable to move disk metadata on vm %s", vm->def->name);
+
+    /* unlock the write lock on the original image as qemu will no longer write to it */
+    virDomainLockImageDetach(driver->lockManager, vm, dd->disk->src);
+
+    /* unlock also the new image if the VM is paused to follow the locking semantics */
+    if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING)
+        virDomainLockImageDetach(driver->lockManager, vm, dd->src);
+
+    /* the old disk image is now readonly */
+    dd->disk->src->readonly = true;
+
+    dd->disk->src->relPath = g_steal_pointer(&dd->relPath);
+    dd->src->backingStore = g_steal_pointer(&dd->disk->src);
+    dd->disk->src = g_steal_pointer(&dd->src);
+
+    /* fix numbering of disks */
+    if (!blockdev)
+        qemuSnapshotDiskUpdateSourceRenumber(dd->disk->src);
+
+    if (dd->persistdisk) {
+        dd->persistdisk->src->readonly = true;
+        dd->persistsrc->backingStore = g_steal_pointer(&dd->persistdisk->src);
+        dd->persistdisk->src = g_steal_pointer(&dd->persistsrc);
+    }
+}
+
+
+/* The domain is expected to be locked and active. */
+static int
+qemuSnapshotCreateDiskActive(virQEMUDriverPtr driver,
+                             virDomainObjPtr vm,
+                             virDomainMomentObjPtr snap,
+                             virHashTablePtr blockNamedNodeData,
+                             unsigned int flags,
+                             virQEMUDriverConfigPtr cfg,
+                             qemuDomainAsyncJob asyncJob)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    g_autoptr(virJSONValue) actions = NULL;
+    int rc;
+    int ret = -1;
+    size_t i;
+    bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
+    qemuSnapshotDiskDataPtr diskdata = NULL;
+    size_t ndiskdata = 0;
+    bool blockdev =  virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV);
+
+    if (virDomainObjCheckActive(vm) < 0)
+        return -1;
+
+    actions = virJSONValueNewArray();
+
+    /* prepare a list of objects to use in the vm definition so that we don't
+     * have to roll back later */
+    if (qemuSnapshotDiskPrepare(driver, vm, snap, cfg, reuse, blockdev,
+                                blockNamedNodeData, asyncJob,
+                                &diskdata, &ndiskdata, actions) < 0)
+        goto cleanup;
+
+    /* check whether there's anything to do */
+    if (ndiskdata == 0) {
+        ret = 0;
+        goto cleanup;
+    }
+
+    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
+        goto cleanup;
+
+    rc = qemuMonitorTransaction(priv->mon, &actions);
+
+    if (qemuDomainObjExitMonitor(driver, vm) < 0)
+        rc = -1;
+
+    for (i = 0; i < ndiskdata; i++) {
+        qemuSnapshotDiskDataPtr dd = &diskdata[i];
+
+        virDomainAuditDisk(vm, dd->disk->src, dd->src, "snapshot", rc >= 0);
+
+        if (rc == 0)
+            qemuSnapshotDiskUpdateSource(driver, vm, dd, blockdev);
+    }
+
+    if (rc < 0)
+        goto cleanup;
+
+    if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0 ||
+        (vm->newDef && virDomainDefSave(vm->newDef, driver->xmlopt,
+                                        cfg->configDir) < 0))
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    qemuSnapshotDiskCleanup(diskdata, ndiskdata, driver, vm, asyncJob);
+    return ret;
+}
+
+
+static int
+qemuSnapshotCreateActiveExternal(virQEMUDriverPtr driver,
+                                 virDomainObjPtr vm,
+                                 virDomainMomentObjPtr snap,
+                                 virQEMUDriverConfigPtr cfg,
+                                 unsigned int flags)
+{
+    virObjectEventPtr event;
+    bool resume = false;
+    int ret = -1;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    g_autofree char *xml = NULL;
+    virDomainSnapshotDefPtr snapdef = virDomainSnapshotObjGetDef(snap);
+    bool memory = snapdef->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+    bool memory_unlink = false;
+    int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */
+    bool pmsuspended = false;
+    int compressed;
+    g_autoptr(virCommand) compressor = NULL;
+    virQEMUSaveDataPtr data = NULL;
+    g_autoptr(virHashTable) blockNamedNodeData = NULL;
+
+    /* If quiesce was requested, then issue a freeze command, and a
+     * counterpart thaw command when it is actually sent to agent.
+     * The command will fail if the guest is paused or the guest agent
+     * is not running, or is already quiesced.  */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) {
+        int freeze;
+
+        if (qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) < 0)
+            goto cleanup;
+
+        if (virDomainObjCheckActive(vm) < 0) {
+            qemuDomainObjEndAgentJob(vm);
+            goto cleanup;
+        }
+
+        freeze = qemuSnapshotFSFreeze(vm, NULL, 0);
+        qemuDomainObjEndAgentJob(vm);
+
+        if (freeze < 0) {
+            /* the helper reported the error */
+            if (freeze == -2)
+                thaw = -1; /* the command is sent but agent failed */
+            goto cleanup;
+        }
+        thaw = 1;
+    }
+
+    /* We need to track what state the guest is in, since taking the
+     * snapshot may alter that state and we must restore it later.  */
+    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PMSUSPENDED) {
+        pmsuspended = true;
+    } else if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+        /* For full system external snapshots (those with memory), the guest
+         * must pause (either by libvirt up front, or by qemu after
+         * _LIVE converges). */
+        if (memory)
+            resume = true;
+
+        if (memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) {
+            if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
+                                    QEMU_ASYNC_JOB_SNAPSHOT) < 0)
+                goto cleanup;
+
+            if (!virDomainObjIsActive(vm)) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("guest unexpectedly quit"));
+                goto cleanup;
+            }
+
+            resume = true;
+        }
+    }
+
+    /* We need to collect reply from 'query-named-block-nodes' prior to the
+     * migration step as qemu deactivates bitmaps after migration so the result
+     * would be wrong */
+    if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
+        !(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_SNAPSHOT)))
+        goto cleanup;
+
+    /* do the memory snapshot if necessary */
+    if (memory) {
+        /* check if migration is possible */
+        if (!qemuMigrationSrcIsAllowed(driver, vm, false, 0))
+            goto cleanup;
+
+        priv->job.current->statsType = QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP;
+
+        /* allow the migration job to be cancelled or the domain to be paused */
+        qemuDomainObjSetAsyncJobMask(vm, (QEMU_JOB_DEFAULT_MASK |
+                                          JOB_MASK(QEMU_JOB_SUSPEND) |
+                                          JOB_MASK(QEMU_JOB_MIGRATION_OP)));
+
+        if ((compressed = qemuSaveImageGetCompressionProgram(cfg->snapshotImageFormat,
+                                                             &compressor,
+                                                             "snapshot", false)) < 0)
+            goto cleanup;
+
+        if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
+                                            vm->def, priv->origCPU,
+                                            true, true)) ||
+            !(snapdef->cookie = (virObjectPtr) qemuDomainSaveCookieNew(vm)))
+            goto cleanup;
+
+        if (!(data = virQEMUSaveDataNew(xml,
+                                        (qemuDomainSaveCookiePtr) snapdef->cookie,
+                                        resume, compressed, driver->xmlopt)))
+            goto cleanup;
+        xml = NULL;
+
+        if ((ret = qemuSaveImageCreate(driver, vm, snapdef->file, data,
+                                      compressor, 0,
+                                      QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+            goto cleanup;
+
+        /* the memory image was created, remove it on errors */
+        memory_unlink = true;
+
+        /* forbid any further manipulation */
+        qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_DEFAULT_MASK);
+    }
+
+    /* the domain is now paused if a memory snapshot was requested */
+
+    if ((ret = qemuSnapshotCreateDiskActive(driver, vm, snap,
+                                            blockNamedNodeData, flags, cfg,
+                                            QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+        goto cleanup;
+
+    /* the snapshot is complete now */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
+        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
+        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
+                        QEMU_ASYNC_JOB_SNAPSHOT, 0);
+        virDomainAuditStop(vm, "from-snapshot");
+        resume = false;
+        thaw = 0;
+        virObjectEventStateQueue(driver->domainEventState, event);
+    } else if (memory && pmsuspended) {
+        /* qemu 1.3 is unable to save a domain in pm-suspended (S3)
+         * state; so we must emit an event stating that it was
+         * converted to paused.  */
+        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
+                             VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
+        event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT);
+        virObjectEventStateQueue(driver->domainEventState, event);
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (resume && virDomainObjIsActive(vm) &&
+        qemuProcessStartCPUs(driver, vm,
+                             VIR_DOMAIN_RUNNING_UNPAUSED,
+                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
+        event = virDomainEventLifecycleNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
+        virObjectEventStateQueue(driver->domainEventState, event);
+        if (virGetLastErrorCode() == VIR_ERR_OK) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("resuming after snapshot failed"));
+        }
+
+        ret = -1;
+    }
+
+    if (thaw != 0 &&
+        qemuDomainObjBeginAgentJob(driver, vm, QEMU_AGENT_JOB_MODIFY) >= 0 &&
+        virDomainObjIsActive(vm)) {
+        if (qemuSnapshotFSThaw(vm, ret == 0 && thaw > 0) < 0) {
+            /* helper reported the error, if it was needed */
+            if (thaw > 0)
+                ret = -1;
+        }
+
+        qemuDomainObjEndAgentJob(vm);
+    }
+
+    virQEMUSaveDataFree(data);
+    if (memory_unlink && ret < 0)
+        unlink(snapdef->file);
+
+    return ret;
+}
+
+
+virDomainSnapshotPtr
+qemuSnapshotCreateXML(virDomainPtr domain,
+                      virDomainObjPtr vm,
+                      const char *xmlDesc,
+                      unsigned int flags)
+{
+    virQEMUDriverPtr driver = domain->conn->privateData;
+    g_autofree char *xml = NULL;
+    virDomainMomentObjPtr snap = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    virDomainMomentObjPtr current = NULL;
+    bool update_current = true;
+    bool redefine = flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
+    unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS;
+    int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
+    bool align_match = true;
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virDomainSnapshotState state;
+    g_autoptr(virDomainSnapshotDef) def = NULL;
+
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_HALT |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_LIVE |
+                  VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE, NULL);
+
+    VIR_REQUIRE_FLAG_RET(VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE,
+                         VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY,
+                         NULL);
+    VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_SNAPSHOT_CREATE_LIVE,
+                            VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE,
+                            NULL);
+
+    if ((redefine && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) ||
+        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA))
+        update_current = false;
+    if (redefine)
+        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE;
+
+    if (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0)
+        goto cleanup;
+
+    if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("cannot halt after transient domain snapshot"));
+        goto cleanup;
+    }
+    if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) ||
+        !virDomainObjIsActive(vm))
+        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE;
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE)
+        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE;
+
+    if (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->xmlopt,
+                                                priv->qemuCaps, NULL, parse_flags)))
+        goto cleanup;
+
+    /* reject snapshot names containing slashes or starting with dot as
+     * snapshot definitions are saved in files named by the snapshot name */
+    if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
+        if (strchr(def->parent.name, '/')) {
+            virReportError(VIR_ERR_XML_DETAIL,
+                           _("invalid snapshot name '%s': "
+                             "name can't contain '/'"),
+                           def->parent.name);
+            goto cleanup;
+        }
+
+        if (def->parent.name[0] == '.') {
+            virReportError(VIR_ERR_XML_DETAIL,
+                           _("invalid snapshot name '%s': "
+                             "name can't start with '.'"),
+                           def->parent.name);
+            goto cleanup;
+        }
+    }
+
+    /* reject the VIR_DOMAIN_SNAPSHOT_CREATE_LIVE flag where not supported */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE &&
+        (!virDomainObjIsActive(vm) ||
+         def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("live snapshot creation is supported only "
+                         "during full system snapshots"));
+        goto cleanup;
+    }
+
+    /* allow snapshots only in certain states */
+    state = redefine ? def->state : vm->state.state;
+    switch (state) {
+        /* valid states */
+    case VIR_DOMAIN_SNAPSHOT_RUNNING:
+    case VIR_DOMAIN_SNAPSHOT_PAUSED:
+    case VIR_DOMAIN_SNAPSHOT_SHUTDOWN:
+    case VIR_DOMAIN_SNAPSHOT_SHUTOFF:
+    case VIR_DOMAIN_SNAPSHOT_CRASHED:
+        break;
+
+    case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT:
+        if (!redefine) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"),
+                           virDomainSnapshotStateTypeToString(state));
+            goto cleanup;
+        }
+        break;
+
+    case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED:
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("qemu doesn't support taking snapshots of "
+                         "PMSUSPENDED guests"));
+        goto cleanup;
+
+        /* invalid states */
+    case VIR_DOMAIN_SNAPSHOT_NOSTATE:
+    case VIR_DOMAIN_SNAPSHOT_BLOCKED: /* invalid state, unused in qemu */
+    case VIR_DOMAIN_SNAPSHOT_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid domain state %s"),
+                       virDomainSnapshotStateTypeToString(state));
+        goto cleanup;
+    }
+
+    /* We are going to modify the domain below. Internal snapshots would use
+     * a regular job, so we need to set the job mask to disallow query as
+     * 'savevm' blocks the monitor. External snapshot will then modify the
+     * job mask appropriately. */
+    if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_SNAPSHOT,
+                                   VIR_DOMAIN_JOB_OPERATION_SNAPSHOT, flags) < 0)
+        goto cleanup;
+
+    qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE);
+
+    if (redefine) {
+        if (virDomainSnapshotRedefinePrep(vm, &def, &snap,
+                                          driver->xmlopt,
+                                          flags) < 0)
+            goto endjob;
+    } else {
+        /* Easiest way to clone inactive portion of vm->def is via
+         * conversion in and back out of xml.  */
+        if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
+                                            vm->def, priv->origCPU,
+                                            true, true)) ||
+            !(def->parent.dom = virDomainDefParseString(xml, driver->xmlopt,
+                                                        priv->qemuCaps,
+                                                        VIR_DOMAIN_DEF_PARSE_INACTIVE |
+                                                        VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
+            goto endjob;
+
+        if (vm->newDef) {
+            def->parent.inactiveDom = virDomainDefCopy(vm->newDef,
+                                                       driver->xmlopt, priv->qemuCaps, true);
+            if (!def->parent.inactiveDom)
+                goto endjob;
+        }
+
+        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
+            align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+            align_match = false;
+            if (virDomainObjIsActive(vm))
+                def->state = VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT;
+            else
+                def->state = VIR_DOMAIN_SNAPSHOT_SHUTOFF;
+            def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE;
+        } else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+            def->state = virDomainObjGetState(vm, NULL);
+            align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+            align_match = false;
+        } else {
+            def->state = virDomainObjGetState(vm, NULL);
+
+            if (virDomainObjIsActive(vm) &&
+                def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) {
+                virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                               _("internal snapshot of a running VM "
+                                 "must include the memory state"));
+                goto endjob;
+            }
+
+            def->memory = (def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF ?
+                           VIR_DOMAIN_SNAPSHOT_LOCATION_NONE :
+                           VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL);
+        }
+        if (virDomainSnapshotAlignDisks(def, align_location,
+                                        align_match) < 0 ||
+            qemuSnapshotPrepare(vm, def, &flags) < 0)
+            goto endjob;
+    }
+
+    if (!snap) {
+        if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def)))
+            goto endjob;
+
+        def = NULL;
+    }
+
+    current = virDomainSnapshotGetCurrent(vm->snapshots);
+    if (current) {
+        if (!redefine)
+            snap->def->parent_name = g_strdup(current->def->name);
+    }
+
+    /* actually do the snapshot */
+    if (redefine) {
+        /* XXX Should we validate that the redefined snapshot even
+         * makes sense, such as checking that qemu-img recognizes the
+         * snapshot name in at least one of the domain's disks?  */
+    } else if (virDomainObjIsActive(vm)) {
+        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY ||
+            virDomainSnapshotObjGetDef(snap)->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+            /* external full system or disk snapshot */
+            if (qemuSnapshotCreateActiveExternal(driver, vm, snap, cfg, flags) < 0)
+                goto endjob;
+        } else {
+            /* internal full system */
+            if (qemuSnapshotCreateActiveInternal(driver, vm, snap, flags) < 0)
+                goto endjob;
+        }
+    } else {
+        /* inactive; qemuSnapshotPrepare guaranteed that we
+         * aren't mixing internal and external, and altered flags to
+         * contain DISK_ONLY if there is an external disk.  */
+        if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
+            bool reuse = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT);
+
+            if (qemuSnapshotCreateInactiveExternal(driver, vm, snap, reuse) < 0)
+                goto endjob;
+        } else {
+            if (qemuSnapshotCreateInactiveInternal(driver, vm, snap) < 0)
+                goto endjob;
+        }
+    }
+
+    /* If we fail after this point, there's not a whole lot we can
+     * do; we've successfully taken the snapshot, and we are now running
+     * on it, so we have to go forward the best we can
+     */
+    snapshot = virGetDomainSnapshot(domain, snap->def->name);
+
+ endjob:
+    if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
+        if (update_current)
+            virDomainSnapshotSetCurrent(vm->snapshots, snap);
+        if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                            driver->xmlopt,
+                                            cfg->snapshotDir) < 0) {
+            /* if writing of metadata fails, error out rather than trying
+             * to silently carry on without completing the snapshot */
+            virObjectUnref(snapshot);
+            snapshot = NULL;
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("unable to save metadata for snapshot %s"),
+                           snap->def->name);
+            virDomainSnapshotObjListRemove(vm->snapshots, snap);
+        } else {
+            virDomainSnapshotLinkParent(vm->snapshots, snap);
+        }
+    } else if (snap) {
+        virDomainSnapshotObjListRemove(vm->snapshots, snap);
+    }
+
+    qemuDomainObjEndAsyncJob(driver, vm);
+
+ cleanup:
+    return snapshot;
+}
+
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuSnapshotRevertInactive(virQEMUDriverPtr driver,
+                           virDomainObjPtr vm,
+                           virDomainMomentObjPtr snap)
+{
+    /* Try all disks, but report failure if we skipped any.  */
+    int ret = qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-a", true);
+    return ret > 0 ? -1 : ret;
+}
+
+
+int
+qemuSnapshotRevert(virDomainObjPtr vm,
+                   virDomainSnapshotPtr snapshot,
+                   unsigned int flags)
+{
+    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
+    int ret = -1;
+    virDomainMomentObjPtr snap = NULL;
+    virDomainSnapshotDefPtr snapdef;
+    virObjectEventPtr event = NULL;
+    virObjectEventPtr event2 = NULL;
+    int detail;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    int rc;
+    virDomainDefPtr config = NULL;
+    virDomainDefPtr inactiveConfig = NULL;
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+    bool was_stopped = false;
+    qemuDomainSaveCookiePtr cookie;
+    virCPUDefPtr origCPU = NULL;
+    unsigned int start_flags = VIR_QEMU_PROCESS_START_GEN_VMID;
+    qemuDomainAsyncJob jobType = QEMU_ASYNC_JOB_START;
+    bool defined = false;
+
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
+                  VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
+                  VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1);
+
+    /* We have the following transitions, which create the following events:
+     * 1. inactive -> inactive: none
+     * 2. inactive -> running:  EVENT_STARTED
+     * 3. inactive -> paused:   EVENT_STARTED, EVENT_PAUSED
+     * 4. running  -> inactive: EVENT_STOPPED
+     * 5. running  -> running:  none
+     * 6. running  -> paused:   EVENT_PAUSED
+     * 7. paused   -> inactive: EVENT_STOPPED
+     * 8. paused   -> running:  EVENT_RESUMED
+     * 9. paused   -> paused:   none
+     * Also, several transitions occur even if we fail partway through,
+     * and use of FORCE can cause multiple transitions.
+     */
+
+    if (qemuDomainHasBlockjob(vm, false)) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("domain has active block job"));
+        goto cleanup;
+    }
+
+    if (qemuProcessBeginJob(driver, vm,
+                            VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT,
+                            flags) < 0)
+        goto cleanup;
+
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
+        goto endjob;
+    snapdef = virDomainSnapshotObjGetDef(snap);
+
+    if (!vm->persistent &&
+        snapdef->state != VIR_DOMAIN_SNAPSHOT_RUNNING &&
+        snapdef->state != VIR_DOMAIN_SNAPSHOT_PAUSED &&
+        (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
+                  VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("transient domain needs to request run or pause "
+                         "to revert to inactive snapshot"));
+        goto endjob;
+    }
+
+    if (virDomainSnapshotIsExternal(snap)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("revert to external snapshot not supported yet"));
+        goto endjob;
+    }
+
+    if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
+        if (!snap->def->dom) {
+            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
+                           _("snapshot '%s' lacks domain '%s' rollback info"),
+                           snap->def->name, vm->def->name);
+            goto endjob;
+        }
+        if (virDomainObjIsActive(vm) &&
+            !(snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
+              snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) &&
+            (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
+                      VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
+            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
+                           _("must respawn qemu to start inactive snapshot"));
+            goto endjob;
+        }
+        if (vm->hasManagedSave &&
+            !(snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
+              snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED)) {
+            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
+                           _("snapshot without memory state, removal of "
+                             "existing managed saved state strongly "
+                             "recommended to avoid corruption"));
+            goto endjob;
+        }
+    }
+
+    if (snap->def->dom) {
+        config = virDomainDefCopy(snap->def->dom,
+                                  driver->xmlopt, priv->qemuCaps, true);
+        if (!config)
+            goto endjob;
+    }
+
+    if (snap->def->inactiveDom) {
+        inactiveConfig = virDomainDefCopy(snap->def->inactiveDom,
+                                          driver->xmlopt, priv->qemuCaps, true);
+        if (!inactiveConfig)
+            goto endjob;
+    } else {
+        /* Inactive domain definition is missing:
+         * - either this is an old active snapshot and we need to copy the
+         *   active definition as an inactive one
+         * - or this is an inactive snapshot which means config contains the
+         *   inactive definition.
+         */
+        if (snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
+            snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) {
+            inactiveConfig = virDomainDefCopy(snap->def->dom,
+                                              driver->xmlopt, priv->qemuCaps, true);
+            if (!inactiveConfig)
+                goto endjob;
+        } else {
+            inactiveConfig = g_steal_pointer(&config);
+        }
+    }
+
+    cookie = (qemuDomainSaveCookiePtr) snapdef->cookie;
+
+    switch ((virDomainSnapshotState) snapdef->state) {
+    case VIR_DOMAIN_SNAPSHOT_RUNNING:
+    case VIR_DOMAIN_SNAPSHOT_PAUSED:
+        start_flags |= VIR_QEMU_PROCESS_START_PAUSED;
+
+        /* Transitions 2, 3, 5, 6, 8, 9 */
+        /* When using the loadvm monitor command, qemu does not know
+         * whether to pause or run the reverted domain, and just stays
+         * in the same state as before the monitor command, whether
+         * that is paused or running.  We always pause before loadvm,
+         * to have finer control.  */
+        if (virDomainObjIsActive(vm)) {
+            /* Transitions 5, 6, 8, 9 */
+            /* Check for ABI compatibility. We need to do this check against
+             * the migratable XML or it will always fail otherwise */
+            if (config) {
+                bool compatible;
+
+                /* Replace the CPU in config and put the original one in priv
+                 * once we're done. When we have the updated CPU def in the
+                 * cookie, we don't want to replace the CPU in migratable def
+                 * when doing ABI checks to make sure the current CPU exactly
+                 * matches the one used at the time the snapshot was taken.
+                 */
+                if (cookie && cookie->cpu && config->cpu) {
+                    origCPU = config->cpu;
+                    if (!(config->cpu = virCPUDefCopy(cookie->cpu)))
+                        goto endjob;
+
+                    compatible = qemuDomainDefCheckABIStability(driver,
+                                                                priv->qemuCaps,
+                                                                vm->def,
+                                                                config);
+                } else {
+                    compatible = qemuDomainCheckABIStability(driver, vm, config);
+                }
+
+                /* If using VM GenID, there is no way currently to change
+                 * the genid for the running guest, so set an error,
+                 * mark as incompatible, and don't allow change of genid
+                 * if the revert force flag would start the guest again. */
+                if (compatible && config->genidRequested) {
+                    virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                                   _("domain genid update requires restart"));
+                    compatible = false;
+                    start_flags &= ~VIR_QEMU_PROCESS_START_GEN_VMID;
+                }
+
+                if (!compatible) {
+                    virErrorPtr err = virGetLastError();
+
+                    if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
+                        /* Re-spawn error using correct category. */
+                        if (err->code == VIR_ERR_CONFIG_UNSUPPORTED)
+                            virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
+                                           err->str2);
+                        goto endjob;
+                    }
+                    virResetError(err);
+                    qemuProcessStop(driver, vm,
+                                    VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
+                                    QEMU_ASYNC_JOB_START, 0);
+                    virDomainAuditStop(vm, "from-snapshot");
+                    detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
+                    event = virDomainEventLifecycleNewFromObj(vm,
+                                                     VIR_DOMAIN_EVENT_STOPPED,
+                                                     detail);
+                    virObjectEventStateQueue(driver->domainEventState, event);
+                    /* Start after stop won't be an async start job, so
+                     * reset to none */
+                    jobType = QEMU_ASYNC_JOB_NONE;
+                    goto load;
+                }
+            }
+
+            if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+                /* Transitions 5, 6 */
+                if (qemuProcessStopCPUs(driver, vm,
+                                        VIR_DOMAIN_PAUSED_FROM_SNAPSHOT,
+                                        QEMU_ASYNC_JOB_START) < 0)
+                    goto endjob;
+                if (!virDomainObjIsActive(vm)) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                   _("guest unexpectedly quit"));
+                    goto endjob;
+                }
+            }
+
+            if (qemuDomainObjEnterMonitorAsync(driver, vm,
+                                               QEMU_ASYNC_JOB_START) < 0)
+                goto endjob;
+            rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name);
+            if (qemuDomainObjExitMonitor(driver, vm) < 0)
+                goto endjob;
+            if (rc < 0) {
+                /* XXX resume domain if it was running before the
+                 * failed loadvm attempt? */
+                goto endjob;
+            }
+            if (config) {
+                virCPUDefFree(priv->origCPU);
+                priv->origCPU = g_steal_pointer(&origCPU);
+            }
+
+            if (cookie && !cookie->slirpHelper)
+                priv->disableSlirp = true;
+
+            if (inactiveConfig) {
+                virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
+                inactiveConfig = NULL;
+                defined = true;
+            }
+        } else {
+            /* Transitions 2, 3 */
+        load:
+            was_stopped = true;
+
+            if (inactiveConfig) {
+                virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
+                inactiveConfig = NULL;
+                defined = true;
+            }
+
+            if (config) {
+                virDomainObjAssignDef(vm, config, true, NULL);
+                config = NULL;
+            }
+
+            /* No cookie means libvirt which saved the domain was too old to
+             * mess up the CPU definitions.
+             */
+            if (cookie &&
+                qemuDomainFixupCPUs(vm, &cookie->cpu) < 0)
+                goto cleanup;
+
+            rc = qemuProcessStart(snapshot->domain->conn, driver, vm,
+                                  cookie ? cookie->cpu : NULL,
+                                  jobType, NULL, -1, NULL, snap,
+                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
+                                  start_flags);
+            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
+            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+            event = virDomainEventLifecycleNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             detail);
+            if (rc < 0)
+                goto endjob;
+        }
+
+        /* Touch up domain state.  */
+        if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) &&
+            (snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED ||
+             (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
+            /* Transitions 3, 6, 9 */
+            virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
+                                 VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
+            if (was_stopped) {
+                /* Transition 3, use event as-is and add event2 */
+                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
+                event2 = virDomainEventLifecycleNewFromObj(vm,
+                                                  VIR_DOMAIN_EVENT_SUSPENDED,
+                                                  detail);
+            } /* else transition 6 and 9 use event as-is */
+        } else {
+            /* Transitions 2, 5, 8 */
+            if (!virDomainObjIsActive(vm)) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("guest unexpectedly quit"));
+                goto endjob;
+            }
+            rc = qemuProcessStartCPUs(driver, vm,
+                                      VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
+                                      jobType);
+            if (rc < 0)
+                goto endjob;
+            virObjectUnref(event);
+            event = NULL;
+            if (was_stopped) {
+                /* Transition 2 */
+                detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+                event = virDomainEventLifecycleNewFromObj(vm,
+                                                 VIR_DOMAIN_EVENT_STARTED,
+                                                 detail);
+            }
+        }
+        break;
+
+    case VIR_DOMAIN_SNAPSHOT_SHUTDOWN:
+    case VIR_DOMAIN_SNAPSHOT_SHUTOFF:
+    case VIR_DOMAIN_SNAPSHOT_CRASHED:
+        /* Transitions 1, 4, 7 */
+        /* Newer qemu -loadvm refuses to revert to the state of a snapshot
+         * created by qemu-img snapshot -c.  If the domain is running, we
+         * must take it offline; then do the revert using qemu-img.
+         */
+
+        if (virDomainObjIsActive(vm)) {
+            /* Transitions 4, 7 */
+            qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
+                            QEMU_ASYNC_JOB_START, 0);
+            virDomainAuditStop(vm, "from-snapshot");
+            detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
+            event = virDomainEventLifecycleNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STOPPED,
+                                             detail);
+        }
+
+        if (qemuSnapshotRevertInactive(driver, vm, snap) < 0) {
+            qemuDomainRemoveInactive(driver, vm);
+            qemuProcessEndJob(driver, vm);
+            goto cleanup;
+        }
+
+        if (inactiveConfig) {
+            virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
+            inactiveConfig = NULL;
+            defined = true;
+        }
+
+        if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
+                     VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {
+            /* Flush first event, now do transition 2 or 3 */
+            bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0;
+
+            start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;
+
+            virObjectEventStateQueue(driver->domainEventState, event);
+            rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL,
+                                  QEMU_ASYNC_JOB_START, NULL, -1, NULL, NULL,
+                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
+                                  start_flags);
+            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
+            if (rc < 0) {
+                qemuDomainRemoveInactive(driver, vm);
+                qemuProcessEndJob(driver, vm);
+                goto cleanup;
+            }
+            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+            event = virDomainEventLifecycleNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             detail);
+            if (paused) {
+                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
+                event2 = virDomainEventLifecycleNewFromObj(vm,
+                                                  VIR_DOMAIN_EVENT_SUSPENDED,
+                                                  detail);
+            }
+        }
+        break;
+
+    case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED:
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("qemu doesn't support reversion of snapshot taken in "
+                         "PMSUSPENDED state"));
+        goto endjob;
+
+    case VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT:
+        /* Rejected earlier as an external snapshot */
+    case VIR_DOMAIN_SNAPSHOT_NOSTATE:
+    case VIR_DOMAIN_SNAPSHOT_BLOCKED:
+    case VIR_DOMAIN_SNAPSHOT_LAST:
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Invalid target domain state '%s'. Refusing "
+                         "snapshot reversion"),
+                       virDomainSnapshotStateTypeToString(snapdef->state));
+        goto endjob;
+    }
+
+    ret = 0;
+
+ endjob:
+    qemuProcessEndJob(driver, vm);
+
+ cleanup:
+    if (ret == 0) {
+        virDomainSnapshotSetCurrent(vm->snapshots, snap);
+        if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                            driver->xmlopt,
+                                            cfg->snapshotDir) < 0) {
+            virDomainSnapshotSetCurrent(vm->snapshots, NULL);
+            ret = -1;
+        }
+    }
+    if (ret == 0 && defined && vm->persistent &&
+        !(ret = virDomainDefSave(vm->newDef ? vm->newDef : vm->def,
+                                 driver->xmlopt, cfg->configDir))) {
+        detail = VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT;
+        virObjectEventStateQueue(driver->domainEventState,
+            virDomainEventLifecycleNewFromObj(vm,
+                                              VIR_DOMAIN_EVENT_DEFINED,
+                                              detail));
+    }
+    virObjectEventStateQueue(driver->domainEventState, event);
+    virObjectEventStateQueue(driver->domainEventState, event2);
+    virCPUDefFree(origCPU);
+    virDomainDefFree(config);
+    virDomainDefFree(inactiveConfig);
+
+    return ret;
+}
+
+
+typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
+typedef virQEMUMomentReparent *virQEMUMomentReparentPtr;
+struct _virQEMUMomentReparent {
+    const char *dir;
+    virDomainMomentObjPtr parent;
+    virDomainObjPtr vm;
+    virDomainXMLOptionPtr xmlopt;
+    int err;
+    int (*writeMetadata)(virDomainObjPtr, virDomainMomentObjPtr,
+                         virDomainXMLOptionPtr, const char *);
+};
+
+
+static int
+qemuSnapshotChildrenReparent(void *payload,
+                             const void *name G_GNUC_UNUSED,
+                             void *data)
+{
+    virDomainMomentObjPtr moment = payload;
+    virQEMUMomentReparentPtr rep = data;
+
+    if (rep->err < 0)
+        return 0;
+
+    VIR_FREE(moment->def->parent_name);
+
+    if (rep->parent->def)
+        moment->def->parent_name = g_strdup(rep->parent->def->name);
+
+    rep->err = rep->writeMetadata(rep->vm, moment, rep->xmlopt,
+                                  rep->dir);
+    return 0;
+}
+
+
+int
+qemuSnapshotDelete(virDomainObjPtr vm,
+                   virDomainSnapshotPtr snapshot,
+                   unsigned int flags)
+{
+    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
+    int ret = -1;
+    virDomainMomentObjPtr snap = NULL;
+    virQEMUMomentRemove rem;
+    virQEMUMomentReparent rep;
+    bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
+    int external = 0;
+    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
+
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+                  VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
+                  VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1);
+
+    if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
+        goto endjob;
+
+    if (!metadata_only) {
+        if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) &&
+            virDomainSnapshotIsExternal(snap))
+            external++;
+        if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+                     VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))
+            virDomainMomentForEachDescendant(snap,
+                                             qemuSnapshotCountExternal,
+                                             &external);
+        if (external) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("deletion of %d external disk snapshots not "
+                             "supported yet"), external);
+            goto endjob;
+        }
+    }
+
+    if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+                 VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
+        rem.driver = driver;
+        rem.vm = vm;
+        rem.metadata_only = metadata_only;
+        rem.err = 0;
+        rem.current = virDomainSnapshotGetCurrent(vm->snapshots);
+        rem.found = false;
+        rem.momentDiscard = qemuDomainSnapshotDiscard;
+        virDomainMomentForEachDescendant(snap, qemuDomainMomentDiscardAll,
+                                         &rem);
+        if (rem.err < 0)
+            goto endjob;
+        if (rem.found) {
+            virDomainSnapshotSetCurrent(vm->snapshots, snap);
+            if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
+                if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                                    driver->xmlopt,
+                                                    cfg->snapshotDir) < 0) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("failed to set snapshot '%s' as current"),
+                                   snap->def->name);
+                    virDomainSnapshotSetCurrent(vm->snapshots, NULL);
+                    goto endjob;
+                }
+            }
+        }
+    } else if (snap->nchildren) {
+        rep.dir = cfg->snapshotDir;
+        rep.parent = snap->parent;
+        rep.vm = vm;
+        rep.err = 0;
+        rep.xmlopt = driver->xmlopt;
+        rep.writeMetadata = qemuDomainSnapshotWriteMetadata;
+        virDomainMomentForEachChild(snap,
+                                    qemuSnapshotChildrenReparent,
+                                    &rep);
+        if (rep.err < 0)
+            goto endjob;
+        virDomainMomentMoveChildren(snap, snap->parent);
+    }
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
+        virDomainMomentDropChildren(snap);
+        ret = 0;
+    } else {
+        ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only);
+    }
+
+ endjob:
+    qemuDomainObjEndJob(driver, vm);
+
+ cleanup:
+    return ret;
+}
diff --git a/src/qemu/qemu_snapshot.h b/src/qemu/qemu_snapshot.h
new file mode 100644
index 0000000000..8b3ebe87b1
--- /dev/null
+++ b/src/qemu/qemu_snapshot.h
@@ -0,0 +1,55 @@
+/*
+ * qemu_snapshot.h: Implementation and handling of snapshots
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "virconftypes.h"
+#include "datatypes.h"
+#include "qemu_conf.h"
+
+virDomainMomentObjPtr
+qemuSnapObjFromName(virDomainObjPtr vm,
+                    const char *name);
+
+virDomainMomentObjPtr
+qemuSnapObjFromSnapshot(virDomainObjPtr vm,
+                        virDomainSnapshotPtr snapshot);
+
+int
+qemuSnapshotFSFreeze(virDomainObjPtr vm,
+                     const char **mountpoints,
+                     unsigned int nmountpoints);
+int
+qemuSnapshotFSThaw(virDomainObjPtr vm,
+                   bool report);
+
+virDomainSnapshotPtr
+qemuSnapshotCreateXML(virDomainPtr domain,
+                      virDomainObjPtr vm,
+                      const char *xmlDesc,
+                      unsigned int flags);
+
+int
+qemuSnapshotRevert(virDomainObjPtr vm,
+                   virDomainSnapshotPtr snapshot,
+                   unsigned int flags);
+
+int
+qemuSnapshotDelete(virDomainObjPtr vm,
+                   virDomainSnapshotPtr snapshot,
+                   unsigned int flags);
-- 
2.26.2




More information about the libvir-list mailing list