Work in progress - the checkpoint code is not quite passing tests (part of that is figuring out the minimal XML that is still valid as a <domain> element, or just use --no-domain flag). Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- src/conf/checkpoint_conf.h | 171 ++++ src/conf/domain_conf.h | 12 + src/conf/virdomainobjlist.h | 9 +- po/POTFILES | 1 + src/conf/Makefile.inc.am | 2 + src/conf/checkpoint_conf.c | 1240 +++++++++++++++++++++++++++ src/conf/domain_conf.c | 28 +- src/conf/virdomainobjlist.c | 12 +- src/libvirt_private.syms | 24 + tests/Makefile.am | 9 +- tests/domaincheckpointxml2xmltest.c | 233 +++++ 11 files changed, 1734 insertions(+), 7 deletions(-) create mode 100644 src/conf/checkpoint_conf.h create mode 100644 src/conf/checkpoint_conf.c create mode 100644 tests/domaincheckpointxml2xmltest.c diff --git a/src/conf/checkpoint_conf.h b/src/conf/checkpoint_conf.h new file mode 100644 index 0000000000..45ed8b0d29 --- /dev/null +++ b/src/conf/checkpoint_conf.h @@ -0,0 +1,171 @@ +/* + * checkpoint_conf.h: domain checkpoint XML processing + * + * Copyright (C) 2006-2019 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * 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/>. + */ + +#ifndef LIBVIRT_CHECKPOINT_CONF_H +# define LIBVIRT_CHECKPOINT_CONF_H + +# include "internal.h" +# include "domain_conf.h" + +/* Items related to checkpoint state */ + +typedef enum { + VIR_DOMAIN_CHECKPOINT_TYPE_DEFAULT = 0, + VIR_DOMAIN_CHECKPOINT_TYPE_NONE, + VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP, + + VIR_DOMAIN_CHECKPOINT_TYPE_LAST +} virDomainCheckpointType; + +/* Stores disk-checkpoint information */ +typedef struct _virDomainCheckpointDiskDef virDomainCheckpointDiskDef; +typedef virDomainCheckpointDiskDef *virDomainCheckpointDiskDefPtr; +struct _virDomainCheckpointDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + int idx; /* index within checkpoint->dom->disks that matches name */ + int type; /* virDomainCheckpointType */ + char *bitmap; /* bitmap name, if type is bitmap */ + unsigned long long size; /* current checkpoint size in bytes */ +}; + +/* Stores the complete checkpoint metadata */ +typedef struct _virDomainCheckpointDef virDomainCheckpointDef; +typedef virDomainCheckpointDef *virDomainCheckpointDefPtr; +struct _virDomainCheckpointDef { + /* Public XML. */ + char *name; + char *description; + char *parent; + long long creationTime; /* in seconds */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainCheckpointDiskDef *disks; + + virDomainDefPtr dom; + + /* Internal use. */ + bool current; /* At most one checkpoint in the list should have this set */ +}; + +struct _virDomainCheckpointObj { + virDomainCheckpointDefPtr def; /* non-NULL except for metaroot */ + + virDomainCheckpointObjPtr parent; /* non-NULL except for metaroot, before + virDomainCheckpointUpdateRelations, or + after virDomainCheckpointDropParent */ + virDomainCheckpointObjPtr sibling; /* NULL if last child of parent */ + size_t nchildren; + virDomainCheckpointObjPtr first_child; /* NULL if no children */ +}; + +virDomainCheckpointObjListPtr virDomainCheckpointObjListNew(void); +void virDomainCheckpointObjListFree(virDomainCheckpointObjListPtr checkpoints); + +typedef enum { + VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE = 1 << 0, + VIR_DOMAIN_CHECKPOINT_PARSE_DISKS = 1 << 1, + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL = 1 << 2, +} virDomainCheckpointParseFlags; + +typedef enum { + VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE = 1 << 0, + VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN = 1 << 1, + VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE = 1 << 2, + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL = 1 << 3, +} virDomainCheckpointFormatFlags; + +unsigned int virDomainCheckpointFormatConvertXMLFlags(unsigned int flags); + +virDomainCheckpointDefPtr virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +virDomainCheckpointDefPtr virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +int virDomainCheckpointObjListParse(const char *xmlStr, + const unsigned char *domain_uuid, + virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr *current_check, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +void virDomainCheckpointDefFree(virDomainCheckpointDefPtr def); +char *virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +int virDomainCheckpointObjListFormat(virBufferPtr buf, + virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr current_check, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +int virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr checkpoint); +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointDefPtr def); + +virDomainCheckpointObjPtr virDomainCheckpointFindByName(virDomainCheckpointObjListPtr checkpoints, + const char *name); +void virDomainCheckpointObjListRemove(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr checkpoint); +int virDomainCheckpointForEach(virDomainCheckpointObjListPtr checkpoints, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachChild(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachDescendant(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data); +int virDomainCheckpointUpdateRelations(virDomainCheckpointObjListPtr checkpoints); +void virDomainCheckpointDropParent(virDomainCheckpointObjPtr checkpoint); + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA \ + (VIR_DOMAIN_CHECKPOINT_LIST_METADATA | \ + VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA) + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES \ + (VIR_DOMAIN_CHECKPOINT_LIST_LEAVES | \ + VIR_DOMAIN_CHECKPOINT_LIST_NO_LEAVES) + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_ALL \ + (VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA | \ + VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) + +int virDomainListAllCheckpoints(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + virDomainPtr dom, + virDomainCheckpointPtr **objs, + unsigned int flags); + +int virDomainCheckpointRedefinePrep(virDomainPtr domain, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *def, + virDomainCheckpointObjPtr *checkpoint, + virDomainXMLOptionPtr xmlopt, + bool *update_current); + +VIR_ENUM_DECL(virDomainCheckpoint); + +#endif /* LIBVIRT_CHECKPOINT_CONF_H */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index da89e94083..c3d5f53bdd 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -119,6 +119,12 @@ typedef virDomainMemballoonDef *virDomainMemballoonDefPtr; typedef struct _virDomainNVRAMDef virDomainNVRAMDef; typedef virDomainNVRAMDef *virDomainNVRAMDefPtr; +typedef struct _virDomainCheckpointObj virDomainCheckpointObj; +typedef virDomainCheckpointObj *virDomainCheckpointObjPtr; + +typedef struct _virDomainCheckpointObjList virDomainCheckpointObjList; +typedef virDomainCheckpointObjList *virDomainCheckpointObjListPtr; + typedef struct _virDomainSnapshotObj virDomainSnapshotObj; typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; @@ -2705,6 +2711,9 @@ struct _virDomainObj { bool hasManagedSave; + virDomainCheckpointObjListPtr checkpoints; + virDomainCheckpointObjPtr current_checkpoint; + void *privateData; void (*privateDataFreeFunc)(void *); @@ -3121,6 +3130,7 @@ typedef enum { VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT = 1 << 7, VIR_DOMAIN_DEF_FORMAT_CLOCK_ADJUST = 1 << 8, VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS = 1 << 9, + VIR_DOMAIN_DEF_FORMAT_CHECKPOINTS = 1 << 10, } virDomainDefFormatFlags; /* Use these flags to skip specific domain ABI consistency checks done @@ -3201,6 +3211,8 @@ typedef struct _virDomainDefFormatData { virCapsPtr caps; virDomainSnapshotObjListPtr snapshots; virDomainSnapshotObjPtr current_snapshot; + virDomainCheckpointObjListPtr checkpoints; + virDomainCheckpointObjPtr current_check; } virDomainDefFormatData; typedef struct _virDomainDefFormatData *virDomainDefFormatDataPtr; diff --git a/src/conf/virdomainobjlist.h b/src/conf/virdomainobjlist.h index c1ffee76ad..32f3dcfcc2 100644 --- a/src/conf/virdomainobjlist.h +++ b/src/conf/virdomainobjlist.h @@ -1,7 +1,7 @@ /* * virdomainobjlist.h: domain objects list utilities * - * Copyright (C) 2006-2015 Red Hat, Inc. + * Copyright (C) 2006-2019 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. * @@ -121,13 +121,18 @@ int virDomainObjListForEach(virDomainObjListPtr doms, (VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT | \ VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) +# define VIR_CONNECT_LIST_DOMAINS_FILTERS_CHECKPOINT \ + (VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT | \ + VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT) + # define VIR_CONNECT_LIST_DOMAINS_FILTERS_ALL \ (VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE | \ VIR_CONNECT_LIST_DOMAINS_FILTERS_PERSISTENT | \ VIR_CONNECT_LIST_DOMAINS_FILTERS_STATE | \ VIR_CONNECT_LIST_DOMAINS_FILTERS_MANAGEDSAVE | \ VIR_CONNECT_LIST_DOMAINS_FILTERS_AUTOSTART | \ - VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT) + VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT | \ + VIR_CONNECT_LIST_DOMAINS_FILTERS_CHECKPOINT) int virDomainObjListCollect(virDomainObjListPtr doms, virConnectPtr conn, diff --git a/po/POTFILES b/po/POTFILES index 88af551664..57c55fb35f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -15,6 +15,7 @@ src/bhyve/bhyve_monitor.c src/bhyve/bhyve_parse_command.c src/bhyve/bhyve_process.c src/conf/capabilities.c +src/conf/checkpoint_conf.c src/conf/cpu_conf.c src/conf/device_conf.c src/conf/domain_addr.c diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index fb2ec0e785..a058045a43 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -10,6 +10,8 @@ NETDEV_CONF_SOURCES = \ DOMAIN_CONF_SOURCES = \ conf/capabilities.c \ conf/capabilities.h \ + conf/checkpoint_conf.c \ + conf/checkpoint_conf.h \ conf/domain_addr.c \ conf/domain_addr.h \ conf/domain_capabilities.c \ diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c new file mode 100644 index 0000000000..e5e4c5e306 --- /dev/null +++ b/src/conf/checkpoint_conf.c @@ -0,0 +1,1240 @@ +/* + * checkpoint_conf.c: domain checkpoint XML processing + * + * Copyright (C) 2006-2019 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> + +#include "internal.h" +#include "virbitmap.h" +#include "virbuffer.h" +#include "count-one-bits.h" +#include "datatypes.h" +#include "domain_conf.h" +#include "virlog.h" +#include "viralloc.h" +#include "netdev_bandwidth_conf.h" +#include "netdev_vport_profile_conf.h" +#include "nwfilter_conf.h" +#include "secret_conf.h" +#include "checkpoint_conf.h" +#include "virstoragefile.h" +#include "viruuid.h" +#include "virfile.h" +#include "virerror.h" +#include "virxml.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_DOMAIN_CHECKPOINT + +VIR_LOG_INIT("conf.checkpoint_conf"); + +VIR_ENUM_IMPL(virDomainCheckpoint, VIR_DOMAIN_CHECKPOINT_TYPE_LAST, + "default", "no", "bitmap"); + +struct _virDomainCheckpointObjList { + /* name string -> virDomainCheckpointObj mapping + * for O(1), lockless lookup-by-name */ + virHashTable *objs; + + virDomainCheckpointObj metaroot; /* Special parent of all root checkpoints */ +}; + +/* Checkpoint Def functions */ +static void +virDomainCheckpointDiskDefClear(virDomainCheckpointDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->bitmap); +} + +void virDomainCheckpointDefFree(virDomainCheckpointDefPtr def) +{ + size_t i; + + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->description); + VIR_FREE(def->parent); + for (i = 0; i < def->ndisks; i++) + virDomainCheckpointDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); + virDomainDefFree(def->dom); + VIR_FREE(def); +} + +static int +virDomainCheckpointDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virDomainCheckpointDiskDefPtr def) +{ + int ret = -1; + char *checkpoint = NULL; + char *bitmap = NULL; + xmlNodePtr saved = ctxt->node; + + ctxt->node = node; + + def->name = virXMLPropString(node, "name"); + if (!def->name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk checkpoint element")); + goto cleanup; + } + + checkpoint = virXMLPropString(node, "checkpoint"); + if (checkpoint) { + def->type = virDomainCheckpointTypeFromString(checkpoint); + if (def->type <= 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown disk checkpoint setting '%s'"), + checkpoint); + goto cleanup; + } + } else { + def->type = VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + } + + bitmap = virXMLPropString(node, "bitmap"); + if (bitmap) { + if (def->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk checkpoint bitmap '%s' requires " + "type='bitmap'"), + bitmap); + goto cleanup; + } + VIR_STEAL_PTR(def->bitmap, bitmap); + } + + ret = 0; + cleanup: + ctxt->node = saved; + + VIR_FREE(checkpoint); + VIR_FREE(bitmap); + if (ret < 0) + virDomainCheckpointDiskDefClear(def); + return ret; +} + +/* flags is bitwise-or of virDomainCheckpointParseFlags. + * If flags does not include VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE, then + * caps are ignored. + */ +static virDomainCheckpointDefPtr +virDomainCheckpointDefParse(xmlXPathContextPtr ctxt, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainCheckpointDefPtr def = NULL; + virDomainCheckpointDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n; + char *creation = NULL; + struct timeval tv; + int active; + char *tmp; + + if (VIR_ALLOC(def) < 0) + goto cleanup; + + gettimeofday(&tv, NULL); + + def->name = virXPathString("string(./name)", ctxt); + if (def->name == NULL) { + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("a redefined checkpoint must have a name")); + goto cleanup; + } + if (virAsprintf(&def->name, "%lld", (long long)tv.tv_sec) < 0) + goto cleanup; + } + + def->description = virXPathString("string(./description)", ctxt); + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) { + if (virXPathLongLong("string(./creationTime)", ctxt, + &def->creationTime) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing creationTime from existing checkpoint")); + goto cleanup; + } + + def->parent = virXPathString("string(./parent/name)", ctxt); + + if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { + int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + xmlNodePtr domainNode = virXPathNode("./domain", ctxt); + + VIR_FREE(tmp); + if (!domainNode) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + def->dom = virDomainDefParseNode(ctxt->node->doc, domainNode, + caps, xmlopt, NULL, domainflags); + if (!def->dom) + goto cleanup; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint redefine")); + goto cleanup; + } + } else { + def->creationTime = tv.tv_sec; + } + + if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_DISKS) { + if (n && VIR_ALLOC_N(def->disks, n) < 0) + goto cleanup; + def->ndisks = n; + for (i = 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefParseXML(nodes[i], ctxt, + &def->disks[i]) < 0) + goto cleanup; + } + VIR_FREE(nodes); + } else if (n) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("unable to handle disk requests in checkpoint")); + goto cleanup; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL) { + if (virXPathInt("string(./active)", ctxt, &active) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find 'active' element")); + goto cleanup; + } + def->current = active != 0; + } + + VIR_STEAL_PTR(ret, def); + + cleanup: + VIR_FREE(creation); + VIR_FREE(nodes); + virDomainCheckpointDefFree(def); + + return ret; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + xmlXPathContextPtr ctxt = NULL; + virDomainCheckpointDefPtr def = NULL; + + if (!virXMLNodeNameEqual(root, "domaincheckpoint")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domaincheckpoint")); + goto cleanup; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node = root; + def = virDomainCheckpointDefParse(ctxt, caps, xmlopt, flags); + cleanup: + xmlXPathFreeContext(ctxt); + return def; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainCheckpointDefPtr ret = NULL; + xmlDocPtr xml; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + if ((xml = virXMLParse(NULL, xmlStr, _("(domain_checkpoint)")))) { + xmlKeepBlanksDefault(keepBlanksDefault); + ret = virDomainCheckpointDefParseNode(xml, xmlDocGetRootElement(xml), + caps, xmlopt, flags); + xmlFreeDoc(xml); + } + xmlKeepBlanksDefault(keepBlanksDefault); + + return ret; +} + +int +virDomainCheckpointObjListParse(const char *xmlStr, + const unsigned char *domain_uuid, + virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr *current_check, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + int ret = -1; + xmlDocPtr xml; + xmlNodePtr root; + xmlXPathContextPtr ctxt = NULL; + char *current = NULL; + int n; + size_t i; + VIR_AUTOFREE(xmlNodePtr *) nodes = NULL; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + if (!(flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) || + (flags & VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("incorrect flags for bulk parse")); + return -1; + } + if (checkpoints->metaroot.nchildren || *current_check) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("bulk define of checkoints only possible with " + "no existing checkpoint")); + return -1; + } + + if (!(xml = virXMLParse(NULL, xmlStr, _("(domain_checkpoint)")))) + goto cleanup; + + root = xmlDocGetRootElement(xml); + if (!virXMLNodeNameEqual(root, "checkpoints")) { + virReportError(VIR_ERR_XML_ERROR, + _("unexpected root element <%s>, " + "expecting <checkpoints>"), root->name); + goto cleanup; + } + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(); + goto cleanup; + } + ctxt->node = root; + current = virXMLPropString(root, "current"); + + if ((n = virXPathNodeSet("./domaincheckpoint", ctxt, &nodes)) < 0) + goto cleanup; + if (!n) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("expected at least one <domaincheckpoint> child")); + goto cleanup; + } + + for (i = 0; i < n; i++) { + virDomainCheckpointDefPtr def; + virDomainCheckpointObjPtr chk; + + def = virDomainCheckpointDefParseNode(xml, nodes[i], caps, xmlopt, + flags); + if (!def) + goto cleanup; + if (!(chk = virDomainCheckpointAssignDef(checkpoints, def))) { + virDomainCheckpointDefFree(def); + goto cleanup; + } + /* Sanity checking, similar to virDomainCheckpointRedefinePrep */ + if (def->dom) { + if (memcmp(def->dom->uuid, domain_uuid, VIR_UUID_BUFLEN)) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(domain_uuid, uuidstr); + virReportError(VIR_ERR_INVALID_ARG, + _("definition for checkpoint %s must use " + "uuid %s"), def->name, uuidstr); + goto cleanup; + } + if (virDomainCheckpointAlignDisks(def) < 0) + goto cleanup; + } + } + + if (virDomainCheckpointUpdateRelations(checkpoints) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("<checkpoints> contains inconsistent parent-child " + "relationships")); + goto cleanup; + } + + if (current) { + if (!(*current_check = virDomainCheckpointFindByName(checkpoints, + current))) { + virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT, + _("no checkpoint matching current='%s'"), current); + goto cleanup; + } + (*current_check)->def->current = true; + } + + ret = 0; + cleanup: + if (ret < 0) { + /* There were no checkpoints before this call; so on error, just + * blindly delete anything created before the failure. */ + virHashRemoveAll(checkpoints->objs); + checkpoints->metaroot.nchildren = 0; + checkpoints->metaroot.first_child = NULL; + } + VIR_FREE(current); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + xmlKeepBlanksDefault(keepBlanksDefault); + return ret; +} + + +/** + * virDomainCheckpointDefAssignBitmapNames: + * @def: checkpoint def object + * + * Generate default bitmap names for checkpoint targets. Returns 0 on + * success, -1 on error. + */ +static int +virDomainCheckpointDefAssignBitmapNames(virDomainCheckpointDefPtr def) +{ + size_t i; + + for (i = 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk = &def->disks[i]; + + if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP || + disk->bitmap) + continue; + + if (VIR_STRDUP(disk->bitmap, def->name) < 0) + return -1; + } + + return 0; +} + + +static int +virDomainCheckpointCompareDiskIndex(const void *a, const void *b) +{ + const virDomainCheckpointDiskDef *diska = a; + const virDomainCheckpointDiskDef *diskb = b; + + /* Integer overflow shouldn't be a problem here. */ + return diska->idx - diskb->idx; +} + +/* Align def->disks to def->domain. Sort the list of def->disks, + * filling in any missing disks with appropriate default. Convert + * paths to disk targets for uniformity. Issue an error and return -1 + * if any def->disks[n]->name appears more than once or does not map + * to dom->disks. */ +int +virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr def) +{ + int ret = -1; + virBitmapPtr map = NULL; + size_t i; + int ndisks; + int checkpoint_default = VIR_DOMAIN_CHECKPOINT_TYPE_NONE; + + if (!def->dom) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + + if (def->ndisks > def->dom->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("too many disk checkpoint requests for domain")); + goto cleanup; + } + + /* Unlikely to have a guest without disks but technically possible. */ + if (!def->dom->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("domain must have at least one disk to perform " + "checkpoints")); + goto cleanup; + } + + /* If <disks> omitted, do bitmap on all disks; otherwise, do nothing + * for omitted disks */ + if (!def->ndisks) + checkpoint_default = VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + + if (!(map = virBitmapNew(def->dom->ndisks))) + goto cleanup; + + /* Double check requested disks. */ + for (i = 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk = &def->disks[i]; + int idx = virDomainDiskIndexByName(def->dom, disk->name, false); + + if (idx < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no disk named '%s'"), disk->name); + goto cleanup; + } + + if (virBitmapIsBitSet(map, idx)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' specified twice"), + disk->name); + goto cleanup; + } + ignore_value(virBitmapSetBit(map, idx)); + disk->idx = idx; + + if (STRNEQ(disk->name, def->dom->disks[idx]->dst)) { + VIR_FREE(disk->name); + if (VIR_STRDUP(disk->name, def->dom->disks[idx]->dst) < 0) + goto cleanup; + } + } + + /* Provide defaults for all remaining disks. */ + ndisks = def->ndisks; + if (VIR_EXPAND_N(def->disks, def->ndisks, + def->dom->ndisks - def->ndisks) < 0) + goto cleanup; + + for (i = 0; i < def->dom->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk; + + if (virBitmapIsBitSet(map, i)) + continue; + disk = &def->disks[ndisks++]; + if (VIR_STRDUP(disk->name, def->dom->disks[i]->dst) < 0) + goto cleanup; + disk->idx = i; + + /* Don't checkpoint empty drives */ + if (virStorageSourceIsEmpty(def->dom->disks[i]->src)) + disk->type = VIR_DOMAIN_CHECKPOINT_TYPE_NONE; + else + disk->type = checkpoint_default; + } + + qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]), + virDomainCheckpointCompareDiskIndex); + + /* Generate default bitmap names for checkpoint */ + if (virDomainCheckpointDefAssignBitmapNames(def) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBitmapFree(map); + return ret; +} + + +/* Converts public VIR_DOMAIN_CHECKPOINT_XML_* into + * VIR_DOMAIN_CHECKPOINT_FORMAT_* flags, and silently ignores any other + * flags. */ +unsigned int virDomainCheckpointFormatConvertXMLFlags(unsigned int flags) +{ + unsigned int formatFlags = 0; + + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SECURE) + formatFlags |= VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE; + if (flags & VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN) + formatFlags |= VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN; + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + formatFlags |= VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE; + + return formatFlags; +} + + +static int +virDomainCheckpointDiskDefFormat(virBufferPtr buf, + virDomainCheckpointDiskDefPtr disk, + unsigned int flags) +{ + if (!disk->name) + return 0; + + virBufferEscapeString(buf, "<disk name='%s'", disk->name); + if (disk->type) + virBufferAsprintf(buf, " checkpoint='%s'", + virDomainCheckpointTypeToString(disk->type)); + if (disk->bitmap) { + virBufferEscapeString(buf, " bitmap='%s'", disk->bitmap); + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + virBufferAsprintf(buf, " size='%llu'", disk->size); + } + virBufferAddLit(buf, "/>\n"); + return 0; +} + + +static int +virDomainCheckpointDefFormatInternal(virBufferPtr buf, + virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + size_t i; + unsigned int domainflags = VIR_DOMAIN_DEF_FORMAT_INACTIVE; + virDomainDefFormatData data = { + .caps = caps, + }; + + if (flags & VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE) + domainflags |= VIR_DOMAIN_DEF_FORMAT_SECURE; + + virBufferAddLit(buf, "<domaincheckpoint>\n"); + virBufferAdjustIndent(buf, 2); + + virBufferEscapeString(buf, "<name>%s</name>\n", def->name); + if (def->description) + virBufferEscapeString(buf, "<description>%s</description>\n", + def->description); + + if (def->parent) { + virBufferAddLit(buf, "<parent>\n"); + virBufferAdjustIndent(buf, 2); + virBufferEscapeString(buf, "<name>%s</name>\n", def->parent); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</parent>\n"); + } + + virBufferAsprintf(buf, "<creationTime>%lld</creationTime>\n", + def->creationTime); + + if (def->ndisks) { + virBufferAddLit(buf, "<disks>\n"); + virBufferAdjustIndent(buf, 2); + for (i = 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefFormat(buf, &def->disks[i], + flags) < 0) + goto error; + } + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</disks>\n"); + } + + if (!(flags & VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN) && + virDomainDefFormatInternal(buf, def->dom, &data, domainflags, + xmlopt) < 0) + goto error; + + if (flags & VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL) + virBufferAsprintf(buf, "<active>%d</active>\n", def->current); + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</domaincheckpoint>\n"); + + if (virBufferCheckError(buf) < 0) + goto error; + + return 0; + + error: + virBufferFreeAndReset(buf); + return -1; +} + +char * +virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE | + VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN | + VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE | + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL, NULL); + if (virDomainCheckpointDefFormatInternal(&buf, def, caps, xmlopt, + flags) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +} + +struct virDomainCheckpointFormatData { + virBufferPtr buf; + virCapsPtr caps; + virDomainXMLOptionPtr xmlopt; + unsigned int flags; +}; + +static int +virDomainCheckpointFormatOne(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainCheckpointObjPtr chk = payload; + struct virDomainCheckpointFormatData *data = opaque; + + return virDomainCheckpointDefFormatInternal(data->buf, chk->def, data->caps, + data->xmlopt, data->flags); +} + + +int +virDomainCheckpointObjListFormat(virBufferPtr buf, + virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr current_check, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + struct virDomainCheckpointFormatData data = { + .buf = buf, + .caps = caps, + .xmlopt = xmlopt, + .flags = flags, + }; + + virBufferAddLit(buf, "<checkpoints"); + if (current_check) + virBufferEscapeString(buf, " current='%s'", + current_check->def->name); + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + if (virDomainCheckpointForEach(checkpoints, virDomainCheckpointFormatOne, + &data) < 0) { + virBufferFreeAndReset(buf); + return -1; + } + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</checkpoints>\n"); + return 0; +} + + +/* Checkpoint Obj functions */ +static virDomainCheckpointObjPtr virDomainCheckpointObjNew(void) +{ + virDomainCheckpointObjPtr checkpoint; + + if (VIR_ALLOC(checkpoint) < 0) + return NULL; + + VIR_DEBUG("obj=%p", checkpoint); + + return checkpoint; +} + +static void virDomainCheckpointObjFree(virDomainCheckpointObjPtr checkpoint) +{ + if (!checkpoint) + return; + + VIR_DEBUG("obj=%p", checkpoint); + + virDomainCheckpointDefFree(checkpoint->def); + VIR_FREE(checkpoint); +} + +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointDefPtr def) +{ + virDomainCheckpointObjPtr chk; + + if (virHashLookup(checkpoints->objs, def->name) != NULL) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("domain checkpoint %s already exists"), + def->name); + return NULL; + } + + if (!(chk = virDomainCheckpointObjNew())) + return NULL; + chk->def = def; + + if (virHashAddEntry(checkpoints->objs, chk->def->name, chk) < 0) { + VIR_FREE(chk); + return NULL; + } + + return chk; +} + +/* Checkpoint Obj List functions */ +static void +virDomainCheckpointObjListDataFree(void *payload, + const void *name ATTRIBUTE_UNUSED) +{ + virDomainCheckpointObjPtr obj = payload; + + virDomainCheckpointObjFree(obj); +} + +virDomainCheckpointObjListPtr +virDomainCheckpointObjListNew(void) +{ + virDomainCheckpointObjListPtr checkpoints; + if (VIR_ALLOC(checkpoints) < 0) + return NULL; + checkpoints->objs = virHashCreate(50, virDomainCheckpointObjListDataFree); + if (!checkpoints->objs) { + VIR_FREE(checkpoints); + return NULL; + } + return checkpoints; +} + +void +virDomainCheckpointObjListFree(virDomainCheckpointObjListPtr checkpoints) +{ + if (!checkpoints) + return; + virHashFree(checkpoints->objs); + VIR_FREE(checkpoints); +} + +struct virDomainCheckpointNameData { + char **const names; + int maxnames; + unsigned int flags; + int count; + bool error; +}; + +static int +virDomainCheckpointObjListCopyNames(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainCheckpointObjPtr obj = payload; + struct virDomainCheckpointNameData *data = opaque; + + if (data->error) + return 0; + /* Caller already sanitized flags. Filtering on DESCENDANTS was + * done by choice of iteration in the caller. */ + if ((data->flags & VIR_DOMAIN_CHECKPOINT_LIST_LEAVES) && obj->nchildren) + return 0; + if ((data->flags & VIR_DOMAIN_CHECKPOINT_LIST_NO_LEAVES) && !obj->nchildren) + return 0; + + if (data->names && data->count < data->maxnames && + VIR_STRDUP(data->names[data->count], obj->def->name) < 0) { + data->error = true; + return 0; + } + data->count++; + return 0; +} + +static int +virDomainCheckpointObjListGetNames(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + char **const names, int maxnames, + unsigned int flags) +{ + struct virDomainCheckpointNameData data = { names, maxnames, flags, 0, + false }; + size_t i; + + if (!from) { + /* LIST_ROOTS and LIST_DESCENDANTS have the same bit value, + * but opposite semantics. Toggle here to get the correct + * traversal on the metaroot. */ + flags ^= VIR_DOMAIN_CHECKPOINT_LIST_ROOTS; + from = &checkpoints->metaroot; + } + + /* We handle LIST_ROOT/LIST_DESCENDANTS directly, mask that bit + * out to determine when we must use the filter callback. */ + data.flags &= ~VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS; + + /* If this common code is being used, we assume that all checkpoints + * have metadata, and thus can handle METADATA up front as an + * all-or-none filter. XXX This might not always be true, if we + * add the ability to track qcow2 bitmaps without the + * use of metadata. */ + if ((data.flags & VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA) == + VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA) + return 0; + data.flags &= ~VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA; + + /* For ease of coding the visitor, it is easier to zero each group + * where all of the bits are set. */ + if ((data.flags & VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) == + VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) + data.flags &= ~VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES; + + if (flags & VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS) { + if (from->def) + virDomainCheckpointForEachDescendant(from, + virDomainCheckpointObjListCopyNames, + &data); + else if (names || data.flags) + virHashForEach(checkpoints->objs, + virDomainCheckpointObjListCopyNames, + &data); + else + data.count = virHashSize(checkpoints->objs); + } else if (names || data.flags) { + virDomainCheckpointForEachChild(from, + virDomainCheckpointObjListCopyNames, + &data); + } else { + data.count = from->nchildren; + } + + if (data.error) { + for (i = 0; i < data.count; i++) + VIR_FREE(names[i]); + return -1; + } + + return data.count; +} + +static int +virDomainCheckpointObjListNum(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + unsigned int flags) +{ + return virDomainCheckpointObjListGetNames(checkpoints, from, NULL, 0, + flags); +} + +virDomainCheckpointObjPtr +virDomainCheckpointFindByName(virDomainCheckpointObjListPtr checkpoints, + const char *name) +{ + return name ? virHashLookup(checkpoints->objs, name) : + &checkpoints->metaroot; +} + +void virDomainCheckpointObjListRemove(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr checkpoint) +{ + virHashRemoveEntry(checkpoints->objs, checkpoint->def->name); +} + +int +virDomainCheckpointForEach(virDomainCheckpointObjListPtr checkpoints, + virHashIterator iter, + void *data) +{ + return virHashForEach(checkpoints->objs, iter, data); +} + +/* Run iter(data) on all direct children of checkpoint, while ignoring all + * other entries in checkpoints. Return the number of children + * visited. No particular ordering is guaranteed. */ +int +virDomainCheckpointForEachChild(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data) +{ + virDomainCheckpointObjPtr child = checkpoint->first_child; + + while (child) { + virDomainCheckpointObjPtr next = child->sibling; + (iter)(child, child->def->name, data); + child = next; + } + + return checkpoint->nchildren; +} + +struct checkpoint_act_on_descendant { + int number; + virHashIterator iter; + void *data; +}; + +static int +virDomainCheckpointActOnDescendant(void *payload, + const void *name, + void *data) +{ + virDomainCheckpointObjPtr obj = payload; + struct checkpoint_act_on_descendant *curr = data; + + curr->number += 1 + virDomainCheckpointForEachDescendant(obj, + curr->iter, + curr->data); + (curr->iter)(payload, name, curr->data); + return 0; +} + +/* Run iter(data) on all descendants of checkpoint, while ignoring all + * other entries in checkpoints. Return the number of descendants + * visited. No particular ordering is guaranteed. */ +int +virDomainCheckpointForEachDescendant(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data) +{ + struct checkpoint_act_on_descendant act; + + act.number = 0; + act.iter = iter; + act.data = data; + virDomainCheckpointForEachChild(checkpoint, + virDomainCheckpointActOnDescendant, &act); + + return act.number; +} + +/* Struct and callback function used as a hash table callback; each call + * inspects the pre-existing checkpoint->def->parent field, and adjusts + * the checkpoint->parent field as well as the parent's child fields to + * wire up the hierarchical relations for the given checkpoint. The error + * indicator gets set if a parent is missing or a requested parent would + * cause a circular parent chain. */ +struct checkpoint_set_relation { + virDomainCheckpointObjListPtr checkpoints; + int err; +}; +static int +virDomainCheckpointSetRelations(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainCheckpointObjPtr obj = payload; + struct checkpoint_set_relation *curr = data; + virDomainCheckpointObjPtr tmp; + + obj->parent = virDomainCheckpointFindByName(curr->checkpoints, + obj->def->parent); + if (!obj->parent) { + curr->err = -1; + obj->parent = &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s lacks parent", obj->def->name); + } else { + tmp = obj->parent; + while (tmp && tmp->def) { + if (tmp == obj) { + curr->err = -1; + obj->parent = &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s in circular chain", obj->def->name); + break; + } + tmp = tmp->parent; + } + } + obj->parent->nchildren++; + obj->sibling = obj->parent->first_child; + obj->parent->first_child = obj; + return 0; +} + +/* Populate parent link and child count of all checkpoints, with all + * relations starting as 0/NULL. Return 0 on success, -1 if a parent + * is missing or if a circular relationship was requested. */ +int +virDomainCheckpointUpdateRelations(virDomainCheckpointObjListPtr checkpoints) +{ + struct checkpoint_set_relation act = { checkpoints, 0 }; + + virHashForEach(checkpoints->objs, virDomainCheckpointSetRelations, &act); + return act.err; +} + +/* Prepare to reparent or delete checkpoint, by removing it from its + * current listed parent. Note that when bulk removing all children + * of a parent, it is faster to just 0 the count rather than calling + * this function on each child. */ +void +virDomainCheckpointDropParent(virDomainCheckpointObjPtr checkpoint) +{ + virDomainCheckpointObjPtr prev = NULL; + virDomainCheckpointObjPtr curr = NULL; + + checkpoint->parent->nchildren--; + curr = checkpoint->parent->first_child; + while (curr != checkpoint) { + if (!curr) { + VIR_WARN("inconsistent checkpoint relations"); + return; + } + prev = curr; + curr = curr->sibling; + } + if (prev) + prev->sibling = checkpoint->sibling; + else + checkpoint->parent->first_child = checkpoint->sibling; + checkpoint->parent = NULL; + checkpoint->sibling = NULL; +} + +int +virDomainListAllCheckpoints(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + virDomainPtr dom, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + int count = virDomainCheckpointObjListNum(checkpoints, from, flags); + virDomainCheckpointPtr *list = NULL; + char **names; + int ret = -1; + size_t i; + + if (!chks || count < 0) + return count; + if (VIR_ALLOC_N(names, count) < 0 || + VIR_ALLOC_N(list, count + 1) < 0) + goto cleanup; + + if (virDomainCheckpointObjListGetNames(checkpoints, from, names, count, + flags) < 0) + goto cleanup; + for (i = 0; i < count; i++) + if ((list[i] = virGetDomainCheckpoint(dom, names[i])) == NULL) + goto cleanup; + + ret = count; + *chks = list; + + cleanup: + for (i = 0; i < count; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + if (ret < 0 && list) { + for (i = 0; i < count; i++) + virObjectUnref(list[i]); + VIR_FREE(list); + } + return ret; +} + + +int +virDomainCheckpointRedefinePrep(virDomainPtr domain, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *defptr, + virDomainCheckpointObjPtr *chk, + virDomainXMLOptionPtr xmlopt, + bool *update_current) +{ + virDomainCheckpointDefPtr def = *defptr; + int ret = -1; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainCheckpointObjPtr other; + + virUUIDFormat(domain->uuid, uuidstr); + + /* Prevent circular chains */ + if (def->parent) { + if (STREQ(def->name, def->parent)) { + virReportError(VIR_ERR_INVALID_ARG, + _("cannot set checkpoint %s as its own parent"), + def->name); + goto cleanup; + } + other = virDomainCheckpointFindByName(vm->checkpoints, def->parent); + if (!other) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s for checkpoint %s not found"), + def->parent, def->name); + goto cleanup; + } + while (other->def->parent) { + if (STREQ(other->def->parent, def->name)) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s would create cycle to %s"), + other->def->name, def->name); + goto cleanup; + } + other = virDomainCheckpointFindByName(vm->checkpoints, + other->def->parent); + if (!other) { + VIR_WARN("checkpoints are inconsistent for %s", + vm->def->name); + break; + } + } + } + + if (def->dom && + memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) { + virReportError(VIR_ERR_INVALID_ARG, + _("definition for checkpoint %s must use uuid %s"), + def->name, uuidstr); + goto cleanup; + } + + other = virDomainCheckpointFindByName(vm->checkpoints, def->name); + if (other) { + if (other->def->dom) { + if (def->dom) { + if (!virDomainDefCheckABIStability(other->def->dom, + def->dom, xmlopt)) + goto cleanup; + } else { + /* Transfer the domain def */ + def->dom = other->def->dom; + other->def->dom = NULL; + } + } + + if (def->dom) { + if (virDomainCheckpointAlignDisks(def) < 0) { + /* revert stealing of the checkpoint domain definition */ + if (def->dom && !other->def->dom) { + other->def->dom = def->dom; + def->dom = NULL; + } + goto cleanup; + } + } + + if (other == vm->current_checkpoint) { + *update_current = true; + vm->current_checkpoint = NULL; + } + + /* Drop and rebuild the parent relationship, but keep all + * child relations by reusing chk. */ + virDomainCheckpointDropParent(other); + virDomainCheckpointDefFree(other->def); + other->def = def; + *defptr = NULL; + *chk = other; + } else if (def->dom && virDomainCheckpointAlignDisks(def) < 0) { + goto cleanup; + } + + ret = 0; + cleanup: + return ret; +} diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index c12ae57768..010ac2edb1 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -29,6 +29,7 @@ #include "configmake.h" #include "internal.h" #include "virerror.h" +#include "checkpoint_conf.h" #include "datatypes.h" #include "domain_addr.h" #include "domain_conf.h" @@ -3330,6 +3331,7 @@ static void virDomainObjDispose(void *obj) (dom->privateDataFreeFunc)(dom->privateData); virDomainSnapshotObjListFree(dom->snapshots); + virDomainCheckpointObjListFree(dom->checkpoints); } virDomainObjPtr @@ -3359,6 +3361,9 @@ virDomainObjNew(virDomainXMLOptionPtr xmlopt) if (!(domain->snapshots = virDomainSnapshotObjListNew())) goto error; + if (!(domain->checkpoints = virDomainCheckpointObjListNew())) + goto error; + virObjectLock(domain); virDomainObjSetState(domain, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN); @@ -27920,7 +27925,8 @@ virDomainDefFormatInternal(virBufferPtr buf, VIR_DOMAIN_DEF_FORMAT_ACTUAL_NET | VIR_DOMAIN_DEF_FORMAT_PCI_ORIG_STATES | VIR_DOMAIN_DEF_FORMAT_CLOCK_ADJUST | - VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS, + VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS | + VIR_DOMAIN_DEF_FORMAT_CHECKPOINTS, -1); if (!(type = virDomainVirtTypeToString(def->virtType))) { @@ -28424,6 +28430,21 @@ virDomainDefFormatInternal(virBufferPtr buf, goto error; } + if (flags & VIR_DOMAIN_DEF_FORMAT_CHECKPOINTS) { + unsigned int snapflags = flags & VIR_DOMAIN_DEF_FORMAT_SECURE ? + VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE : 0; + + if (!(data && data->checkpoints)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("checkpoints requested but not provided")); + goto error; + } + if (virDomainCheckpointObjListFormat(buf, data->checkpoints, + data->current_check, caps, + xmlopt, snapflags) < 0) + goto error; + } + virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "</domain>\n"); @@ -28455,6 +28476,8 @@ unsigned int virDomainDefFormatConvertXMLFlags(unsigned int flags) formatFlags |= VIR_DOMAIN_DEF_FORMAT_MIGRATABLE; if (flags & VIR_DOMAIN_XML_SNAPSHOTS) formatFlags |= VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS; + if (flags & VIR_DOMAIN_XML_CHECKPOINTS) + formatFlags |= VIR_DOMAIN_DEF_FORMAT_CHECKPOINTS; return formatFlags; } @@ -28481,7 +28504,8 @@ virDomainDefFormatFull(virDomainDefPtr def, virBuffer buf = VIR_BUFFER_INITIALIZER; virCheckFlags(VIR_DOMAIN_DEF_FORMAT_COMMON_FLAGS | - VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS, NULL); + VIR_DOMAIN_DEF_FORMAT_SNAPSHOTS | + VIR_DOMAIN_DEF_FORMAT_CHECKPOINTS, NULL); if (virDomainDefFormatInternal(&buf, def, data, flags, NULL) < 0) return NULL; diff --git a/src/conf/virdomainobjlist.c b/src/conf/virdomainobjlist.c index 0e943d0a6c..73395e394d 100644 --- a/src/conf/virdomainobjlist.c +++ b/src/conf/virdomainobjlist.c @@ -1,7 +1,7 @@ /* * virdomainobjlist.c: domain objects list utilities * - * Copyright (C) 2006-2015 Red Hat, Inc. + * Copyright (C) 2006-2019 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. * @@ -25,6 +25,7 @@ #include "internal.h" #include "datatypes.h" #include "virdomainobjlist.h" +#include "checkpoint_conf.h" #include "snapshot_conf.h" #include "viralloc.h" #include "virfile.h" @@ -879,6 +880,15 @@ virDomainObjMatchFilter(virDomainObjPtr vm, return false; } + /* filter by checkpoint existence */ + if (MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_CHECKPOINT)) { + int nchk = virDomainListAllCheckpoints(vm->checkpoints, NULL, NULL, + NULL, 0); + if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT) && nchk > 0) || + (MATCH(VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT) && nchk <= 0))) + return false; + } + return true; } #undef MATCH diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 13ef94149b..84e5d3dea2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -69,6 +69,30 @@ virCapabilitiesSetHostCPU; virCapabilitiesSetNetPrefix; +# conf/checkpoint_conf.h +virDomainCheckpointAlignDisks; +virDomainCheckpointAssignDef; +virDomainCheckpointDefFormat; +virDomainCheckpointDefFree; +virDomainCheckpointDefParseString; +virDomainCheckpointDropParent; +virDomainCheckpointFindByName; +virDomainCheckpointForEach; +virDomainCheckpointForEachChild; +virDomainCheckpointForEachDescendant; +virDomainCheckpointFormatConvertXMLFlags; +virDomainCheckpointObjListFormat; +virDomainCheckpointObjListFree; +virDomainCheckpointObjListNew; +virDomainCheckpointObjListParse; +virDomainCheckpointObjListRemove; +virDomainCheckpointRedefinePrep; +virDomainCheckpointTypeFromString; +virDomainCheckpointTypeToString; +virDomainCheckpointUpdateRelations; +virDomainListAllCheckpoints; + + # conf/cpu_conf.h virCPUCacheModeTypeFromString; virCPUCacheModeTypeToString; diff --git a/tests/Makefile.am b/tests/Makefile.am index 98b99afcb1..a985a95eb3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -288,7 +288,7 @@ endif WITH_LIBXL if WITH_QEMU test_programs += qemuxml2argvtest qemuxml2xmltest \ - qemuargv2xmltest domainsnapshotxml2xmltest \ + qemuargv2xmltest domaincheckpointxml2xmltest domainsnapshotxml2xmltest \ qemumonitorjsontest qemuhotplugtest \ qemuagenttest qemucapabilitiestest qemucaps2xmltest \ qemumemlocktest \ @@ -678,6 +678,11 @@ qemublocktest_LDADD = \ $(LDADDS) \ $(NULL) +domaincheckpointxml2xmltest_SOURCES = \ + domaincheckpointxml2xmltest.c testutilsqemu.c testutilsqemu.h \ + testutils.c testutils.h +domaincheckpointxml2xmltest_LDADD = $(qemu_LDADDS) $(LDADDS) + domainsnapshotxml2xmltest_SOURCES = \ domainsnapshotxml2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h @@ -706,7 +711,7 @@ qemusecuritytest_LDADD = $(qemu_LDADDS) $(LDADDS) else ! WITH_QEMU EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c \ - domainsnapshotxml2xmltest.c \ + domaincheckpointxml2xmltest.c domainsnapshotxml2xmltest.c \ testutilsqemu.c testutilsqemu.h \ testutilsqemuschema.c testutilsqemuschema.h \ qemumonitorjsontest.c qemuhotplugtest.c \ diff --git a/tests/domaincheckpointxml2xmltest.c b/tests/domaincheckpointxml2xmltest.c new file mode 100644 index 0000000000..0fff9fcbdf --- /dev/null +++ b/tests/domaincheckpointxml2xmltest.c @@ -0,0 +1,233 @@ +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/types.h> +#include <fcntl.h> + +#include <regex.h> + +#include "testutils.h" + +#ifdef WITH_QEMU + +# include "internal.h" +# include "qemu/qemu_conf.h" +# include "qemu/qemu_domain.h" +# include "checkpoint_conf.h" +# include "testutilsqemu.h" +# include "virstring.h" + +# define VIR_FROM_THIS VIR_FROM_NONE + +static virQEMUDriver driver; + +/* This regex will skip the following XML constructs in test files + * that are dynamically generated and thus problematic to test: + * <name>1234352345</name> if the checkpoint has no name, + * <creationTime>23523452345</creationTime>. + */ +static const char *testCheckpointXMLVariableLineRegexStr = + "<(name|creationTime)>[0-9]+</(name|creationTime)>"; + +regex_t *testCheckpointXMLVariableLineRegex = NULL; + +static char * +testFilterXML(char *xml) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char **xmlLines = NULL; + char **xmlLine; + char *ret = NULL; + + if (!(xmlLines = virStringSplit(xml, "\n", 0))) { + VIR_FREE(xml); + goto cleanup; + } + VIR_FREE(xml); + + for (xmlLine = xmlLines; *xmlLine; xmlLine++) { + if (regexec(testCheckpointXMLVariableLineRegex, + *xmlLine, 0, NULL, 0) == 0) + continue; + + virBufferStrcat(&buf, *xmlLine, "\n", NULL); + } + + if (virBufferCheckError(&buf) < 0) + goto cleanup; + + ret = virBufferContentAndReset(&buf); + + cleanup: + virBufferFreeAndReset(&buf); + virStringListFree(xmlLines); + return ret; +} + +static int +testCompareXMLToXMLFiles(const char *inxml, + const char *outxml, + bool internal, + bool redefine) +{ + char *inXmlData = NULL; + char *outXmlData = NULL; + char *actual = NULL; + int ret = -1; + virDomainCheckpointDefPtr def = NULL; + unsigned int parseflags = VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + unsigned int formatflags = VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE; + + if (internal) { + parseflags |= VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL; + formatflags |= VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL; + } + + if (redefine) + parseflags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + + if (virTestLoadFile(inxml, &inXmlData) < 0) + goto cleanup; + + if (virTestLoadFile(outxml, &outXmlData) < 0) + goto cleanup; + + if (!(def = virDomainCheckpointDefParseString(inXmlData, driver.caps, + driver.xmlopt, + parseflags))) + goto cleanup; + + /* Parsing XML does not populate the domain definition, so add a + * canned bare-bones fallback */ + if (!def->dom) { + // HACK + ret = 77; + goto cleanup; + const char *def_dom = "" + "<domain type='qemu'>" + " <name>fedora</name>" + " <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid>" +/* arch='x86_64' machine='pc'*/ + " <os><type>hvm</type></os>" + "</domain>"; + int dom_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE; + if (!(def->dom = virDomainDefParseString(def_dom, driver.caps, + driver.xmlopt, NULL, + dom_flags))) + goto cleanup; + } + + if (!(actual = virDomainCheckpointDefFormat(def, driver.caps, + driver.xmlopt, + formatflags))) + goto cleanup; + + if (!redefine) { + if (!(actual = testFilterXML(actual))) + goto cleanup; + + if (!(outXmlData = testFilterXML(outXmlData))) + goto cleanup; + } + + if (STRNEQ(outXmlData, actual)) { + virTestDifferenceFull(stderr, outXmlData, outxml, actual, inxml); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FREE(inXmlData); + VIR_FREE(outXmlData); + VIR_FREE(actual); + virDomainCheckpointDefFree(def); + return ret; +} + +struct testInfo { + const char *inxml; + const char *outxml; + bool internal; + bool redefine; +}; + + +static int +testCompareXMLToXMLHelper(const void *data) +{ + const struct testInfo *info = data; + + return testCompareXMLToXMLFiles(info->inxml, info->outxml, + info->internal, info->redefine); +} + + +static int +mymain(void) +{ + int ret = 0; + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + if (VIR_ALLOC(testCheckpointXMLVariableLineRegex) < 0) + goto cleanup; + + if (regcomp(testCheckpointXMLVariableLineRegex, + testCheckpointXMLVariableLineRegexStr, + REG_EXTENDED | REG_NOSUB) != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "failed to compile test regex"); + goto cleanup; + } + + +# define DO_TEST(prefix, name, inpath, outpath, internal, redefine) \ + do { \ + const struct testInfo info = {abs_srcdir "/" inpath "/" name ".xml", \ + abs_srcdir "/" outpath "/" name ".xml", \ + internal, redefine}; \ + if (virTestRun("CHECKPOINT XML-2-XML " prefix " " name, \ + testCompareXMLToXMLHelper, &info) < 0) \ + ret = -1; \ + } while (0) + +# define DO_TEST_INOUT(name, internal, redefine) \ + DO_TEST("in->out", name,\ + "domaincheckpointxml2xmlin",\ + "domaincheckpointxml2xmlout",\ + internal, redefine) + + /* Unset or set all envvars here that are copied in qemudBuildCommandLine + * using ADD_ENV_COPY, otherwise these tests may fail due to unexpected + * values for these envvars */ + setenv("PATH", "/bin", 1); + + DO_TEST_INOUT("empty", false, false); + DO_TEST_INOUT("sample", false, false); + + cleanup: + if (testCheckpointXMLVariableLineRegex) + regfree(testCheckpointXMLVariableLineRegex); + VIR_FREE(testCheckpointXMLVariableLineRegex); + qemuTestDriverFree(&driver); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) + +#else + +int +main(void) +{ + return EXIT_AM_SKIP; +} + +#endif /* WITH_QEMU */ -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list