---
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