libvirt/src/qemu/qemu_snapshot.c
Pavel Hrdina 3ad5817053 qemu_snapshot: fix reverting to inactive snapshot
When reverting to inactive snapshot updating the domain definition needs
to happen after the new overlays are created otherwise qemu-img will
correctly fail with error:

    Trying to create an image with the same filename as the backing file

Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2023-11-16 15:29:45 +01:00

3889 lines
130 KiB
C

/*
* 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 <fcntl.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 "virdomainsnapshotobjlist.h"
#include "virqemu.h"
#include "storage_source.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_snapshot");
/**
* qemuSnapshotSetCurrent: Set currently active snapshot
*
* @vm: domain object
* @newcurrent: snapshot object to set as current/active
*
* Sets @newcurrent as the 'current' snapshot of @vm. This helper ensures that
* the snapshot which was 'current' previously is updated.
*/
static void
qemuSnapshotSetCurrent(virDomainObj *vm,
virDomainMomentObj *newcurrent)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
virDomainMomentObj *oldcurrent = virDomainSnapshotGetCurrent(vm->snapshots);
virDomainSnapshotSetCurrent(vm->snapshots, newcurrent);
/* we need to write out metadata for the old snapshot to update the
* 'active' property */
if (oldcurrent &&
oldcurrent != newcurrent) {
if (qemuDomainSnapshotWriteMetadata(vm, oldcurrent, driver->xmlopt, cfg->snapshotDir) < 0)
VIR_WARN("failed to update old current snapshot");
}
}
/* Looks up snapshot object from VM and name */
virDomainMomentObj *
qemuSnapObjFromName(virDomainObj *vm,
const char *name)
{
virDomainMomentObj *snap = NULL;
snap = virDomainSnapshotFindByName(vm->snapshots, name);
if (!snap)
virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
_("no domain snapshot with matching name '%1$s'"),
name);
return snap;
}
/* Looks up snapshot object from VM and snapshotPtr */
virDomainMomentObj *
qemuSnapObjFromSnapshot(virDomainObj *vm,
virDomainSnapshotPtr snapshot)
{
return qemuSnapObjFromName(vm, snapshot->name);
}
int
qemuSnapshotFSFreeze(virDomainObj *vm,
const char **mountpoints,
unsigned int nmountpoints)
{
qemuAgent *agent;
int frozen;
if (!qemuDomainAgentAvailable(vm, true))
return -1;
agent = qemuDomainObjEnterAgent(vm);
frozen = qemuAgentFSFreeze(agent, mountpoints, nmountpoints);
qemuDomainObjExitAgent(vm, agent);
return frozen;
}
/* Return -1 on error, otherwise number of thawed filesystems. */
int
qemuSnapshotFSThaw(virDomainObj *vm,
bool report)
{
qemuAgent *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;
}
static int
qemuSnapshotDomainDefUpdateDisk(virDomainDef *domdef,
virDomainSnapshotDef *snapdef,
bool reuse)
{
size_t i;
for (i = 0; i < snapdef->ndisks; i++) {
g_autoptr(virStorageSource) newsrc = NULL;
virDomainSnapshotDiskDef *snapdisk = &(snapdef->disks[i]);
virDomainDiskDef *defdisk = virDomainDiskByName(domdef, snapdisk->name, false);
if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (!defdisk)
continue;
if (!(newsrc = virStorageSourceCopy(snapdisk->src, false)))
return -1;
if (virStorageSourceInitChainElement(newsrc, defdisk->src, false) < 0)
return -1;
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);
}
return 0;
}
/**
* qemuSnapshotCreateQcow2Files:
* @driver: QEMU driver
* @def: domain definition
* @snapdef: snapshot definition
* @created: bitmap to store which disks were created
*
* Create new qcow2 images based on snapshot definition @snapdef and use
* domain definition @def as source for backing images.
*
* Returns 0 on success, -1 on error.
*/
static int
qemuSnapshotCreateQcow2Files(virQEMUDriver *driver,
virDomainDef *def,
virDomainSnapshotDef *snapdef,
virBitmap *created)
{
size_t i;
const char *qemuImgPath;
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virDomainSnapshotDiskDef *snapdisk = NULL;
virDomainDiskDef *defdisk = NULL;
if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
return -1;
for (i = 0; i < snapdef->ndisks; i++) {
g_autoptr(virCommand) cmd = NULL;
snapdisk = &(snapdef->disks[i]);
defdisk = def->disks[i];
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)
return -1;
/* creates cmd line args: qemu-img create -f qcow2 -o */
if (!(cmd = virCommandNewArgList(qemuImgPath,
"create",
"-f",
virStorageFileFormatTypeToString(snapdisk->src->format),
"-o",
NULL)))
return -1;
/* 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)
return -1;
}
return 0;
}
/* The domain is expected to be locked and inactive. */
static int
qemuSnapshotCreateInactiveInternal(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap)
{
return qemuDomainSnapshotForEachQcow2(driver, vm->def, snap, "-c", false);
}
/* The domain is expected to be locked and inactive. */
static int
qemuSnapshotCreateInactiveExternal(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap,
bool reuse)
{
virDomainSnapshotDiskDef *snapdisk;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
int ret = -1;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
g_autoptr(virBitmap) created = virBitmapNew(snapdef->ndisks);
/* If reuse is true, then qemuSnapshotPrepare already
* ensured that the new files exist, and it was up to the user to
* create them correctly. */
if (!reuse && qemuSnapshotCreateQcow2Files(driver, vm->def, snapdef, created) < 0)
goto cleanup;
/* update disk definitions */
if (qemuSnapshotDomainDefUpdateDisk(vm->def, snapdef, reuse) < 0)
goto cleanup;
if (virDomainDefSave(vm->def, driver->xmlopt, cfg->configDir) < 0)
goto cleanup;
ret = 0;
cleanup:
/* 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);
}
}
return ret;
}
/* The domain is expected to be locked and active. */
static int
qemuSnapshotCreateActiveInternal(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap,
unsigned int flags)
{
qemuDomainObjPrivate *priv = vm->privateData;
virObjectEvent *event = NULL;
bool resume = false;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
int ret = -1;
if (!qemuMigrationSrcIsAllowed(vm, false, VIR_ASYNC_JOB_SNAPSHOT, 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,
VIR_ASYNC_JOB_SNAPSHOT) < 0)
goto cleanup;
resume = true;
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("guest unexpectedly quit"));
goto cleanup;
}
}
if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_SNAPSHOT) < 0) {
resume = false;
goto cleanup;
}
ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name);
qemuDomainObjExitMonitor(vm);
if (ret < 0)
goto cleanup;
if (!(snapdef->cookie = (virObject *) 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,
VIR_ASYNC_JOB_SNAPSHOT, 0);
virDomainAuditStop(vm, "from-snapshot");
resume = false;
}
cleanup:
if (resume && virDomainObjIsActive(vm) &&
qemuProcessStartCPUs(driver, vm,
VIR_DOMAIN_RUNNING_UNPAUSED,
VIR_ASYNC_JOB_SNAPSHOT) < 0) {
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_SUSPENDED,
VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) {
virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
VIR_DOMAIN_PAUSED_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(virDomainSnapshotDiskDef *snapdisk,
virDomainDiskDef *domdisk)
{
if (!domdisk->src->shared || domdisk->src->readonly)
return 0;
if (!qemuBlockStorageSourceSupportsConcurrentAccess(snapdisk->src)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("shared access for disk '%1$s' requires use of supported storage format"),
domdisk->dst);
return -1;
}
return 0;
}
static int
qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDef *snapdisk,
virDomainDiskDef *domdisk)
{
virStorageType domDiskType = virStorageSourceGetActualType(domdisk->src);
virStorageType snapDiskType = virStorageSourceGetActualType(snapdisk->src);
switch (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_NFS:
case VIR_STORAGE_NET_PROTOCOL_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("external inactive snapshots are not supported on 'network' disks using '%1$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_VHOST_USER:
case VIR_STORAGE_TYPE_VHOST_VDPA:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("external inactive snapshots are not supported on '%1$s' disks"),
virStorageTypeToString(domDiskType));
return -1;
}
switch (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_VHOST_USER:
case VIR_STORAGE_TYPE_VHOST_VDPA:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("external inactive snapshots are not supported on '%1$s' disks"),
virStorageTypeToString(snapDiskType));
return -1;
}
if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
return -1;
return 0;
}
static int
qemuSnapshotPrepareDiskExternalActive(virDomainSnapshotDiskDef *snapdisk,
virDomainDiskDef *domdisk)
{
virStorageType actualType = virStorageSourceGetActualType(snapdisk->src);
if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_MANUAL)
return 0;
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(domdisk))
return -1;
switch (actualType) {
case VIR_STORAGE_TYPE_BLOCK:
case VIR_STORAGE_TYPE_FILE:
case VIR_STORAGE_TYPE_NETWORK:
break;
case VIR_STORAGE_TYPE_DIR:
case VIR_STORAGE_TYPE_VOLUME:
case VIR_STORAGE_TYPE_NVME:
case VIR_STORAGE_TYPE_VHOST_USER:
case VIR_STORAGE_TYPE_VHOST_VDPA:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("external active snapshots are not supported on '%1$s' disks"),
virStorageTypeToString(actualType));
return -1;
}
if (qemuSnapshotPrepareDiskShared(snapdisk, domdisk) < 0)
return -1;
return 0;
}
static int
qemuSnapshotPrepareDiskExternal(virDomainDiskDef *disk,
virDomainSnapshotDiskDef *snapdisk,
bool active,
bool reuse)
{
if (!snapdisk->src->format) {
snapdisk->src->format = VIR_STORAGE_FILE_QCOW2;
} else if (snapdisk->src->format != VIR_STORAGE_FILE_QCOW2 &&
snapdisk->src->format != VIR_STORAGE_FILE_QED) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("external snapshot format for disk %1$s is unsupported: %2$s"),
snapdisk->name,
virStorageFileFormatTypeToString(snapdisk->src->format));
return -1;
}
if (snapdisk->src->metadataCacheMaxSize > 0) {
if (snapdisk->src->format != VIR_STORAGE_FILE_QCOW2) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("metadata cache max size control is supported only with qcow2 images"));
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(snapdisk, disk) < 0)
return -1;
}
if (virStorageSourceIsLocalStorage(snapdisk->src)) {
struct stat st;
int err;
int rc;
if (virStorageSourceInit(snapdisk->src) < 0)
return -1;
rc = virStorageSourceStat(snapdisk->src, &st);
err = errno;
virStorageSourceDeinit(snapdisk->src);
if (rc < 0) {
if (err != ENOENT) {
virReportSystemError(err,
_("unable to stat for disk %1$s: %2$s"),
snapdisk->name, snapdisk->src->path);
return -1;
}
if (reuse) {
virReportSystemError(err,
_("missing existing file for disk %1$s: %2$s"),
snapdisk->name, snapdisk->src->path);
return -1;
}
if (snapdisk->src->type == VIR_STORAGE_TYPE_BLOCK) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("block device snapshot target '%1$s' doesn't exist"),
snapdisk->src->path);
return -1;
}
} else {
/* at this point VIR_STORAGE_TYPE_DIR was already rejected */
if ((snapdisk->src->type == VIR_STORAGE_TYPE_BLOCK && !S_ISBLK(st.st_mode)) ||
(snapdisk->src->type == VIR_STORAGE_TYPE_FILE && !S_ISREG(st.st_mode))) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("mismatch between configured type for snapshot disk '%1$s' and the type of existing file '%2$s'"),
snapdisk->name, snapdisk->src->path);
return -1;
}
if (!reuse &&
snapdisk->src->type == VIR_STORAGE_TYPE_FILE &&
st.st_size > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("external snapshot file for disk %1$s already exists and is not a block device: %2$s"),
snapdisk->name, snapdisk->src->path);
return -1;
}
}
}
return 0;
}
static int
qemuSnapshotPrepareDiskInternal(virDomainDiskDef *disk,
bool active)
{
virStorageType 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 (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_NFS:
case VIR_STORAGE_NET_PROTOCOL_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("internal inactive snapshots are not supported on 'network' disks using '%1$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_VHOST_USER:
case VIR_STORAGE_TYPE_VHOST_VDPA:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("internal inactive snapshots are not supported on '%1$s' disks"),
virStorageTypeToString(actualType));
return -1;
}
return 0;
}
static int
qemuSnapshotPrepare(virDomainObj *vm,
virDomainSnapshotDef *def,
bool *has_manual,
unsigned int *flags)
{
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++) {
virDomainSnapshotDiskDef *disk = &def->disks[i];
virDomainDiskDef *dom_disk = vm->def->disks[i];
if (disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
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 %1$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 %1$s unsupported for storage type %2$s"),
disk->name,
virStorageFileFormatTypeToString(dom_disk->src->format));
return -1;
}
break;
case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL:
if (qemuSnapshotPrepareDiskExternal(dom_disk, disk, active, reuse) < 0)
return -1;
external++;
break;
case VIR_DOMAIN_SNAPSHOT_LOCATION_MANUAL:
*has_manual = true;
forbid_internal = true;
break;
case VIR_DOMAIN_SNAPSHOT_LOCATION_NO:
/* 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_NO) {
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
*
* While offline snapshot would not snapshot the varstore at all, this used
* to work as auto-detected UEFI firmware was not present in the offline
* definition. Since in most cases the varstore doesn't change it's usually
* not an issue. Allow this as there are existing users of this case.
*
* Avoid the issues by forbidding internal snapshot with pflash if the
* VM is active.
*/
if (active &&
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 {
virStorageSource *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 */
virDomainDiskDef *disk;
char *relPath; /* relative path component to fill into original disk */
qemuBlockStorageSourceChainData *crdata;
bool blockdevadded;
virStorageSource *persistsrc;
virDomainDiskDef *persistdisk;
};
typedef struct _qemuSnapshotDiskData qemuSnapshotDiskData;
static void
qemuSnapshotDiskCleanup(qemuSnapshotDiskData *data,
size_t ndata,
virDomainObj *vm,
virDomainAsyncJob asyncJob)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
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(vm, asyncJob) == 0) {
qemuBlockStorageSourceAttachRollback(qemuDomainGetMonitor(vm),
data[i].crdata->srcdata[0]);
qemuDomainObjExitMonitor(vm);
}
}
if (data[i].created &&
virStorageSourceUnlink(data[i].src) < 0) {
VIR_WARN("Unable to remove just-created %s",
NULLSTR(data[i].src->path));
}
if (data[i].initialized)
virStorageSourceDeinit(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);
}
struct _qemuSnapshotDiskContext {
qemuSnapshotDiskData *dd;
size_t ndd;
virJSONValue *actions;
virQEMUDriverConfig *cfg;
/* needed for automatic cleanup of 'dd' */
virDomainObj *vm;
virDomainAsyncJob asyncJob;
};
typedef struct _qemuSnapshotDiskContext qemuSnapshotDiskContext;
qemuSnapshotDiskContext *
qemuSnapshotDiskContextNew(size_t ndisks,
virDomainObj *vm,
virDomainAsyncJob asyncJob)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
qemuSnapshotDiskContext *ret = g_new0(qemuSnapshotDiskContext, 1);
ret->dd = g_new0(qemuSnapshotDiskData, ndisks);
ret->actions = virJSONValueNewArray();
ret->vm = vm;
ret->cfg = virQEMUDriverGetConfig(driver);
ret->asyncJob = asyncJob;
return ret;
}
void
qemuSnapshotDiskContextCleanup(qemuSnapshotDiskContext *snapctxt)
{
if (!snapctxt)
return;
virJSONValueFree(snapctxt->actions);
qemuSnapshotDiskCleanup(snapctxt->dd, snapctxt->ndd, snapctxt->vm, snapctxt->asyncJob);
virObjectUnref(snapctxt->cfg);
g_free(snapctxt);
}
/**
* 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(qemuSnapshotDiskData *dd,
virJSONValue *actions,
GHashTable *blockNamedNodeData)
{
qemuBlockNamedNodeData *entry;
size_t i;
if (!(entry = virHashLookup(blockNamedNodeData,
qemuBlockStorageSourceGetEffectiveNodename(dd->disk->src))))
return 0;
for (i = 0; i < entry->nbitmaps; i++) {
qemuBlockNamedNodeDataBitmap *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,
qemuBlockStorageSourceGetEffectiveNodename(dd->src),
bitmap->name, true, false,
bitmap->granularity) < 0)
return -1;
}
return 0;
}
static int
qemuSnapshotDiskPrepareOneBlockdev(virDomainObj *vm,
qemuSnapshotDiskData *dd,
virQEMUDriverConfig *cfg,
bool reuse,
GHashTable *blockNamedNodeData,
virDomainAsyncJob asyncJob)
{
qemuDomainObjPrivate *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 */
terminator = virStorageSourceNew();
if (qemuDomainPrepareStorageSourceBlockdev(dd->disk, dd->src,
priv, cfg) < 0)
return -1;
if (!(dd->crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(dd->src,
terminator)))
return -1;
if (reuse) {
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
return -1;
rc = qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm),
dd->crdata->srcdata[0]);
qemuDomainObjExitMonitor(vm);
if (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;
}
int
qemuSnapshotDiskPrepareOne(qemuSnapshotDiskContext *snapctxt,
virDomainDiskDef *disk,
virDomainSnapshotDiskDef *snapdisk,
GHashTable *blockNamedNodeData,
bool reuse,
bool updateConfig)
{
virDomainObj *vm = snapctxt->vm;
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
virDomainDiskDef *persistdisk;
qemuSnapshotDiskData *dd = snapctxt->dd + snapctxt->ndd++;
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 (updateConfig &&
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;
}
if (virStorageSourceSupportsCreate(dd->src)) {
if (qemuDomainStorageFileInit(driver, vm, dd->src, NULL) < 0)
return -1;
dd->initialized = true;
if (!reuse) {
/* pre-create the image file so that we can label it before handing it to qemu */
if (dd->src->type != VIR_STORAGE_TYPE_BLOCK) {
if (virStorageSourceCreate(dd->src) < 0) {
virReportSystemError(errno, _("failed to create image file '%1$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 (qemuSnapshotDiskPrepareOneBlockdev(vm, dd, snapctxt->cfg, reuse,
blockNamedNodeData, snapctxt->asyncJob) < 0)
return -1;
if (qemuSnapshotDiskBitmapsPropagate(dd, snapctxt->actions, blockNamedNodeData) < 0)
return -1;
if (qemuBlockSnapshotAddBlockdev(snapctxt->actions, dd->disk, dd->src) < 0)
return -1;
return 0;
}
/**
* qemuSnapshotDiskPrepareActiveExternal:
*
* Collects and prepares a list of structures that hold information about disks
* that are selected for the snapshot.
*/
static qemuSnapshotDiskContext *
qemuSnapshotDiskPrepareActiveExternal(virDomainObj *vm,
virDomainMomentObj *snap,
bool reuse,
GHashTable *blockNamedNodeData,
virDomainAsyncJob asyncJob)
{
g_autoptr(qemuSnapshotDiskContext) snapctxt = NULL;
size_t i;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
snapctxt = qemuSnapshotDiskContextNew(snapdef->ndisks, vm, asyncJob);
for (i = 0; i < snapdef->ndisks; i++) {
if (snapdef->disks[i].snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (qemuSnapshotDiskPrepareOne(snapctxt,
vm->def->disks[i],
snapdef->disks + i,
blockNamedNodeData,
reuse,
true) < 0)
return NULL;
}
return g_steal_pointer(&snapctxt);
}
virDomainSnapshotDiskDef *
qemuSnapshotGetTransientDiskDef(virDomainDiskDef *domdisk,
const char *suffix)
{
g_autoptr(virDomainSnapshotDiskDef) snapdisk = g_new0(virDomainSnapshotDiskDef, 1);
snapdisk->name = g_strdup(domdisk->dst);
snapdisk->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
snapdisk->src = virStorageSourceNew();
snapdisk->src->type = VIR_STORAGE_TYPE_FILE;
snapdisk->src->format = VIR_STORAGE_FILE_QCOW2;
snapdisk->src->path = g_strdup_printf("%s.TRANSIENT-%s",
domdisk->src->path, suffix);
if (virFileExists(snapdisk->src->path)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("Overlay file '%1$s' for transient disk '%2$s' already exists"),
snapdisk->src->path, domdisk->dst);
return NULL;
}
return g_steal_pointer(&snapdisk);
}
/**
* qemuSnapshotDiskUpdateSource:
* @vm: domain object
* @dd: snapshot disk data object
*
* Updates disk definition after a successful snapshot.
*/
static void
qemuSnapshotDiskUpdateSource(virDomainObj *vm,
qemuSnapshotDiskData *dd)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
/* storage driver access won'd be needed */
if (dd->initialized)
virStorageSourceDeinit(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);
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);
}
}
int
qemuSnapshotDiskCreate(qemuSnapshotDiskContext *snapctxt)
{
qemuDomainObjPrivate *priv = snapctxt->vm->privateData;
virQEMUDriver *driver = priv->driver;
size_t i;
int rc;
/* check whether there's anything to do */
if (snapctxt->ndd == 0)
return 0;
if (qemuDomainObjEnterMonitorAsync(snapctxt->vm, snapctxt->asyncJob) < 0)
return -1;
rc = qemuMonitorTransaction(priv->mon, &snapctxt->actions);
qemuDomainObjExitMonitor(snapctxt->vm);
for (i = 0; i < snapctxt->ndd; i++) {
qemuSnapshotDiskData *dd = snapctxt->dd + i;
virDomainAuditDisk(snapctxt->vm, dd->disk->src, dd->src, "snapshot", rc >= 0);
if (rc == 0)
qemuSnapshotDiskUpdateSource(snapctxt->vm, dd);
}
if (rc < 0)
return -1;
if (virDomainObjSave(snapctxt->vm, driver->xmlopt, snapctxt->cfg->stateDir) < 0 ||
(snapctxt->vm->newDef && virDomainDefSave(snapctxt->vm->newDef, driver->xmlopt,
snapctxt->cfg->configDir) < 0))
return -1;
return 0;
}
/* The domain is expected to be locked and active. */
static int
qemuSnapshotCreateActiveExternalDisks(virDomainObj *vm,
virDomainMomentObj *snap,
GHashTable *blockNamedNodeData,
unsigned int flags,
virDomainAsyncJob asyncJob)
{
bool reuse = (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT) != 0;
g_autoptr(qemuSnapshotDiskContext) snapctxt = NULL;
if (virDomainObjCheckActive(vm) < 0)
return -1;
/* prepare a list of objects to use in the vm definition so that we don't
* have to roll back later */
if (!(snapctxt = qemuSnapshotDiskPrepareActiveExternal(vm, snap, reuse,
blockNamedNodeData, asyncJob)))
return -1;
if (qemuSnapshotDiskCreate(snapctxt) < 0)
return -1;
return 0;
}
static int
qemuSnapshotCreateActiveExternal(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap,
virQEMUDriverConfig *cfg,
bool has_manual,
unsigned int flags)
{
virObjectEvent *event;
bool resume = false;
int ret = -1;
qemuDomainObjPrivate *priv = vm->privateData;
g_autofree char *xml = NULL;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
bool memory = snapdef->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
bool memory_unlink = false;
bool memory_existing = false;
bool thaw = false;
bool pmsuspended = false;
int compressed;
g_autoptr(virCommand) compressor = NULL;
virQEMUSaveData *data = NULL;
g_autoptr(GHashTable) 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 frozen;
if (virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_MODIFY) < 0)
goto cleanup;
if (virDomainObjCheckActive(vm) < 0) {
virDomainObjEndAgentJob(vm);
goto cleanup;
}
frozen = qemuSnapshotFSFreeze(vm, NULL, 0);
virDomainObjEndAgentJob(vm);
if (frozen < 0)
goto cleanup;
if (frozen > 0)
thaw = true;
}
/* 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). We don't want to unpause it though if user has
* elected to manually snapshot some disks */
if (memory && !has_manual)
resume = true;
/* we need to pause the VM even when we aren't taking a memory snapshot
* when the user wants to manually snapshot some disks */
if (((memory || has_manual) && !(flags & VIR_DOMAIN_SNAPSHOT_CREATE_LIVE))) {
if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SNAPSHOT,
VIR_ASYNC_JOB_SNAPSHOT) < 0)
goto cleanup;
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("guest unexpectedly quit"));
goto cleanup;
}
}
}
/* 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 (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, VIR_ASYNC_JOB_SNAPSHOT)))
goto cleanup;
/* do the memory snapshot if necessary */
if (memory) {
/* check if migration is possible */
if (!qemuMigrationSrcIsAllowed(vm, false, VIR_ASYNC_JOB_SNAPSHOT, 0))
goto cleanup;
qemuDomainJobSetStatsType(vm->job->current,
QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP);
/* allow the migration job to be cancelled or the domain to be paused */
qemuDomainObjSetAsyncJobMask(vm, (VIR_JOB_DEFAULT_MASK |
JOB_MASK(VIR_JOB_SUSPEND) |
JOB_MASK(VIR_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 = (virObject *) qemuDomainSaveCookieNew(vm)))
goto cleanup;
if (!(data = virQEMUSaveDataNew(xml,
(qemuDomainSaveCookie *) snapdef->cookie,
resume, compressed, driver->xmlopt)))
goto cleanup;
xml = NULL;
memory_existing = virFileExists(snapdef->memorysnapshotfile);
if ((ret = qemuSaveImageCreate(driver, vm, snapdef->memorysnapshotfile,
data, compressor, 0,
VIR_ASYNC_JOB_SNAPSHOT)) < 0)
goto cleanup;
/* the memory image was created, remove it on errors */
if (!memory_existing)
memory_unlink = true;
/* forbid any further manipulation */
qemuDomainObjSetAsyncJobMask(vm, VIR_JOB_DEFAULT_MASK);
}
/* the domain is now paused if a memory snapshot was requested */
if ((ret = qemuSnapshotCreateActiveExternalDisks(vm, snap,
blockNamedNodeData, flags,
VIR_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,
VIR_ASYNC_JOB_SNAPSHOT, 0);
virDomainAuditStop(vm, "from-snapshot");
resume = false;
thaw = false;
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,
VIR_ASYNC_JOB_SNAPSHOT) < 0) {
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_SUSPENDED,
VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR);
virObjectEventStateQueue(driver->domainEventState, event);
if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) {
virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
VIR_DOMAIN_PAUSED_API_ERROR);
}
if (virGetLastErrorCode() == VIR_ERR_OK) {
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
_("resuming after snapshot failed"));
}
ret = -1;
}
if (thaw &&
virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_MODIFY) >= 0 &&
virDomainObjIsActive(vm)) {
/* report error only on an otherwise successful snapshot */
if (qemuSnapshotFSThaw(vm, ret == 0) < 0)
ret = -1;
virDomainObjEndAgentJob(vm);
}
virQEMUSaveDataFree(data);
if (memory_unlink && ret < 0)
unlink(snapdef->memorysnapshotfile);
return ret;
}
static virDomainSnapshotDef*
qemuSnapshotCreateXMLParse(virDomainObj *vm,
virQEMUDriver *driver,
const char *xmlDesc,
unsigned int flags)
{
qemuDomainObjPrivate *priv = vm->privateData;
unsigned int parse_flags = 0;
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE)
parse_flags |= VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE;
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;
return virDomainSnapshotDefParseString(xmlDesc, driver->xmlopt,
priv->qemuCaps, NULL, parse_flags);
}
static int
qemuSnapshotCreateXMLValidateDef(virDomainObj *vm,
virDomainSnapshotDef *def,
unsigned int flags)
{
bool redefine = flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
virDomainSnapshotState state;
/* 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 '%1$s': name can't contain '/'"),
def->parent.name);
return -1;
}
if (def->parent.name[0] == '.') {
virReportError(VIR_ERR_XML_DETAIL,
_("invalid snapshot name '%1$s': name can't start with '.'"),
def->parent.name);
return -1;
}
}
/* 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"));
return -1;
}
/* 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 %1$s"),
virDomainSnapshotStateTypeToString(state));
return -1;
}
break;
case VIR_DOMAIN_SNAPSHOT_PMSUSPENDED:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("qemu doesn't support taking snapshots of PMSUSPENDED guests"));
return -1;
/* 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 %1$s"),
virDomainSnapshotStateTypeToString(state));
return -1;
}
return 0;
}
static int
qemuSnapshotCreateAlignDisks(virDomainObj *vm,
virDomainSnapshotDef *def,
virQEMUDriver *driver,
unsigned int flags)
{
g_autofree char *xml = NULL;
qemuDomainObjPrivate *priv = vm->privateData;
virDomainSnapshotLocation align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
/* 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)))
return -1;
if (vm->newDef) {
def->parent.inactiveDom = virDomainDefCopy(vm->newDef,
driver->xmlopt, priv->qemuCaps, true);
if (!def->parent.inactiveDom)
return -1;
}
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) {
align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
if (virDomainObjIsActive(vm))
def->state = VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT;
else
def->state = VIR_DOMAIN_SNAPSHOT_SHUTOFF;
def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NO;
} else if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
def->state = virDomainObjGetState(vm, NULL);
align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
} else {
def->state = virDomainObjGetState(vm, NULL);
if (virDomainObjIsActive(vm) &&
def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_NO) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("internal snapshot of a running VM must include the memory state"));
return -1;
}
if (def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF)
def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NO;
else
def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
}
if (virDomainSnapshotAlignDisks(def, NULL, align_location, true, false) < 0)
return -1;
return 0;
}
static int
qemuSnapshotCreateWriteMetadata(virDomainObj *vm,
virDomainMomentObj *snap,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg)
{
if (qemuDomainSnapshotWriteMetadata(vm, snap,
driver->xmlopt,
cfg->snapshotDir) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to save metadata for snapshot %1$s"),
snap->def->name);
return -1;
}
virDomainSnapshotLinkParent(vm->snapshots, snap);
return 0;
}
static void
qemuSnapshotClearRevertdisks(virDomainMomentObj *current)
{
virDomainSnapshotDef *curdef = NULL;
if (!current)
return;
curdef = virDomainSnapshotObjGetDef(current);
if (curdef->revertdisks) {
size_t i;
for (i = 0; i < curdef->nrevertdisks; i++)
virDomainSnapshotDiskDefClear(&curdef->revertdisks[i]);
g_clear_pointer(&curdef->revertdisks, g_free);
curdef->nrevertdisks = 0;
}
}
static virDomainSnapshotPtr
qemuSnapshotRedefine(virDomainObj *vm,
virDomainPtr domain,
virDomainSnapshotDef *snapdeftmp,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg,
unsigned int flags)
{
virDomainMomentObj *snap = NULL;
virDomainMomentObj *current = virDomainSnapshotGetCurrent(vm->snapshots);
virDomainSnapshotPtr ret = NULL;
g_autoptr(virDomainSnapshotDef) snapdef = virObjectRef(snapdeftmp);
if (virDomainSnapshotRedefinePrep(vm, snapdef, &snap, driver->xmlopt, flags) < 0)
return NULL;
if (snap) {
virDomainSnapshotReplaceDef(snap, &snapdef);
} else {
if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, &snapdef)))
return NULL;
}
/* 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? */
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT) {
qemuSnapshotClearRevertdisks(current);
qemuSnapshotSetCurrent(vm, snap);
}
if (qemuSnapshotCreateWriteMetadata(vm, snap, driver, cfg) < 0)
goto error;
ret = virGetDomainSnapshot(domain, snap->def->name);
if (!ret)
goto error;
return ret;
error:
virDomainSnapshotObjListRemove(vm->snapshots, snap);
return NULL;
}
static virDomainSnapshotPtr
qemuSnapshotCreate(virDomainObj *vm,
virDomainPtr domain,
virDomainSnapshotDef *snapdeftmp,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg,
unsigned int flags)
{
g_autoptr(virDomainSnapshotDef) snapdef = virObjectRef(snapdeftmp);
g_autoptr(virDomainMomentObj) tmpsnap = NULL;
virDomainMomentObj *snap = NULL;
virDomainMomentObj *current = NULL;
virDomainSnapshotPtr ret = NULL;
bool has_manual = false; /* user wants to manually snapshot some disks */
if (qemuSnapshotCreateAlignDisks(vm, snapdef, driver, flags) < 0)
return NULL;
if (qemuSnapshotPrepare(vm, snapdef, &has_manual, &flags) < 0)
return NULL;
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA) {
snap = tmpsnap = virDomainMomentObjNew();
snap->def = &snapdef->parent;
snapdef = NULL;
} else {
if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, &snapdef)))
return NULL;
if ((current = virDomainSnapshotGetCurrent(vm->snapshots))) {
snap->def->parent_name = g_strdup(current->def->name);
}
}
/* actually do the snapshot */
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, has_manual, flags) < 0)
goto error;
} else {
/* internal full system */
if (qemuSnapshotCreateActiveInternal(driver, vm, snap, flags) < 0)
goto error;
}
} 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 error;
} else {
if (qemuSnapshotCreateInactiveInternal(driver, vm, snap) < 0)
goto error;
}
}
if (!tmpsnap) {
qemuSnapshotClearRevertdisks(current);
qemuSnapshotSetCurrent(vm, snap);
if (qemuSnapshotCreateWriteMetadata(vm, snap, driver, cfg) < 0)
goto error;
}
ret = virGetDomainSnapshot(domain, snap->def->name);
if (!ret)
goto error;
return ret;
error:
if (!tmpsnap)
virDomainSnapshotObjListRemove(vm->snapshots, snap);
return NULL;
}
virDomainSnapshotPtr
qemuSnapshotCreateXML(virDomainPtr domain,
virDomainObj *vm,
const char *xmlDesc,
unsigned int flags)
{
virQEMUDriver *driver = domain->conn->privateData;
virDomainSnapshotPtr snapshot = NULL;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
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 (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0)
return NULL;
if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("cannot halt after transient domain snapshot"));
return NULL;
}
if (!(def = qemuSnapshotCreateXMLParse(vm, driver, xmlDesc, flags)))
return NULL;
if (qemuSnapshotCreateXMLValidateDef(vm, def, flags) < 0)
return NULL;
/* 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 (virDomainObjBeginAsyncJob(vm, VIR_ASYNC_JOB_SNAPSHOT,
VIR_DOMAIN_JOB_OPERATION_SNAPSHOT, flags) < 0)
return NULL;
qemuDomainObjSetAsyncJobMask(vm, VIR_JOB_NONE);
if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE) {
snapshot = qemuSnapshotRedefine(vm, domain, def, driver, cfg, flags);
} else {
snapshot = qemuSnapshotCreate(vm, domain, def, driver, cfg, flags);
}
virDomainObjEndAsyncJob(vm);
return snapshot;
}
static int
qemuSnapshotRevertValidate(virDomainObj *vm,
virDomainMomentObj *snap,
virDomainSnapshotDef *snapdef,
unsigned int flags)
{
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"));
return -1;
}
if (!snap->def->dom) {
virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
_("snapshot '%1$s' lacks domain '%2$s' rollback info"),
snap->def->name, vm->def->name);
return -1;
}
if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
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"));
return -1;
}
}
return 0;
}
static int
qemuSnapshotRevertPrep(virDomainMomentObj *snap,
virDomainSnapshotDef *snapdef,
virQEMUDriver *driver,
virDomainObj *vm,
virDomainDef **retConfig,
virDomainDef **retInactiveConfig)
{
qemuDomainObjPrivate *priv = vm->privateData;
g_autoptr(virDomainDef) config = NULL;
g_autoptr(virDomainDef) inactiveConfig = NULL;
config = virDomainDefCopy(snap->def->dom,
driver->xmlopt, priv->qemuCaps, true);
if (!config)
return -1;
if (STRNEQ(config->name, vm->def->name)) {
VIR_FREE(config->name);
config->name = g_strdup(vm->def->name);
}
if (snap->def->inactiveDom) {
inactiveConfig = virDomainDefCopy(snap->def->inactiveDom,
driver->xmlopt, priv->qemuCaps, true);
if (!inactiveConfig)
return -1;
if (STRNEQ(inactiveConfig->name, vm->def->name)) {
VIR_FREE(inactiveConfig->name);
inactiveConfig->name = g_strdup(vm->def->name);
}
} 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)
return -1;
} else {
inactiveConfig = g_steal_pointer(&config);
}
}
*retConfig = g_steal_pointer(&config);
*retInactiveConfig = g_steal_pointer(&inactiveConfig);
return 0;
}
static int
qemuSnapshotRevertWriteMetadata(virDomainObj *vm,
virDomainMomentObj *snap,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg,
bool defined)
{
qemuSnapshotSetCurrent(vm, snap);
if (qemuDomainSnapshotWriteMetadata(vm, snap,
driver->xmlopt,
cfg->snapshotDir) < 0) {
virDomainSnapshotSetCurrent(vm->snapshots, NULL);
return -1;
}
if (defined && vm->persistent) {
int detail;
virObjectEvent *event = NULL;
virDomainDef *saveDef = vm->newDef ? vm->newDef : vm->def;
if (virDomainDefSave(saveDef, driver->xmlopt, cfg->configDir) < 0)
return -1;
detail = VIR_DOMAIN_EVENT_DEFINED_FROM_SNAPSHOT;
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_DEFINED,
detail);
virObjectEventStateQueue(driver->domainEventState, event);
}
return 0;
}
typedef struct _qemuSnapshotRevertMemoryData {
int fd;
char *path;
virQEMUSaveData *data;
} qemuSnapshotRevertMemoryData;
static void
qemuSnapshotClearRevertMemoryData(qemuSnapshotRevertMemoryData *memdata)
{
VIR_FORCE_CLOSE(memdata->fd);
virQEMUSaveDataFree(memdata->data);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(qemuSnapshotRevertMemoryData, qemuSnapshotClearRevertMemoryData);
/**
* qemuSnapshotRevertExternalPrepare:
* @vm: domain object
* @tmpsnapdef: temporary snapshot definition
* @snap: snapshot object we are reverting to
* @config: live domain definition
* @inactiveConfig: offline domain definition
* @memdata: struct with data to load memory state
*
* Prepare new temporary snapshot definition @tmpsnapdef that will
* be used while creating new overlay files after reverting to snapshot
* @snap. In case we are reverting to snapshot with memory state it will
* open it and store necessary data in @memdata. Caller is responsible
* to clear the data by using qemuSnapshotClearRevertMemoryData().
*
* Returns 0 in success, -1 on error.
*/
static int
qemuSnapshotRevertExternalPrepare(virDomainObj *vm,
virDomainSnapshotDef *tmpsnapdef,
virDomainMomentObj *snap,
virDomainDef *config,
virDomainDef *inactiveConfig,
qemuSnapshotRevertMemoryData *memdata)
{
size_t i;
bool active = virDomainObjIsActive(vm);
virDomainDef *domdef = NULL;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
if (config) {
domdef = config;
} else {
domdef = inactiveConfig;
}
/* We need this to generate creation timestamp that is used as default
* snapshot name. */
if (virDomainMomentDefPostParse(&tmpsnapdef->parent) < 0)
return -1;
if (virDomainSnapshotAlignDisks(tmpsnapdef, domdef,
VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL,
false, true) < 0) {
return -1;
}
for (i = 0; i < tmpsnapdef->ndisks; i++) {
virDomainSnapshotDiskDef *snapdisk = &tmpsnapdef->disks[i];
virDomainDiskDef *domdisk = domdef->disks[i];
if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (qemuSnapshotPrepareDiskExternal(domdisk, snapdisk, active, false) < 0)
return -1;
}
if (memdata && snapdef->memorysnapshotfile) {
virQEMUDriver *driver = ((qemuDomainObjPrivate *) vm->privateData)->driver;
g_autoptr(virDomainDef) savedef = NULL;
memdata->path = snapdef->memorysnapshotfile;
memdata->fd = qemuSaveImageOpen(driver, NULL, memdata->path,
&savedef, &memdata->data,
false, NULL,
false, false);
if (memdata->fd < 0)
return -1;
if (!virDomainDefCheckABIStability(savedef, domdef, driver->xmlopt))
return -1;
}
return 0;
}
/**
* qemuSnapshotRevertExternalActive:
* @vm: domain object
* @tmpsnapdef: temporary snapshot definition
*
* Creates a new disk overlays using the temporary snapshot
* definition @tmpsnapdef for running VM by calling QMP APIs.
*
* Returns 0 on success, -1 on error.
*/
static int
qemuSnapshotRevertExternalActive(virDomainObj *vm,
virDomainSnapshotDef *tmpsnapdef)
{
size_t i;
g_autoptr(GHashTable) blockNamedNodeData = NULL;
g_autoptr(qemuSnapshotDiskContext) snapctxt = NULL;
snapctxt = qemuSnapshotDiskContextNew(tmpsnapdef->ndisks, vm, VIR_ASYNC_JOB_SNAPSHOT);
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, VIR_ASYNC_JOB_SNAPSHOT)))
return -1;
for (i = 0; i < tmpsnapdef->ndisks; i++) {
if (tmpsnapdef->disks[i].snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (qemuSnapshotDiskPrepareOne(snapctxt,
vm->def->disks[i],
tmpsnapdef->disks + i,
blockNamedNodeData,
false,
true) < 0)
return -1;
}
if (qemuSnapshotDiskCreate(snapctxt) < 0)
return -1;
return 0;
}
/**
* qemuSnapshotRevertExternalInactive:
* @vm: domain object
* @tmpsnapdef: temporary snapshot definition
* @domdef: offline domain definition
*
* Creates a new disk overlays using the temporary snapshot
* definition @tmpsnapdef for offline VM by calling qemu-img.
*
* Returns 0 on success, -1 on error.
*/
static int
qemuSnapshotRevertExternalInactive(virDomainObj *vm,
virDomainSnapshotDef *tmpsnapdef,
virDomainDef *domdef)
{
virQEMUDriver *driver = QEMU_DOMAIN_PRIVATE(vm)->driver;
g_autoptr(virBitmap) created = NULL;
int ret = -1;
created = virBitmapNew(tmpsnapdef->ndisks);
if (qemuSnapshotCreateQcow2Files(driver, domdef, tmpsnapdef, created) < 0)
goto cleanup;
if (qemuSnapshotDomainDefUpdateDisk(domdef, tmpsnapdef, false) < 0)
goto cleanup;
ret = 0;
cleanup:
if (ret < 0 && created) {
ssize_t bit = -1;
virErrorPtr err = NULL;
virErrorPreserveLast(&err);
while ((bit = virBitmapNextSetBit(created, bit)) >= 0) {
virDomainSnapshotDiskDef *snapdisk = &(tmpsnapdef->disks[bit]);
if (virStorageSourceInit(snapdisk->src) < 0 ||
virStorageSourceUnlink(snapdisk->src) < 0) {
VIR_WARN("Failed to remove snapshot image '%s'",
snapdisk->src->path);
}
}
virErrorRestore(&err);
}
return ret;
}
/**
* qemuSnapshotRevertExternalFinish:
* @vm: domain object
* @tmpsnapdef: temporary snapshot definition
* @snap: snapshot object we are reverting to
*
* Finishes disk overlay creation by removing existing overlays that
* will no longer be used if there are any and updating snapshot @snap
* metadata and current snapshot metadata so it can be saved once the
* revert is completed.
*/
static void
qemuSnapshotRevertExternalFinish(virDomainObj *vm,
virDomainSnapshotDef *tmpsnapdef,
virDomainMomentObj *snap)
{
size_t i;
virDomainMomentObj *curSnap = virDomainSnapshotGetCurrent(vm->snapshots);
virDomainSnapshotDef *curdef = virDomainSnapshotObjGetDef(curSnap);
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
if (curdef->revertdisks) {
for (i = 0; i < curdef->nrevertdisks; i++) {
virDomainSnapshotDiskDef *snapdisk = &(curdef->revertdisks[i]);
if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (virStorageSourceInit(snapdisk->src) < 0 ||
virStorageSourceUnlink(snapdisk->src) < 0) {
VIR_WARN("Failed to remove snapshot image '%s'",
snapdisk->src->path);
}
virDomainSnapshotDiskDefClear(snapdisk);
}
g_clear_pointer(&curdef->revertdisks, g_free);
curdef->nrevertdisks = 0;
} else {
for (i = 0; i < curdef->ndisks; i++) {
virDomainSnapshotDiskDef *snapdisk = &(curdef->disks[i]);
if (snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (virStorageSourceInit(snapdisk->src) < 0 ||
virStorageSourceUnlink(snapdisk->src) < 0) {
VIR_WARN("Failed to remove snapshot image '%s'",
snapdisk->src->path);
}
}
}
if (snap->nchildren != 0) {
snapdef->revertdisks = g_steal_pointer(&tmpsnapdef->disks);
snapdef->nrevertdisks = tmpsnapdef->ndisks;
tmpsnapdef->ndisks = 0;
} else {
for (i = 0; i < snapdef->ndisks; i++) {
virDomainSnapshotDiskDefClear(&snapdef->disks[i]);
}
g_free(snapdef->disks);
snapdef->disks = g_steal_pointer(&tmpsnapdef->disks);
snapdef->ndisks = tmpsnapdef->ndisks;
tmpsnapdef->ndisks = 0;
}
}
static int
qemuSnapshotRevertActive(virDomainObj *vm,
virDomainSnapshotPtr snapshot,
virDomainMomentObj *snap,
virDomainSnapshotDef *snapdef,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg,
virDomainDef **config,
virDomainDef **inactiveConfig,
unsigned int start_flags,
unsigned int flags)
{
virObjectEvent *event = NULL;
virObjectEvent *event2 = NULL;
virDomainMomentObj *loadSnap = NULL;
int detail;
bool defined = false;
int rc;
g_autoptr(virDomainSnapshotDef) tmpsnapdef = NULL;
g_auto(qemuSnapshotRevertMemoryData) memdata = { -1, NULL, NULL };
bool started = false;
start_flags |= VIR_QEMU_PROCESS_START_PAUSED;
/* Transitions 2, 3, 5, 6, 8, 9 */
if (virDomainObjIsActive(vm)) {
/* Transitions 5, 6, 8, 9 */
qemuProcessStop(driver, vm,
VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT,
VIR_ASYNC_JOB_SNAPSHOT, 0);
virDomainAuditStop(vm, "from-snapshot");
detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
detail);
virObjectEventStateQueue(driver->domainEventState, event);
}
if (virDomainSnapshotIsExternal(snap)) {
if (!(tmpsnapdef = virDomainSnapshotDefNew()))
return -1;
if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap,
*config, *inactiveConfig,
&memdata) < 0) {
return -1;
}
} else {
loadSnap = snap;
}
if (*inactiveConfig) {
virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
defined = true;
}
virDomainObjAssignDef(vm, config, true, NULL);
if (qemuProcessStartWithMemoryState(snapshot->domain->conn, driver, vm,
&memdata.fd, memdata.path, loadSnap,
memdata.data, VIR_ASYNC_JOB_SNAPSHOT,
start_flags, "from-snapshot",
&started) < 0) {
if (started) {
qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED,
VIR_ASYNC_JOB_SNAPSHOT,
VIR_QEMU_PROCESS_STOP_MIGRATED);
}
return -1;
}
detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STARTED,
detail);
virObjectEventStateQueue(driver->domainEventState, event);
if (virDomainSnapshotIsExternal(snap)) {
if (qemuSnapshotRevertExternalActive(vm, tmpsnapdef) < 0)
return -1;
qemuSnapshotRevertExternalFinish(vm, tmpsnapdef, snap);
}
/* 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);
detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
event2 = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_SUSPENDED,
detail);
virObjectEventStateQueue(driver->domainEventState, event2);
} else {
/* Transitions 2, 5, 8 */
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("guest unexpectedly quit"));
return -1;
}
rc = qemuProcessStartCPUs(driver, vm,
VIR_DOMAIN_RUNNING_FROM_SNAPSHOT,
VIR_ASYNC_JOB_SNAPSHOT);
if (rc < 0)
return -1;
}
return qemuSnapshotRevertWriteMetadata(vm, snap, driver, cfg, defined);
}
/* The domain is expected to be locked and inactive. */
static int
qemuSnapshotInternalRevertInactive(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap)
{
size_t i;
/* Prefer action on the disks in use at the time the snapshot was
* created; but fall back to current definition if dealing with a
* snapshot created prior to libvirt 0.9.5. */
virDomainDef *def = snap->def->dom;
if (!def)
def = vm->def;
for (i = 0; i < def->ndisks; i++) {
if (virDomainDiskTranslateSourcePool(def->disks[i]) < 0)
return -1;
}
/* Try all disks, but report failure if we skipped any. */
if (qemuDomainSnapshotForEachQcow2(driver, def, snap, "-a", true) != 0)
return -1;
return 0;
}
static int
qemuSnapshotRevertInactive(virDomainObj *vm,
virDomainSnapshotPtr snapshot,
virDomainMomentObj *snap,
virQEMUDriver *driver,
virQEMUDriverConfig *cfg,
virDomainDef **inactiveConfig,
unsigned int start_flags,
unsigned int flags)
{
virObjectEvent *event = NULL;
virObjectEvent *event2 = NULL;
int detail;
bool defined = false;
int rc;
g_autoptr(virDomainSnapshotDef) tmpsnapdef = NULL;
/* 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,
VIR_ASYNC_JOB_SNAPSHOT, 0);
virDomainAuditStop(vm, "from-snapshot");
detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT;
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
detail);
virObjectEventStateQueue(driver->domainEventState, event);
}
if (virDomainSnapshotIsExternal(snap)) {
if (!(tmpsnapdef = virDomainSnapshotDefNew()))
return -1;
if (qemuSnapshotRevertExternalPrepare(vm, tmpsnapdef, snap,
NULL, *inactiveConfig, NULL) < 0) {
return -1;
}
if (qemuSnapshotRevertExternalInactive(vm, tmpsnapdef,
*inactiveConfig) < 0) {
return -1;
}
qemuSnapshotRevertExternalFinish(vm, tmpsnapdef, snap);
} else {
if (qemuSnapshotInternalRevertInactive(driver, vm, snap) < 0) {
qemuDomainRemoveInactive(driver, vm, 0, false);
return -1;
}
}
if (*inactiveConfig) {
virDomainObjAssignDef(vm, inactiveConfig, false, 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;
rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL,
VIR_ASYNC_JOB_SNAPSHOT, NULL, -1, NULL, NULL,
VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
start_flags);
virDomainAuditStart(vm, "from-snapshot", rc >= 0);
if (rc < 0) {
qemuDomainRemoveInactive(driver, vm, 0, false);
return -1;
}
detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT;
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STARTED,
detail);
virObjectEventStateQueue(driver->domainEventState, event);
if (paused) {
detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
event2 = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_SUSPENDED,
detail);
virObjectEventStateQueue(driver->domainEventState, event2);
}
}
return qemuSnapshotRevertWriteMetadata(vm, snap, driver, cfg, defined);
}
int
qemuSnapshotRevert(virDomainObj *vm,
virDomainSnapshotPtr snapshot,
unsigned int flags)
{
virQEMUDriver *driver = snapshot->domain->conn->privateData;
int ret = -1;
virDomainMomentObj *snap = NULL;
virDomainSnapshotDef *snapdef;
g_autoptr(virDomainDef) config = NULL;
g_autoptr(virDomainDef) inactiveConfig = NULL;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
unsigned int start_flags = VIR_QEMU_PROCESS_START_GEN_VMID;
virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
VIR_DOMAIN_SNAPSHOT_REVERT_FORCE |
VIR_DOMAIN_SNAPSHOT_REVERT_RESET_NVRAM, -1);
if (flags & VIR_DOMAIN_SNAPSHOT_REVERT_RESET_NVRAM)
start_flags |= VIR_QEMU_PROCESS_START_RESET_NVRAM;
/* 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_SUSPENDED
* 4. running -> inactive: EVENT_STOPPED
* 5. running -> running: EVENT_STOPPED, EVENT_STARTED
* 6. running -> paused: EVENT_STOPPED, EVENT_STARTED, EVENT_SUSPENDED
* 7. paused -> inactive: EVENT_STOPPED
* 8. paused -> running: EVENT_STOPPED, EVENT_STARTED
* 9. paused -> paused: EVENT_STOPPED, EVENT_STARTED, EVENT_SUSPENDED
* Also, several transitions occur even if we fail partway through.
*/
if (qemuDomainHasBlockjob(vm, false)) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("domain has active block job"));
return -1;
}
if (virDomainObjBeginAsyncJob(vm, VIR_ASYNC_JOB_SNAPSHOT,
VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT,
flags) < 0) {
return -1;
}
if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
goto endjob;
snapdef = virDomainSnapshotObjGetDef(snap);
if (qemuSnapshotRevertValidate(vm, snap, snapdef, flags) < 0)
goto endjob;
if (qemuSnapshotRevertPrep(snap, snapdef, driver, vm,
&config, &inactiveConfig) < 0) {
goto endjob;
}
switch ((virDomainSnapshotState) snapdef->state) {
case VIR_DOMAIN_SNAPSHOT_RUNNING:
case VIR_DOMAIN_SNAPSHOT_PAUSED:
ret = qemuSnapshotRevertActive(vm, snapshot, snap, snapdef,
driver, cfg,
&config, &inactiveConfig,
start_flags, flags);
goto endjob;
case VIR_DOMAIN_SNAPSHOT_SHUTDOWN:
case VIR_DOMAIN_SNAPSHOT_SHUTOFF:
case VIR_DOMAIN_SNAPSHOT_CRASHED:
ret = qemuSnapshotRevertInactive(vm, snapshot, snap,
driver, cfg,
&inactiveConfig,
start_flags, flags);
goto endjob;
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 '%1$s'. Refusing snapshot reversion"),
virDomainSnapshotStateTypeToString(snapdef->state));
goto endjob;
}
endjob:
virDomainObjEndAsyncJob(vm);
return ret;
}
typedef struct _qemuSnapshotDeleteExternalData {
virDomainSnapshotDiskDef *snapDisk; /* snapshot disk definition */
virDomainDiskDef *domDisk; /* VM disk definition */
virStorageSource *diskSrc; /* source of disk we are deleting */
virDomainMomentObj *parentSnap;
virDomainDiskDef *parentDomDisk; /* disk definition from snapshot metadata */
virStorageSource *parentDiskSrc; /* backing disk source of the @diskSrc */
virStorageSource *prevDiskSrc; /* source of disk for which @diskSrc is
backing disk */
GSList *disksWithBacking; /* list of storage source data for which the
deleted storage source is backing store */
qemuBlockJobData *job;
bool merge;
} qemuSnapshotDeleteExternalData;
static void
qemuSnapshotDeleteExternalDataFree(qemuSnapshotDeleteExternalData *data)
{
if (!data)
return;
virObjectUnref(data->job);
g_slist_free_full(data->disksWithBacking, g_free);
g_free(data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuSnapshotDeleteExternalData, qemuSnapshotDeleteExternalDataFree);
/**
* qemuSnapshotFindParentSnapForDisk:
* @snap: snapshot object that is about to be deleted
* @snapDisk: snapshot disk definition
*
* Find parent snapshot object that contains snapshot of the @snapDisk.
* It may not be the next snapshot in the snapshot tree as the disk in
* question may be skipped by using VIR_DOMAIN_SNAPSHOT_LOCATION_NO.
*
* Returns virDomainMomentObj* on success or NULL if there is no parent
* snapshot containing the @snapDisk.
* */
static virDomainMomentObj*
qemuSnapshotFindParentSnapForDisk(virDomainMomentObj *snap,
virDomainSnapshotDiskDef *snapDisk)
{
virDomainMomentObj *parentSnap = snap->parent;
while (parentSnap) {
ssize_t i;
virDomainSnapshotDef *parentSnapdef = virDomainSnapshotObjGetDef(parentSnap);
if (!parentSnapdef)
break;
for (i = 0; i < parentSnapdef->ndisks; i++) {
virDomainSnapshotDiskDef *parentSnapDisk = &(parentSnapdef->disks[i]);
if (parentSnapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
STREQ(snapDisk->name, parentSnapDisk->name)) {
return parentSnap;
}
}
parentSnap = parentSnap->parent;
}
return NULL;
}
struct _qemuSnapshotDisksWithBackingStoreData {
virStorageSource *diskSrc;
uid_t uid;
gid_t gid;
};
struct _qemuSnapshotDisksWithBackingStoreIterData {
virDomainMomentObj *current;
virStorageSource *diskSrc;
GSList **disksWithBacking;
virQEMUDriverConfig *cfg;
};
static int
qemuSnapshotDiskHasBackingDisk(void *payload,
const char *name G_GNUC_UNUSED,
void *opaque)
{
virDomainMomentObj *snap = payload;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
struct _qemuSnapshotDisksWithBackingStoreIterData *iterdata = opaque;
ssize_t i;
/* skip snapshots that are within the active snapshot tree as it will be handled
* by qemu */
if (virDomainMomentIsAncestor(iterdata->current, snap) || iterdata->current == snap)
return 0;
for (i = 0; i < snapdef->parent.dom->ndisks; i++) {
virDomainDiskDef *disk = snapdef->parent.dom->disks[i];
uid_t uid;
gid_t gid;
if (!virStorageSourceIsLocalStorage(disk->src))
continue;
qemuDomainGetImageIds(iterdata->cfg, snapdef->parent.dom, disk->src,
NULL, &uid, &gid);
if (!disk->src->backingStore)
ignore_value(virStorageSourceGetMetadata(disk->src, uid, gid, 1, false));
if (virStorageSourceIsSameLocation(disk->src->backingStore, iterdata->diskSrc)) {
struct _qemuSnapshotDisksWithBackingStoreData *data =
g_new0(struct _qemuSnapshotDisksWithBackingStoreData, 1);
data->diskSrc = disk->src;
data->uid = uid;
data->gid = gid;
*iterdata->disksWithBacking = g_slist_prepend(*iterdata->disksWithBacking, data);
}
}
return 0;
}
static void
qemuSnapshotGetDisksWithBackingStore(virDomainObj *vm,
virDomainMomentObj *snap,
qemuSnapshotDeleteExternalData *data)
{
struct _qemuSnapshotDisksWithBackingStoreIterData iterData;
virQEMUDriver *driver = QEMU_DOMAIN_PRIVATE(vm)->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
iterData.current = virDomainSnapshotGetCurrent(vm->snapshots);
iterData.diskSrc = data->diskSrc;
iterData.disksWithBacking = &data->disksWithBacking;
iterData.cfg = cfg;
virDomainMomentForEachDescendant(snap, qemuSnapshotDiskHasBackingDisk, &iterData);
}
/**
* qemuSnapshotExternalGetSnapDiskSrc:
* @vm: domain object
* @snap: snapshot object
* @snapDisk: disk definition from snapshost
*
* Try to get actual disk source for @snapDisk as the source stored in
* snapshot metadata is not always the correct source we need to work with.
* This happens mainly after reverting to non-leaf snapshot and creating
* new branch with new snapshot.
*
* Returns disk source on success, NULL on error.
*/
static virStorageSource *
qemuSnapshotExternalGetSnapDiskSrc(virDomainObj *vm,
virDomainMomentObj *snap,
virDomainSnapshotDiskDef *snapDisk)
{
virDomainDiskDef *disk = NULL;
/* Should never happen when deleting external snapshot as for now we do
* not support this specific case for now. */
if (snap->nchildren > 1)
return snapDisk->src;
if (snap->first_child) {
disk = qemuDomainDiskByName(snap->first_child->def->dom, snapDisk->name);
} else if (virDomainSnapshotGetCurrent(vm->snapshots) == snap) {
disk = qemuDomainDiskByName(vm->def, snapDisk->name);
}
if (disk)
return disk->src;
return snapDisk->src;
}
/**
* qemuSnapshotDeleteExternalPrepareData:
* @vm: domain object
* @snap: snapshot object
* @merge: whether we are just deleting image or not
* @externalData: prepared data to delete external snapshot
*
* Validate if we can delete selected snapshot @snap and prepare all necessary
* data that will be used when deleting snapshot as @externalData.
*
* If @merge is set to true we will merge the deleted snapshot into parent one
* instead of just deleting it. This is necessary when operating on snapshot
* that has existing overlay files.
*
* Returns -1 on error, 0 on success.
*/
static int
qemuSnapshotDeleteExternalPrepareData(virDomainObj *vm,
virDomainMomentObj *snap,
bool merge,
GSList **externalData)
{
ssize_t i;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
g_autoslist(qemuSnapshotDeleteExternalData) ret = NULL;
for (i = 0; i < snapdef->ndisks; i++) {
g_autofree qemuSnapshotDeleteExternalData *data = NULL;
virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
if (snapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
continue;
if (snapDisk->snapshotDeleteInProgress) {
virReportError(VIR_ERR_OPERATION_INVALID,
_("snapshot disk '%1$s' was target of not completed snapshot delete"),
snapDisk->name);
return -1;
}
data = g_new0(qemuSnapshotDeleteExternalData, 1);
data->snapDisk = snapDisk;
data->merge = merge;
data->parentDomDisk = virDomainDiskByTarget(snapdef->parent.dom,
data->snapDisk->name);
if (!data->parentDomDisk) {
virReportError(VIR_ERR_OPERATION_FAILED,
_("failed to find disk '%1$s' in snapshot VM XML"),
snapDisk->name);
return -1;
}
if (data->merge) {
virStorageSource *snapDiskSrc = NULL;
data->domDisk = qemuDomainDiskByName(vm->def, snapDisk->name);
if (!data->domDisk)
return -1;
snapDiskSrc = qemuSnapshotExternalGetSnapDiskSrc(vm, snap, data->snapDisk);
if (virDomainObjIsActive(vm)) {
data->diskSrc = virStorageSourceChainLookupBySource(data->domDisk->src,
snapDiskSrc,
&data->prevDiskSrc);
if (!data->diskSrc)
return -1;
if (!virStorageSourceIsSameLocation(data->diskSrc, snapDiskSrc)) {
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
_("VM disk source and snapshot disk source are not the same"));
return -1;
}
data->parentDiskSrc = data->diskSrc->backingStore;
if (!virStorageSourceIsBacking(data->parentDiskSrc)) {
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
_("failed to find parent disk source in backing chain"));
return -1;
}
if (!virStorageSourceIsSameLocation(data->parentDiskSrc,
data->parentDomDisk->src)) {
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
_("snapshot VM disk source and parent disk source are not the same"));
return -1;
}
qemuSnapshotGetDisksWithBackingStore(vm, snap, data);
}
data->parentSnap = qemuSnapshotFindParentSnapForDisk(snap, data->snapDisk);
if (data->parentSnap && !virDomainSnapshotIsExternal(data->parentSnap)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("deleting external snapshot that has internal snapshot as parent not supported"));
return -1;
}
}
ret = g_slist_prepend(ret, g_steal_pointer(&data));
}
ret = g_slist_reverse(ret);
*externalData = g_steal_pointer(&ret);
return 0;
}
/**
* qemuSnapshotDeleteExternalPrepare:
* @vm: domain object
* @snap: snapshot object we are deleting
* @flags: flags passed to virDomainSnapshotDelete
* @externalData: pointer to GSList of qemuSnapshotDeleteExternalData
* @stop_qemu: pointer to boolean indicating QEMU process was started
*
* Validates and prepares data for snapshot @snap we are deleting and
* store it in @externalData. For offline VMs we need to start QEMU
* process in order to delete external snapshots and caller will need
* to stop that process, @stop_qemu will be set to True.
*
* Return 0 on success, -1 on error.
*/
static int
qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
virDomainMomentObj *snap,
unsigned int flags,
GSList **externalData,
bool *stop_qemu)
{
virQEMUDriver *driver = ((qemuDomainObjPrivate *) vm->privateData)->driver;
g_autoslist(qemuSnapshotDeleteExternalData) tmpData = NULL;
if (!virDomainSnapshotIsExternal(snap))
return 0;
if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
return 0;
}
/* Leaf non-active snapshot doesn't have overlay files for the disk images
* so there is no need to do any merge and we can just delete the files
* directly. */
if (snap != virDomainSnapshotGetCurrent(vm->snapshots) &&
snap->nchildren == 0) {
if (qemuSnapshotDeleteExternalPrepareData(vm, snap, false, externalData) < 0)
return -1;
} else {
/* this also serves as validation whether the snapshot can be deleted */
if (qemuSnapshotDeleteExternalPrepareData(vm, snap, true, &tmpData) < 0)
return -1;
if (!virDomainObjIsActive(vm)) {
if (qemuProcessStart(NULL, driver, vm, NULL, VIR_ASYNC_JOB_SNAPSHOT,
NULL, -1, NULL, NULL,
VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
VIR_QEMU_PROCESS_START_PAUSED) < 0) {
return -1;
}
*stop_qemu = true;
/* Call the prepare again as some data require that the VM is
* running to get everything we need. */
if (qemuSnapshotDeleteExternalPrepareData(vm, snap, true, externalData) < 0)
return -1;
} else {
qemuDomainJobPrivate *jobPriv = vm->job->privateData;
*externalData = g_steal_pointer(&tmpData);
/* If the VM is running we need to indicate that the async snapshot
* job is snapshot delete job. */
jobPriv->snapshotDelete = true;
qemuDomainSaveStatus(vm);
}
}
return 0;
}
typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
struct _virQEMUMomentReparent {
const char *dir;
virDomainMomentObj *parent;
virDomainObj *vm;
virDomainXMLOption *xmlopt;
int err;
int (*writeMetadata)(virDomainObj *, virDomainMomentObj *,
virDomainXMLOption *, const char *);
};
static int
qemuSnapshotChildrenReparent(void *payload,
const char *name G_GNUC_UNUSED,
void *data)
{
virDomainMomentObj *moment = payload;
virQEMUMomentReparent *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;
}
typedef struct _qemuSnapshotUpdateDisksData qemuSnapshotUpdateDisksData;
struct _qemuSnapshotUpdateDisksData {
virDomainMomentObj *snap;
virDomainObj *vm;
int error;
};
/**
* qemuSnapshotUpdateDisksSingle:
* @snap: snapshot object where we are updating disks
* @def: active or inactive definition from @snap
* @parentDef: parent snapshot object of snapshot that we are deleting
* @snapDisk: snapshot disk definition from snapshot we are deleting
*
* When deleting external snapshots we need to modify remaining metadata
* files stored by libvirt.
*
* The first part updates only metadata for external snapshots where we need
* to update the disk source in the domain definition stored within the
* snapshot metadata. There is no need to do it for internal snapshots as
* they don't create new disk files.
*
* The second part needs to be done for all metadata files. Both internal and
* external snapshot metadata files have in the domain definition backingStore
* that could contain the deleted disk.
*
* Returns 0 on success, -1 on error.
* */
static int
qemuSnapshotUpdateDisksSingle(virDomainMomentObj *snap,
virDomainDef *def,
virDomainDef *parentDef,
virDomainSnapshotDiskDef *snapDisk)
{
virDomainDiskDef *disk = NULL;
if (!(disk = virDomainDiskByName(def, snapDisk->name, true)))
return 0;
if (virDomainSnapshotIsExternal(snap)) {
virDomainDiskDef *parentDisk = NULL;
if (!(parentDisk = qemuDomainDiskByName(parentDef, snapDisk->name)))
return -1;
if (virStorageSourceIsSameLocation(snapDisk->src, disk->src)) {
virObjectUnref(disk->src);
disk->src = virStorageSourceCopy(parentDisk->src, false);
}
}
if (disk->src->backingStore) {
virStorageSource *cur = disk->src;
virStorageSource *next = disk->src->backingStore;
while (next) {
if (virStorageSourceIsSameLocation(snapDisk->src, next)) {
cur->backingStore = next->backingStore;
next->backingStore = NULL;
virObjectUnref(next);
break;
}
cur = next;
next = cur->backingStore;
}
}
return 0;
}
static int
qemuSnapshotDeleteUpdateDisks(void *payload,
const char *name G_GNUC_UNUSED,
void *opaque)
{
virDomainMomentObj *snap = payload;
qemuSnapshotUpdateDisksData *data = opaque;
qemuDomainObjPrivate *priv = data->vm->privateData;
virQEMUDriver *driver = priv->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(data->snap);
ssize_t i;
for (i = 0; i < snapdef->ndisks; i++) {
virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
if (snapDisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)
continue;
if (qemuSnapshotUpdateDisksSingle(snap, snap->def->dom,
data->snap->def->dom, snapDisk) < 0) {
data->error = -1;
}
if (snap->def->inactiveDom) {
virDomainDef *dom = data->snap->def->inactiveDom;
if (!dom)
dom = data->snap->def->dom;
if (qemuSnapshotUpdateDisksSingle(snap, snap->def->inactiveDom,
dom, snapDisk) < 0) {
data->error = -1;
}
}
}
if (qemuDomainSnapshotWriteMetadata(data->vm,
snap,
driver->xmlopt,
cfg->snapshotDir) < 0) {
data->error = -1;
}
return 0;
}
/* Deleting external snapshot is started by running qemu block-commit job.
* We need to wait for all block-commit jobs to be 'ready' or 'pending' to
* continue with external snapshot deletion. */
static int
qemuSnapshotDeleteBlockJobIsRunning(qemuBlockjobState state)
{
switch (state) {
case QEMU_BLOCKJOB_STATE_NEW:
case QEMU_BLOCKJOB_STATE_RUNNING:
case QEMU_BLOCKJOB_STATE_ABORTING:
case QEMU_BLOCKJOB_STATE_PIVOTING:
return 1;
case QEMU_BLOCKJOB_STATE_COMPLETED:
case QEMU_BLOCKJOB_STATE_FAILED:
case QEMU_BLOCKJOB_STATE_CANCELLED:
case QEMU_BLOCKJOB_STATE_READY:
case QEMU_BLOCKJOB_STATE_CONCLUDED:
case QEMU_BLOCKJOB_STATE_PENDING:
case QEMU_BLOCKJOB_STATE_LAST:
break;
}
return 0;
}
/* When finishing or aborting qemu blockjob we only need to know if the
* job is still active or not. */
static int
qemuSnapshotDeleteBlockJobIsActive(qemuBlockjobState state)
{
switch (state) {
case QEMU_BLOCKJOB_STATE_READY:
case QEMU_BLOCKJOB_STATE_NEW:
case QEMU_BLOCKJOB_STATE_RUNNING:
case QEMU_BLOCKJOB_STATE_ABORTING:
case QEMU_BLOCKJOB_STATE_PENDING:
case QEMU_BLOCKJOB_STATE_PIVOTING:
return 1;
case QEMU_BLOCKJOB_STATE_COMPLETED:
case QEMU_BLOCKJOB_STATE_FAILED:
case QEMU_BLOCKJOB_STATE_CANCELLED:
case QEMU_BLOCKJOB_STATE_CONCLUDED:
case QEMU_BLOCKJOB_STATE_LAST:
break;
}
return 0;
}
/* Wait for qemu blockjob to finish 'block-commit' operation until it is
* ready to be finished by calling 'block-pivot' or 'block-finalize'. */
static int
qemuSnapshotDeleteBlockJobRunning(virDomainObj *vm,
qemuBlockJobData *job)
{
int rc;
qemuBlockJobUpdate(vm, job, VIR_ASYNC_JOB_SNAPSHOT);
while ((rc = qemuSnapshotDeleteBlockJobIsRunning(job->state)) > 0) {
if (qemuDomainObjWait(vm) < 0)
return -1;
qemuBlockJobUpdate(vm, job, VIR_ASYNC_JOB_SNAPSHOT);
}
if (rc < 0)
return -1;
return 0;
}
/* Wait for qemu blockjob to be done after 'block-pivot' or 'block-finalize'
* was started. */
static int
qemuSnapshotDeleteBlockJobFinishing(virDomainObj *vm,
qemuBlockJobData *job)
{
int rc;
qemuBlockJobUpdate(vm, job, VIR_ASYNC_JOB_SNAPSHOT);
while ((rc = qemuSnapshotDeleteBlockJobIsActive(job->state)) > 0) {
if (qemuDomainObjWait(vm) < 0)
return -1;
qemuBlockJobUpdate(vm, job, VIR_ASYNC_JOB_SNAPSHOT);
}
if (rc < 0)
return -1;
return 0;
}
/**
* qemuSnapshotSetInvalid:
* @vm: vm object
* @snap: snapshot object that contains parent disk
* @disk: disk from the snapshot we are deleting
* @invalid: boolean to set/unset invalid state
*
* @snap should point to a ancestor snapshot from the snapshot tree that
* affected the @disk which doesn't have to be the direct parent.
*
* When setting snapshot with parent disk as invalid due to snapshot being
* deleted we should not mark the whole snapshot as invalid but only the
* affected disks because the snapshot can contain other disks that we are
* not modifying at the moment.
*
* Return 0 on success, -1 on error.
* */
static int
qemuSnapshotSetInvalid(virDomainObj *vm,
virDomainMomentObj *snap,
virDomainSnapshotDiskDef *disk,
bool invalid)
{
ssize_t i;
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
virDomainSnapshotDef *snapdef = NULL;
if (!snap)
return 0;
snapdef = virDomainSnapshotObjGetDef(snap);
if (!snapdef)
return 0;
for (i = 0; i < snapdef->ndisks; i++) {
virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
if (STREQ(snapDisk->name, disk->name))
snapDisk->snapshotDeleteInProgress = invalid;
}
return qemuDomainSnapshotWriteMetadata(vm, snap, driver->xmlopt, cfg->snapshotDir);
}
static void
qemuSnapshotUpdateBackingStore(virDomainObj *vm,
qemuSnapshotDeleteExternalData *data)
{
GSList *cur = NULL;
const char *qemuImgPath;
virQEMUDriver *driver = QEMU_DOMAIN_PRIVATE(vm)->driver;
if (!(qemuImgPath = qemuFindQemuImgBinary(driver)))
return;
for (cur = data->disksWithBacking; cur; cur = g_slist_next(cur)) {
struct _qemuSnapshotDisksWithBackingStoreData *backingData = cur->data;
g_autoptr(virCommand) cmd = NULL;
/* creates cmd line args: qemu-img create -f qcow2 -o */
if (!(cmd = virCommandNewArgList(qemuImgPath,
"rebase",
"-u",
"-F",
virStorageFileFormatTypeToString(data->parentDiskSrc->format),
"-f",
virStorageFileFormatTypeToString(backingData->diskSrc->format),
"-b",
data->parentDiskSrc->path,
backingData->diskSrc->path,
NULL)))
continue;
virCommandSetUID(cmd, backingData->uid);
virCommandSetGID(cmd, backingData->gid);
ignore_value(virCommandRun(cmd, NULL));
}
}
static int
qemuSnapshotDiscardExternal(virDomainObj *vm,
virDomainMomentObj *snap,
GSList *externalData)
{
GSList *cur = NULL;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
for (cur = externalData; cur; cur = g_slist_next(cur)) {
qemuSnapshotDeleteExternalData *data = cur->data;
virTristateBool autofinalize = VIR_TRISTATE_BOOL_NO;
unsigned int commitFlags = VIR_DOMAIN_BLOCK_COMMIT_DELETE;
if (data->merge) {
if (data->domDisk->src == data->diskSrc) {
commitFlags |= VIR_DOMAIN_BLOCK_COMMIT_ACTIVE;
autofinalize = VIR_TRISTATE_BOOL_YES;
}
if (qemuSnapshotSetInvalid(vm, data->parentSnap, data->snapDisk, true) < 0)
goto error;
data->job = qemuBlockCommit(vm,
data->domDisk,
data->parentDiskSrc,
data->diskSrc,
data->prevDiskSrc,
0,
VIR_ASYNC_JOB_SNAPSHOT,
autofinalize,
commitFlags);
if (!data->job)
goto error;
} else {
if (virStorageSourceInit(data->parentDomDisk->src) < 0 ||
virStorageSourceUnlink(data->parentDomDisk->src) < 0) {
VIR_WARN("Failed to remove snapshot image '%s'",
data->snapDisk->name);
}
}
}
for (cur = externalData; cur; cur = g_slist_next(cur)) {
qemuSnapshotDeleteExternalData *data = cur->data;
if (!data->merge)
continue;
if (qemuSnapshotDeleteBlockJobRunning(vm, data->job) < 0)
goto error;
if (data->job->state == QEMU_BLOCKJOB_STATE_FAILED) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("block commit failed while deleting disk '%1$s' snapshot: '%2$s'"),
data->snapDisk->name, data->job->errmsg);
goto error;
}
}
for (cur = externalData; cur; cur = g_slist_next(cur)) {
qemuSnapshotDeleteExternalData *data = cur->data;
if (!data->merge)
continue;
if (data->job->state == QEMU_BLOCKJOB_STATE_READY) {
if (qemuBlockPivot(vm, data->job, VIR_ASYNC_JOB_SNAPSHOT, NULL) < 0)
goto error;
} else if (data->job->state == QEMU_BLOCKJOB_STATE_PENDING) {
if (qemuBlockFinalize(vm, data->job, VIR_ASYNC_JOB_SNAPSHOT) < 0)
goto error;
}
if (qemuSnapshotDeleteBlockJobFinishing(vm, data->job) < 0)
goto error;
if (data->job->state == QEMU_BLOCKJOB_STATE_FAILED) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("finishing block job failed while deleting disk '%1$s' snapshot: '%2$s'"),
data->snapDisk->name, data->job->errmsg);
goto error;
}
qemuBlockJobSyncEnd(vm, data->job, VIR_ASYNC_JOB_SNAPSHOT);
qemuSnapshotUpdateBackingStore(vm, data);
if (qemuSnapshotSetInvalid(vm, data->parentSnap, data->snapDisk, false) < 0)
goto error;
}
if (snapdef->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL &&
snapdef->memorysnapshotfile) {
if (unlink(snapdef->memorysnapshotfile) < 0) {
VIR_WARN("failed to remove memory snapshot '%s'",
snapdef->memorysnapshotfile);
}
}
return 0;
error:
for (cur = externalData; cur; cur = g_slist_next(cur)) {
qemuDomainObjPrivate *priv = vm->privateData;
qemuSnapshotDeleteExternalData *data = cur->data;
if (!data->job)
continue;
qemuBlockJobUpdate(vm, data->job, VIR_ASYNC_JOB_SNAPSHOT);
if (qemuSnapshotDeleteBlockJobIsActive(data->job->state)) {
if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_SNAPSHOT) == 0) {
ignore_value(qemuMonitorBlockJobCancel(priv->mon, data->job->name, false));
qemuDomainObjExitMonitor(vm);
data->job->state = QEMU_BLOCKJOB_STATE_ABORTING;
}
}
qemuBlockJobSyncEnd(vm, data->job, VIR_ASYNC_JOB_SNAPSHOT);
}
return -1;
}
static int
qemuSnapshotDeleteUpdateParent(virDomainObj *vm,
virDomainMomentObj *parent)
{
size_t i;
virQEMUDriver *driver = QEMU_DOMAIN_PRIVATE(vm)->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
virDomainSnapshotDef *parentDef = virDomainSnapshotObjGetDef(parent);
if (!parentDef)
return 0;
if (!parentDef->revertdisks)
return 0;
for (i = 0; i < parentDef->ndisks; i++) {
virDomainSnapshotDiskDefClear(&parentDef->disks[i]);
}
g_free(parentDef->disks);
parentDef->disks = g_steal_pointer(&parentDef->revertdisks);
parentDef->ndisks = parentDef->nrevertdisks;
parentDef->nrevertdisks = 0;
if (qemuDomainSnapshotWriteMetadata(vm,
parent,
driver->xmlopt,
cfg->snapshotDir) < 0) {
return -1;
}
return 0;
}
static int
qemuSnapshotDiscardMetadata(virDomainObj *vm,
virDomainMomentObj *snap,
bool update_parent)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
g_autofree char *snapFile = NULL;
int ret = 0;
if (update_parent && snap->nchildren) {
virQEMUMomentReparent rep;
qemuSnapshotUpdateDisksData data;
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)
ret = -1;
data.snap = snap;
data.vm = vm;
data.error = 0;
virDomainMomentForEachDescendant(snap,
qemuSnapshotDeleteUpdateDisks,
&data);
if (data.error < 0)
ret = -1;
virDomainMomentMoveChildren(snap, snap->parent);
}
if (update_parent && snap->parent) {
if (qemuSnapshotDeleteUpdateParent(vm, snap->parent) < 0)
ret = -1;
}
snapFile = g_strdup_printf("%s/%s/%s.xml", cfg->snapshotDir, vm->def->name,
snap->def->name);
if (snap == virDomainSnapshotGetCurrent(vm->snapshots)) {
virDomainSnapshotSetCurrent(vm->snapshots, NULL);
if (update_parent && snap->def->parent_name) {
virDomainMomentObj *parentsnap = NULL;
parentsnap = virDomainSnapshotFindByName(vm->snapshots,
snap->def->parent_name);
if (!parentsnap) {
VIR_WARN("missing parent snapshot matching name '%s'",
snap->def->parent_name);
} else {
virDomainSnapshotSetCurrent(vm->snapshots, parentsnap);
if (qemuDomainSnapshotWriteMetadata(vm, parentsnap,
driver->xmlopt,
cfg->snapshotDir) < 0) {
VIR_WARN("failed to set parent snapshot '%s' as current",
snap->def->parent_name);
virDomainSnapshotSetCurrent(vm->snapshots, NULL);
}
}
}
}
if (unlink(snapFile) < 0)
VIR_WARN("Failed to unlink %s", snapFile);
if (update_parent)
virDomainMomentDropParent(snap);
virDomainSnapshotObjListRemove(vm->snapshots, snap);
return ret;
}
/* Discard one snapshot (or its metadata), without reparenting any children. */
static int
qemuSnapshotDiscardImpl(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap,
GSList *externalData,
bool update_parent,
bool metadata_only)
{
if (!metadata_only) {
if (!virDomainObjIsActive(vm)) {
size_t i;
/* Ignore any skipped disks */
/* Prefer action on the disks in use at the time the snapshot was
* created; but fall back to current definition if dealing with a
* snapshot created prior to libvirt 0.9.5. */
virDomainDef *def = snap->def->dom;
if (!def)
def = vm->def;
for (i = 0; i < def->ndisks; i++) {
if (virDomainDiskTranslateSourcePool(def->disks[i]) < 0)
return -1;
}
if (virDomainSnapshotIsExternal(snap)) {
if (qemuSnapshotDiscardExternal(vm, snap, externalData) < 0)
return -1;
} else {
if (qemuDomainSnapshotForEachQcow2(driver, def, snap, "-d", true) < 0)
return -1;
}
} else {
if (virDomainSnapshotIsExternal(snap)) {
if (qemuSnapshotDiscardExternal(vm, snap, externalData) < 0)
return -1;
} else {
/* Similarly as internal snapshot creation we would use a regular job
* here so set a mask to forbid any other job. */
qemuDomainObjSetAsyncJobMask(vm, VIR_JOB_NONE);
if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_SNAPSHOT) < 0)
return -1;
/* we continue on even in the face of error */
qemuMonitorDeleteSnapshot(qemuDomainGetMonitor(vm), snap->def->name);
qemuDomainObjExitMonitor(vm);
qemuDomainObjSetAsyncJobMask(vm, VIR_JOB_DEFAULT_MASK);
}
}
}
if (qemuSnapshotDiscardMetadata(vm, snap, update_parent) < 0)
return -1;
return 0;
}
static int
qemuSnapshotDiscard(virQEMUDriver *driver,
virDomainObj *vm,
virDomainMomentObj *snap,
bool update_parent,
bool metadata_only)
{
return qemuSnapshotDiscardImpl(driver, vm, snap, NULL, update_parent, metadata_only);
}
int
qemuSnapshotDiscardAllMetadata(virQEMUDriver *driver,
virDomainObj *vm)
{
virQEMUMomentRemove rem = {
.driver = driver,
.vm = vm,
.metadata_only = true,
.momentDiscard = qemuSnapshotDiscard,
};
virDomainSnapshotForEach(vm->snapshots, qemuDomainMomentDiscardAll, &rem);
virDomainSnapshotObjListRemoveAll(vm->snapshots);
return rem.err;
}
static int
qemuSnapshotDeleteSingle(virDomainObj *vm,
virDomainMomentObj *snap,
GSList *externalData,
bool metadata_only)
{
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
return qemuSnapshotDiscardImpl(driver, vm, snap, externalData, true, metadata_only);
}
struct qemuSnapshotDeleteAllData {
virDomainObj *vm;
bool metadata_only;
int error;
};
static int
qemuSnapshotDeleteAllHelper(void *payload,
const char *name G_GNUC_UNUSED,
void *opaque)
{
int error;
virDomainMomentObj *snap = payload;
struct qemuSnapshotDeleteAllData *data = opaque;
error = qemuSnapshotDeleteSingle(data->vm, snap, NULL, data->metadata_only);
if (error != 0)
data->error = error;
return 0;
}
/**
* qemuSnapshotDeleteChildren:
* @vm: domain object
* @snap: snapshot object
* @metadata_only: if true only snapshots metadata are deleted
* @children_only: if true only snapshot children are deleted
*
* Delete children snapshots of snapshot provided by @snap. If @metadata_only
* is true only snapshot metadata files are delete, disk data are left intact.
* If @children_only is true it will delete only children snapshots of @snap
* and leave @snap intact.
*
* Returns 0 on success, -1 on error.
*/
static int
qemuSnapshotDeleteChildren(virDomainObj *vm,
virDomainMomentObj *snap,
bool metadata_only,
bool children_only)
{
struct qemuSnapshotDeleteAllData data = {
.vm = vm,
.metadata_only = metadata_only,
};
virDomainMomentForEachDescendant(snap, qemuSnapshotDeleteAllHelper, &data);
if (data.error < 0)
return -1;
if (!children_only &&
qemuSnapshotDeleteSingle(vm, snap, NULL, metadata_only) < 0) {
return -1;
}
return 0;
}
typedef struct {
int external;
int internal;
} qemuSnapshotCount;
static int
qemuSnapshotCountExternalInternal(void *payload,
const char *name G_GNUC_UNUSED,
void *data)
{
virDomainMomentObj *snap = payload;
qemuSnapshotCount *count = data;
if (virDomainSnapshotIsExternal(snap)) {
count->external++;
} else {
count->internal++;
}
return 0;
}
static int
qemuSnapshotDeleteValidate(virDomainObj *vm,
virDomainMomentObj *snap,
unsigned int flags)
{
if (!virDomainSnapshotIsExternal(snap) &&
virDomainObjIsActive(vm)) {
ssize_t i;
virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
for (i = 0; i < snapdef->ndisks; i++) {
virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
virDomainDiskDef *vmdisk = NULL;
virDomainDiskDef *disk = NULL;
vmdisk = qemuDomainDiskByName(vm->def, snapDisk->name);
disk = qemuDomainDiskByName(snapdef->parent.dom, snapDisk->name);
if (!virStorageSourceIsSameLocation(vmdisk->src, disk->src)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("disk image '%1$s' for internal snapshot '%2$s' is not the same as disk image currently used by VM"),
snapDisk->name, snap->def->name);
return -1;
}
}
}
if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
qemuSnapshotCount count = { 0 };
virDomainMomentForEachDescendant(snap,
qemuSnapshotCountExternalInternal,
&count);
if (count.external > 0 && count.internal > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of external and internal children disk snapshots not supported"));
return -1;
}
if (count.external > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of external children disk snapshots not supported"));
return -1;
}
}
if (virDomainSnapshotIsExternal(snap)) {
virDomainMomentObj *current = virDomainSnapshotGetCurrent(vm->snapshots);
if (qemuDomainHasBlockjob(vm, false)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("cannot delete external snapshots when there is another active block job"));
return -1;
}
if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of external disk snapshots with children not supported"));
return -1;
}
if (snap == current && snap->nchildren != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of active external snapshot that is not a leaf snapshot is not supported"));
return -1;
}
if (snap != current && snap->nchildren != 0 &&
virDomainMomentIsAncestor(snap, current)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of non-leaf external snapshot that is not in active chain is not supported"));
return -1;
}
if (snap->nchildren > 1) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("deletion of external disk snapshot with multiple children snapshots not supported"));
return -1;
}
}
return 0;
}
int
qemuSnapshotDelete(virDomainObj *vm,
virDomainSnapshotPtr snapshot,
unsigned int flags)
{
int ret = -1;
virDomainMomentObj *snap = NULL;
bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
bool stop_qemu = false;
qemuDomainObjPrivate *priv = vm->privateData;
virQEMUDriver *driver = priv->driver;
g_autoslist(qemuSnapshotDeleteExternalData) externalData = NULL;
virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1);
if (virDomainObjBeginAsyncJob(vm, VIR_ASYNC_JOB_SNAPSHOT,
VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_DELETE,
flags) < 0) {
return -1;
}
if (!(snap = qemuSnapObjFromSnapshot(vm, snapshot)))
goto endjob;
if (!metadata_only) {
if (qemuSnapshotDeleteValidate(vm, snap, flags) < 0)
goto endjob;
if (qemuSnapshotDeleteExternalPrepare(vm, snap, flags,
&externalData, &stop_qemu) < 0) {
goto endjob;
}
}
if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) {
bool children_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY);
ret = qemuSnapshotDeleteChildren(vm, snap, metadata_only, children_only);
} else {
ret = qemuSnapshotDeleteSingle(vm, snap, externalData, metadata_only);
}
endjob:
if (stop_qemu) {
qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN,
VIR_ASYNC_JOB_SNAPSHOT, 0);
}
virDomainObjEndAsyncJob(vm);
return ret;
}