libvirt/src/qemu/qemu_checkpoint.c
Michal Privoznik 13eb6c1468 qemu: Tell secdrivers which images are top parent
When preparing images for block jobs we modify their seclabels so
that QEMU can open them. However, as mentioned in the previous
commit, secdrivers base some it their decisions whether the image
they are working on is top of of the backing chain. Fortunately,
in places where we call secdrivers we know this and the
information can be passed to secdrivers.

The problem is the following: after the first blockcommit from
the base to one of the parents the XATTRs on the base image are
not cleared and therefore the second attempt to do another
blockcommit fails. This is caused by blockcommit code calling
qemuSecuritySetImageLabel() over the base image, possibly
multiple times (to ensure RW/RO access). A naive fix would be to
call the restore function. But this is not possible, because that
would deny QEMU the access to the base image.  Fortunately, we
can use the fact that seclabels are remembered only for the top
of the backing chain and not for the rest of the backing chain.
And thanks to the previous commit we can tell secdrivers which
images are top of the backing chain.

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1803551

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2020-03-09 14:14:55 +01:00

834 lines
28 KiB
C

/*
* qemu_checkpoint.c: checkpoint 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 <sys/types.h>
#include "qemu_checkpoint.h"
#include "qemu_capabilities.h"
#include "qemu_monitor.h"
#include "qemu_domain.h"
#include "qemu_block.h"
#include "virerror.h"
#include "virlog.h"
#include "datatypes.h"
#include "viralloc.h"
#include "domain_conf.h"
#include "libvirt_internal.h"
#include "virxml.h"
#include "virstring.h"
#include "virdomaincheckpointobjlist.h"
#include "virdomainsnapshotobjlist.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_checkpoint");
/* Looks up the domain object from checkpoint and unlocks the
* driver. The returned domain object is locked and ref'd and the
* caller must call virDomainObjEndAPI() on it. */
virDomainObjPtr
qemuDomObjFromCheckpoint(virDomainCheckpointPtr checkpoint)
{
return qemuDomainObjFromDomain(checkpoint->domain);
}
/* Looks up checkpoint object from VM and name */
virDomainMomentObjPtr
qemuCheckpointObjFromName(virDomainObjPtr vm,
const char *name)
{
virDomainMomentObjPtr chk = NULL;
chk = virDomainCheckpointFindByName(vm->checkpoints, name);
if (!chk)
virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT,
_("no domain checkpoint with matching name '%s'"),
name);
return chk;
}
/* Looks up checkpoint object from VM and checkpointPtr */
virDomainMomentObjPtr
qemuCheckpointObjFromCheckpoint(virDomainObjPtr vm,
virDomainCheckpointPtr checkpoint)
{
return qemuCheckpointObjFromName(vm, checkpoint->name);
}
static int
qemuCheckpointWriteMetadata(virDomainObjPtr vm,
virDomainMomentObjPtr checkpoint,
virDomainXMLOptionPtr xmlopt,
const char *checkpointDir)
{
unsigned int flags = VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE;
virDomainCheckpointDefPtr def = virDomainCheckpointObjGetDef(checkpoint);
g_autofree char *newxml = NULL;
g_autofree char *chkDir = NULL;
g_autofree char *chkFile = NULL;
newxml = virDomainCheckpointDefFormat(def, xmlopt, flags);
if (newxml == NULL)
return -1;
chkDir = g_strdup_printf("%s/%s", checkpointDir, vm->def->name);
if (virFileMakePath(chkDir) < 0) {
virReportSystemError(errno, _("cannot create checkpoint directory '%s'"),
chkDir);
return -1;
}
chkFile = g_strdup_printf("%s/%s.xml", chkDir, def->parent.name);
return virXMLSaveFile(chkFile, NULL, "checkpoint-edit", newxml);
}
/**
* qemuCheckpointFindActiveDiskInParent:
* @vm: domain object
* @from: starting moment object
* @diskname: name (target) of the disk to find
*
* Find the first checkpoint starting from @from continuing through parents
* of the checkpoint which describes disk @diskname. Return the pointer to the
* definition of the disk.
*/
static virDomainCheckpointDiskDef *
qemuCheckpointFindActiveDiskInParent(virDomainObjPtr vm,
virDomainMomentObjPtr from,
const char *diskname)
{
virDomainMomentObjPtr parent = from;
virDomainCheckpointDefPtr parentdef = NULL;
size_t i;
while (parent) {
parentdef = virDomainCheckpointObjGetDef(parent);
for (i = 0; i < parentdef->ndisks; i++) {
virDomainCheckpointDiskDef *chkdisk = &parentdef->disks[i];
if (STRNEQ(chkdisk->name, diskname))
continue;
/* currently inspected checkpoint doesn't describe the disk,
* continue into parent checkpoint */
if (chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
break;
return chkdisk;
}
parent = virDomainCheckpointFindByName(vm->checkpoints,
parentdef->parent.parent_name);
}
return NULL;
}
int
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
virHashTablePtr blockNamedNodeData,
const char *delbitmap,
const char *parentbitmap,
virJSONValuePtr actions,
const char *diskdst,
GSList **reopenimages)
{
virStorageSourcePtr n = src;
/* find the backing chain entry with bitmap named '@delbitmap' */
while (n) {
qemuBlockNamedNodeDataBitmapPtr tmp;
if ((tmp = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
n, delbitmap))) {
break;
}
n = n->backingStore;
}
if (!n) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("bitmap '%s' not found in backing chain of '%s'"),
delbitmap, diskdst);
return -1;
}
while (n) {
qemuBlockNamedNodeDataBitmapPtr srcbitmap;
if (!(srcbitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
n, delbitmap)))
break;
/* For the actual checkpoint deletion we will merge any bitmap into the
* bitmap of the parent checkpoint (@parentbitmap) or for any image
* where the parent checkpoint bitmap is not present we must rename
* the bitmap of the deleted checkpoint into the bitmap of the parent
* checkpoint as qemu can't currently take the allocation map and turn
* it into a bitmap and thus we wouldn't be able to do a backup. */
if (parentbitmap) {
qemuBlockNamedNodeDataBitmapPtr dstbitmap;
g_autoptr(virJSONValue) arr = NULL;
dstbitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
n, parentbitmap);
if (dstbitmap) {
if (srcbitmap->recording && !dstbitmap->recording) {
if (qemuMonitorTransactionBitmapEnable(actions,
n->nodeformat,
dstbitmap->name) < 0)
return -1;
}
} else {
if (qemuMonitorTransactionBitmapAdd(actions,
n->nodeformat,
parentbitmap,
true,
!srcbitmap->recording,
srcbitmap->granularity) < 0)
return -1;
}
arr = virJSONValueNewArray();
if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr,
n->nodeformat,
srcbitmap->name) < 0)
return -1;
if (qemuMonitorTransactionBitmapMerge(actions,
n->nodeformat,
parentbitmap, &arr) < 0)
return -1;
}
if (qemuMonitorTransactionBitmapRemove(actions,
n->nodeformat,
srcbitmap->name) < 0)
return -1;
if (n != src)
*reopenimages = g_slist_prepend(*reopenimages, n);
n = n->backingStore;
}
return 0;
}
static int
qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
virDomainCheckpointDefPtr chkdef,
virDomainMomentObjPtr parent)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virQEMUDriverPtr driver = priv->driver;
g_autoptr(virHashTable) blockNamedNodeData = NULL;
int rc = -1;
g_autoptr(virJSONValue) actions = NULL;
size_t i;
g_autoptr(GSList) reopenimages = NULL;
g_autoptr(GSList) relabelimages = NULL;
GSList *next;
actions = virJSONValueNewArray();
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_NONE)))
return -1;
for (i = 0; i < chkdef->ndisks; i++) {
virDomainCheckpointDiskDef *chkdisk = &chkdef->disks[i];
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
virDomainCheckpointDiskDef *parentchkdisk = NULL;
const char *parentbitmap = NULL;
/* domdisk can be missing e.g. when it was unplugged */
if (!domdisk)
continue;
if (chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
continue;
/* If any ancestor checkpoint has a bitmap for the same
* disk, then this bitmap must be merged to the
* ancestor. */
if ((parentchkdisk = qemuCheckpointFindActiveDiskInParent(vm, parent,
chkdisk->name)))
parentbitmap = parentchkdisk->name;
if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, blockNamedNodeData,
chkdisk->bitmap, parentbitmap,
actions, domdisk->dst,
&reopenimages) < 0)
return -1;
}
/* label any non-top images for read-write access */
for (next = reopenimages; next; next = next->next) {
virStorageSourcePtr src = next->data;
if (qemuDomainStorageSourceAccessAllow(driver, vm, src,
false, false, false) < 0)
goto relabel;
relabelimages = g_slist_prepend(relabelimages, src);
}
qemuDomainObjEnterMonitor(driver, vm);
rc = qemuMonitorTransaction(priv->mon, &actions);
if (qemuDomainObjExitMonitor(driver, vm) < 0)
return -1;
relabel:
for (next = relabelimages; next; next = next->next) {
virStorageSourcePtr src = next->data;
ignore_value(qemuDomainStorageSourceAccessAllow(driver, vm, src,
true, false, false));
}
return rc;
}
static int
qemuCheckpointDiscard(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virDomainMomentObjPtr chk,
bool update_parent,
bool metadata_only)
{
virDomainMomentObjPtr parent = NULL;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
g_autofree char *chkFile = NULL;
bool chkcurrent = chk == virDomainCheckpointGetCurrent(vm->checkpoints);
if (!metadata_only && !virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("cannot remove checkpoint from inactive domain"));
return -1;
}
chkFile = g_strdup_printf("%s/%s/%s.xml", cfg->checkpointDir, vm->def->name,
chk->def->name);
if (!metadata_only) {
virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk);
parent = virDomainCheckpointFindByName(vm->checkpoints,
chk->def->parent_name);
if (qemuCheckpointDiscardBitmaps(vm, chkdef, parent) < 0)
return -1;
}
if (chkcurrent) {
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
if (update_parent && parent) {
virDomainCheckpointSetCurrent(vm->checkpoints, parent);
if (qemuCheckpointWriteMetadata(vm, parent,
driver->xmlopt,
cfg->checkpointDir) < 0) {
VIR_WARN("failed to set parent checkpoint '%s' as current",
chk->def->parent_name);
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
}
}
}
if (unlink(chkFile) < 0)
VIR_WARN("Failed to unlink %s", chkFile);
if (update_parent)
virDomainMomentDropParent(chk);
virDomainCheckpointObjListRemove(vm->checkpoints, chk);
return 0;
}
int
qemuCheckpointDiscardAllMetadata(virQEMUDriverPtr driver,
virDomainObjPtr vm)
{
virQEMUMomentRemove rem = {
.driver = driver,
.vm = vm,
.metadata_only = true,
.momentDiscard = qemuCheckpointDiscard,
};
virDomainCheckpointForEach(vm->checkpoints, qemuDomainMomentDiscardAll,
&rem);
virDomainCheckpointObjListRemoveAll(vm->checkpoints);
return rem.err;
}
/* Called inside job lock */
static int
qemuCheckpointPrepare(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virDomainCheckpointDefPtr def)
{
int ret = -1;
size_t i;
char *xml = NULL;
qemuDomainObjPrivatePtr priv = vm->privateData;
/* Easiest way to clone inactive portion of vm->def is via
* conversion in and back out of xml. */
if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
vm->def, priv->origCPU,
true, true)) ||
!(def->parent.dom = virDomainDefParseString(xml, driver->xmlopt,
priv->qemuCaps,
VIR_DOMAIN_DEF_PARSE_INACTIVE |
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
goto cleanup;
if (virDomainCheckpointAlignDisks(def) < 0)
goto cleanup;
for (i = 0; i < def->ndisks; i++) {
virDomainCheckpointDiskDefPtr disk = &def->disks[i];
if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
continue;
if (STRNEQ(disk->bitmap, def->parent.name)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("bitmap for disk '%s' must match checkpoint name '%s'"),
disk->name, def->parent.name);
goto cleanup;
}
if (vm->def->disks[i]->src->format != VIR_STORAGE_FILE_QCOW2) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("checkpoint for disk %s unsupported "
"for storage type %s"),
disk->name,
virStorageFileFormatTypeToString(
vm->def->disks[i]->src->format));
goto cleanup;
}
}
ret = 0;
cleanup:
VIR_FREE(xml);
return ret;
}
static int
qemuCheckpointAddActions(virDomainObjPtr vm,
virJSONValuePtr actions,
virDomainMomentObjPtr old_current,
virDomainCheckpointDefPtr def)
{
size_t i;
for (i = 0; i < def->ndisks; i++) {
virDomainCheckpointDiskDef *chkdisk = &def->disks[i];
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
virDomainCheckpointDiskDef *parentchkdisk = NULL;
/* checkpoint definition validator mandates that the corresponding
* domdisk should exist */
if (!domdisk ||
chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
continue;
if (qemuMonitorTransactionBitmapAdd(actions, domdisk->src->nodeformat,
chkdisk->bitmap, true, false, 0) < 0)
return -1;
/* We only want one active bitmap for a disk along the
* checkpoint chain, then later differential backups will
* merge the bitmaps (only one active) between the bounding
* checkpoint and the leaf checkpoint. If the same disks are
* involved in each checkpoint, this search terminates in one
* iteration; but it is also possible to have to search
* further than the immediate parent to find another
* checkpoint with a bitmap on the same disk. */
if ((parentchkdisk = qemuCheckpointFindActiveDiskInParent(vm, old_current,
chkdisk->name))) {
if (qemuMonitorTransactionBitmapDisable(actions,
domdisk->src->nodeformat,
parentchkdisk->bitmap) < 0)
return -1;
}
}
return 0;
}
static virDomainMomentObjPtr
qemuCheckpointRedefine(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virDomainCheckpointDefPtr *def,
bool *update_current)
{
virDomainMomentObjPtr chk = NULL;
if (virDomainCheckpointRedefinePrep(vm, def, &chk, driver->xmlopt,
update_current) < 0)
return NULL;
/* XXX Should we validate that the redefined checkpoint even
* makes sense, such as checking that qemu-img recognizes the
* checkpoint bitmap name in at least one of the domain's disks? */
if (chk)
return chk;
chk = virDomainCheckpointAssignDef(vm->checkpoints, *def);
*def = NULL;
return chk;
}
int
qemuCheckpointCreateCommon(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virDomainCheckpointDefPtr *def,
virJSONValuePtr *actions,
virDomainMomentObjPtr *chk)
{
g_autoptr(virJSONValue) tmpactions = NULL;
virDomainMomentObjPtr parent;
if (qemuCheckpointPrepare(driver, vm, *def) < 0)
return -1;
if ((parent = virDomainCheckpointGetCurrent(vm->checkpoints)))
(*def)->parent.parent_name = g_strdup(parent->def->name);
tmpactions = virJSONValueNewArray();
if (qemuCheckpointAddActions(vm, tmpactions, parent, *def) < 0)
return -1;
if (!(*chk = virDomainCheckpointAssignDef(vm->checkpoints, *def)))
return -1;
*def = NULL;
*actions = g_steal_pointer(&tmpactions);
return 0;
}
/**
* qemuCheckpointRollbackMetadata:
* @vm: domain object
* @chk: checkpoint object
*
* If @chk is not null remove the @chk object from the list of checkpoints of @vm.
*/
void
qemuCheckpointRollbackMetadata(virDomainObjPtr vm,
virDomainMomentObjPtr chk)
{
if (!chk)
return;
virDomainCheckpointObjListRemove(vm->checkpoints, chk);
}
static virDomainMomentObjPtr
qemuCheckpointCreate(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virDomainCheckpointDefPtr *def)
{
g_autoptr(virJSONValue) actions = NULL;
virDomainMomentObjPtr chk = NULL;
int rc;
if (qemuCheckpointCreateCommon(driver, vm, def, &actions, &chk) < 0)
return NULL;
qemuDomainObjEnterMonitor(driver, vm);
rc = qemuMonitorTransaction(qemuDomainGetMonitor(vm), &actions);
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) {
qemuCheckpointRollbackMetadata(vm, chk);
return NULL;
}
return chk;
}
int
qemuCheckpointCreateFinalize(virQEMUDriverPtr driver,
virDomainObjPtr vm,
virQEMUDriverConfigPtr cfg,
virDomainMomentObjPtr chk,
bool update_current)
{
if (update_current)
virDomainCheckpointSetCurrent(vm->checkpoints, chk);
if (qemuCheckpointWriteMetadata(vm, chk,
driver->xmlopt,
cfg->checkpointDir) < 0) {
/* if writing of metadata fails, error out rather than trying
* to silently carry on without completing the checkpoint */
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to save metadata for checkpoint %s"),
chk->def->name);
qemuCheckpointRollbackMetadata(vm, chk);
return -1;
}
virDomainCheckpointLinkParent(vm->checkpoints, chk);
return 0;
}
virDomainCheckpointPtr
qemuCheckpointCreateXML(virDomainPtr domain,
virDomainObjPtr vm,
const char *xmlDesc,
unsigned int flags)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virQEMUDriverPtr driver = priv->driver;
virDomainMomentObjPtr chk = NULL;
virDomainCheckpointPtr checkpoint = NULL;
bool update_current = true;
bool redefine = flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE;
unsigned int parse_flags = 0;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
g_autoptr(virDomainCheckpointDef) def = NULL;
virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE, NULL);
if (redefine) {
parse_flags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE;
update_current = false;
}
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("incremental backup is not supported yet"));
return NULL;
}
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("cannot create checkpoint for inactive domain"));
return NULL;
}
if (!(def = virDomainCheckpointDefParseString(xmlDesc, driver->xmlopt,
priv->qemuCaps, parse_flags)))
return NULL;
/* Unlike snapshots, the RNG schema already ensured a sane filename. */
/* We are going to modify the domain below. */
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
return NULL;
if (redefine) {
chk = qemuCheckpointRedefine(driver, vm, &def, &update_current);
} else {
chk = qemuCheckpointCreate(driver, vm, &def);
}
if (!chk)
goto endjob;
if (qemuCheckpointCreateFinalize(driver, vm, cfg, chk, update_current) < 0)
goto endjob;
/* If we fail after this point, there's not a whole lot we can do;
* we've successfully created the checkpoint, so we have to go
* forward the best we can.
*/
checkpoint = virGetDomainCheckpoint(domain, chk->def->name);
endjob:
qemuDomainObjEndJob(driver, vm);
return checkpoint;
}
char *
qemuCheckpointGetXMLDesc(virDomainObjPtr vm,
virDomainCheckpointPtr checkpoint,
unsigned int flags)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virQEMUDriverPtr driver = priv->driver;
virDomainMomentObjPtr chk = NULL;
virDomainCheckpointDefPtr chkdef;
unsigned int format_flags;
virCheckFlags(VIR_DOMAIN_CHECKPOINT_XML_SECURE |
VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN, NULL);
if (!(chk = qemuCheckpointObjFromCheckpoint(vm, checkpoint)))
return NULL;
chkdef = virDomainCheckpointObjGetDef(chk);
format_flags = virDomainCheckpointFormatConvertXMLFlags(flags);
return virDomainCheckpointDefFormat(chkdef, driver->xmlopt,
format_flags);
}
struct virQEMUCheckpointReparent {
const char *dir;
virDomainMomentObjPtr parent;
virDomainObjPtr vm;
virDomainXMLOptionPtr xmlopt;
int err;
};
static int
qemuCheckpointReparentChildren(void *payload,
const void *name G_GNUC_UNUSED,
void *data)
{
virDomainMomentObjPtr moment = payload;
struct virQEMUCheckpointReparent *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 = qemuCheckpointWriteMetadata(rep->vm, moment,
rep->xmlopt, rep->dir);
return 0;
}
int
qemuCheckpointDelete(virDomainObjPtr vm,
virDomainCheckpointPtr checkpoint,
unsigned int flags)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virQEMUDriverPtr driver = priv->driver;
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
int ret = -1;
virDomainMomentObjPtr chk = NULL;
virQEMUMomentRemove rem;
struct virQEMUCheckpointReparent rep;
bool metadata_only = !!(flags & VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY);
virCheckFlags(VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN |
VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY |
VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY, -1);
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
return -1;
if (!metadata_only) {
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("incremental backup is not supported yet"));
goto endjob;
}
if (!virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("cannot delete checkpoint for inactive domain"));
goto endjob;
}
}
if (!(chk = qemuCheckpointObjFromCheckpoint(vm, checkpoint)))
goto endjob;
if (flags & (VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN |
VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)) {
rem.driver = driver;
rem.vm = vm;
rem.metadata_only = metadata_only;
rem.err = 0;
rem.current = virDomainCheckpointGetCurrent(vm->checkpoints);
rem.found = false;
rem.momentDiscard = qemuCheckpointDiscard;
virDomainMomentForEachDescendant(chk, qemuDomainMomentDiscardAll,
&rem);
if (rem.err < 0)
goto endjob;
if (rem.found) {
virDomainCheckpointSetCurrent(vm->checkpoints, chk);
if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) {
if (qemuCheckpointWriteMetadata(vm, chk,
driver->xmlopt,
cfg->checkpointDir) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("failed to set checkpoint '%s' as current"),
chk->def->name);
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
goto endjob;
}
}
}
} else if (chk->nchildren) {
rep.dir = cfg->checkpointDir;
rep.parent = chk->parent;
rep.vm = vm;
rep.err = 0;
rep.xmlopt = driver->xmlopt;
virDomainMomentForEachChild(chk, qemuCheckpointReparentChildren,
&rep);
if (rep.err < 0)
goto endjob;
virDomainMomentMoveChildren(chk, chk->parent);
}
if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) {
virDomainMomentDropChildren(chk);
ret = 0;
} else {
ret = qemuCheckpointDiscard(driver, vm, chk, true, metadata_only);
}
endjob:
qemuDomainObjEndJob(driver, vm);
return ret;
}