[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[libvirt] [PATCH 2/2] qemu: Split out snapshot related functions to qemu_snapshot.c



---
 po/POTFILES.in           |    1 +
 src/Makefile.am          |    1 +
 src/qemu/qemu_driver.c   | 1699 +-------------------------------------------
 src/qemu/qemu_snapshot.c | 1752 ++++++++++++++++++++++++++++++++++++++++++++++
 src/qemu/qemu_snapshot.h |   38 +
 5 files changed, 1793 insertions(+), 1698 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 4edacfa..bc80df8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -101,6 +101,7 @@ src/qemu/qemu_monitor.c
 src/qemu/qemu_monitor_json.c
 src/qemu/qemu_monitor_text.c
 src/qemu/qemu_process.c
+src/qemu/qemu_snapshot.c
 src/qemu/qemu_util.c
 src/remote/remote_client_bodies.h
 src/remote/remote_driver.c
diff --git a/src/Makefile.am b/src/Makefile.am
index f76a2ea..5414799 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -516,6 +516,7 @@ QEMU_DRIVER_SOURCES =							\
 		qemu/qemu_process.c qemu/qemu_process.h			\
 		qemu/qemu_migration.c qemu/qemu_migration.h		\
 		qemu/qemu_util.c qemu/qemu_util.h		\
+		qemu/qemu_snapshot.c qemu/qemu_snapshot.h		\
 		qemu/qemu_monitor.c qemu/qemu_monitor.h			\
 		qemu/qemu_monitor_text.c				\
 		qemu/qemu_monitor_text.h				\
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index a9c03b6..d64c545 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -58,6 +58,7 @@
 #include "qemu_process.h"
 #include "qemu_migration.h"
 #include "qemu_util.h"
+#include "qemu_snapshot.h"

 #include "virerror.h"
 #include "virlog.h"
@@ -1987,19 +1988,6 @@ cleanup:
 }


-/* Count how many snapshots in a set are external snapshots or checkpoints.  */
-static void
-qemuDomainSnapshotCountExternal(void *payload,
-                                const void *name ATTRIBUTE_UNUSED,
-                                void *data)
-{
-    virDomainSnapshotObjPtr snap = payload;
-    int *count = data;
-
-    if (virDomainSnapshotIsExternal(snap))
-        (*count)++;
-}
-
 static int
 qemuDomainDestroyFlags(virDomainPtr dom,
                        unsigned int flags)
@@ -9922,1193 +9910,6 @@ cleanup:
 }


