From nobody Mon Feb 9 10:25:54 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1551937730001206.90701753364522; Wed, 6 Mar 2019 21:48:50 -0800 (PST) Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 535E7318A60D; Thu, 7 Mar 2019 05:48:48 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 295581001E69; Thu, 7 Mar 2019 05:48:48 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id D4D0E181A268; Thu, 7 Mar 2019 05:48:47 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x275mjZt004086 for ; Thu, 7 Mar 2019 00:48:45 -0500 Received: by smtp.corp.redhat.com (Postfix) id B412E5D791; Thu, 7 Mar 2019 05:48:45 +0000 (UTC) Received: from blue.redhat.com (ovpn-118-35.phx2.redhat.com [10.3.118.35]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8D2775D783; Thu, 7 Mar 2019 05:48:44 +0000 (UTC) From: Eric Blake To: libvir-list@redhat.com Date: Wed, 6 Mar 2019 23:47:47 -0600 Message-Id: <20190307054752.19522-16-eblake@redhat.com> In-Reply-To: <20190307054752.19522-1-eblake@redhat.com> References: <20190307054752.19522-1-eblake@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-loop: libvir-list@redhat.com Cc: amureini@redhat.com, derez@redhat.com, vsementsov@virtuozzo.com, zhanhouliang@outlook.com, bharadwaj.rayala@rubrik.com, ydary@redhat.com, nsoffer@redhat.com, jsnow@redhat.com, suman.swaroop@rubrik.com Subject: [libvirt] [PATCH v5 15/20] backup: qemu: Implement metadata tracking for checkpoint APIs X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.41]); Thu, 07 Mar 2019 05:48:48 +0000 (UTC) Content-Type: text/plain; charset="utf-8" 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 --- 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(virDomainDiskDefP= tr disk) return ret; } + +const char * +qemuBlockNodeLookup(virDomainObjPtr vm, const char *disk) +{ + size_t i; + + for (i =3D 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 priv= ileged) 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 priv= ileged) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/qemu/snapshot", cfg->config= BaseDir) < 0) goto error; + if (virAsprintf(&cfg->checkpointDir, "%s/qemu/checkpoint", cfg->co= nfigBaseDir) < 0) + goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/qemu/dump", cfg->configBas= eDir) < 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 driv= er, virQEMUCapsPtr qemuCaps =3D NULL; virDomainDefFormatData data =3D { 0 }; bool snapshots =3D flags & VIR_DOMAIN_XML_SNAPSHOTS; + bool checkpoints =3D 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 =3D virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -7906,6 +7907,15 @@ qemuDomainDefFormatBufInternal(virQEMUDriverPtr driv= er, data.snapshots =3D vm->snapshots; data.current_snapshot =3D vm->current_snapshot; } + if (checkpoints) { + if (!vm) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("checkpoints XML requested but not provided")= ); + goto cleanup; + } + data.checkpoints =3D vm->checkpoints; + data.current_check =3D vm->current_checkpoint; + } ret =3D virDomainDefFormatInternal(buf, def, &data, virDomainDefFormatConvertXMLFlags(fla= gs), 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 =3D NULL; + int ret =3D -1; + char *chkDir =3D NULL; + char *chkFile =3D NULL; + + newxml =3D virDomainCheckpointDefFormat( + checkpoint->def, caps, xmlopt, + virDomainDefFormatConvertXMLFlags(QEMU_DOMAIN_FORMAT_LIVE_FLAGS) | + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL); + if (newxml =3D=3D 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 =3D 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 =3D payload; - virQEMUSnapRemovePtr curr =3D data; + virQEMUDependentRemovePtr curr =3D data; int err; if (snap->def->current) @@ -8631,7 +8680,7 @@ int qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver, virDomainObjPtr vm) { - virQEMUSnapRemove rem; + virQEMUDependentRemove rem; rem.driver =3D driver; rem.vm =3D vm; @@ -8648,12 +8697,122 @@ qemuDomainSnapshotDiscardAllMetadata(virQEMUDriver= Ptr driver, } +/* Discard one checkpoint (or its metadata), without reparenting any child= ren. */ +int +qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainCheckpointObjPtr chk, + bool update_parent, + bool metadata_only) +{ + char *chkFile =3D NULL; + int ret =3D -1; + virDomainCheckpointObjPtr parentchk =3D NULL; + virQEMUDriverConfigPtr cfg =3D virQEMUDriverGetConfig(driver); + + if (!metadata_only) { + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot remove checkpoint from inactive domai= n")); + goto cleanup; + } else { + /* TODO: Implement QMP sequence to merge bitmaps */ + // qemuDomainObjPrivatePtr priv; + // priv =3D 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 =3D=3D vm->current_checkpoint) { + if (update_parent && chk->def->parent) { + parentchk =3D virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent); + if (!parentchk) { + VIR_WARN("missing parent checkpoint matching name '%s'", + chk->def->parent); + } else { + parentchk->def->current =3D true; + if (qemuDomainCheckpointWriteMetadata(vm, parentchk, drive= r->caps, + driver->xmlopt, + cfg->checkpointDir) = < 0) { + VIR_WARN("failed to set parent checkpoint '%s' as curr= ent", + chk->def->parent); + parentchk->def->current =3D false; + parentchk =3D NULL; + } + } + } + vm->current_checkpoint =3D parentchk; + } + + if (unlink(chkFile) < 0) + VIR_WARN("Failed to unlink %s", chkFile); + if (update_parent) + virDomainCheckpointDropParent(chk); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + + ret =3D 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 =3D payload; + virQEMUDependentRemovePtr curr =3D data; + int err; + + if (chk->def->current) + curr->current =3D true; + err =3D qemuDomainCheckpointDiscard(curr->driver, curr->vm, chk, false, + curr->metadata_only); + if (err && !curr->err) + curr->err =3D err; + return 0; +} + + +int +qemuDomainCheckpointDiscardAllMetadata(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + virQEMUDependentRemove rem; + + rem.driver =3D driver; + rem.vm =3D vm; + rem.metadata_only =3D true; + rem.err =3D 0; + virDomainCheckpointForEach(vm->checkpoints, qemuDomainCheckpointDiscar= dAll, + &rem); + if (rem.current) + vm->current_checkpoint =3D NULL; + if (virDomainCheckpointUpdateRelations(vm->checkpoints) < 0 && !rem.er= r) + rem.err =3D -1; + + return rem.err; +} + + static void qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driver, virDomainObjPtr vm) { virQEMUDriverConfigPtr cfg; VIR_AUTOFREE(char *) snapDir =3D NULL; + VIR_AUTOFREE(char *) chkDir =3D NULL; cfg =3D virQEMUDriverGetConfig(driver); @@ -8668,6 +8827,17 @@ qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driv= er, } else if (rmdir(snapDir) < 0 && errno !=3D 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 !=3D 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 =3D NULL; + chk =3D 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 =3D (char *)data; + char *chkDir =3D NULL; + DIR *dir =3D NULL; + struct dirent *entry; + char *xmlStr; + char *fullpath; + virDomainCheckpointDefPtr def =3D NULL; + virDomainCheckpointObjPtr chk =3D NULL; + virDomainCheckpointObjPtr current =3D NULL; + unsigned int flags =3D (VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_PARSE_DISKS | + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL); + int ret =3D -1; + virCapsPtr caps =3D 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 =3D virQEMUDriverGetCapabilities(qemu_driver, false))) + goto cleanup; + + VIR_INFO("Scanning for checkpoints for domain %s in %s", vm->def->name, + chkDir); + + if (virDirOpenIfExists(&dir, chkDir) <=3D 0) + goto cleanup; + + while ((direrr =3D 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 =3D 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 =3D virDomainCheckpointAssignDef(vm->checkpoints, def); + if (chk =3D=3D NULL) { + virDomainCheckpointDefFree(def); + } else if (chk->def->current) { + current =3D chk; + if (!vm->current_checkpoint) + vm->current_checkpoint =3D 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 !=3D current) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Too many checkpoints claiming to be current for = domain %s"), + vm->def->name); + vm->current_checkpoint =3D NULL; + } + + if (virDomainCheckpointUpdateRelations(vm->checkpoints) < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Checkpoints have inconsistent relations for doma= in %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 relations which we find + * important in our metadata. + */ + + virResetLastError(); + + ret =3D 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 =3D 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, N= ULL); if (!(vm =3D 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 =3D qemuDomainManagedSavePath(driver, vm); if (name =3D=3D NULL) @@ -16887,7 +17059,7 @@ qemuDomainSnapshotDelete(virDomainSnapshotPtr snaps= hot, virDomainObjPtr vm =3D NULL; int ret =3D -1; virDomainSnapshotObjPtr snap =3D NULL; - virQEMUSnapRemove rem; + virQEMUDependentRemove rem; virQEMUSnapReparent rep; bool metadata_only =3D !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_= ONLY); int external =3D 0; @@ -16991,6 +17163,755 @@ qemuDomainSnapshotDelete(virDomainSnapshotPtr sna= pshot, return ret; } + +/* Called prior to job lock */ +static virDomainCheckpointDefPtr +qemuDomainCheckpointDefParseString(virQEMUDriverPtr driver, virCapsPtr cap= s, + const char *xmlDesc, unsigned int flags) +{ + virDomainCheckpointDefPtr def =3D NULL; + virDomainCheckpointDefPtr ret =3D NULL; + unsigned int parse_flags =3D VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + + if (flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE) + parse_flags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if (!(def =3D 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 n= ame */ + 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] =3D=3D '.') { + 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 =3D -1; + size_t i; + char *xml =3D NULL; + qemuDomainObjPrivatePtr priv =3D vm->privateData; + + /* Easiest way to clone inactive portion of vm->def is via + * conversion in and back out of xml. */ + if (!(xml =3D qemuDomainDefFormatLive(driver, vm->def, priv->origCPU, + true, true)) || + !(def->dom =3D virDomainDefParseString(xml, caps, driver->xmlopt, = NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE= | + VIR_DOMAIN_DEF_PARSE_SKIP_VAL= IDATE))) + goto cleanup; + + if (virDomainCheckpointAlignDisks(def) < 0 || + qemuBlockNodeNamesDetect(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) + goto cleanup; + + for (i =3D 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk =3D &def->disks[i]; + + if (disk->type !=3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + + if (vm->def->disks[i]->src->format > 0 && + vm->def->disks[i]->src->format !=3D 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 =3D 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 =3D payload; + struct qemuDomainCheckpointBulk *data =3D 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 =3D domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + char *xml =3D NULL; + virDomainCheckpointObjPtr chk =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + virDomainCheckpointDefPtr def =3D NULL; + bool update_current =3D true; + bool redefine =3D flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; + unsigned int parse_flags =3D VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + virDomainCheckpointObjPtr other =3D NULL; + virQEMUDriverConfigPtr cfg =3D NULL; + virCapsPtr caps =3D 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 |=3D VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if ((redefine && !(flags & VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT)) || + (flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) + update_current =3D false; + + if (!(vm =3D qemuDomObjFromDomain(domain))) + goto cleanup; + + cfg =3D virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointCreateXMLEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (!(caps =3D 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 =3D { + .vm =3D vm, + .driver =3D driver, + .checkpointDir =3D cfg->checkpointDir, + .flags =3D 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 =3D virSaveLastError(); + + qemuDomainCheckpointDiscardAllMetadata(driver, vm); + virSetError(orig_err); + virFreeError(orig_err); + goto cleanup; + } + /* Return is arbitrary, so use the first root */ + chk =3D virDomainCheckpointFindByName(vm->checkpoints, NULL); + checkpoint =3D virGetDomainCheckpoint(domain, + chk->first_child->def->name); + goto cleanup; + } + + if (!(def =3D 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 =3D virDomainCheckpointAssignDef(vm->checkpoints, def))) + goto endjob; + + def =3D NULL; + } + + if (update_current) + chk->def->current =3D 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 =3D false; + if (qemuDomainCheckpointWriteMetadata(vm, vm->current_checkpoi= nt, + driver->caps, driver->xm= lopt, + cfg->checkpointDir) < 0) + goto endjob; + vm->current_checkpoint =3D 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 =3D 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 =3D 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 =3D chk; + other =3D virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent); + chk->parent =3D other; + other->nchildren++; + chk->sibling =3D other->first_child; + other->first_child =3D 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 =3D NULL; + int n =3D -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_ROOTS | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return -1; + + if (virDomainListCheckpointsEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + n =3D virDomainListAllCheckpoints(vm->checkpoints, NULL, domain, chks,= flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static int +qemuDomainCheckpointListChildren(virDomainCheckpointPtr checkpoint, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + virDomainCheckpointObjPtr chk =3D NULL; + int n =3D -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointListChildrenEnsureACL(checkpoint->domain->conn,= vm->def) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + n =3D virDomainListAllCheckpoints(vm->checkpoints, chk, checkpoint->do= main, chks, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointObjPtr chk =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + + virCheckFlags(0, NULL); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return NULL; + + if (virDomainCheckpointLookupByNameEnsureACL(domain->conn, vm->def) < = 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromName(vm, name))) + goto cleanup; + + checkpoint =3D virGetDomainCheckpoint(domain, chk->def->name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static int +qemuDomainHasCurrentCheckpoint(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm; + int ret =3D -1; + + virCheckFlags(0, -1); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return -1; + + if (virDomainHasCurrentCheckpointEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret =3D (vm->current_checkpoint !=3D NULL); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointGetParent(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointObjPtr chk =3D NULL; + virDomainCheckpointPtr parent =3D NULL; + + virCheckFlags(0, NULL); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetParentEnsureACL(checkpoint->domain->conn, vm= ->def) < 0) + goto cleanup; + + if (!(chk =3D 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 =3D virGetDomainCheckpoint(checkpoint->domain, chk->def->parent= ); + + cleanup: + virDomainObjEndAPI(&vm); + return parent; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointCurrent(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointPtr checkpoint =3D NULL; + + virCheckFlags(0, NULL); + + if (!(vm =3D 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 =3D virGetDomainCheckpoint(domain, vm->current_checkpoint->= def->name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static char * +qemuDomainCheckpointGetXMLDesc(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D checkpoint->domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + char *xml =3D NULL; + virDomainCheckpointObjPtr chk =3D 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 =3D qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetXMLDescEnsureACL(checkpoint->domain->conn, v= m->def, flags) < 0) + goto cleanup; + + if (!(chk =3D 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=3Dtmp 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 checkpoi= nt '%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 =3D 0; i < chk->def->ndisks; i++) { + virDomainCheckpointDiskDef *disk =3D &chk->def->disks[i]; + + if (disk->type !=3D 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->nodeforma= t, + qemuBlockNodeLookup(vm, disk->name)) < 0) + goto endjob; + } + + priv =3D vm->privateData; + qemuDomainObjEnterMonitor(driver, vm); + rc =3D qemuMonitorUpdateCheckpointSize(priv->mon, chk->def); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + goto endjob; + if (rc < 0) + goto endjob; + } + + xml =3D virDomainCheckpointDefFormat(chk->def, driver->caps, driver->x= mlopt, + 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 =3D NULL; + int ret =3D -1; + virDomainCheckpointObjPtr chk =3D NULL; + + virCheckFlags(0, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointIsCurrentEnsureACL(checkpoint->domain->conn, vm= ->def) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + ret =3D (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 =3D NULL; + int ret =3D -1; + virDomainCheckpointObjPtr chk =3D NULL; + + virCheckFlags(0, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointHasMetadataEnsureACL(checkpoint->domain->conn, = vm->def) < 0) + goto cleanup; + + if (!(chk =3D 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 =3D 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 =3D payload; + virQEMUCheckReparentPtr rep =3D data; + + if (rep->err < 0) + return 0; + + VIR_FREE(chk->def->parent); + chk->parent =3D rep->parent; + + if (rep->parent->def && + VIR_STRDUP(chk->def->parent, rep->parent->def->name) < 0) { + rep->err =3D -1; + return 0; + } + + if (!chk->sibling) + rep->last =3D chk; + + rep->err =3D qemuDomainCheckpointWriteMetadata(rep->vm, chk, + rep->caps, rep->xmlopt, + rep->cfg->checkpointDir); + return 0; +} + + +static int +qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D checkpoint->domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + int ret =3D -1; + virDomainCheckpointObjPtr chk =3D NULL; + virQEMUDependentRemove rem; + virQEMUCheckReparent rep; + bool metadata_only =3D !!(flags & VIR_DOMAIN_CHECKPOINT_DELETE_METADAT= A_ONLY); + virQEMUDriverConfigPtr cfg =3D NULL; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN | + VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY | + VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + cfg =3D virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointDeleteEnsureACL(checkpoint->domain->conn, vm->d= ef) < 0) + goto cleanup; + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto endjob; + + if (flags & (VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN | + VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)) { + rem.driver =3D driver; + rem.vm =3D vm; + rem.metadata_only =3D metadata_only; + rem.err =3D 0; + rem.current =3D false; + virDomainCheckpointForEachDescendant(chk, + qemuDomainCheckpointDiscardAl= l, + &rem); + if (rem.err < 0) + goto endjob; + if (rem.current) { + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + chk->def->current =3D true; + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->cap= s, + driver->xmlopt, + cfg->checkpointDir) = < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to set checkpoint '%s' as cur= rent"), + chk->def->name); + chk->def->current =3D false; + goto endjob; + } + } + vm->current_checkpoint =3D chk; + } + } else if (chk->nchildren) { + rep.cfg =3D cfg; + rep.parent =3D chk->parent; + rep.vm =3D vm; + rep.err =3D 0; + rep.last =3D NULL; + rep.caps =3D driver->caps; + rep.xmlopt =3D driver->xmlopt; + virDomainCheckpointForEachChild(chk, + qemuDomainCheckpointReparentChildr= en, + &rep); + if (rep.err < 0) + goto endjob; + /* Can't modify siblings during ForEachChild, so do it now. */ + chk->parent->nchildren +=3D chk->nchildren; + rep.last->sibling =3D chk->parent->first_child; + chk->parent->first_child =3D chk->first_child; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + chk->nchildren =3D 0; + chk->first_child =3D NULL; + ret =3D 0; + } else { + ret =3D qemuDomainCheckpointDiscard(driver, vm, chk, true, metadat= a_only); + } + + endjob: + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + virObjectUnref(cfg); + return ret; +} + static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *c= md, char **result, unsigned int flags) { @@ -17101,6 +18022,16 @@ static virDomainPtr qemuDomainQemuAttach(virConnec= tPtr conn, goto cleanup; } + if (qemuProcessAttach(conn, driver, vm, pid, + pidfile, monConfig, monJSON) < 0) { + monConfig =3D NULL; + qemuDomainRemoveInactive(driver, vm); + qemuDomainObjEndJob(driver, vm); + goto cleanup; + } + + monConfig =3D 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, flag= s) > 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 = =3D { .connectBaselineHypervisorCPU =3D qemuConnectBaselineHypervisorCPU, /*= 4.4.0 */ .nodeGetSEVInfo =3D qemuNodeGetSEVInfo, /* 4.5.0 */ .domainGetLaunchSecurityInfo =3D qemuDomainGetLaunchSecurityInfo, /* 4= .5.0 */ + .domainCheckpointCreateXML =3D qemuDomainCheckpointCreateXML, /* 5.2.0= */ + .domainCheckpointGetXMLDesc =3D qemuDomainCheckpointGetXMLDesc, /* 5.2= .0 */ + + .domainListCheckpoints =3D qemuDomainListCheckpoints, /* 5.2.0 */ + .domainCheckpointListChildren =3D qemuDomainCheckpointListChildren, /*= 5.2.0 */ + .domainCheckpointLookupByName =3D qemuDomainCheckpointLookupByName, /*= 5.2.0 */ + .domainHasCurrentCheckpoint =3D qemuDomainHasCurrentCheckpoint, /* 5.2= .0 */ + .domainCheckpointGetParent =3D qemuDomainCheckpointGetParent, /* 5.2.0= */ + .domainCheckpointCurrent =3D qemuDomainCheckpointCurrent, /* 5.2.0 */ + .domainCheckpointIsCurrent =3D qemuDomainCheckpointIsCurrent, /* 5.2.0= */ + .domainCheckpointHasMetadata =3D qemuDomainCheckpointHasMetadata, /* 5= .2.0 */ + .domainCheckpointDelete =3D qemuDomainCheckpointDelete, /* 5.2.0 */ }; --=20 2.20.1 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list