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