-/* this function expects the driver lock to be held by the caller */
-static int
-qemuDomainSnapshotFSFreeze(virQEMUDriverPtr driver,
-                           virDomainObjPtr vm) {
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    int freezed;
-
-    if (priv->agentError) {
-        virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
-                       _("QEMU guest agent is not "
-                         "available due to an error"));
-        return -1;
-    }
-    if (!priv->agent) {
-        virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
-                       _("QEMU guest agent is not configured"));
-        return -1;
-    }
-
-    qemuDomainObjEnterAgentWithDriver(driver, vm);
-    freezed = qemuAgentFSFreeze(priv->agent);
-    qemuDomainObjExitAgentWithDriver(driver, vm);
-
-    return freezed;
-}
-
-static int
-qemuDomainSnapshotFSThaw(virQEMUDriverPtr driver,
-                         virDomainObjPtr vm, bool report)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    int thawed;
-    virErrorPtr err = NULL;
-
-    if (priv->agentError) {
-        if (report)
-            virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
-                           _("QEMU guest agent is not "
-                             "available due to an error"));
-        return -1;
-    }
-    if (!priv->agent) {
-        if (report)
-            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
-                           _("QEMU guest agent is not configured"));
-        return -1;
-    }
-
-    qemuDomainObjEnterAgent(driver, vm);
-    if (!report)
-        err = virSaveLastError();
-    thawed = qemuAgentFSThaw(priv->agent);
-    if (!report)
-        virSetError(err);
-    qemuDomainObjExitAgent(driver, vm);
-
-    virFreeError(err);
-    return thawed;
-}
-
-/* The domain is expected to be locked and inactive. */
-static int
-qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         virDomainSnapshotObjPtr snap)
-{
-    return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false);
-}
-
-/* The domain is expected to be locked and inactive. */
-static int
-qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         virDomainSnapshotObjPtr snap,
-                                         bool reuse)
-{
-    int i;
-    virDomainSnapshotDiskDefPtr snapdisk;
-    virDomainDiskDefPtr defdisk;
-    virCommandPtr cmd = NULL;
-    const char *qemuImgPath;
-    virBitmapPtr created;
-
-    int ret = -1;
-
-    if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
-        return -1;
-
-    if (!(created = virBitmapNew(snap->def->ndisks))) {
-        virReportOOMError();
-        return -1;
-    }
-
-    /* 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 < snap->def->ndisks && !reuse; i++) {
-        snapdisk = &(snap->def->disks[i]);
-        defdisk = snap->def->dom->disks[snapdisk->index];
-        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
-            continue;
-
-        if (!snapdisk->format)
-            snapdisk->format = VIR_STORAGE_FILE_QCOW2;
-
-        /* creates cmd line args: qemu-img create -f qcow2 -o */
-        if (!(cmd = virCommandNewArgList(qemuImgPath,
-                                         "create",
-                                         "-f",
-                                         virStorageFileFormatTypeToString(snapdisk->format),
-                                         "-o",
-                                         NULL)))
-            goto cleanup;
-
-        if (defdisk->format > 0) {
-            /* adds cmd line arg: backing_file=/path/to/backing/file,backing_fmd=format */
-            virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s",
-                                   defdisk->src,
-                                   virStorageFileFormatTypeToString(defdisk->format));
-        } else {
-            if (!driver->allowDiskFormatProbing) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("unknown image format of '%s' and "
-                                 "format probing is disabled"),
-                               defdisk->src);
-                goto cleanup;
-            }
-
-            /* adds cmd line arg: backing_file=/path/to/backing/file */
-            virCommandAddArgFormat(cmd, "backing_file=%s", defdisk->src);
-        }
-
-        /* adds cmd line args: /path/to/target/file */
-        virCommandAddArg(cmd, snapdisk->file);
-
-        /* If the target does not exist, we're going to create it possibly */
-        if (!virFileExists(snapdisk->file))
-            ignore_value(virBitmapSetBit(created, i));
-
-        if (virCommandRun(cmd, NULL) < 0)
-            goto cleanup;
-
-        virCommandFree(cmd);
-        cmd = NULL;
-    }
-
-    /* update disk definitions */
-    for (i = 0; i < snap->def->ndisks; i++) {
-        snapdisk = &(snap->def->disks[i]);
-        defdisk = vm->def->disks[snapdisk->index];
-
-        if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-            VIR_FREE(defdisk->src);
-            if (!(defdisk->src = strdup(snapdisk->file))) {
-                /* we cannot rollback here in a sane way */
-                virReportOOMError();
-                goto cleanup;
-            }
-            defdisk->format = snapdisk->format;
-        }
-    }
-
-    ret = 0;
-
-cleanup:
-    virCommandFree(cmd);
-
-    /* unlink images if creation has failed */
-    if (ret < 0) {
-        ssize_t bit = -1;
-        while ((bit = virBitmapNextSetBit(created, bit)) >= 0) {
-            snapdisk = &(snap->def->disks[bit]);
-            if (unlink(snapdisk->file) < 0)
-                VIR_WARN("Failed to remove snapshot image '%s'",
-                         snapdisk->file);
-        }
-    }
-    virBitmapFree(created);
-
-    return ret;
-}
-
-
-/* The domain is expected to be locked and active. */
-static int
-qemuDomainSnapshotCreateActiveInternal(virConnectPtr conn,
-                                       virQEMUDriverPtr driver,
-                                       virDomainObjPtr *vmptr,
-                                       virDomainSnapshotObjPtr snap,
-                                       unsigned int flags)
-{
-    virDomainObjPtr vm = *vmptr;
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    virDomainEventPtr event = NULL;
-    bool resume = false;
-    int ret = -1;
-
-    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
-        return -1;
-
-    if (!virDomainObjIsActive(vm)) {
-        virReportError(VIR_ERR_OPERATION_INVALID,
-                       "%s", _("domain is not running"));
-        goto endjob;
-    }
-
-    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_NONE) < 0)
-            goto cleanup;
-
-        resume = true;
-        if (!virDomainObjIsActive(vm)) {
-            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                           _("guest unexpectedly quit"));
-            goto cleanup;
-        }
-    }
-
-    qemuDomainObjEnterMonitorWithDriver(driver, vm);
-    ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name);
-    qemuDomainObjExitMonitorWithDriver(driver, vm);
-    if (ret < 0)
-        goto cleanup;
-
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
-        event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
-                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
-        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
-        virDomainAuditStop(vm, "from-snapshot");
-        /* We already filtered the _HALT flag for persistent domains
-         * only, so this end job never drops the last reference.  */
-        ignore_value(qemuDomainObjEndJob(driver, vm));
-        resume = false;
-        vm = NULL;
-    }
-
-cleanup:
-    if (resume && virDomainObjIsActive(vm) &&
-        qemuProcessStartCPUs(driver, vm, conn,
-                             VIR_DOMAIN_RUNNING_UNPAUSED,
-                             QEMU_ASYNC_JOB_NONE) < 0) {
-        event = virDomainEventNewFromObj(vm,
-                                         VIR_DOMAIN_EVENT_SUSPENDED,
-                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
-        if (virGetLastError() == NULL) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("resuming after snapshot failed"));
-        }
-    }
-
-endjob:
-    if (vm && qemuDomainObjEndJob(driver, vm) == 0) {
-        /* Only possible if a transient vm quit while our locks were down,
-         * in which case we don't want to save snapshot metadata.  */
-        *vmptr = NULL;
-        ret = -1;
-    }
-
-    if (event)
-        qemuDomainEventQueue(driver, event);
-
-    return ret;
-}
-
-static int
-qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def,
-                          unsigned int *flags)
-{
-    int ret = -1;
-    int i;
-    bool active = virDomainObjIsActive(vm);
-    struct stat st;
-    bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
-    bool atomic = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC) != 0;
-    bool found_internal = false;
-    int external = 0;
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-
-    if (def->state == VIR_DOMAIN_DISK_SNAPSHOT &&
-        reuse && !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("reuse is not supported with this QEMU binary"));
-        goto cleanup;
-    }
-
-    for (i = 0; i < def->ndisks; i++) {
-        virDomainSnapshotDiskDefPtr disk = &def->disks[i];
-        virDomainDiskDefPtr dom_disk = vm->def->disks[i];
-
-        switch (disk->snapshot) {
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL:
-            if (def->state != VIR_DOMAIN_DISK_SNAPSHOT &&
-                dom_disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK &&
-                (dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG ||
-                 dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD)) {
-                break;
-            }
-            if (vm->def->disks[i]->format > 0 &&
-                vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("internal snapshot for disk %s unsupported "
-                                 "for storage type %s"),
-                               disk->name,
-                               virStorageFileFormatTypeToString(
-                                   vm->def->disks[i]->format));
-                goto cleanup;
-            }
-            if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && active) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("active qemu domains require external disk "
-                                 "snapshots; disk %s requested internal"),
-                               disk->name);
-                goto cleanup;
-            }
-            found_internal = true;
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL:
-            if (!disk->format) {
-                disk->format = VIR_STORAGE_FILE_QCOW2;
-            } else if (disk->format != VIR_STORAGE_FILE_QCOW2 &&
-                       disk->format != VIR_STORAGE_FILE_QED) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("external snapshot format for disk %s "
-                                 "is unsupported: %s"),
-                               disk->name,
-                               virStorageFileFormatTypeToString(disk->format));
-                goto cleanup;
-            }
-            if (stat(disk->file, &st) < 0) {
-                if (errno != ENOENT) {
-                    virReportSystemError(errno,
-                                         _("unable to stat for disk %s: %s"),
-                                         disk->name, disk->file);
-                    goto cleanup;
-                } else if (reuse) {
-                    virReportSystemError(errno,
-                                         _("missing existing file for disk %s: %s"),
-                                         disk->name, disk->file);
-                    goto cleanup;
-                }
-            } 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"),
-                               disk->name, disk->file);
-                goto cleanup;
-            }
-            external++;
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE:
-            break;
-
-        case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT:
-        default:
-            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                           _("unexpected code path"));
-            goto cleanup;
-        }
-    }
-
-    /* internal snapshot requires a disk image to store the memory image to */
-    if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL &&
-        !found_internal) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("internal checkpoints require at least "
-                         "one disk to be selected for snapshot"));
-        goto cleanup;
-    }
-
-    /* disk snapshot requires at least one disk */
-    if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && !external) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("disk-only snapshots require at least "
-                         "one disk to be selected for snapshot"));
-        goto cleanup;
-    }
-
-    /* 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) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("mixing internal and external snapshots is not "
-                         "supported yet"));
-        goto cleanup;
-    }
-
-    /* Alter flags to let later users know what we learned.  */
-    if (external && !active)
-        *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
-
-    if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && active) {
-        if (external == 1 ||
-            qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
-            *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
-        } else if (atomic && external > 1) {
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                           _("atomic live snapshot of multiple disks "
-                             "is unsupported"));
-            goto cleanup;
-        }
-    }
-
-    ret = 0;
-
-cleanup:
-    return ret;
-}
-
-/* The domain is expected to hold monitor lock.  */
-static int
-qemuDomainSnapshotCreateSingleDiskActive(virQEMUDriverPtr driver,
-                                         virDomainObjPtr vm,
-                                         virCgroupPtr cgroup,
-                                         virDomainSnapshotDiskDefPtr snap,
-                                         virDomainDiskDefPtr disk,
-                                         virDomainDiskDefPtr persistDisk,
-                                         virJSONValuePtr actions,
-                                         bool reuse)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    char *device = NULL;
-    char *source = NULL;
-    int format = snap->format;
-    const char *formatStr = NULL;
-    char *persistSource = NULL;
-    int ret = -1;
-    int fd = -1;
-    bool need_unlink = false;
-
-    if (snap->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                       _("unexpected code path"));
-        return -1;
-    }
-
-    if (virAsprintf(&device, "drive-%s", disk->info.alias) < 0 ||
-        !(source = strdup(snap->file)) ||
-        (persistDisk &&
-         !(persistSource = strdup(source)))) {
-        virReportOOMError();
-        goto cleanup;
-    }
-
-    /* create the stub file and set selinux labels; manipulate disk in
-     * place, in a way that can be reverted on failure. */
-    if (!reuse) {
-        fd = qemuOpenFile(driver, source, O_WRONLY | O_TRUNC | O_CREAT,
-                          &need_unlink, NULL);
-        if (fd < 0)
-            goto cleanup;
-        VIR_FORCE_CLOSE(fd);
-    }
-
-    /* XXX Here, we know we are about to alter disk->backingChain if
-     * successful, so we nuke the existing chain so that future
-     * commands will recompute it.  Better would be storing the chain
-     * ourselves rather than reprobing, but this requires modifying
-     * domain_conf and our XML to fully track the chain across
-     * libvirtd restarts.  */
-    virStorageFileFreeMetadata(disk->backingChain);
-    disk->backingChain = NULL;
-
-    if (qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source,
-                                          VIR_DISK_CHAIN_READ_WRITE) < 0) {
-        qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source,
-                                          VIR_DISK_CHAIN_NO_ACCESS);
-        goto cleanup;
-    }
-
-    /* create the actual snapshot */
-    if (snap->format)
-        formatStr = virStorageFileFormatTypeToString(snap->format);
-    ret = qemuMonitorDiskSnapshot(priv->mon, actions, device, source,
-                                  formatStr, reuse);
-    virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0);
-    if (ret < 0)
-        goto cleanup;
-
-    /* Update vm in place to match changes.  */
-    need_unlink = false;
-    VIR_FREE(disk->src);
-    disk->src = source;
-    source = NULL;
-    disk->format = format;
-    if (persistDisk) {
-        VIR_FREE(persistDisk->src);
-        persistDisk->src = persistSource;
-        persistSource = NULL;
-        persistDisk->format = format;
-    }
-
-cleanup:
-    if (need_unlink && unlink(source))
-        VIR_WARN("unable to unlink just-created %s", source);
-    VIR_FREE(device);
-    VIR_FREE(source);
-    VIR_FREE(persistSource);
-    return ret;
-}
-
-/* The domain is expected to hold monitor lock.  This is the
- * counterpart to qemuDomainSnapshotCreateSingleDiskActive, called
- * only on a failed transaction. */
-static void
-qemuDomainSnapshotUndoSingleDiskActive(virQEMUDriverPtr driver,
-                                       virDomainObjPtr vm,
-                                       virCgroupPtr cgroup,
-                                       virDomainDiskDefPtr origdisk,
-                                       virDomainDiskDefPtr disk,
-                                       virDomainDiskDefPtr persistDisk,
-                                       bool need_unlink)
-{
-    char *source = NULL;
-    char *persistSource = NULL;
-    struct stat st;
-
-    if (!(source = strdup(origdisk->src)) ||
-        (persistDisk &&
-         !(persistSource = strdup(source)))) {
-        virReportOOMError();
-        goto cleanup;
-    }
-
-    qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, origdisk->src,
-                                      VIR_DISK_CHAIN_NO_ACCESS);
-    if (need_unlink && stat(disk->src, &st) == 0 &&
-        S_ISREG(st.st_mode) && unlink(disk->src) < 0)
-        VIR_WARN("Unable to remove just-created %s", disk->src);
-
-    /* Update vm in place to match changes.  */
-    VIR_FREE(disk->src);
-    disk->src = source;
-    source = NULL;
-    disk->format = origdisk->format;
-    if (persistDisk) {
-        VIR_FREE(persistDisk->src);
-        persistDisk->src = persistSource;
-        persistSource = NULL;
-        persistDisk->format = origdisk->format;
-    }
-
-cleanup:
-    VIR_FREE(source);
-    VIR_FREE(persistSource);
-}
-
-/* The domain is expected to be locked and active. */
-static int
-qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver,
-                                   virDomainObjPtr vm,
-                                   virDomainSnapshotObjPtr snap,
-                                   unsigned int flags,
-                                   enum qemuDomainAsyncJob asyncJob)
-{
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    virJSONValuePtr actions = NULL;
-    int ret = -1;
-    int i;
-    bool persist = false;
-    bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
-    virCgroupPtr cgroup = NULL;
-
-    if (!virDomainObjIsActive(vm)) {
-        virReportError(VIR_ERR_OPERATION_INVALID,
-                       "%s", _("domain is not running"));
-        goto cleanup;
-    }
-
-    if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES) &&
-        virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0)) {
-        virReportError(VIR_ERR_INTERNAL_ERROR,
-                       _("Unable to find cgroup for %s"),
-                       vm->def->name);
-        goto cleanup;
-    }
-    /* 'cgroup' is still NULL if cgroups are disabled.  */
-
-    if (qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
-        if (!(actions = virJSONValueNewArray())) {
-            virReportOOMError();
-            goto cleanup;
-        }
-    } else if (!qemuCapsGet(priv->caps, QEMU_CAPS_DISK_SNAPSHOT)) {
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("live disk snapshot not supported with this "
-                         "QEMU binary"));
-        goto cleanup;
-    }
-
-    /* No way to roll back if first disk succeeds but later disks
-     * fail, unless we have transaction support.
-     * Based on earlier qemuDomainSnapshotPrepare, all
-     * disks in this list are now either SNAPSHOT_NO, or
-     * SNAPSHOT_EXTERNAL with a valid file name and qcow2 format.  */
-    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
-        goto cleanup;
-
-    for (i = 0; i < snap->def->ndisks; i++) {
-        virDomainDiskDefPtr persistDisk = NULL;
-
-        if (snap->def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
-            continue;
-        if (vm->newDef) {
-            int indx = virDomainDiskIndexByName(vm->newDef,
-                                                vm->def->disks[i]->dst,
-                                                false);
-            if (indx >= 0) {
-                persistDisk = vm->newDef->disks[indx];
-                persist = true;
-            }
-        }
-
-        ret = qemuDomainSnapshotCreateSingleDiskActive(driver, vm, cgroup,
-                                                       &snap->def->disks[i],
-                                                       vm->def->disks[i],
-                                                       persistDisk, actions,
-                                                       reuse);
-        if (ret < 0)
-            break;
-    }
-    if (actions) {
-        if (ret == 0)
-            ret = qemuMonitorTransaction(priv->mon, actions);
-        virJSONValueFree(actions);
-        if (ret < 0) {
-            /* Transaction failed; undo the changes to vm.  */
-            bool need_unlink = !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT);
-            while (--i >= 0) {
-                virDomainDiskDefPtr persistDisk = NULL;
-
-                if (snap->def->disks[i].snapshot ==
-                    VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
-                    continue;
-                if (vm->newDef) {
-                    int indx = virDomainDiskIndexByName(vm->newDef,
-                                                        vm->def->disks[i]->dst,
-                                                        false);
-                    if (indx >= 0)
-                        persistDisk = vm->newDef->disks[indx];
-                }
-
-                qemuDomainSnapshotUndoSingleDiskActive(driver, vm, cgroup,
-                                                       snap->def->dom->disks[i],
-                                                       vm->def->disks[i],
-                                                       persistDisk,
-                                                       need_unlink);
-            }
-        }
-    }
-    qemuDomainObjExitMonitorWithDriver(driver, vm);
-
-cleanup:
-    virCgroupFree(&cgroup);
-
-    if (ret == 0 || !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
-        if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 ||
-            (persist && virDomainSaveConfig(driver->configDir, vm->newDef) < 0))
-            ret = -1;
-    }
-
-    return ret;
-}
-
-
-static int
-qemuDomainSnapshotCreateActiveExternal(virConnectPtr conn,
-                                       virQEMUDriverPtr driver,
-                                       virDomainObjPtr *vmptr,
-                                       virDomainSnapshotObjPtr snap,
-                                       unsigned int flags)
-{
-    bool resume = false;
-    int ret = -1;
-    virDomainObjPtr vm = *vmptr;
-    qemuDomainObjPrivatePtr priv = vm->privateData;
-    char *xml = NULL;
-    bool memory = snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-    bool memory_unlink = false;
-    bool atomic = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC);
-    bool transaction = qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION);
-    int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */
-
-    if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm,
-                                             QEMU_ASYNC_JOB_SNAPSHOT) < 0)
-        goto cleanup;
-
-    /* If quiesce was requested, then issue a freeze command, and a
-     * counterpart thaw command, no matter what.  The command will
-     * fail if the guest is paused or the guest agent is not
-     * running.  */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) {
-        if (qemuDomainSnapshotFSFreeze(driver, vm) < 0) {
-            /* helper reported the error */
-            thaw = -1;
-            goto endjob;
-        } else {
-            thaw = 1;
-        }
-    }
-
-    /* we need to resume the guest only if it was previously running */
-    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
-        resume = true;
-
-        /* For external checkpoints (those with memory), the guest
-         * must pause (either by libvirt up front, or by qemu after
-         * _LIVE converges).  For disk-only snapshots with multiple
-         * disks, libvirt must pause externally to get all snapshots
-         * to be at the same point in time, unless qemu supports
-         * transactions.  For a single disk, snapshot is atomic
-         * without requiring a pause.  Thanks to
-         * qemuDomainSnapshotPrepare, if we got to this point, the
-         * atomic flag now says whether we need to pause, and a
-         * capability bit says whether to use transaction.
-         */
-        if ((memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) ||
-            (!memory && atomic && !transaction)) {
-            if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
-                                    QEMU_ASYNC_JOB_SNAPSHOT) < 0)
-                goto endjob;
-
-            if (!virDomainObjIsActive(vm)) {
-                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                               _("guest unexpectedly quit"));
-                goto endjob;
-            }
-        }
-    }
-
-    /* do the memory snapshot if necessary */
-    if (memory) {
-        /* check if migration is possible */
-        if (!qemuMigrationIsAllowed(driver, vm, vm->def, false))
-            goto endjob;
-
-        /* allow the migration job to be cancelled or the domain to be paused */
-        qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK |
-                                     JOB_MASK(QEMU_JOB_SUSPEND) |
-                                     JOB_MASK(QEMU_JOB_MIGRATION_OP));
-
-        if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, false)))
-            goto endjob;
-
-        if ((ret = qemuDomainSaveMemory(driver, vm, snap->def->file,
-                                        xml, QEMU_SAVE_FORMAT_RAW,
-                                        resume, 0,
-                                        QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
-            goto endjob;
-
-        /* the memory image was created, remove it on errors */
-        memory_unlink = true;
-
-        /* forbid any further manipulation */
-        qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK);
-    }
-
-    /* now the domain is now paused if:
-     * - if a memory snapshot was requested
-     * - an atomic snapshot was requested AND
-     *   qemu does not support transactions
-     *
-     * Next we snapshot the disks.
-     */
-    if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap, flags,
-                                                  QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
-        goto endjob;
-
-    /* the snapshot is complete now */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
-        virDomainEventPtr event;
-
-        event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
-                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
-        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
-        virDomainAuditStop(vm, "from-snapshot");
-        /* We already filtered the _HALT flag for persistent domains
-         * only, so this end job never drops the last reference.  */
-        ignore_value(qemuDomainObjEndAsyncJob(driver, vm));
-        resume = false;
-        thaw = 0;
-        vm = NULL;
-        if (event)
-            qemuDomainEventQueue(driver, event);
-    }
-
-    ret = 0;
-
-endjob:
-    if (resume && vm && virDomainObjIsActive(vm) &&
-        qemuProcessStartCPUs(driver, vm, conn,
-                             VIR_DOMAIN_RUNNING_UNPAUSED,
-                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
-        virDomainEventPtr event = NULL;
-        event = virDomainEventNewFromObj(vm,
-                                         VIR_DOMAIN_EVENT_SUSPENDED,
-                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
-        if (event)
-            qemuDomainEventQueue(driver, event);
-        if (virGetLastError() == NULL) {
-            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
-                           _("resuming after snapshot failed"));
-        }
-
-        ret = -1;
-        goto cleanup;
-    }
-    if (vm && thaw != 0 &&
-        qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) {
-        /* helper reported the error, if it was needed */
-        if (thaw > 0)
-            ret = -1;
-    }
-    if (vm && !qemuDomainObjEndAsyncJob(driver, vm)) {
-        /* Only possible if a transient vm quit while our locks were down,
-         * in which case we don't want to save snapshot metadata.
-         */
-        *vmptr = NULL;
-        ret = -1;
-    }
-
-cleanup:
-    VIR_FREE(xml);
-    if (memory_unlink && ret < 0)
-        unlink(snap->def->file);
-
-    return ret;
-}
-
-
-static virDomainSnapshotPtr
-qemuDomainSnapshotCreateXML(virDomainPtr domain,
-                            const char *xmlDesc,
-                            unsigned int flags)
-{
-    virQEMUDriverPtr driver = domain->conn->privateData;
-    virDomainObjPtr vm = NULL;
-    char *xml = NULL;
-    virDomainSnapshotObjPtr snap = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    char uuidstr[VIR_UUID_STRING_BUFLEN];
-    virDomainSnapshotDefPtr def = NULL;
-    bool update_current = true;
-    unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS;
-    virDomainSnapshotObjPtr other = NULL;
-    int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
-    int align_match = true;
-
-    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, NULL);
-
-    if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) &&
-        !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)) {
-        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
-                       _("quiesce requires disk-only"));
-        return NULL;
-    }
-
-    if (((flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) &&
-         !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) ||
-        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA))
-        update_current = false;
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)
-        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE;
-
-    qemuDriverLock(driver);
-    virUUIDFormat(domain->uuid, uuidstr);
-    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
-    if (!vm) {
-        virReportError(VIR_ERR_NO_DOMAIN,
-                       _("no domain with matching uuid '%s'"), uuidstr);
-        goto cleanup;
-    }
-
-    if (qemuProcessAutoDestroyActive(driver, vm)) {
-        virReportError(VIR_ERR_OPERATION_INVALID,
-                       "%s", _("domain is marked for auto destroy"));
-        goto cleanup;
-    }
-    if (virDomainHasDiskMirror(vm)) {
-        virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s",
-                       _("domain has active block copy job"));
-        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 (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->caps,
-                                                QEMU_EXPECTED_VIRT_TYPES,
-                                                parse_flags)))
-        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 ||
-         flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) {
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("live snapshot creation is supported only "
-                         "with external checkpoints"));
-        goto cleanup;
-    }
-    if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL ||
-         def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL) &&
-        flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
-        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
-                       _("disk-only snapshot creation is not compatible with "
-                         "memory snapshot"));
-        goto cleanup;
-    }
-
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) {
-        /* Prevent circular chains */
-        if (def->parent) {
-            if (STREQ(def->name, def->parent)) {
-                virReportError(VIR_ERR_INVALID_ARG,
-                               _("cannot set snapshot %s as its own parent"),
-                               def->name);
-                goto cleanup;
-            }
-            other = virDomainSnapshotFindByName(vm->snapshots, def->parent);
-            if (!other) {
-                virReportError(VIR_ERR_INVALID_ARG,
-                               _("parent %s for snapshot %s not found"),
-                               def->parent, def->name);
-                goto cleanup;
-            }
-            while (other->def->parent) {
-                if (STREQ(other->def->parent, def->name)) {
-                    virReportError(VIR_ERR_INVALID_ARG,
-                                   _("parent %s would create cycle to %s"),
-                                   other->def->name, def->name);
-                    goto cleanup;
-                }
-                other = virDomainSnapshotFindByName(vm->snapshots,
-                                                    other->def->parent);
-                if (!other) {
-                    VIR_WARN("snapshots are inconsistent for %s",
-                             vm->def->name);
-                    break;
-                }
-            }
-        }
-
-        /* Check that any replacement is compatible */
-        if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) &&
-            def->state != VIR_DOMAIN_DISK_SNAPSHOT) {
-            virReportError(VIR_ERR_INVALID_ARG,
-                           _("disk-only flag for snapshot %s requires "
-                             "disk-snapshot state"),
-                           def->name);
-            goto cleanup;
-
-        }
-
-        if (def->dom &&
-            memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) {
-            virReportError(VIR_ERR_INVALID_ARG,
-                           _("definition for snapshot %s must use uuid %s"),
-                           def->name, uuidstr);
-            goto cleanup;
-        }
-
-        other = virDomainSnapshotFindByName(vm->snapshots, def->name);
-        if (other) {
-            if ((other->def->state == VIR_DOMAIN_RUNNING ||
-                 other->def->state == VIR_DOMAIN_PAUSED) !=
-                (def->state == VIR_DOMAIN_RUNNING ||
-                 def->state == VIR_DOMAIN_PAUSED)) {
-                virReportError(VIR_ERR_INVALID_ARG,
-                               _("cannot change between online and offline "
-                                 "snapshot state in snapshot %s"),
-                               def->name);
-                goto cleanup;
-            }
-
-            if ((other->def->state == VIR_DOMAIN_DISK_SNAPSHOT) !=
-                (def->state == VIR_DOMAIN_DISK_SNAPSHOT)) {
-                virReportError(VIR_ERR_INVALID_ARG,
-                               _("cannot change between disk snapshot and "
-                                 "system checkpoint in snapshot %s"),
-                               def->name);
-                goto cleanup;
-            }
-
-            if (other->def->dom) {
-                if (def->dom) {
-                    if (!virDomainDefCheckABIStability(other->def->dom,
-                                                       def->dom))
-                        goto cleanup;
-                } else {
-                    /* Transfer the domain def */
-                    def->dom = other->def->dom;
-                    other->def->dom = NULL;
-                }
-            }
-
-            if (def->dom) {
-                if (def->state == VIR_DOMAIN_DISK_SNAPSHOT ||
-                    virDomainSnapshotDefIsExternal(def)) {
-                    align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-                    align_match = false;
-                }
-
-                if (virDomainSnapshotAlignDisks(def, align_location,
-                                                align_match) < 0) {
-                    /* revert stealing of the snapshot domain definition */
-                    if (def->dom && !other->def->dom) {
-                        other->def->dom = def->dom;
-                        def->dom = NULL;
-                    }
-                    goto cleanup;
-                }
-            }
-
-            if (other == vm->current_snapshot) {
-                update_current = true;
-                vm->current_snapshot = NULL;
-            }
-
-            /* Drop and rebuild the parent relationship, but keep all
-             * child relations by reusing snap.  */
-            virDomainSnapshotDropParent(other);
-            virDomainSnapshotDefFree(other->def);
-            other->def = def;
-            def = NULL;
-            snap = other;
-        } else {
-            if (def->dom) {
-                if (def->state == VIR_DOMAIN_DISK_SNAPSHOT ||
-                    def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-                    align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
-                    align_match = false;
-                }
-                if (virDomainSnapshotAlignDisks(def, align_location,
-                                                align_match) < 0)
-                    goto cleanup;
-            }
-        }
-    } else {
-        /* Easiest way to clone inactive portion of vm->def is via
-         * conversion in and back out of xml.  */
-        if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, true)) ||
-            !(def->dom = virDomainDefParseString(driver->caps, xml,
-                                                 QEMU_EXPECTED_VIRT_TYPES,
-                                                 VIR_DOMAIN_XML_INACTIVE)))
-            goto cleanup;
-
-        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_DISK_SNAPSHOT;
-            else
-                def->state = VIR_DOMAIN_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);
-            def->memory = (def->state == VIR_DOMAIN_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 cleanup;
-    }
-
-    if (!snap) {
-        if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def)))
-            goto cleanup;
-
-        def = NULL;
-    }
-
-    if (update_current)
-        snap->def->current = true;
-    if (vm->current_snapshot) {
-        if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) {
-            snap->def->parent = strdup(vm->current_snapshot->def->name);
-            if (snap->def->parent == NULL) {
-                virReportOOMError();
-                goto cleanup;
-            }
-        }
-        if (update_current) {
-            vm->current_snapshot->def->current = false;
-            if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
-                                                driver->snapshotDir) < 0)
-                goto cleanup;
-            vm->current_snapshot = NULL;
-        }
-    }
-
-    /* actually do the snapshot */
-    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_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 ||
-            snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
-            /* external checkpoint or disk snapshot */
-            if (qemuDomainSnapshotCreateActiveExternal(domain->conn, driver,
-                                                       &vm, snap, flags) < 0)
-                goto cleanup;
-        } else {
-            /* internal checkpoint */
-            if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver,
-                                                       &vm, snap, flags) < 0)
-                goto cleanup;
-        }
-    } 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 cleanup;
-        } else {
-            if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap) < 0)
-                goto cleanup;
-        }
-    }
-
-    /* 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);
-
-cleanup:
-    if (vm) {
-        if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
-            if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                                driver->snapshotDir) < 0) {
-                VIR_WARN("unable to save metadata for snapshot %s",
-                         snap->def->name);
-            } else {
-                if (update_current)
-                    vm->current_snapshot = snap;
-                other = virDomainSnapshotFindByName(vm->snapshots,
-                                                    snap->def->parent);
-                snap->parent = other;
-                other->nchildren++;
-                snap->sibling = other->first_child;
-                other->first_child = snap;
-            }
-        } else if (snap) {
-            virDomainSnapshotObjListRemove(vm->snapshots, snap);
-        }
-        virDomainObjUnlock(vm);
-    }
-    virDomainSnapshotDefFree(def);
-    VIR_FREE(xml);
-    qemuDriverUnlock(driver);
-    return snapshot;
-}
-
 static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names,
                                        int nameslen,
                                        unsigned int flags)
@@ -11430,504 +10231,6 @@ cleanup:
     return ret;
 }

-/* The domain is expected to be locked and inactive. */
-static int
-qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver,
-                                 virDomainObjPtr vm,
-                                 virDomainSnapshotObjPtr 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;
-    virDomainSnapshotObjPtr snap = NULL;
-    char uuidstr[VIR_UUID_STRING_BUFLEN];
-    virDomainEventPtr event = NULL;
-    virDomainEventPtr event2 = NULL;
-    int detail;
-    qemuDomainObjPrivatePtr priv;
-    int rc;
-    virDomainDefPtr config = NULL;
-
-    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.
-     */
-
-    qemuDriverLock(driver);
-    virUUIDFormat(snapshot->domain->uuid, uuidstr);
-    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
-    if (!vm) {
-        virReportError(VIR_ERR_NO_DOMAIN,
-                       _("no domain with matching uuid '%s'"), uuidstr);
-        goto cleanup;
-    }
-    if (virDomainHasDiskMirror(vm)) {
-        virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s",
-                       _("domain has active block copy job"));
-        goto cleanup;
-    }
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto cleanup;
-
-    if (!vm->persistent &&
-        snap->def->state != VIR_DOMAIN_RUNNING &&
-        snap->def->state != VIR_DOMAIN_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 cleanup;
-    }
-    if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) {
-        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                       _("revert to external disk snapshot not supported "
-                         "yet"));
-        goto cleanup;
-    }
-    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 cleanup;
-        }
-        if (virDomainObjIsActive(vm) &&
-            !(snap->def->state == VIR_DOMAIN_RUNNING
-              || snap->def->state == VIR_DOMAIN_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 cleanup;
-        }
-    }
-
-
-    if (vm->current_snapshot) {
-        vm->current_snapshot->def->current = false;
-        if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
-                                            driver->snapshotDir) < 0)
-            goto cleanup;
-        vm->current_snapshot = NULL;
-        /* XXX Should we restore vm->current_snapshot after this point
-         * in the failure cases where we know there was no change?  */
-    }
-
-    /* Prepare to copy the snapshot inactive xml as the config of this
-     * domain.
-     *
-     * XXX Should domain snapshots track live xml rather
-     * than inactive xml?  */
-    snap->def->current = true;
-    if (snap->def->dom) {
-        config = virDomainDefCopy(driver->caps, snap->def->dom, true);
-        if (!config)
-            goto cleanup;
-    }
-
-    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
-        goto cleanup;
-
-    if (snap->def->state == VIR_DOMAIN_RUNNING
-        || snap->def->state == VIR_DOMAIN_PAUSED) {
-        /* Transitions 2, 3, 5, 6, 8, 9 */
-        bool was_running = false;
-        bool was_stopped = false;
-
-        /* 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.  */
-            if (config && !virDomainDefCheckABIStability(vm->def, config)) {
-                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, 0);
-                virDomainAuditStop(vm, "from-snapshot");
-                detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
-                event = virDomainEventNewFromObj(vm,
-                                                 VIR_DOMAIN_EVENT_STOPPED,
-                                                 detail);
-                if (event)
-                    qemuDomainEventQueue(driver, event);
-                goto load;
-            }
-
-            priv = vm->privateData;
-            if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
-                /* Transitions 5, 6 */
-                was_running = true;
-                if (qemuProcessStopCPUs(driver, vm,
-                                        VIR_DOMAIN_PAUSED_FROM_SNAPSHOT,
-                                        QEMU_ASYNC_JOB_NONE) < 0)
-                    goto endjob;
-                /* Create an event now in case the restore fails, so
-                 * that user will be alerted that they are now paused.
-                 * If restore later succeeds, we might replace this. */
-                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
-                event = virDomainEventNewFromObj(vm,
-                                                 VIR_DOMAIN_EVENT_SUSPENDED,
-                                                 detail);
-                if (!virDomainObjIsActive(vm)) {
-                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                                   _("guest unexpectedly quit"));
-                    goto endjob;
-                }
-            }
-            qemuDomainObjEnterMonitorWithDriver(driver, vm);
-            rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name);
-            qemuDomainObjExitMonitorWithDriver(driver, vm);
-            if (rc < 0) {
-                /* XXX resume domain if it was running before the
-                 * failed loadvm attempt? */
-                goto endjob;
-            }
-            if (config)
-                virDomainObjAssignDef(vm, config, false);
-        } else {
-            /* Transitions 2, 3 */
-        load:
-            was_stopped = true;
-            if (config)
-                virDomainObjAssignDef(vm, config, false);
-
-            rc = qemuProcessStart(snapshot->domain->conn,
-                                  driver, vm, NULL, -1, NULL, snap,
-                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
-                                  VIR_QEMU_PROCESS_START_PAUSED);
-            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
-            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-            event = virDomainEventNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STARTED,
-                                             detail);
-            if (rc < 0)
-                goto endjob;
-        }
-
-        /* Touch up domain state.  */
-        if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) &&
-            (snap->def->state == VIR_DOMAIN_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 = virDomainEventNewFromObj(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, snapshot->domain->conn,
-                                      VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
-                                      QEMU_ASYNC_JOB_NONE);
-            if (rc < 0)
-                goto endjob;
-            virDomainEventFree(event);
-            event = NULL;
-            if (was_stopped) {
-                /* Transition 2 */
-                detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-                event = virDomainEventNewFromObj(vm,
-                                                 VIR_DOMAIN_EVENT_STARTED,
-                                                 detail);
-            } else if (was_running) {
-                /* Transition 8 */
-                detail = VIR_DOMAIN_EVENT_RESUMED;
-                event = virDomainEventNewFromObj(vm,
-                                                 VIR_DOMAIN_EVENT_RESUMED,
-                                                 detail);
-            }
-        }
-    } else {
-        /* 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, 0);
-            virDomainAuditStop(vm, "from-snapshot");
-            detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
-            event = virDomainEventNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STOPPED,
-                                             detail);
-        }
-
-        if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) {
-            if (!vm->persistent) {
-                if (qemuDomainObjEndJob(driver, vm) > 0)
-                    qemuDomainRemoveInactive(driver, vm);
-                vm = NULL;
-                goto cleanup;
-            }
-            goto endjob;
-        }
-        if (config)
-            virDomainObjAssignDef(vm, config, false);
-
-        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;
-            unsigned int start_flags = 0;
-
-            start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;
-
-            if (event)
-                qemuDomainEventQueue(driver, event);
-            rc = qemuProcessStart(snapshot->domain->conn,
-                                  driver, vm, NULL, -1, NULL, NULL,
-                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
-                                  start_flags);
-            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
-            if (rc < 0) {
-                if (!vm->persistent) {
-                    if (qemuDomainObjEndJob(driver, vm) > 0)
-                        qemuDomainRemoveInactive(driver, vm);
-                    vm = NULL;
-                    goto cleanup;
-                }
-                goto endjob;
-            }
-            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
-            event = virDomainEventNewFromObj(vm,
-                                             VIR_DOMAIN_EVENT_STARTED,
-                                             detail);
-            if (paused) {
-                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
-                event2 = virDomainEventNewFromObj(vm,
-                                                  VIR_DOMAIN_EVENT_SUSPENDED,
-                                                  detail);
-            }
-        }
-    }
-
-    ret = 0;
-
-endjob:
-    if (vm && qemuDomainObjEndJob(driver, vm) == 0)
-        vm = NULL;
-
-cleanup:
-    if (vm && ret == 0) {
-        if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                            driver->snapshotDir) < 0)
-            ret = -1;
-        else
-            vm->current_snapshot = snap;
-    } else if (snap) {
-        snap->def->current = false;
-    }
-    if (event) {
-        qemuDomainEventQueue(driver, event);
-        if (event2)
-            qemuDomainEventQueue(driver, event2);
-    }
-    if (vm)
-        virDomainObjUnlock(vm);
-    qemuDriverUnlock(driver);
-
-    return ret;
-}
-
-
-typedef struct _virQEMUSnapReparent virQEMUSnapReparent;
-typedef virQEMUSnapReparent *virQEMUSnapReparentPtr;
-struct _virQEMUSnapReparent {
-    virQEMUDriverPtr driver;
-    virDomainSnapshotObjPtr parent;
-    virDomainObjPtr vm;
-    int err;
-    virDomainSnapshotObjPtr last;
-};
-
-static void
-qemuDomainSnapshotReparentChildren(void *payload,
-                                   const void *name ATTRIBUTE_UNUSED,
-                                   void *data)
-{
-    virDomainSnapshotObjPtr snap = payload;
-    virQEMUSnapReparentPtr rep = data;
-
-    if (rep->err < 0) {
-        return;
-    }
-
-    VIR_FREE(snap->def->parent);
-    snap->parent = rep->parent;
-
-    if (rep->parent->def) {
-        snap->def->parent = strdup(rep->parent->def->name);
-
-        if (snap->def->parent == NULL) {
-            virReportOOMError();
-            rep->err = -1;
-            return;
-        }
-    }
-
-    if (!snap->sibling)
-        rep->last = snap;
-
-    rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap,
-                                               rep->driver->snapshotDir);
-}
-
-static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
-                                    unsigned int flags)
-{
-    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
-    virDomainObjPtr vm = NULL;
-    int ret = -1;
-    virDomainSnapshotObjPtr snap = NULL;
-    char uuidstr[VIR_UUID_STRING_BUFLEN];
-    virQEMUSnapRemove rem;
-    virQEMUSnapReparent rep;
-    bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
-    int external = 0;
-
-    virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
-                  VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
-                  VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1);
-
-    qemuDriverLock(driver);
-    virUUIDFormat(snapshot->domain->uuid, uuidstr);
-    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
-    if (!vm) {
-        virReportError(VIR_ERR_NO_DOMAIN,
-                       _("no domain with matching uuid '%s'"), uuidstr);
-        goto cleanup;
-    }
-
-    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
-        goto cleanup;
-
-    if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)) {
-        if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) &&
-            virDomainSnapshotIsExternal(snap))
-            external++;
-        if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN)
-            virDomainSnapshotForEachDescendant(snap,
-                                               qemuDomainSnapshotCountExternal,
-                                               &external);
-        if (external) {
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                           _("deletion of %d external disk snapshots not "
-                             "supported yet"), external);
-            goto cleanup;
-        }
-    }
-
-    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
-        goto cleanup;
-
-    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 = false;
-        virDomainSnapshotForEachDescendant(snap,
-                                           qemuDomainSnapshotDiscardAll,
-                                           &rem);
-        if (rem.err < 0)
-            goto endjob;
-        if (rem.current) {
-            if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
-                snap->def->current = true;
-                if (qemuDomainSnapshotWriteMetadata(vm, snap,
-                                                    driver->snapshotDir) < 0) {
-                    virReportError(VIR_ERR_INTERNAL_ERROR,
-                                   _("failed to set snapshot '%s' as current"),
-                                   snap->def->name);
-                    snap->def->current = false;
-                    goto endjob;
-                }
-            }
-            vm->current_snapshot = snap;
-        }
-    } else if (snap->nchildren) {
-        rep.driver = driver;
-        rep.parent = snap->parent;
-        rep.vm = vm;
-        rep.err = 0;
-        rep.last = NULL;
-        virDomainSnapshotForEachChild(snap,
-                                      qemuDomainSnapshotReparentChildren,
-                                      &rep);
-        if (rep.err < 0)
-            goto endjob;
-        /* Can't modify siblings during ForEachChild, so do it now.  */
-        snap->parent->nchildren += snap->nchildren;
-        rep.last->sibling = snap->parent->first_child;
-        snap->parent->first_child = snap->first_child;
-    }
-
-    if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
-        snap->nchildren = 0;
-        snap->first_child = NULL;
-        ret = 0;
-    } else {
-        virDomainSnapshotDropParent(snap);
-        ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only);
-    }
-
-endjob:
-    if (qemuDomainObjEndJob(driver, vm) == 0)
-        vm = NULL;
-
-cleanup:
-    if (vm)
-        virDomainObjUnlock(vm);
-    qemuDriverUnlock(driver);
-    return ret;
-}

 static int qemuDomainMonitorCommand(virDomainPtr domain, const char *cmd,
                                     char **result, unsigned int flags)
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
new file mode 100644
index 0000000..ac5c481
--- /dev/null
+++ b/src/qemu/qemu_snapshot.c
@@ -0,0 +1,1752 @@
+/*
+ * qemu_snapshot.c: QEMU snapshot handling
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "qemu_snapshot.h"
+#include "qemu_monitor.h"
+#include "qemu_domain.h"
+#include "qemu_process.h"
+#include "qemu_capabilities.h"
+#include "qemu_cgroup.h"
+#include "qemu_util.h"
+#include "qemu_migration.h"
+
+#include "internal.h"
+
+#include "domain_audit.h"
+#include "virerror.h"
+#include "virlog.h"
+#include "virutil.h"
+#include "viralloc.h"
+#include "virfile.h"
+#include "datatypes.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+/* Count how many snapshots in a set are external snapshots or checkpoints.  */
+static void
+qemuDomainSnapshotCountExternal(void *payload,
+                                const void *name ATTRIBUTE_UNUSED,
+                                void *data)
+{
+    virDomainSnapshotObjPtr snap = payload;
+    int *count = data;
+
+    if (virDomainSnapshotIsExternal(snap))
+        (*count)++;
+}
+
+
+/* this function expects the driver lock to be held by the caller */
+static int
+qemuDomainSnapshotFSFreeze(virQEMUDriverPtr driver,
+                           virDomainObjPtr vm) {
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    int freezed;
+
+    if (priv->agentError) {
+        virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+                       _("QEMU guest agent is not "
+                         "available due to an error"));
+        return -1;
+    }
+    if (!priv->agent) {
+        virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                       _("QEMU guest agent is not configured"));
+        return -1;
+    }
+
+    qemuDomainObjEnterAgentWithDriver(driver, vm);
+    freezed = qemuAgentFSFreeze(priv->agent);
+    qemuDomainObjExitAgentWithDriver(driver, vm);
+
+    return freezed;
+}
+
+static int
+qemuDomainSnapshotFSThaw(virQEMUDriverPtr driver,
+                         virDomainObjPtr vm, bool report)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    int thawed;
+    virErrorPtr err = NULL;
+
+    if (priv->agentError) {
+        if (report)
+            virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+                           _("QEMU guest agent is not "
+                             "available due to an error"));
+        return -1;
+    }
+    if (!priv->agent) {
+        if (report)
+            virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                           _("QEMU guest agent is not configured"));
+        return -1;
+    }
+
+    qemuDomainObjEnterAgent(driver, vm);
+    if (!report)
+        err = virSaveLastError();
+    thawed = qemuAgentFSThaw(priv->agent);
+    if (!report)
+        virSetError(err);
+    qemuDomainObjExitAgent(driver, vm);
+
+    virFreeError(err);
+    return thawed;
+}
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuDomainSnapshotCreateInactiveInternal(virQEMUDriverPtr driver,
+                                         virDomainObjPtr vm,
+                                         virDomainSnapshotObjPtr snap)
+{
+    return qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-c", false);
+}
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuDomainSnapshotCreateInactiveExternal(virQEMUDriverPtr driver,
+                                         virDomainObjPtr vm,
+                                         virDomainSnapshotObjPtr snap,
+                                         bool reuse)
+{
+    int i;
+    virDomainSnapshotDiskDefPtr snapdisk;
+    virDomainDiskDefPtr defdisk;
+    virCommandPtr cmd = NULL;
+    const char *qemuImgPath;
+    virBitmapPtr created;
+
+    int ret = -1;
+
+    if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
+        return -1;
+
+    if (!(created = virBitmapNew(snap->def->ndisks))) {
+        virReportOOMError();
+        return -1;
+    }
+
+    /* 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 < snap->def->ndisks && !reuse; i++) {
+        snapdisk = &(snap->def->disks[i]);
+        defdisk = snap->def->dom->disks[snapdisk->index];
+        if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
+            continue;
+
+        if (!snapdisk->format)
+            snapdisk->format = VIR_STORAGE_FILE_QCOW2;
+
+        /* creates cmd line args: qemu-img create -f qcow2 -o */
+        if (!(cmd = virCommandNewArgList(qemuImgPath,
+                                         "create",
+                                         "-f",
+                                         virStorageFileFormatTypeToString(snapdisk->format),
+                                         "-o",
+                                         NULL)))
+            goto cleanup;
+
+        if (defdisk->format > 0) {
+            /* adds cmd line arg: backing_file=/path/to/backing/file,backing_fmd=format */
+            virCommandAddArgFormat(cmd, "backing_file=%s,backing_fmt=%s",
+                                   defdisk->src,
+                                   virStorageFileFormatTypeToString(defdisk->format));
+        } else {
+            if (!driver->allowDiskFormatProbing) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("unknown image format of '%s' and "
+                                 "format probing is disabled"),
+                               defdisk->src);
+                goto cleanup;
+            }
+
+            /* adds cmd line arg: backing_file=/path/to/backing/file */
+            virCommandAddArgFormat(cmd, "backing_file=%s", defdisk->src);
+        }
+
+        /* adds cmd line args: /path/to/target/file */
+        virCommandAddArg(cmd, snapdisk->file);
+
+        /* If the target does not exist, we're going to create it possibly */
+        if (!virFileExists(snapdisk->file))
+            ignore_value(virBitmapSetBit(created, i));
+
+        if (virCommandRun(cmd, NULL) < 0)
+            goto cleanup;
+
+        virCommandFree(cmd);
+        cmd = NULL;
+    }
+
+    /* update disk definitions */
+    for (i = 0; i < snap->def->ndisks; i++) {
+        snapdisk = &(snap->def->disks[i]);
+        defdisk = vm->def->disks[snapdisk->index];
+
+        if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+            VIR_FREE(defdisk->src);
+            if (!(defdisk->src = strdup(snapdisk->file))) {
+                /* we cannot rollback here in a sane way */
+                virReportOOMError();
+                goto cleanup;
+            }
+            defdisk->format = snapdisk->format;
+        }
+    }
+
+    ret = 0;
+
+cleanup:
+    virCommandFree(cmd);
+
+    /* unlink images if creation has failed */
+    if (ret < 0) {
+        ssize_t bit = -1;
+        while ((bit = virBitmapNextSetBit(created, bit)) >= 0) {
+            snapdisk = &(snap->def->disks[bit]);
+            if (unlink(snapdisk->file) < 0)
+                VIR_WARN("Failed to remove snapshot image '%s'",
+                         snapdisk->file);
+        }
+    }
+    virBitmapFree(created);
+
+    return ret;
+}
+
+
+/* The domain is expected to be locked and active. */
+static int
+qemuDomainSnapshotCreateActiveInternal(virConnectPtr conn,
+                                       virQEMUDriverPtr driver,
+                                       virDomainObjPtr *vmptr,
+                                       virDomainSnapshotObjPtr snap,
+                                       unsigned int flags)
+{
+    virDomainObjPtr vm = *vmptr;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virDomainEventPtr event = NULL;
+    bool resume = false;
+    int ret = -1;
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
+        return -1;
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_OPERATION_INVALID,
+                       "%s", _("domain is not running"));
+        goto endjob;
+    }
+
+    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_NONE) < 0)
+            goto cleanup;
+
+        resume = true;
+        if (!virDomainObjIsActive(vm)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("guest unexpectedly quit"));
+            goto cleanup;
+        }
+    }
+
+    qemuDomainObjEnterMonitorWithDriver(driver, vm);
+    ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name);
+    qemuDomainObjExitMonitorWithDriver(driver, vm);
+    if (ret < 0)
+        goto cleanup;
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
+        event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
+        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
+        virDomainAuditStop(vm, "from-snapshot");
+        /* We already filtered the _HALT flag for persistent domains
+         * only, so this end job never drops the last reference.  */
+        ignore_value(qemuDomainObjEndJob(driver, vm));
+        resume = false;
+        vm = NULL;
+    }
+
+cleanup:
+    if (resume && virDomainObjIsActive(vm) &&
+        qemuProcessStartCPUs(driver, vm, conn,
+                             VIR_DOMAIN_RUNNING_UNPAUSED,
+                             QEMU_ASYNC_JOB_NONE) < 0) {
+        event = virDomainEventNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
+        if (virGetLastError() == NULL) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("resuming after snapshot failed"));
+        }
+    }
+
+endjob:
+    if (vm && qemuDomainObjEndJob(driver, vm) == 0) {
+        /* Only possible if a transient vm quit while our locks were down,
+         * in which case we don't want to save snapshot metadata.  */
+        *vmptr = NULL;
+        ret = -1;
+    }
+
+    if (event)
+        qemuDomainEventQueue(driver, event);
+
+    return ret;
+}
+
+static int
+qemuDomainSnapshotPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def,
+                          unsigned int *flags)
+{
+    int ret = -1;
+    int i;
+    bool active = virDomainObjIsActive(vm);
+    struct stat st;
+    bool reuse = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
+    bool atomic = (*flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC) != 0;
+    bool found_internal = false;
+    int external = 0;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+
+    if (def->state == VIR_DOMAIN_DISK_SNAPSHOT &&
+        reuse && !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("reuse is not supported with this QEMU binary"));
+        goto cleanup;
+    }
+
+    for (i = 0; i < def->ndisks; i++) {
+        virDomainSnapshotDiskDefPtr disk = &def->disks[i];
+        virDomainDiskDefPtr dom_disk = vm->def->disks[i];
+
+        switch (disk->snapshot) {
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL:
+            if (def->state != VIR_DOMAIN_DISK_SNAPSHOT &&
+                dom_disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK &&
+                (dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG ||
+                 dom_disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD)) {
+                break;
+            }
+            if (vm->def->disks[i]->format > 0 &&
+                vm->def->disks[i]->format != VIR_STORAGE_FILE_QCOW2) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("internal snapshot for disk %s unsupported "
+                                 "for storage type %s"),
+                               disk->name,
+                               virStorageFileFormatTypeToString(
+                                   vm->def->disks[i]->format));
+                goto cleanup;
+            }
+            if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && active) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("active qemu domains require external disk "
+                                 "snapshots; disk %s requested internal"),
+                               disk->name);
+                goto cleanup;
+            }
+            found_internal = true;
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL:
+            if (!disk->format) {
+                disk->format = VIR_STORAGE_FILE_QCOW2;
+            } else if (disk->format != VIR_STORAGE_FILE_QCOW2 &&
+                       disk->format != VIR_STORAGE_FILE_QED) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("external snapshot format for disk %s "
+                                 "is unsupported: %s"),
+                               disk->name,
+                               virStorageFileFormatTypeToString(disk->format));
+                goto cleanup;
+            }
+            if (stat(disk->file, &st) < 0) {
+                if (errno != ENOENT) {
+                    virReportSystemError(errno,
+                                         _("unable to stat for disk %s: %s"),
+                                         disk->name, disk->file);
+                    goto cleanup;
+                } else if (reuse) {
+                    virReportSystemError(errno,
+                                         _("missing existing file for disk %s: %s"),
+                                         disk->name, disk->file);
+                    goto cleanup;
+                }
+            } 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"),
+                               disk->name, disk->file);
+                goto cleanup;
+            }
+            external++;
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE:
+            break;
+
+        case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT:
+        default:
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("unexpected code path"));
+            goto cleanup;
+        }
+    }
+
+    /* internal snapshot requires a disk image to store the memory image to */
+    if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL &&
+        !found_internal) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("internal checkpoints require at least "
+                         "one disk to be selected for snapshot"));
+        goto cleanup;
+    }
+
+    /* disk snapshot requires at least one disk */
+    if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && !external) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("disk-only snapshots require at least "
+                         "one disk to be selected for snapshot"));
+        goto cleanup;
+    }
+
+    /* 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) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("mixing internal and external snapshots is not "
+                         "supported yet"));
+        goto cleanup;
+    }
+
+    /* Alter flags to let later users know what we learned.  */
+    if (external && !active)
+        *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
+
+    if (def->state != VIR_DOMAIN_DISK_SNAPSHOT && active) {
+        if (external == 1 ||
+            qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
+            *flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
+        } else if (atomic && external > 1) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("atomic live snapshot of multiple disks "
+                             "is unsupported"));
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+
+cleanup:
+    return ret;
+}
+
+/* The domain is expected to hold monitor lock.  */
+static int
+qemuDomainSnapshotCreateSingleDiskActive(virQEMUDriverPtr driver,
+                                         virDomainObjPtr vm,
+                                         virCgroupPtr cgroup,
+                                         virDomainSnapshotDiskDefPtr snap,
+                                         virDomainDiskDefPtr disk,
+                                         virDomainDiskDefPtr persistDisk,
+                                         virJSONValuePtr actions,
+                                         bool reuse)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    char *device = NULL;
+    char *source = NULL;
+    int format = snap->format;
+    const char *formatStr = NULL;
+    char *persistSource = NULL;
+    int ret = -1;
+    int fd = -1;
+    bool need_unlink = false;
+
+    if (snap->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("unexpected code path"));
+        return -1;
+    }
+
+    if (virAsprintf(&device, "drive-%s", disk->info.alias) < 0 ||
+        !(source = strdup(snap->file)) ||
+        (persistDisk &&
+         !(persistSource = strdup(source)))) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    /* create the stub file and set selinux labels; manipulate disk in
+     * place, in a way that can be reverted on failure. */
+    if (!reuse) {
+        fd = qemuOpenFile(driver, source, O_WRONLY | O_TRUNC | O_CREAT,
+                          &need_unlink, NULL);
+        if (fd < 0)
+            goto cleanup;
+        VIR_FORCE_CLOSE(fd);
+    }
+
+    /* XXX Here, we know we are about to alter disk->backingChain if
+     * successful, so we nuke the existing chain so that future
+     * commands will recompute it.  Better would be storing the chain
+     * ourselves rather than reprobing, but this requires modifying
+     * domain_conf and our XML to fully track the chain across
+     * libvirtd restarts.  */
+    virStorageFileFreeMetadata(disk->backingChain);
+    disk->backingChain = NULL;
+
+    if (qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source,
+                                          VIR_DISK_CHAIN_READ_WRITE) < 0) {
+        qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, source,
+                                          VIR_DISK_CHAIN_NO_ACCESS);
+        goto cleanup;
+    }
+
+    /* create the actual snapshot */
+    if (snap->format)
+        formatStr = virStorageFileFormatTypeToString(snap->format);
+    ret = qemuMonitorDiskSnapshot(priv->mon, actions, device, source,
+                                  formatStr, reuse);
+    virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0);
+    if (ret < 0)
+        goto cleanup;
+
+    /* Update vm in place to match changes.  */
+    need_unlink = false;
+    VIR_FREE(disk->src);
+    disk->src = source;
+    source = NULL;
+    disk->format = format;
+    if (persistDisk) {
+        VIR_FREE(persistDisk->src);
+        persistDisk->src = persistSource;
+        persistSource = NULL;
+        persistDisk->format = format;
+    }
+
+cleanup:
+    if (need_unlink && unlink(source))
+        VIR_WARN("unable to unlink just-created %s", source);
+    VIR_FREE(device);
+    VIR_FREE(source);
+    VIR_FREE(persistSource);
+    return ret;
+}
+
+/* The domain is expected to hold monitor lock.  This is the
+ * counterpart to qemuDomainSnapshotCreateSingleDiskActive, called
+ * only on a failed transaction. */
+static void
+qemuDomainSnapshotUndoSingleDiskActive(virQEMUDriverPtr driver,
+                                       virDomainObjPtr vm,
+                                       virCgroupPtr cgroup,
+                                       virDomainDiskDefPtr origdisk,
+                                       virDomainDiskDefPtr disk,
+                                       virDomainDiskDefPtr persistDisk,
+                                       bool need_unlink)
+{
+    char *source = NULL;
+    char *persistSource = NULL;
+    struct stat st;
+
+    if (!(source = strdup(origdisk->src)) ||
+        (persistDisk &&
+         !(persistSource = strdup(source)))) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    qemuDomainPrepareDiskChainElement(driver, vm, cgroup, disk, origdisk->src,
+                                      VIR_DISK_CHAIN_NO_ACCESS);
+    if (need_unlink && stat(disk->src, &st) == 0 &&
+        S_ISREG(st.st_mode) && unlink(disk->src) < 0)
+        VIR_WARN("Unable to remove just-created %s", disk->src);
+
+    /* Update vm in place to match changes.  */
+    VIR_FREE(disk->src);
+    disk->src = source;
+    source = NULL;
+    disk->format = origdisk->format;
+    if (persistDisk) {
+        VIR_FREE(persistDisk->src);
+        persistDisk->src = persistSource;
+        persistSource = NULL;
+        persistDisk->format = origdisk->format;
+    }
+
+cleanup:
+    VIR_FREE(source);
+    VIR_FREE(persistSource);
+}
+
+/* The domain is expected to be locked and active. */
+static int
+qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver,
+                                   virDomainObjPtr vm,
+                                   virDomainSnapshotObjPtr snap,
+                                   unsigned int flags,
+                                   enum qemuDomainAsyncJob asyncJob)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virJSONValuePtr actions = NULL;
+    int ret = -1;
+    int i;
+    bool persist = false;
+    bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
+    virCgroupPtr cgroup = NULL;
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_OPERATION_INVALID,
+                       "%s", _("domain is not running"));
+        goto cleanup;
+    }
+
+    if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES) &&
+        virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Unable to find cgroup for %s"),
+                       vm->def->name);
+        goto cleanup;
+    }
+    /* 'cgroup' is still NULL if cgroups are disabled.  */
+
+    if (qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
+        if (!(actions = virJSONValueNewArray())) {
+            virReportOOMError();
+            goto cleanup;
+        }
+    } else if (!qemuCapsGet(priv->caps, QEMU_CAPS_DISK_SNAPSHOT)) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("live disk snapshot not supported with this "
+                         "QEMU binary"));
+        goto cleanup;
+    }
+
+    /* No way to roll back if first disk succeeds but later disks
+     * fail, unless we have transaction support.
+     * Based on earlier qemuDomainSnapshotPrepare, all
+     * disks in this list are now either SNAPSHOT_NO, or
+     * SNAPSHOT_EXTERNAL with a valid file name and qcow2 format.  */
+    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
+        goto cleanup;
+
+    for (i = 0; i < snap->def->ndisks; i++) {
+        virDomainDiskDefPtr persistDisk = NULL;
+
+        if (snap->def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
+            continue;
+        if (vm->newDef) {
+            int indx = virDomainDiskIndexByName(vm->newDef,
+                                                vm->def->disks[i]->dst,
+                                                false);
+            if (indx >= 0) {
+                persistDisk = vm->newDef->disks[indx];
+                persist = true;
+            }
+        }
+
+        ret = qemuDomainSnapshotCreateSingleDiskActive(driver, vm, cgroup,
+                                                       &snap->def->disks[i],
+                                                       vm->def->disks[i],
+                                                       persistDisk, actions,
+                                                       reuse);
+        if (ret < 0)
+            break;
+    }
+    if (actions) {
+        if (ret == 0)
+            ret = qemuMonitorTransaction(priv->mon, actions);
+        virJSONValueFree(actions);
+        if (ret < 0) {
+            /* Transaction failed; undo the changes to vm.  */
+            bool need_unlink = !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT);
+            while (--i >= 0) {
+                virDomainDiskDefPtr persistDisk = NULL;
+
+                if (snap->def->disks[i].snapshot ==
+                    VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)
+                    continue;
+                if (vm->newDef) {
+                    int indx = virDomainDiskIndexByName(vm->newDef,
+                                                        vm->def->disks[i]->dst,
+                                                        false);
+                    if (indx >= 0)
+                        persistDisk = vm->newDef->disks[indx];
+                }
+
+                qemuDomainSnapshotUndoSingleDiskActive(driver, vm, cgroup,
+                                                       snap->def->dom->disks[i],
+                                                       vm->def->disks[i],
+                                                       persistDisk,
+                                                       need_unlink);
+            }
+        }
+    }
+    qemuDomainObjExitMonitorWithDriver(driver, vm);
+
+cleanup:
+    virCgroupFree(&cgroup);
+
+    if (ret == 0 || !qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION)) {
+        if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0 ||
+            (persist && virDomainSaveConfig(driver->configDir, vm->newDef) < 0))
+            ret = -1;
+    }
+
+    return ret;
+}
+
+
+static int
+qemuDomainSnapshotCreateActiveExternal(virConnectPtr conn,
+                                       virQEMUDriverPtr driver,
+                                       virDomainObjPtr *vmptr,
+                                       virDomainSnapshotObjPtr snap,
+                                       unsigned int flags)
+{
+    bool resume = false;
+    int ret = -1;
+    virDomainObjPtr vm = *vmptr;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    char *xml = NULL;
+    bool memory = snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+    bool memory_unlink = false;
+    bool atomic = !!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC);
+    bool transaction = qemuCapsGet(priv->caps, QEMU_CAPS_TRANSACTION);
+    int thaw = 0; /* 1 if freeze succeeded, -1 if freeze failed */
+
+    if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm,
+                                             QEMU_ASYNC_JOB_SNAPSHOT) < 0)
+        goto cleanup;
+
+    /* If quiesce was requested, then issue a freeze command, and a
+     * counterpart thaw command, no matter what.  The command will
+     * fail if the guest is paused or the guest agent is not
+     * running.  */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) {
+        if (qemuDomainSnapshotFSFreeze(driver, vm) < 0) {
+            /* helper reported the error */
+            thaw = -1;
+            goto endjob;
+        } else {
+            thaw = 1;
+        }
+    }
+
+    /* we need to resume the guest only if it was previously running */
+    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+        resume = true;
+
+        /* For external checkpoints (those with memory), the guest
+         * must pause (either by libvirt up front, or by qemu after
+         * _LIVE converges).  For disk-only snapshots with multiple
+         * disks, libvirt must pause externally to get all snapshots
+         * to be at the same point in time, unless qemu supports
+         * transactions.  For a single disk, snapshot is atomic
+         * without requiring a pause.  Thanks to
+         * qemuDomainSnapshotPrepare, if we got to this point, the
+         * atomic flag now says whether we need to pause, and a
+         * capability bit says whether to use transaction.
+         */
+        if ((memory && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE)) ||
+            (!memory && atomic && !transaction)) {
+            if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
+                                    QEMU_ASYNC_JOB_SNAPSHOT) < 0)
+                goto endjob;
+
+            if (!virDomainObjIsActive(vm)) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("guest unexpectedly quit"));
+                goto endjob;
+            }
+        }
+    }
+
+    /* do the memory snapshot if necessary */
+    if (memory) {
+        /* check if migration is possible */
+        if (!qemuMigrationIsAllowed(driver, vm, vm->def, false))
+            goto endjob;
+
+        /* allow the migration job to be cancelled or the domain to be paused */
+        qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK |
+                                     JOB_MASK(QEMU_JOB_SUSPEND) |
+                                     JOB_MASK(QEMU_JOB_MIGRATION_OP));
+
+        if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, false)))
+            goto endjob;
+
+        if ((ret = qemuDomainSaveMemory(driver, vm, snap->def->file,
+                                        xml, QEMU_SAVE_FORMAT_RAW,
+                                        resume, 0,
+                                        QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+            goto endjob;
+
+        /* the memory image was created, remove it on errors */
+        memory_unlink = true;
+
+        /* forbid any further manipulation */
+        qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK);
+    }
+
+    /* now the domain is now paused if:
+     * - if a memory snapshot was requested
+     * - an atomic snapshot was requested AND
+     *   qemu does not support transactions
+     *
+     * Next we snapshot the disks.
+     */
+    if ((ret = qemuDomainSnapshotCreateDiskActive(driver, vm, snap, flags,
+                                                  QEMU_ASYNC_JOB_SNAPSHOT)) < 0)
+        goto endjob;
+
+    /* the snapshot is complete now */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) {
+        virDomainEventPtr event;
+
+        event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED,
+                                         VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
+        qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT, 0);
+        virDomainAuditStop(vm, "from-snapshot");
+        /* We already filtered the _HALT flag for persistent domains
+         * only, so this end job never drops the last reference.  */
+        ignore_value(qemuDomainObjEndAsyncJob(driver, vm));
+        resume = false;
+        thaw = 0;
+        vm = NULL;
+        if (event)
+            qemuDomainEventQueue(driver, event);
+    }
+
+    ret = 0;
+
+endjob:
+    if (resume && vm && virDomainObjIsActive(vm) &&
+        qemuProcessStartCPUs(driver, vm, conn,
+                             VIR_DOMAIN_RUNNING_UNPAUSED,
+                             QEMU_ASYNC_JOB_SNAPSHOT) < 0) {
+        virDomainEventPtr event = NULL;
+        event = virDomainEventNewFromObj(vm,
+                                         VIR_DOMAIN_EVENT_SUSPENDED,
+                                         VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
+        if (event)
+            qemuDomainEventQueue(driver, event);
+        if (virGetLastError() == NULL) {
+            virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+                           _("resuming after snapshot failed"));
+        }
+
+        ret = -1;
+        goto cleanup;
+    }
+    if (vm && thaw != 0 &&
+        qemuDomainSnapshotFSThaw(driver, vm, thaw > 0) < 0) {
+        /* helper reported the error, if it was needed */
+        if (thaw > 0)
+            ret = -1;
+    }
+    if (vm && !qemuDomainObjEndAsyncJob(driver, vm)) {
+        /* Only possible if a transient vm quit while our locks were down,
+         * in which case we don't want to save snapshot metadata.
+         */
+        *vmptr = NULL;
+        ret = -1;
+    }
+
+cleanup:
+    VIR_FREE(xml);
+    if (memory_unlink && ret < 0)
+        unlink(snap->def->file);
+
+    return ret;
+}
+
+
+virDomainSnapshotPtr
+qemuDomainSnapshotCreateXML(virDomainPtr domain,
+                            const char *xmlDesc,
+                            unsigned int flags)
+{
+    virQEMUDriverPtr driver = domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    char *xml = NULL;
+    virDomainSnapshotObjPtr snap = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    virDomainSnapshotDefPtr def = NULL;
+    bool update_current = true;
+    unsigned int parse_flags = VIR_DOMAIN_SNAPSHOT_PARSE_DISKS;
+    virDomainSnapshotObjPtr other = NULL;
+    int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
+    int align_match = true;
+
+    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, NULL);
+
+    if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE) &&
+        !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("quiesce requires disk-only"));
+        return NULL;
+    }
+
+    if (((flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) &&
+         !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) ||
+        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA))
+        update_current = false;
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)
+        parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE;
+
+    qemuDriverLock(driver);
+    virUUIDFormat(domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, domain->uuid);
+    if (!vm) {
+        virReportError(VIR_ERR_NO_DOMAIN,
+                       _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (qemuProcessAutoDestroyActive(driver, vm)) {
+        virReportError(VIR_ERR_OPERATION_INVALID,
+                       "%s", _("domain is marked for auto destroy"));
+        goto cleanup;
+    }
+    if (virDomainHasDiskMirror(vm)) {
+        virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s",
+                       _("domain has active block copy job"));
+        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 (!(def = virDomainSnapshotDefParseString(xmlDesc, driver->caps,
+                                                QEMU_EXPECTED_VIRT_TYPES,
+                                                parse_flags)))
+        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 ||
+         flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("live snapshot creation is supported only "
+                         "with external checkpoints"));
+        goto cleanup;
+    }
+    if ((def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL ||
+         def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL) &&
+        flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+                       _("disk-only snapshot creation is not compatible with "
+                         "memory snapshot"));
+        goto cleanup;
+    }
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) {
+        /* Prevent circular chains */
+        if (def->parent) {
+            if (STREQ(def->name, def->parent)) {
+                virReportError(VIR_ERR_INVALID_ARG,
+                               _("cannot set snapshot %s as its own parent"),
+                               def->name);
+                goto cleanup;
+            }
+            other = virDomainSnapshotFindByName(vm->snapshots, def->parent);
+            if (!other) {
+                virReportError(VIR_ERR_INVALID_ARG,
+                               _("parent %s for snapshot %s not found"),
+                               def->parent, def->name);
+                goto cleanup;
+            }
+            while (other->def->parent) {
+                if (STREQ(other->def->parent, def->name)) {
+                    virReportError(VIR_ERR_INVALID_ARG,
+                                   _("parent %s would create cycle to %s"),
+                                   other->def->name, def->name);
+                    goto cleanup;
+                }
+                other = virDomainSnapshotFindByName(vm->snapshots,
+                                                    other->def->parent);
+                if (!other) {
+                    VIR_WARN("snapshots are inconsistent for %s",
+                             vm->def->name);
+                    break;
+                }
+            }
+        }
+
+        /* Check that any replacement is compatible */
+        if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) &&
+            def->state != VIR_DOMAIN_DISK_SNAPSHOT) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("disk-only flag for snapshot %s requires "
+                             "disk-snapshot state"),
+                           def->name);
+            goto cleanup;
+
+        }
+
+        if (def->dom &&
+            memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("definition for snapshot %s must use uuid %s"),
+                           def->name, uuidstr);
+            goto cleanup;
+        }
+
+        other = virDomainSnapshotFindByName(vm->snapshots, def->name);
+        if (other) {
+            if ((other->def->state == VIR_DOMAIN_RUNNING ||
+                 other->def->state == VIR_DOMAIN_PAUSED) !=
+                (def->state == VIR_DOMAIN_RUNNING ||
+                 def->state == VIR_DOMAIN_PAUSED)) {
+                virReportError(VIR_ERR_INVALID_ARG,
+                               _("cannot change between online and offline "
+                                 "snapshot state in snapshot %s"),
+                               def->name);
+                goto cleanup;
+            }
+
+            if ((other->def->state == VIR_DOMAIN_DISK_SNAPSHOT) !=
+                (def->state == VIR_DOMAIN_DISK_SNAPSHOT)) {
+                virReportError(VIR_ERR_INVALID_ARG,
+                               _("cannot change between disk snapshot and "
+                                 "system checkpoint in snapshot %s"),
+                               def->name);
+                goto cleanup;
+            }
+
+            if (other->def->dom) {
+                if (def->dom) {
+                    if (!virDomainDefCheckABIStability(other->def->dom,
+                                                       def->dom))
+                        goto cleanup;
+                } else {
+                    /* Transfer the domain def */
+                    def->dom = other->def->dom;
+                    other->def->dom = NULL;
+                }
+            }
+
+            if (def->dom) {
+                if (def->state == VIR_DOMAIN_DISK_SNAPSHOT ||
+                    virDomainSnapshotDefIsExternal(def)) {
+                    align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+                    align_match = false;
+                }
+
+                if (virDomainSnapshotAlignDisks(def, align_location,
+                                                align_match) < 0) {
+                    /* revert stealing of the snapshot domain definition */
+                    if (def->dom && !other->def->dom) {
+                        other->def->dom = def->dom;
+                        def->dom = NULL;
+                    }
+                    goto cleanup;
+                }
+            }
+
+            if (other == vm->current_snapshot) {
+                update_current = true;
+                vm->current_snapshot = NULL;
+            }
+
+            /* Drop and rebuild the parent relationship, but keep all
+             * child relations by reusing snap.  */
+            virDomainSnapshotDropParent(other);
+            virDomainSnapshotDefFree(other->def);
+            other->def = def;
+            def = NULL;
+            snap = other;
+        } else {
+            if (def->dom) {
+                if (def->state == VIR_DOMAIN_DISK_SNAPSHOT ||
+                    def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+                    align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
+                    align_match = false;
+                }
+                if (virDomainSnapshotAlignDisks(def, align_location,
+                                                align_match) < 0)
+                    goto cleanup;
+            }
+        }
+    } else {
+        /* Easiest way to clone inactive portion of vm->def is via
+         * conversion in and back out of xml.  */
+        if (!(xml = qemuDomainDefFormatLive(driver, vm->def, true, true)) ||
+            !(def->dom = virDomainDefParseString(driver->caps, xml,
+                                                 QEMU_EXPECTED_VIRT_TYPES,
+                                                 VIR_DOMAIN_XML_INACTIVE)))
+            goto cleanup;
+
+        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_DISK_SNAPSHOT;
+            else
+                def->state = VIR_DOMAIN_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);
+            def->memory = (def->state == VIR_DOMAIN_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 cleanup;
+    }
+
+    if (!snap) {
+        if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def)))
+            goto cleanup;
+
+        def = NULL;
+    }
+
+    if (update_current)
+        snap->def->current = true;
+    if (vm->current_snapshot) {
+        if (!(flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)) {
+            snap->def->parent = strdup(vm->current_snapshot->def->name);
+            if (snap->def->parent == NULL) {
+                virReportOOMError();
+                goto cleanup;
+            }
+        }
+        if (update_current) {
+            vm->current_snapshot->def->current = false;
+            if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
+                                                driver->snapshotDir) < 0)
+                goto cleanup;
+            vm->current_snapshot = NULL;
+        }
+    }
+
+    /* actually do the snapshot */
+    if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_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 ||
+            snap->def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
+            /* external checkpoint or disk snapshot */
+            if (qemuDomainSnapshotCreateActiveExternal(domain->conn, driver,
+                                                       &vm, snap, flags) < 0)
+                goto cleanup;
+        } else {
+            /* internal checkpoint */
+            if (qemuDomainSnapshotCreateActiveInternal(domain->conn, driver,
+                                                       &vm, snap, flags) < 0)
+                goto cleanup;
+        }
+    } 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 cleanup;
+        } else {
+            if (qemuDomainSnapshotCreateInactiveInternal(driver, vm, snap) < 0)
+                goto cleanup;
+        }
+    }
+
+    /* 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);
+
+cleanup:
+    if (vm) {
+        if (snapshot && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA)) {
+            if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                                driver->snapshotDir) < 0) {
+                VIR_WARN("unable to save metadata for snapshot %s",
+                         snap->def->name);
+            } else {
+                if (update_current)
+                    vm->current_snapshot = snap;
+                other = virDomainSnapshotFindByName(vm->snapshots,
+                                                    snap->def->parent);
+                snap->parent = other;
+                other->nchildren++;
+                snap->sibling = other->first_child;
+                other->first_child = snap;
+            }
+        } else if (snap) {
+            virDomainSnapshotObjListRemove(vm->snapshots, snap);
+        }
+        virDomainObjUnlock(vm);
+    }
+    virDomainSnapshotDefFree(def);
+    VIR_FREE(xml);
+    qemuDriverUnlock(driver);
+    return snapshot;
+}
+
+
+/* The domain is expected to be locked and inactive. */
+static int
+qemuDomainSnapshotRevertInactive(virQEMUDriverPtr driver,
+                                 virDomainObjPtr vm,
+                                 virDomainSnapshotObjPtr 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
+qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
+                           unsigned int flags)
+{
+    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int ret = -1;
+    virDomainSnapshotObjPtr snap = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    virDomainEventPtr event = NULL;
+    virDomainEventPtr event2 = NULL;
+    int detail;
+    qemuDomainObjPrivatePtr priv;
+    int rc;
+    virDomainDefPtr config = NULL;
+
+    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.
+     */
+
+    qemuDriverLock(driver);
+    virUUIDFormat(snapshot->domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
+    if (!vm) {
+        virReportError(VIR_ERR_NO_DOMAIN,
+                       _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+    if (virDomainHasDiskMirror(vm)) {
+        virReportError(VIR_ERR_BLOCK_COPY_ACTIVE, "%s",
+                       _("domain has active block copy job"));
+        goto cleanup;
+    }
+
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
+        goto cleanup;
+
+    if (!vm->persistent &&
+        snap->def->state != VIR_DOMAIN_RUNNING &&
+        snap->def->state != VIR_DOMAIN_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 cleanup;
+    }
+    if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("revert to external disk snapshot not supported "
+                         "yet"));
+        goto cleanup;
+    }
+    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 cleanup;
+        }
+        if (virDomainObjIsActive(vm) &&
+            !(snap->def->state == VIR_DOMAIN_RUNNING
+              || snap->def->state == VIR_DOMAIN_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 cleanup;
+        }
+    }
+
+
+    if (vm->current_snapshot) {
+        vm->current_snapshot->def->current = false;
+        if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot,
+                                            driver->snapshotDir) < 0)
+            goto cleanup;
+        vm->current_snapshot = NULL;
+        /* XXX Should we restore vm->current_snapshot after this point
+         * in the failure cases where we know there was no change?  */
+    }
+
+    /* Prepare to copy the snapshot inactive xml as the config of this
+     * domain.
+     *
+     * XXX Should domain snapshots track live xml rather
+     * than inactive xml?  */
+    snap->def->current = true;
+    if (snap->def->dom) {
+        config = virDomainDefCopy(driver->caps, snap->def->dom, true);
+        if (!config)
+            goto cleanup;
+    }
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (snap->def->state == VIR_DOMAIN_RUNNING
+        || snap->def->state == VIR_DOMAIN_PAUSED) {
+        /* Transitions 2, 3, 5, 6, 8, 9 */
+        bool was_running = false;
+        bool was_stopped = false;
+
+        /* 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.  */
+            if (config && !virDomainDefCheckABIStability(vm->def, config)) {
+                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, 0);
+                virDomainAuditStop(vm, "from-snapshot");
+                detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
+                event = virDomainEventNewFromObj(vm,
+                                                 VIR_DOMAIN_EVENT_STOPPED,
+                                                 detail);
+                if (event)
+                    qemuDomainEventQueue(driver, event);
+                goto load;
+            }
+
+            priv = vm->privateData;
+            if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
+                /* Transitions 5, 6 */
+                was_running = true;
+                if (qemuProcessStopCPUs(driver, vm,
+                                        VIR_DOMAIN_PAUSED_FROM_SNAPSHOT,
+                                        QEMU_ASYNC_JOB_NONE) < 0)
+                    goto endjob;
+                /* Create an event now in case the restore fails, so
+                 * that user will be alerted that they are now paused.
+                 * If restore later succeeds, we might replace this. */
+                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
+                event = virDomainEventNewFromObj(vm,
+                                                 VIR_DOMAIN_EVENT_SUSPENDED,
+                                                 detail);
+                if (!virDomainObjIsActive(vm)) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                   _("guest unexpectedly quit"));
+                    goto endjob;
+                }
+            }
+            qemuDomainObjEnterMonitorWithDriver(driver, vm);
+            rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name);
+            qemuDomainObjExitMonitorWithDriver(driver, vm);
+            if (rc < 0) {
+                /* XXX resume domain if it was running before the
+                 * failed loadvm attempt? */
+                goto endjob;
+            }
+            if (config)
+                virDomainObjAssignDef(vm, config, false);
+        } else {
+            /* Transitions 2, 3 */
+        load:
+            was_stopped = true;
+            if (config)
+                virDomainObjAssignDef(vm, config, false);
+
+            rc = qemuProcessStart(snapshot->domain->conn,
+                                  driver, vm, NULL, -1, NULL, snap,
+                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
+                                  VIR_QEMU_PROCESS_START_PAUSED);
+            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
+            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             detail);
+            if (rc < 0)
+                goto endjob;
+        }
+
+        /* Touch up domain state.  */
+        if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) &&
+            (snap->def->state == VIR_DOMAIN_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 = virDomainEventNewFromObj(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, snapshot->domain->conn,
+                                      VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
+                                      QEMU_ASYNC_JOB_NONE);
+            if (rc < 0)
+                goto endjob;
+            virDomainEventFree(event);
+            event = NULL;
+            if (was_stopped) {
+                /* Transition 2 */
+                detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+                event = virDomainEventNewFromObj(vm,
+                                                 VIR_DOMAIN_EVENT_STARTED,
+                                                 detail);
+            } else if (was_running) {
+                /* Transition 8 */
+                detail = VIR_DOMAIN_EVENT_RESUMED;
+                event = virDomainEventNewFromObj(vm,
+                                                 VIR_DOMAIN_EVENT_RESUMED,
+                                                 detail);
+            }
+        }
+    } else {
+        /* 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, 0);
+            virDomainAuditStop(vm, "from-snapshot");
+            detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STOPPED,
+                                             detail);
+        }
+
+        if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) {
+            if (!vm->persistent) {
+                if (qemuDomainObjEndJob(driver, vm) > 0)
+                    qemuDomainRemoveInactive(driver, vm);
+                vm = NULL;
+                goto cleanup;
+            }
+            goto endjob;
+        }
+        if (config)
+            virDomainObjAssignDef(vm, config, false);
+
+        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;
+            unsigned int start_flags = 0;
+
+            start_flags |= paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;
+
+            if (event)
+                qemuDomainEventQueue(driver, event);
+            rc = qemuProcessStart(snapshot->domain->conn,
+                                  driver, vm, NULL, -1, NULL, NULL,
+                                  VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
+                                  start_flags);
+            virDomainAuditStart(vm, "from-snapshot", rc >= 0);
+            if (rc < 0) {
+                if (!vm->persistent) {
+                    if (qemuDomainObjEndJob(driver, vm) > 0)
+                        qemuDomainRemoveInactive(driver, vm);
+                    vm = NULL;
+                    goto cleanup;
+                }
+                goto endjob;
+            }
+            detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
+            event = virDomainEventNewFromObj(vm,
+                                             VIR_DOMAIN_EVENT_STARTED,
+                                             detail);
+            if (paused) {
+                detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
+                event2 = virDomainEventNewFromObj(vm,
+                                                  VIR_DOMAIN_EVENT_SUSPENDED,
+                                                  detail);
+            }
+        }
+    }
+
+    ret = 0;
+
+endjob:
+    if (vm && qemuDomainObjEndJob(driver, vm) == 0)
+        vm = NULL;
+
+cleanup:
+    if (vm && ret == 0) {
+        if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                            driver->snapshotDir) < 0)
+            ret = -1;
+        else
+            vm->current_snapshot = snap;
+    } else if (snap) {
+        snap->def->current = false;
+    }
+    if (event) {
+        qemuDomainEventQueue(driver, event);
+        if (event2)
+            qemuDomainEventQueue(driver, event2);
+    }
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+
+    return ret;
+}
+
+typedef struct _virQEMUSnapReparent virQEMUSnapReparent;
+typedef virQEMUSnapReparent *virQEMUSnapReparentPtr;
+struct _virQEMUSnapReparent {
+    virQEMUDriverPtr driver;
+    virDomainSnapshotObjPtr parent;
+    virDomainObjPtr vm;
+    int err;
+    virDomainSnapshotObjPtr last;
+};
+
+
+static void
+qemuDomainSnapshotReparentChildren(void *payload,
+                                   const void *name ATTRIBUTE_UNUSED,
+                                   void *data)
+{
+    virDomainSnapshotObjPtr snap = payload;
+    virQEMUSnapReparentPtr rep = data;
+
+    if (rep->err < 0) {
+        return;
+    }
+
+    VIR_FREE(snap->def->parent);
+    snap->parent = rep->parent;
+
+    if (rep->parent->def) {
+        snap->def->parent = strdup(rep->parent->def->name);
+
+        if (snap->def->parent == NULL) {
+            virReportOOMError();
+            rep->err = -1;
+            return;
+        }
+    }
+
+    if (!snap->sibling)
+        rep->last = snap;
+
+    rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap,
+                                               rep->driver->snapshotDir);
+}
+
+
+int
+qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
+                         unsigned int flags)
+{
+    virQEMUDriverPtr driver = snapshot->domain->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    int ret = -1;
+    virDomainSnapshotObjPtr snap = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    virQEMUSnapRemove rem;
+    virQEMUSnapReparent rep;
+    bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
+    int external = 0;
+
+    virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+                  VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
+                  VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1);
+
+    qemuDriverLock(driver);
+    virUUIDFormat(snapshot->domain->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid);
+    if (!vm) {
+        virReportError(VIR_ERR_NO_DOMAIN,
+                       _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
+        goto cleanup;
+
+    if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)) {
+        if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) &&
+            virDomainSnapshotIsExternal(snap))
+            external++;
+        if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN)
+            virDomainSnapshotForEachDescendant(snap,
+                                               qemuDomainSnapshotCountExternal,
+                                               &external);
+        if (external) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("deletion of %d external disk snapshots not "
+                             "supported yet"), external);
+            goto cleanup;
+        }
+    }
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    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 = false;
+        virDomainSnapshotForEachDescendant(snap,
+                                           qemuDomainSnapshotDiscardAll,
+                                           &rem);
+        if (rem.err < 0)
+            goto endjob;
+        if (rem.current) {
+            if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
+                snap->def->current = true;
+                if (qemuDomainSnapshotWriteMetadata(vm, snap,
+                                                    driver->snapshotDir) < 0) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("failed to set snapshot '%s' as current"),
+                                   snap->def->name);
+                    snap->def->current = false;
+                    goto endjob;
+                }
+            }
+            vm->current_snapshot = snap;
+        }
+    } else if (snap->nchildren) {
+        rep.driver = driver;
+        rep.parent = snap->parent;
+        rep.vm = vm;
+        rep.err = 0;
+        rep.last = NULL;
+        virDomainSnapshotForEachChild(snap,
+                                      qemuDomainSnapshotReparentChildren,
+                                      &rep);
+        if (rep.err < 0)
+            goto endjob;
+        /* Can't modify siblings during ForEachChild, so do it now.  */
+        snap->parent->nchildren += snap->nchildren;
+        rep.last->sibling = snap->parent->first_child;
+        snap->parent->first_child = snap->first_child;
+    }
+
+    if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) {
+        snap->nchildren = 0;
+        snap->first_child = NULL;
+        ret = 0;
+    } else {
+        virDomainSnapshotDropParent(snap);
+        ret = qemuDomainSnapshotDiscard(driver, vm, snap, true, metadata_only);
+    }
+
+endjob:
+    if (qemuDomainObjEndJob(driver, vm) == 0)
+        vm = NULL;
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return ret;
+}
diff --git a/src/qemu/qemu_snapshot.h b/src/qemu/qemu_snapshot.h
new file mode 100644
index 0000000..eebf167
--- /dev/null
+++ b/src/qemu/qemu_snapshot.h
@@ -0,0 +1,38 @@
+/*
+ * qemu_snapshot.h: QEMU snapshot handling
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * 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/>.
+ *
+ */
+
+#ifndef __QEMU_SNAPSHOT_H__
+# define __QEMU_SNAPSHOT_H__
+
+# include "qemu_domain.h"
+
+virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain,
+                                                 const char *xmlDesc,
+                                                 unsigned int flags);
+
+int qemuDomainRevertToSnapshot(virDomainSnapshotPtr,
+                               unsigned int flags);
+
+int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
+                             unsigned int flags);
+
+
+#endif /* __QEMU_SNAPSHOT_H__ */
-- 
1.8.0.2


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]