--- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list