A lot of this work heavily copies from the existing snapshot APIs. The interaction with qemu during create/delete still needs to be implemented, but this takes care of all the libvirt metadata (saving and restoring XML, and tracking the relations between multiple checkpoints). Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- src/qemu/qemu_block.h | 3 + src/qemu/qemu_conf.h | 2 + src/qemu/qemu_domain.h | 28 +- src/qemu/qemu_block.c | 12 + src/qemu/qemu_conf.c | 5 + src/qemu/qemu_domain.c | 178 +++++++- src/qemu/qemu_driver.c | 955 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 1172 insertions(+), 11 deletions(-) diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h index 9401ab4e12..fbcd019d5c 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -125,4 +125,7 @@ qemuBlockSnapshotAddLegacy(virJSONValuePtr actions, virStorageSourcePtr newsrc, bool reuse); +const char * +qemuBlockNodeLookup(virDomainObjPtr vm, const char *disk); + #endif /* LIBVIRT_QEMU_BLOCK_H */ diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 14c9d15a72..a94c229526 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -29,6 +29,7 @@ # include "capabilities.h" # include "network_conf.h" # include "domain_conf.h" +# include "checkpoint_conf.h" # include "snapshot_conf.h" # include "domain_event.h" # include "virthread.h" @@ -110,6 +111,7 @@ struct _virQEMUDriverConfig { char *cacheDir; char *saveDir; char *snapshotDir; + char *checkpointDir; char *channelTargetDir; char *nvramDir; char *swtpmStorageDir; diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 37c9813ec5..bcceaa5506 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1,7 +1,7 @@ /* * qemu_domain.h: QEMU domain private state * - * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006-2019 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -697,9 +697,10 @@ int qemuDomainSnapshotDiscard(virQEMUDriverPtr driver, bool update_current, bool metadata_only); -typedef struct _virQEMUSnapRemove virQEMUSnapRemove; -typedef virQEMUSnapRemove *virQEMUSnapRemovePtr; -struct _virQEMUSnapRemove { +/* Used for both snapshots and checkpoints */ +typedef struct _virQEMUDependentRemove virQEMUDependentRemove; +typedef virQEMUDependentRemove *virQEMUDependentRemovePtr; +struct _virQEMUDependentRemove { virQEMUDriverPtr driver; virDomainObjPtr vm; int err; @@ -714,6 +715,25 @@ int qemuDomainSnapshotDiscardAll(void *payload, int qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver, virDomainObjPtr vm); +int qemuDomainCheckpointWriteMetadata(virDomainObjPtr vm, + virDomainCheckpointObjPtr checkpoint, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + const char *checkpointDir); + +int qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainCheckpointObjPtr chk, + bool update_current, + bool metadata_only); + +int qemuDomainCheckpointDiscardAll(void *payload, + const void *name, + void *data); + +int qemuDomainCheckpointDiscardAllMetadata(virQEMUDriverPtr driver, + virDomainObjPtr vm); + void qemuDomainRemoveInactive(virQEMUDriverPtr driver, virDomainObjPtr vm); diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index cbf0aa4189..df04abeb07 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -1767,3 +1767,15 @@ qemuBlockStorageGetCopyOnReadProps(virDomainDiskDefPtr disk) return ret; } + +const char * +qemuBlockNodeLookup(virDomainObjPtr vm, const char *disk) +{ + size_t i; + + for (i = 0; i < vm->def->ndisks; i++) { + if (STREQ(vm->def->disks[i]->dst, disk)) + return vm->def->disks[i]->src->nodeformat; + } + return NULL; +} diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 42122dcd97..e58683f571 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -179,6 +179,8 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/snapshot", cfg->libDir) < 0) goto error; + if (virAsprintf(&cfg->checkpointDir, "%s/checkpoint", cfg->libDir) < 0) + goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/dump", cfg->libDir) < 0) goto error; if (virAsprintf(&cfg->channelTargetDir, @@ -242,6 +244,8 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/qemu/snapshot", cfg->configBaseDir) < 0) goto error; + if (virAsprintf(&cfg->checkpointDir, "%s/qemu/checkpoint", cfg->configBaseDir) < 0) + goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/qemu/dump", cfg->configBaseDir) < 0) goto error; if (virAsprintf(&cfg->channelTargetDir, @@ -354,6 +358,7 @@ static void virQEMUDriverConfigDispose(void *obj) VIR_FREE(cfg->cacheDir); VIR_FREE(cfg->saveDir); VIR_FREE(cfg->snapshotDir); + VIR_FREE(cfg->checkpointDir); VIR_FREE(cfg->channelTargetDir); VIR_FREE(cfg->nvramDir); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index e243c07168..f995721bb0 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1,7 +1,7 @@ /* * qemu_domain.c: QEMU domain private state * - * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006-2018 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -7728,9 +7728,10 @@ qemuDomainDefFormatBufInternal(virQEMUDriverPtr driver, virQEMUCapsPtr qemuCaps = NULL; virDomainDefFormatData data = { 0 }; bool snapshots = flags & VIR_DOMAIN_XML_SNAPSHOTS; + bool checkpoints = flags & VIR_DOMAIN_XML_CHECKPOINTS; virCheckFlags(VIR_DOMAIN_XML_COMMON_FLAGS | VIR_DOMAIN_XML_UPDATE_CPU | - VIR_DOMAIN_XML_SNAPSHOTS, -1); + VIR_DOMAIN_XML_SNAPSHOTS | VIR_DOMAIN_XML_CHECKPOINTS, -1); if (!(data.caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -7906,6 +7907,15 @@ qemuDomainDefFormatBufInternal(virQEMUDriverPtr driver, data.snapshots = vm->snapshots; data.current_snapshot = vm->current_snapshot; } + if (checkpoints) { + if (!vm) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("checkpoints XML requested but not provided")); + goto cleanup; + } + data.checkpoints = vm->checkpoints; + data.current_check = vm->current_checkpoint; + } ret = virDomainDefFormatInternal(buf, def, &data, virDomainDefFormatConvertXMLFlags(flags), driver->xmlopt); @@ -8448,6 +8458,45 @@ qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, return ret; } +int +qemuDomainCheckpointWriteMetadata(virDomainObjPtr vm, + virDomainCheckpointObjPtr checkpoint, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + const char *checkpointDir) +{ + char *newxml = NULL; + int ret = -1; + char *chkDir = NULL; + char *chkFile = NULL; + + newxml = virDomainCheckpointDefFormat( + checkpoint->def, caps, xmlopt, + virDomainDefFormatConvertXMLFlags(QEMU_DOMAIN_FORMAT_LIVE_FLAGS) | + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL); + if (newxml == NULL) + return -1; + + if (virAsprintf(&chkDir, "%s/%s", checkpointDir, vm->def->name) < 0) + goto cleanup; + if (virFileMakePath(chkDir) < 0) { + virReportSystemError(errno, _("cannot create checkpoint directory '%s'"), + chkDir); + goto cleanup; + } + + if (virAsprintf(&chkFile, "%s/%s.xml", chkDir, checkpoint->def->name) < 0) + goto cleanup; + + ret = virXMLSaveFile(chkFile, NULL, "checkpoint-edit", newxml); + + cleanup: + VIR_FREE(chkFile); + VIR_FREE(chkDir); + VIR_FREE(newxml); + return ret; +} + /* The domain is expected to be locked and inactive. Return -1 on normal * failure, 1 if we skipped a disk due to try_all. */ static int @@ -8615,7 +8664,7 @@ int qemuDomainSnapshotDiscardAll(void *payload, void *data) { virDomainSnapshotObjPtr snap = payload; - virQEMUSnapRemovePtr curr = data; + virQEMUDependentRemovePtr curr = data; int err; if (snap->def->current) @@ -8631,7 +8680,7 @@ int qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver, virDomainObjPtr vm) { - virQEMUSnapRemove rem; + virQEMUDependentRemove rem; rem.driver = driver; rem.vm = vm; @@ -8648,12 +8697,122 @@ qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver, } +/* Discard one checkpoint (or its metadata), without reparenting any children. */ +int +qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainCheckpointObjPtr chk, + bool update_parent, + bool metadata_only) +{ + char *chkFile = NULL; + int ret = -1; + virDomainCheckpointObjPtr parentchk = NULL; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + + if (!metadata_only) { + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot remove checkpoint from inactive domain")); + goto cleanup; + } else { + /* TODO: Implement QMP sequence to merge bitmaps */ + // qemuDomainObjPrivatePtr priv; + // priv = vm->privateData; + // qemuDomainObjEnterMonitor(driver, vm); + // /* we continue on even in the face of error */ + // qemuMonitorDeleteCheckpoint(priv->mon, chk->def->name); + // ignore_value(qemuDomainObjExitMonitor(driver, vm)); + } + } + + if (virAsprintf(&chkFile, "%s/%s/%s.xml", cfg->checkpointDir, + vm->def->name, chk->def->name) < 0) + goto cleanup; + + if (chk == vm->current_checkpoint) { + if (update_parent && chk->def->parent) { + parentchk = virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent); + if (!parentchk) { + VIR_WARN("missing parent checkpoint matching name '%s'", + chk->def->parent); + } else { + parentchk->def->current = true; + if (qemuDomainCheckpointWriteMetadata(vm, parentchk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + VIR_WARN("failed to set parent checkpoint '%s' as current", + chk->def->parent); + parentchk->def->current = false; + parentchk = NULL; + } + } + } + vm->current_checkpoint = parentchk; + } + + if (unlink(chkFile) < 0) + VIR_WARN("Failed to unlink %s", chkFile); + if (update_parent) + virDomainCheckpointDropParent(chk); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + + ret = 0; + + cleanup: + VIR_FREE(chkFile); + virObjectUnref(cfg); + return ret; +} + +/* Hash iterator callback to discard multiple checkpoints. */ +int qemuDomainCheckpointDiscardAll(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainCheckpointObjPtr chk = payload; + virQEMUDependentRemovePtr curr = data; + int err; + + if (chk->def->current) + curr->current = true; + err = qemuDomainCheckpointDiscard(curr->driver, curr->vm, chk, false, + curr->metadata_only); + if (err && !curr->err) + curr->err = err; + return 0; +} + + +int +qemuDomainCheckpointDiscardAllMetadata(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + virQEMUDependentRemove rem; + + rem.driver = driver; + rem.vm = vm; + rem.metadata_only = true; + rem.err = 0; + virDomainCheckpointForEach(vm->checkpoints, qemuDomainCheckpointDiscardAll, + &rem); + if (rem.current) + vm->current_checkpoint = NULL; + if (virDomainCheckpointUpdateRelations(vm->checkpoints) < 0 && !rem.err) + rem.err = -1; + + return rem.err; +} + + static void qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driver, virDomainObjPtr vm) { virQEMUDriverConfigPtr cfg; VIR_AUTOFREE(char *) snapDir = NULL; + VIR_AUTOFREE(char *) chkDir = NULL; cfg = virQEMUDriverGetConfig(driver); @@ -8668,6 +8827,17 @@ qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driver, } else if (rmdir(snapDir) < 0 && errno != ENOENT) { VIR_WARN("unable to remove snapshot directory %s", snapDir); } + /* Remove any checkpoint metadata prior to removing the domain */ + if (qemuDomainCheckpointDiscardAllMetadata(driver, vm) < 0) { + VIR_WARN("unable to remove all checkpoints for domain %s", + vm->def->name); + } else if (virAsprintf(&chkDir, "%s/%s", cfg->checkpointDir, + vm->def->name) < 0) { + VIR_WARN("unable to remove checkpoint directory %s/%s", + cfg->checkpointDir, vm->def->name); + } else if (rmdir(chkDir) < 0 && errno != ENOENT) { + VIR_WARN("unable to remove snapshot directory %s", chkDir); + } qemuExtDevicesCleanupHost(driver, vm->def); virObjectUnref(cfg); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 55fcb3ba5a..76a62d2d76 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1,7 +1,7 @@ /* * qemu_driver.c: core driver methods for managing qemu guests * - * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006-2019 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -220,6 +220,40 @@ qemuSnapObjFromSnapshot(virDomainObjPtr vm, return qemuSnapObjFromName(vm, snapshot->name); } +/* 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. */ +static virDomainObjPtr +qemuDomObjFromCheckpoint(virDomainCheckpointPtr checkpoint) +{ + return qemuDomObjFromDomain(checkpoint->domain); +} + + +/* Looks up checkpoint object from VM and name */ +static virDomainCheckpointObjPtr +qemuCheckObjFromName(virDomainObjPtr vm, + const char *name) +{ + virDomainCheckpointObjPtr 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 */ +static virDomainCheckpointObjPtr +qemuCheckObjFromCheckpoint(virDomainObjPtr vm, + virDomainCheckpointPtr checkpoint) +{ + return qemuCheckObjFromName(vm, checkpoint->name); +} + static int qemuAutostartDomain(virDomainObjPtr vm, void *opaque) @@ -526,6 +560,127 @@ qemuDomainSnapshotLoad(virDomainObjPtr vm, } +static int +qemuDomainCheckpointLoad(virDomainObjPtr vm, + void *data) +{ + char *baseDir = (char *)data; + char *chkDir = NULL; + DIR *dir = NULL; + struct dirent *entry; + char *xmlStr; + char *fullpath; + virDomainCheckpointDefPtr def = NULL; + virDomainCheckpointObjPtr chk = NULL; + virDomainCheckpointObjPtr current = NULL; + unsigned int flags = (VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_PARSE_DISKS | + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL); + int ret = -1; + virCapsPtr caps = NULL; + int direrr; + + virObjectLock(vm); + if (virAsprintf(&chkDir, "%s/%s", baseDir, vm->def->name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to allocate memory for " + "checkpoint directory for domain %s"), + vm->def->name); + goto cleanup; + } + + if (!(caps = virQEMUDriverGetCapabilities(qemu_driver, false))) + goto cleanup; + + VIR_INFO("Scanning for checkpoints for domain %s in %s", vm->def->name, + chkDir); + + if (virDirOpenIfExists(&dir, chkDir) <= 0) + goto cleanup; + + while ((direrr = virDirRead(dir, &entry, NULL)) > 0) { + /* NB: ignoring errors, so one malformed config doesn't + kill the whole process */ + VIR_INFO("Loading checkpoint file '%s'", entry->d_name); + + if (virAsprintf(&fullpath, "%s/%s", chkDir, entry->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to allocate memory for path")); + continue; + } + + if (virFileReadAll(fullpath, 1024*1024*1, &xmlStr) < 0) { + /* Nothing we can do here, skip this one */ + virReportSystemError(errno, + _("Failed to read checkpoint file %s"), + fullpath); + VIR_FREE(fullpath); + continue; + } + + def = virDomainCheckpointDefParseString(xmlStr, caps, + qemu_driver->xmlopt, + flags); + if (!def || virDomainCheckpointAlignDisks(def) < 0) { + /* Nothing we can do here, skip this one */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse checkpoint XML from file '%s'"), + fullpath); + VIR_FREE(fullpath); + VIR_FREE(xmlStr); + continue; + } + + chk = virDomainCheckpointAssignDef(vm->checkpoints, def); + if (chk == NULL) { + virDomainCheckpointDefFree(def); + } else if (chk->def->current) { + current = chk; + if (!vm->current_checkpoint) + vm->current_checkpoint = chk; + } + + VIR_FREE(fullpath); + VIR_FREE(xmlStr); + } + if (direrr < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to fully read directory %s"), + chkDir); + + if (vm->current_checkpoint != current) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Too many checkpoints claiming to be current for domain %s"), + vm->def->name); + vm->current_checkpoint = NULL; + } + + if (virDomainCheckpointUpdateRelations(vm->checkpoints) < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Checkpoints have inconsistent relations for domain %s"), + vm->def->name); + + /* FIXME: qemu keeps internal track of bitmaps, which form the + * basis for checkpoints; it would be nice if we could update our + * internal state to reflect that information automatically. But + * qemu 3.0 did not have access to this via qemu-img for offline + * images (you have to use QMP commands on a running guest), and + * it also does not track <parent> relations which we find + * important in our metadata. + */ + + virResetLastError(); + + ret = 0; + cleanup: + VIR_DIR_CLOSE(dir); + VIR_FREE(chkDir); + virObjectUnref(caps); + virObjectUnlock(vm); + return ret; +} + + static int qemuDomainNetsRestart(virDomainObjPtr vm, void *data ATTRIBUTE_UNUSED) @@ -656,6 +811,11 @@ qemuStateInitialize(bool privileged, cfg->snapshotDir); goto error; } + if (virFileMakePath(cfg->checkpointDir) < 0) { + virReportSystemError(errno, _("Failed to create checkpoint dir %s"), + cfg->checkpointDir); + goto error; + } if (virFileMakePath(cfg->autoDumpPath) < 0) { virReportSystemError(errno, _("Failed to create dump dir %s"), cfg->autoDumpPath); @@ -763,6 +923,13 @@ qemuStateInitialize(bool privileged, (int)cfg->group); goto error; } + if (chown(cfg->checkpointDir, cfg->user, cfg->group) < 0) { + virReportSystemError(errno, + _("unable to set ownership of '%s' to %d:%d"), + cfg->checkpointDir, (int)cfg->user, + (int)cfg->group); + goto error; + } if (chown(cfg->autoDumpPath, cfg->user, cfg->group) < 0) { virReportSystemError(errno, _("unable to set ownership of '%s' to %d:%d"), @@ -901,6 +1068,10 @@ qemuStateInitialize(bool privileged, qemuDomainSnapshotLoad, cfg->snapshotDir); + virDomainObjListForEach(qemu_driver->domains, + qemuDomainCheckpointLoad, + cfg->checkpointDir); + virDomainObjListForEach(qemu_driver->domains, qemuDomainManagedSaveLoad, qemu_driver); @@ -7330,7 +7501,7 @@ static char char *ret = NULL; virCheckFlags(VIR_DOMAIN_XML_COMMON_FLAGS | VIR_DOMAIN_XML_UPDATE_CPU | - VIR_DOMAIN_XML_SNAPSHOTS, NULL); + VIR_DOMAIN_XML_SNAPSHOTS | VIR_DOMAIN_XML_CHECKPOINTS, NULL); if (!(vm = qemuDomObjFromDomain(dom))) goto cleanup; @@ -7789,6 +7960,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, if (qemuDomainSnapshotDiscardAllMetadata(driver, vm) < 0) goto endjob; } + /* TODO: Restrict deletion if checkpoints exist? */ name = qemuDomainManagedSavePath(driver, vm); if (name == NULL) @@ -16887,7 +17059,7 @@ qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, virDomainObjPtr vm = NULL; int ret = -1; virDomainSnapshotObjPtr snap = NULL; - virQEMUSnapRemove rem; + virQEMUDependentRemove rem; virQEMUSnapReparent rep; bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); int external = 0; @@ -16991,6 +17163,755 @@ qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, return ret; } + +/* Called prior to job lock */ +static virDomainCheckpointDefPtr +qemuDomainCheckpointDefParseString(virQEMUDriverPtr driver, virCapsPtr caps, + const char *xmlDesc, unsigned int flags) +{ + virDomainCheckpointDefPtr def = NULL; + virDomainCheckpointDefPtr ret = NULL; + unsigned int parse_flags = VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + + if (flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE) + parse_flags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if (!(def = virDomainCheckpointDefParseString(xmlDesc, caps, driver->xmlopt, + parse_flags))) + goto cleanup; + + /* reject checkpoint names containing slashes or starting with dot as + * checkpoint definitions are saved in files named by the checkpoint name */ + if (!(flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) { + if (strchr(def->name, '/')) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid checkpoint name '%s': " + "name can't contain '/'"), + def->name); + goto cleanup; + } + + if (def->name[0] == '.') { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid checkpoint name '%s': " + "name can't start with '.'"), + def->name); + goto cleanup; + } + } + + VIR_STEAL_PTR(ret, def); + + cleanup: + virDomainCheckpointDefFree(def); + return ret; +} + + +/* Called inside job lock */ +static int +qemuDomainCheckpointPrepare(virQEMUDriverPtr driver, virCapsPtr caps, + 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, vm->def, priv->origCPU, + true, true)) || + !(def->dom = virDomainDefParseString(xml, caps, driver->xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) + goto cleanup; + + if (virDomainCheckpointAlignDisks(def) < 0 || + qemuBlockNodeNamesDetect(driver, vm, QEMU_ASYNC_JOB_NONE) < 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 (vm->def->disks[i]->src->format > 0 && + 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; +} + + + +/* Struct and hash-iterator callback used when bulk redefining checkpoints */ +struct qemuDomainCheckpointBulk { + virDomainObjPtr vm; + virQEMUDriverPtr driver; + const char *checkpointDir; + unsigned int flags; +}; + +static int +qemuDomainCheckpointBulkRedefine(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainCheckpointObjPtr chk = payload; + struct qemuDomainCheckpointBulk *data = opaque; + + return qemuDomainCheckpointWriteMetadata(data->vm, chk, data->driver->caps, + data->driver->xmlopt, + data->checkpointDir); +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virQEMUDriverPtr driver = domain->conn->privateData; + virDomainObjPtr vm = NULL; + char *xml = NULL; + virDomainCheckpointObjPtr chk = NULL; + virDomainCheckpointPtr checkpoint = NULL; + virDomainCheckpointDefPtr def = NULL; + bool update_current = true; + bool redefine = flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; + unsigned int parse_flags = VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + virDomainCheckpointObjPtr other = NULL; + virQEMUDriverConfigPtr cfg = NULL; + virCapsPtr caps = NULL; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT | + VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA | + VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_LIST, NULL); + /* TODO: VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE */ + + if (redefine) + parse_flags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if ((redefine && !(flags & VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT)) || + (flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) + update_current = false; + + if (!(vm = qemuDomObjFromDomain(domain))) + goto cleanup; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointCreateXMLEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (!(caps = virQEMUDriverGetCapabilities(driver, false))) + goto cleanup; + + if (qemuProcessAutoDestroyActive(driver, vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is marked for auto destroy")); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot create checkpoint for inactive domain")); + goto cleanup; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_LIST) { + struct qemuDomainCheckpointBulk bulk = { + .vm = vm, + .driver = driver, + .checkpointDir = cfg->checkpointDir, + .flags = flags, + }; + + if (virDomainCheckpointObjListParse(xmlDesc, vm->def->uuid, + vm->checkpoints, + &vm->current_checkpoint, + caps, driver->xmlopt, + parse_flags) < 0) + goto cleanup; + /* Validate and save the checkpoints to disk. Since we don't get + * here unless there were no checkpoints beforehand, just delete + * everything if anything failed, ignoring further errors. */ + if (virDomainCheckpointForEach(vm->checkpoints, + qemuDomainCheckpointBulkRedefine, + &bulk) < 0) { + virErrorPtr orig_err = virSaveLastError(); + + qemuDomainCheckpointDiscardAllMetadata(driver, vm); + virSetError(orig_err); + virFreeError(orig_err); + goto cleanup; + } + /* Return is arbitrary, so use the first root */ + chk = virDomainCheckpointFindByName(vm->checkpoints, NULL); + checkpoint = virGetDomainCheckpoint(domain, + chk->first_child->def->name); + goto cleanup; + } + + if (!(def = qemuDomainCheckpointDefParseString(driver, caps, xmlDesc, + parse_flags))) + goto cleanup; + + /* We are going to modify the domain below. */ + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (redefine) { + if (virDomainCheckpointRedefinePrep(domain, vm, &def, &chk, + driver->xmlopt, + &update_current) < 0) + goto endjob; + } else if (qemuDomainCheckpointPrepare(driver, caps, vm, def) < 0) { + goto endjob; + } + + if (!chk) { + if (!(chk = virDomainCheckpointAssignDef(vm->checkpoints, def))) + goto endjob; + + def = NULL; + } + + if (update_current) + chk->def->current = true; + if (vm->current_checkpoint) { + if (!redefine && + VIR_STRDUP(chk->def->parent, vm->current_checkpoint->def->name) < 0) + goto endjob; + if (update_current) { + vm->current_checkpoint->def->current = false; + if (qemuDomainCheckpointWriteMetadata(vm, vm->current_checkpoint, + driver->caps, driver->xmlopt, + cfg->checkpointDir) < 0) + goto endjob; + vm->current_checkpoint = NULL; + } + } + + /* actually do the checkpoint */ + if (redefine) { + /* 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? */ + } else { + /* TODO: issue QMP transaction command */ + } + + /* 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: + if (checkpoint && !(flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) { + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + /* if writing of metadata fails, error out rather than trying + * to silently carry on without completing the checkpoint */ + virObjectUnref(checkpoint); + checkpoint = NULL; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for checkpoint %s"), + chk->def->name); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + } else { + if (update_current) + vm->current_checkpoint = chk; + other = virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent); + chk->parent = other; + other->nchildren++; + chk->sibling = other->first_child; + other->first_child = chk; + } + } else if (chk) { + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + } + + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + virDomainCheckpointDefFree(def); + VIR_FREE(xml); + virObjectUnref(caps); + virObjectUnref(cfg); + return checkpoint; +} + + +static int +qemuDomainListCheckpoints(virDomainPtr domain, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int n = -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_ROOTS | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm = qemuDomObjFromDomain(domain))) + return -1; + + if (virDomainListCheckpointsEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + n = virDomainListAllCheckpoints(vm->checkpoints, NULL, domain, chks, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static int +qemuDomainCheckpointListChildren(virDomainCheckpointPtr checkpoint, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + virDomainCheckpointObjPtr chk = NULL; + int n = -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointListChildrenEnsureACL(checkpoint->domain->conn, vm->def) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + n = virDomainListAllCheckpoints(vm->checkpoints, chk, checkpoint->domain, chks, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointObjPtr chk = NULL; + virDomainCheckpointPtr checkpoint = NULL; + + virCheckFlags(0, NULL); + + if (!(vm = qemuDomObjFromDomain(domain))) + return NULL; + + if (virDomainCheckpointLookupByNameEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromName(vm, name))) + goto cleanup; + + checkpoint = virGetDomainCheckpoint(domain, chk->def->name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static int +qemuDomainHasCurrentCheckpoint(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm; + int ret = -1; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromDomain(domain))) + return -1; + + if (virDomainHasCurrentCheckpointEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = (vm->current_checkpoint != NULL); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointGetParent(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointObjPtr chk = NULL; + virDomainCheckpointPtr parent = NULL; + + virCheckFlags(0, NULL); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetParentEnsureACL(checkpoint->domain->conn, vm->def) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + if (!chk->def->parent) { + virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT, + _("checkpoint '%s' does not have a parent"), + chk->def->name); + goto cleanup; + } + + parent = virGetDomainCheckpoint(checkpoint->domain, chk->def->parent); + + cleanup: + virDomainObjEndAPI(&vm); + return parent; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointCurrent(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointPtr checkpoint = NULL; + + virCheckFlags(0, NULL); + + if (!(vm = qemuDomObjFromDomain(domain))) + return NULL; + + if (virDomainCheckpointCurrentEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (!vm->current_checkpoint) { + virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT, "%s", + _("the domain does not have a current checkpoint")); + goto cleanup; + } + + checkpoint = virGetDomainCheckpoint(domain, vm->current_checkpoint->def->name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static char * +qemuDomainCheckpointGetXMLDesc(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver = checkpoint->domain->conn->privateData; + virDomainObjPtr vm = NULL; + char *xml = NULL; + virDomainCheckpointObjPtr chk = NULL; + qemuDomainObjPrivatePtr priv; + int rc; + size_t i; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_XML_SECURE | + VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN | + VIR_DOMAIN_CHECKPOINT_XML_SIZE, NULL); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetXMLDescEnsureACL(checkpoint->domain->conn, vm->def, flags) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) { + /* TODO: for non-current checkpoint, this requires a QMP sequence per + disk, since the stat of one bitmap in isolation is too low, + and merely adding bitmap sizes may be too high: + block-dirty-bitmap-create tmp + for each bitmap from checkpoint to current: + add bitmap to src_list + block-dirty-bitmap-merge dst=tmp src_list + query-block and read tmp size + block-dirty-bitmap-remove tmp + So for now, go with simpler query-blocks only for current. + */ + if (!vm->current_checkpoint || + STRNEQ(checkpoint->name, vm->current_checkpoint->def->name)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("cannot compute size for non-current checkpoint '%s'"), + checkpoint->name); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + if (virDomainObjCheckActive(vm) < 0) + goto endjob; + + if (qemuBlockNodeNamesDetect(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) + goto endjob; + + /* TODO: Shouldn't need to recompute node names. */ + for (i = 0; i < chk->def->ndisks; i++) { + virDomainCheckpointDiskDef *disk = &chk->def->disks[i]; + + if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + VIR_FREE(chk->def->dom->disks[disk->idx]->src->nodeformat); + if (VIR_STRDUP(chk->def->dom->disks[disk->idx]->src->nodeformat, + qemuBlockNodeLookup(vm, disk->name)) < 0) + goto endjob; + } + + priv = vm->privateData; + qemuDomainObjEnterMonitor(driver, vm); + rc = qemuMonitorUpdateCheckpointSize(priv->mon, chk->def); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + goto endjob; + if (rc < 0) + goto endjob; + } + + xml = virDomainCheckpointDefFormat(chk->def, driver->caps, driver->xmlopt, + flags); + + endjob: + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + return xml; +} + + +static int +qemuDomainCheckpointIsCurrent(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainCheckpointObjPtr chk = NULL; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointIsCurrentEnsureACL(checkpoint->domain->conn, vm->def) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + ret = (vm->current_checkpoint && + STREQ(checkpoint->name, vm->current_checkpoint->def->name)); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static int +qemuDomainCheckpointHasMetadata(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainCheckpointObjPtr chk = NULL; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointHasMetadataEnsureACL(checkpoint->domain->conn, vm->def) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + /* XXX Someday, we should recognize internal bitmaps in qcow2 + * images that are not tied to a libvirt checkpoint; if we ever do + * that, then we would have a reason to return 0 here. */ + ret = 1; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +typedef struct _virQEMUCheckReparent virQEMUCheckReparent; +typedef virQEMUCheckReparent *virQEMUCheckReparentPtr; +struct _virQEMUCheckReparent { + virQEMUDriverConfigPtr cfg; + virDomainCheckpointObjPtr parent; + virDomainObjPtr vm; + virCapsPtr caps; + virDomainXMLOptionPtr xmlopt; + int err; + virDomainCheckpointObjPtr last; +}; + + +static int +qemuDomainCheckpointReparentChildren(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainCheckpointObjPtr chk = payload; + virQEMUCheckReparentPtr rep = data; + + if (rep->err < 0) + return 0; + + VIR_FREE(chk->def->parent); + chk->parent = rep->parent; + + if (rep->parent->def && + VIR_STRDUP(chk->def->parent, rep->parent->def->name) < 0) { + rep->err = -1; + return 0; + } + + if (!chk->sibling) + rep->last = chk; + + rep->err = qemuDomainCheckpointWriteMetadata(rep->vm, chk, + rep->caps, rep->xmlopt, + rep->cfg->checkpointDir); + return 0; +} + + +static int +qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver = checkpoint->domain->conn->privateData; + virDomainObjPtr vm = NULL; + int ret = -1; + virDomainCheckpointObjPtr chk = NULL; + virQEMUDependentRemove rem; + virQEMUCheckReparent rep; + bool metadata_only = !!(flags & VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY); + virQEMUDriverConfigPtr cfg = NULL; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN | + VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY | + VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY, -1); + + if (!(vm = qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointDeleteEnsureACL(checkpoint->domain->conn, vm->def) < 0) + goto cleanup; + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (!(chk = qemuCheckObjFromCheckpoint(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 = false; + virDomainCheckpointForEachDescendant(chk, + qemuDomainCheckpointDiscardAll, + &rem); + if (rem.err < 0) + goto endjob; + if (rem.current) { + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + chk->def->current = true; + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to set checkpoint '%s' as current"), + chk->def->name); + chk->def->current = false; + goto endjob; + } + } + vm->current_checkpoint = chk; + } + } else if (chk->nchildren) { + rep.cfg = cfg; + rep.parent = chk->parent; + rep.vm = vm; + rep.err = 0; + rep.last = NULL; + rep.caps = driver->caps; + rep.xmlopt = driver->xmlopt; + virDomainCheckpointForEachChild(chk, + qemuDomainCheckpointReparentChildren, + &rep); + if (rep.err < 0) + goto endjob; + /* Can't modify siblings during ForEachChild, so do it now. */ + chk->parent->nchildren += chk->nchildren; + rep.last->sibling = chk->parent->first_child; + chk->parent->first_child = chk->first_child; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + chk->nchildren = 0; + chk->first_child = NULL; + ret = 0; + } else { + ret = qemuDomainCheckpointDiscard(driver, vm, chk, true, metadata_only); + } + + endjob: + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + virObjectUnref(cfg); + return ret; +} + static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *cmd, char **result, unsigned int flags) { @@ -17101,6 +18022,16 @@ static virDomainPtr qemuDomainQemuAttach(virConnectPtr conn, goto cleanup; } + if (qemuProcessAttach(conn, driver, vm, pid, + pidfile, monConfig, monJSON) < 0) { + monConfig = NULL; + qemuDomainRemoveInactive(driver, vm); + qemuDomainObjEndJob(driver, vm); + goto cleanup; + } + + monConfig = NULL; + if (qemuProcessAttach(conn, driver, vm, pid, pidfile, monConfig, monJSON) < 0) { qemuDomainRemoveInactive(driver, vm); @@ -21908,6 +22839,12 @@ static int qemuDomainRename(virDomainPtr dom, goto endjob; } + if (virDomainListAllCheckpoints(vm->checkpoints, NULL, dom, NULL, flags) > 0) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cannot rename domain with checkpoints")); + goto endjob; + } + if (virDomainObjListRename(driver->domains, vm, new_name, flags, qemuDomainRenameCallback, driver) < 0) goto endjob; @@ -22728,6 +23665,18 @@ static virHypervisorDriver qemuHypervisorDriver = { .connectBaselineHypervisorCPU = qemuConnectBaselineHypervisorCPU, /* 4.4.0 */ .nodeGetSEVInfo = qemuNodeGetSEVInfo, /* 4.5.0 */ .domainGetLaunchSecurityInfo = qemuDomainGetLaunchSecurityInfo, /* 4.5.0 */ + .domainCheckpointCreateXML = qemuDomainCheckpointCreateXML, /* 5.2.0 */ + .domainCheckpointGetXMLDesc = qemuDomainCheckpointGetXMLDesc, /* 5.2.0 */ + + .domainListCheckpoints = qemuDomainListCheckpoints, /* 5.2.0 */ + .domainCheckpointListChildren = qemuDomainCheckpointListChildren, /* 5.2.0 */ + .domainCheckpointLookupByName = qemuDomainCheckpointLookupByName, /* 5.2.0 */ + .domainHasCurrentCheckpoint = qemuDomainHasCurrentCheckpoint, /* 5.2.0 */ + .domainCheckpointGetParent = qemuDomainCheckpointGetParent, /* 5.2.0 */ + .domainCheckpointCurrent = qemuDomainCheckpointCurrent, /* 5.2.0 */ + .domainCheckpointIsCurrent = qemuDomainCheckpointIsCurrent, /* 5.2.0 */ + .domainCheckpointHasMetadata = qemuDomainCheckpointHasMetadata, /* 5.2.0 */ + .domainCheckpointDelete = qemuDomainCheckpointDelete, /* 5.2.0 */ }; -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list