/* * snapshot_conf.c: domain snapshot 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 * . */ #include #include #include #include #include "configmake.h" #include "internal.h" #include "virbitmap.h" #include "virbuffer.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 "snapshot_conf.h" #include "storage_source_conf.h" #include "viruuid.h" #include "virfile.h" #include "virerror.h" #include "virxml.h" #include "virstring.h" #include "virdomainsnapshotobjlist.h" #define LIBVIRT_SNAPSHOT_CONF_PRIV_H_ALLOW #include "snapshot_conf_priv.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN_SNAPSHOT VIR_LOG_INIT("conf.snapshot_conf"); static virClass *virDomainSnapshotDefClass; static void virDomainSnapshotDefDispose(void *obj); static int virDomainSnapshotOnceInit(void) { if (!VIR_CLASS_NEW(virDomainSnapshotDef, virClassForDomainMomentDef())) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virDomainSnapshot); /* virDomainSnapshotState is really virDomainState plus one extra state */ VIR_ENUM_IMPL(virDomainSnapshotState, VIR_DOMAIN_SNAPSHOT_LAST, "nostate", "running", "blocked", "paused", "shutdown", "shutoff", "crashed", "pmsuspended", "disk-snapshot", ); /* Snapshot Def functions */ static void virDomainSnapshotDiskDefClear(virDomainSnapshotDiskDef *disk) { VIR_FREE(disk->name); g_clear_pointer(&disk->src, virObjectUnref); } void virDomainSnapshotDiskDefFree(virDomainSnapshotDiskDef *disk) { if (!disk) return; virDomainSnapshotDiskDefClear(disk); g_free(disk); } /* Allocate a new virDomainSnapshotDef; free with virObjectUnref() */ virDomainSnapshotDef * virDomainSnapshotDefNew(void) { if (virDomainSnapshotInitialize() < 0) return NULL; return virObjectNew(virDomainSnapshotDefClass); } static void virDomainSnapshotDefDispose(void *obj) { virDomainSnapshotDef *def = obj; size_t i; g_free(def->memorysnapshotfile); for (i = 0; i < def->ndisks; i++) virDomainSnapshotDiskDefClear(&def->disks[i]); g_free(def->disks); virObjectUnref(def->cookie); } int virDomainSnapshotDiskDefParseXML(xmlNodePtr node, xmlXPathContextPtr ctxt, virDomainSnapshotDiskDef *def, unsigned int flags, virDomainXMLOption *xmlopt) { g_autofree char *driver = NULL; g_autofree char *name = NULL; g_autoptr(virStorageSource) src = virStorageSourceNew(); xmlNodePtr cur; VIR_XPATH_NODE_AUTORESTORE(ctxt) ctxt->node = node; if (!(name = virXMLPropString(node, "name"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing name from disk snapshot element")); return -1; } if (virXMLPropEnumDefault(node, "snapshot", virDomainSnapshotLocationTypeFromString, VIR_XML_PROP_NONZERO, &def->snapshot, VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT) < 0) return -1; if (virXMLPropEnumDefault(node, "type", virStorageTypeFromString, VIR_XML_PROP_NONZERO, &src->type, VIR_STORAGE_TYPE_FILE) < 0) return -1; if (src->type == VIR_STORAGE_TYPE_VOLUME || src->type == VIR_STORAGE_TYPE_DIR) { virReportError(VIR_ERR_XML_ERROR, _("unsupported disk snapshot type '%s'"), virStorageTypeToString(src->type)); return -1; } if ((cur = virXPathNode("./source", ctxt)) && virDomainStorageSourceParse(cur, ctxt, src, flags, xmlopt) < 0) return -1; if ((driver = virXPathString("string(./driver/@type)", ctxt)) && (src->format = virStorageFileFormatTypeFromString(driver)) <= 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown disk snapshot driver '%s'"), driver); return -1; } if (virParseScaledValue("./driver/metadata_cache/max_size", NULL, ctxt, &src->metadataCacheMaxSize, 1, ULLONG_MAX, false) < 0) return -1; /* validate that the passed path is absolute */ if (virStorageSourceIsRelative(src)) { virReportError(VIR_ERR_XML_ERROR, _("disk snapshot image path '%s' must be absolute"), src->path); return -1; } if (def->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT && (src->path || src->format)) def->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; def->name = g_steal_pointer(&name); def->src = g_steal_pointer(&src); return 0; } /* flags is bitwise-or of virDomainSnapshotParseFlags. * If flags does not include * VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL, then current is ignored. */ static virDomainSnapshotDef * virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, virDomainXMLOption *xmlopt, void *parseOpaque, bool *current, unsigned int flags) { g_autoptr(virDomainSnapshotDef) def = NULL; g_autofree xmlNodePtr *diskNodes = NULL; size_t i; int n; xmlNodePtr memoryNode = NULL; bool offline = !!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE); virSaveCookieCallbacks *saveCookie = virDomainXMLOptionGetSaveCookie(xmlopt); int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; if (!(def = virDomainSnapshotDefNew())) return NULL; def->parent.name = virXPathString("string(./name)", ctxt); if (def->parent.name == NULL) { if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { virReportError(VIR_ERR_XML_ERROR, "%s", _("a redefined snapshot must have a name")); return NULL; } } def->parent.description = virXPathString("string(./description)", ctxt); if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { g_autofree char *state = NULL; g_autofree char *domtype = NULL; xmlNodePtr inactiveDomNode = NULL; if (virXPathLongLong("string(./creationTime)", ctxt, &def->parent.creationTime) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing creationTime from existing snapshot")); return NULL; } def->parent.parent_name = virXPathString("string(./parent/name)", ctxt); state = virXPathString("string(./state)", ctxt); if (state == NULL) { /* there was no state in an existing snapshot; this * should never happen */ virReportError(VIR_ERR_XML_ERROR, "%s", _("missing state from existing snapshot")); return NULL; } def->state = virDomainSnapshotStateTypeFromString(state); if (def->state <= 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid state '%s' in domain snapshot XML"), state); return NULL; } offline = (def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF || def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT); /* Older snapshots were created with just /, and * lack domain/@type. In that case, leave dom NULL, and * clients will have to decide between best effort * initialization or outright failure. */ if ((domtype = virXPathString("string(./domain/@type)", ctxt))) { xmlNodePtr domainNode = virXPathNode("./domain", ctxt); if (!domainNode) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing domain in snapshot")); return NULL; } def->parent.dom = virDomainDefParseNode(ctxt->node->doc, domainNode, xmlopt, parseOpaque, domainflags); if (!def->parent.dom) return NULL; } else { VIR_WARN("parsing older snapshot that lacks domain"); } /* /inactiveDomain entry saves the config XML present in a running * VM. In case of absent, leave parent.inactiveDom NULL and use * parent.dom for config and live XML. */ if ((inactiveDomNode = virXPathNode("./inactiveDomain", ctxt))) { def->parent.inactiveDom = virDomainDefParseNode(ctxt->node->doc, inactiveDomNode, xmlopt, NULL, domainflags); if (!def->parent.inactiveDom) return NULL; } } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) < 0) { return NULL; } if ((memoryNode = virXPathNode("./memory", ctxt))) { def->memorysnapshotfile = virXMLPropString(memoryNode, "file"); if (virXMLPropEnumDefault(memoryNode, "snapshot", virDomainSnapshotLocationTypeFromString, VIR_XML_PROP_NONZERO, &def->memory, VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT) < 0) return NULL; if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_MANUAL) { virReportError(VIR_ERR_XML_ERROR, "%s", _("'manual' memory snapshot mode not supported")); return NULL; } } if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT) { if (def->memorysnapshotfile) { def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; } else if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { if (offline) { def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_NO; } else { def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; } } } if (def->memorysnapshotfile && def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virReportError(VIR_ERR_XML_ERROR, _("memory filename '%s' requires external snapshot"), def->memorysnapshotfile); return NULL; } if (!def->memorysnapshotfile && def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virReportError(VIR_ERR_XML_ERROR, "%s", _("external memory snapshots require a filename")); return NULL; } if (offline && def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT && def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_NO) { virReportError(VIR_ERR_XML_ERROR, "%s", _("memory state cannot be saved with offline or " "disk-only snapshot")); return NULL; } /* verify that memory path is absolute */ if (def->memorysnapshotfile && !g_path_is_absolute(def->memorysnapshotfile)) { virReportError(VIR_ERR_XML_ERROR, _("memory snapshot file path (%s) must be absolute"), def->memorysnapshotfile); return NULL; } if ((n = virXPathNodeSet("./disks/*", ctxt, &diskNodes)) < 0) return NULL; if (n) def->disks = g_new0(virDomainSnapshotDiskDef, n); def->ndisks = n; for (i = 0; i < def->ndisks; i++) { if (virDomainSnapshotDiskDefParseXML(diskNodes[i], ctxt, &def->disks[i], flags, xmlopt) < 0) return NULL; } if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL) { int active; if (!current) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("internal parse requested with NULL current")); return NULL; } if (virXPathInt("string(./active)", ctxt, &active) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not find 'active' element")); return NULL; } *current = active != 0; } if (!offline && virSaveCookieParse(ctxt, &def->cookie, saveCookie) < 0) return NULL; return g_steal_pointer(&def); } virDomainSnapshotDef * virDomainSnapshotDefParseNode(xmlDocPtr xml, xmlNodePtr root, virDomainXMLOption *xmlopt, void *parseOpaque, bool *current, unsigned int flags) { g_autoptr(xmlXPathContext) ctxt = NULL; if (!virXMLNodeNameEqual(root, "domainsnapshot")) { virReportError(VIR_ERR_XML_ERROR, "%s", _("domainsnapshot")); return NULL; } if (!(ctxt = virXMLXPathContextNew(xml))) return NULL; ctxt->node = root; return virDomainSnapshotDefParse(ctxt, xmlopt, parseOpaque, current, flags); } virDomainSnapshotDef * virDomainSnapshotDefParseString(const char *xmlStr, virDomainXMLOption *xmlopt, void *parseOpaque, bool *current, unsigned int flags) { virDomainSnapshotDef *ret = NULL; g_autoptr(xmlDoc) xml = NULL; int keepBlanksDefault = xmlKeepBlanksDefault(0); if ((xml = virXMLParse(NULL, xmlStr, _("(domain_snapshot)"), "domainsnapshot.rng", flags & VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE))) { xmlKeepBlanksDefault(keepBlanksDefault); ret = virDomainSnapshotDefParseNode(xml, xmlDocGetRootElement(xml), xmlopt, parseOpaque, current, flags); } xmlKeepBlanksDefault(keepBlanksDefault); return ret; } /* Perform sanity checking on a redefined snapshot definition. */ static int virDomainSnapshotRedefineValidate(virDomainSnapshotDef *def, const unsigned char *domain_uuid, virDomainMomentObj *other, virDomainXMLOption *xmlopt, unsigned int flags) { if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) && def->state != VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT) { virReportError(VIR_ERR_INVALID_ARG, _("disk-only flag for snapshot %s requires " "disk-snapshot state"), def->parent.name); return -1; } if (def->parent.dom && memcmp(def->parent.dom->uuid, domain_uuid, VIR_UUID_BUFLEN)) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(domain_uuid, uuidstr); virReportError(VIR_ERR_INVALID_ARG, _("definition for snapshot %s must use uuid %s"), def->parent.name, uuidstr); return -1; } if (other) { virDomainSnapshotDef *otherdef = virDomainSnapshotObjGetDef(other); if ((otherdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING || otherdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) != (def->state == VIR_DOMAIN_SNAPSHOT_RUNNING || def->state == VIR_DOMAIN_SNAPSHOT_PAUSED)) { virReportError(VIR_ERR_INVALID_ARG, _("cannot change between online and offline " "snapshot state in snapshot %s"), def->parent.name); return -1; } if ((otherdef->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT) != (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)) { virReportError(VIR_ERR_INVALID_ARG, _("cannot change between disk only and " "full system in snapshot %s"), def->parent.name); return -1; } if (otherdef->parent.dom) { if (def->parent.dom) { if (!virDomainDefCheckABIStability(otherdef->parent.dom, def->parent.dom, xmlopt)) return -1; } } } return 0; } /** * virDomainSnapshotDefAssignExternalNames: * @def: snapshot def object * * Generate default external file names for snapshot targets. Returns 0 on * success, -1 on error. */ static int virDomainSnapshotDefAssignExternalNames(virDomainSnapshotDef *def) { const char *origpath; char *tmppath; char *tmp; struct stat sb; size_t i; size_t j; for (i = 0; i < def->ndisks; i++) { virDomainSnapshotDiskDef *disk = &def->disks[i]; if (disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL || disk->src->path) continue; if (disk->src->type != VIR_STORAGE_TYPE_FILE) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("cannot generate external snapshot name " "for disk '%s' on a '%s' device"), disk->name, virStorageTypeToString(disk->src->type)); return -1; } if (!(origpath = virDomainDiskGetSource(def->parent.dom->disks[i]))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("cannot generate external snapshot name " "for disk '%s' without source"), disk->name); return -1; } if (stat(origpath, &sb) < 0 || !S_ISREG(sb.st_mode)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("source for disk '%s' is not a regular " "file; refusing to generate external " "snapshot name"), disk->name); return -1; } tmppath = g_strdup(origpath); /* drop suffix of the file name */ if ((tmp = strrchr(tmppath, '.')) && !strchr(tmp, '/')) *tmp = '\0'; disk->src->path = g_strdup_printf("%s.%s", tmppath, def->parent.name); VIR_FREE(tmppath); /* verify that we didn't generate a duplicate name */ for (j = 0; j < i; j++) { if (STREQ_NULLABLE(disk->src->path, def->disks[j].src->path)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("cannot generate external snapshot name for " "disk '%s': collision with disk '%s'"), disk->name, def->disks[j].name); return -1; } } } return 0; } /** * virDomainSnapshotAlignDisks: * @snapdef: Snapshot definition to align * @existingDomainDef: definition of the domain belonging to a redefined snapshot * @default_snapshot: snapshot location to assign to disks which don't have any * @uniform_internal_snapshot: Require that for an internal snapshot all disks * take part in the internal snapshot * * Align snapdef->disks to domain definition, filling in any missing disks or * snapshot state defaults given by the domain, with a fallback to * @default_snapshot. Ensure that there are no duplicate snapshot disk * definitions in @snapdef and there are no disks described in @snapdef but * missing from the domain definition. * * By default the domain definition from @snapdef->parent.dom is used, but when * redefining an existing snapshot the domain definition may be omitted in * @snapdef. In such case callers must pass in the definition from the snapsot * being redefined as @existingDomainDef. In all other cases callers pass NULL. * * When @uniform_internal_snapshot is true and @default_snapshot is * VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL, all disks in @snapdef must take part * in the internal snapshot. This is for hypervisors where granularity of an * internal snapshot can't be controlled. * * Convert paths to disk targets for uniformity. * * On error -1 is returned and a libvirt error is reported. */ int virDomainSnapshotAlignDisks(virDomainSnapshotDef *snapdef, virDomainDef *existingDomainDef, virDomainSnapshotLocation default_snapshot, bool uniform_internal_snapshot) { virDomainDef *domdef = snapdef->parent.dom; g_autoptr(GHashTable) map = virHashNew(NULL); g_autofree virDomainSnapshotDiskDef *olddisks = NULL; bool require_match = false; size_t oldndisks; size_t i; if (!domdef) domdef = existingDomainDef; if (!domdef) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing domain in snapshot")); return -1; } if (snapdef->ndisks > domdef->ndisks) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("too many disk snapshot requests for domain")); return -1; } if (uniform_internal_snapshot && default_snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL) require_match = true; /* Unlikely to have a guest without disks but technically possible. */ if (!domdef->ndisks) return 0; olddisks = g_steal_pointer(&snapdef->disks); oldndisks = snapdef->ndisks; snapdef->disks = g_new0(virDomainSnapshotDiskDef, domdef->ndisks); snapdef->ndisks = domdef->ndisks; /* Double check requested disks. */ for (i = 0; i < oldndisks; i++) { virDomainSnapshotDiskDef *snapdisk = &olddisks[i]; virDomainDiskDef *domdisk = virDomainDiskByName(domdef, snapdisk->name, false); if (!domdisk) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("no disk named '%s'"), snapdisk->name); return -1; } if (virHashHasEntry(map, domdisk->dst)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("disk '%s' specified twice"), snapdisk->name); return -1; } if (virHashAddEntry(map, domdisk->dst, snapdisk) < 0) return -1; if (snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT) { if (domdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT && (!require_match || domdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)) { snapdisk->snapshot = domdisk->snapshot; } else { snapdisk->snapshot = default_snapshot; } } else if (require_match && snapdisk->snapshot != default_snapshot && !(snapdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO && domdisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("disk '%s' must use snapshot mode '%s'"), snapdisk->name, virDomainSnapshotLocationTypeToString(default_snapshot)); return -1; } if (snapdisk->src->path && snapdisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("file '%s' for disk '%s' requires " "use of external snapshot mode"), snapdisk->src->path, snapdisk->name); return -1; } if (STRNEQ(snapdisk->name, domdisk->dst)) { VIR_FREE(snapdisk->name); snapdisk->name = g_strdup(domdisk->dst); } } for (i = 0; i < domdef->ndisks; i++) { virDomainDiskDef *domdisk = domdef->disks[i]; virDomainSnapshotDiskDef *snapdisk = snapdef->disks + i; virDomainSnapshotDiskDef *existing; /* copy existing disks */ if ((existing = virHashLookup(map, domdisk->dst))) { memcpy(snapdisk, existing, sizeof(*snapdisk)); continue; } /* Provide defaults for all remaining disks. */ snapdisk->src = virStorageSourceNew(); snapdisk->name = g_strdup(domdef->disks[i]->dst); /* Don't snapshot empty drives */ if (virStorageSourceIsEmpty(domdef->disks[i]->src)) snapdisk->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NO; else snapdisk->snapshot = domdef->disks[i]->snapshot; snapdisk->src->type = VIR_STORAGE_TYPE_FILE; if (!snapdisk->snapshot) snapdisk->snapshot = default_snapshot; } /* Generate default external file names for external snapshot locations */ if (virDomainSnapshotDefAssignExternalNames(snapdef) < 0) return -1; return 0; } /* Converts public VIR_DOMAIN_SNAPSHOT_XML_* into * VIR_DOMAIN_SNAPSHOT_FORMAT_* flags, and silently ignores any other * flags. */ unsigned int virDomainSnapshotFormatConvertXMLFlags(unsigned int flags) { unsigned int formatFlags = 0; if (flags & VIR_DOMAIN_SNAPSHOT_XML_SECURE) formatFlags |= VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE; return formatFlags; } static int virDomainSnapshotDiskDefFormat(virBuffer *buf, virDomainSnapshotDiskDef *disk, virDomainXMLOption *xmlopt) { g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); if (!disk->name) return 0; virBufferEscapeString(&attrBuf, " name='%s'", disk->name); if (disk->snapshot > 0) virBufferAsprintf(&attrBuf, " snapshot='%s'", virDomainSnapshotLocationTypeToString(disk->snapshot)); if (disk->src->path || disk->src->format != 0) { g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) driverChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf); virBufferAsprintf(&attrBuf, " type='%s'", virStorageTypeToString(disk->src->type)); if (disk->src->format > 0) virBufferEscapeString(&driverAttrBuf, " type='%s'", virStorageFileFormatTypeToString(disk->src->format)); if (disk->src->metadataCacheMaxSize > 0) { g_auto(virBuffer) metadataCacheChildBuf = VIR_BUFFER_INIT_CHILD(&driverChildBuf); virBufferAsprintf(&metadataCacheChildBuf, "%llu\n", disk->src->metadataCacheMaxSize); virXMLFormatElement(&driverChildBuf, "metadata_cache", NULL, &metadataCacheChildBuf); } virXMLFormatElement(&childBuf, "driver", &driverAttrBuf, &driverChildBuf); if (virDomainDiskSourceFormat(&childBuf, disk->src, "source", 0, false, 0, false, false, xmlopt) < 0) return -1; } virXMLFormatElement(buf, "disk", &attrBuf, &childBuf); return 0; } /* Append XML describing def into buf. Return 0 on success, or -1 on * failure with buf cleared. */ static int virDomainSnapshotDefFormatInternal(virBuffer *buf, const char *uuidstr, virDomainSnapshotDef *def, virDomainXMLOption *xmlopt, unsigned int flags) { size_t i; int domainflags = VIR_DOMAIN_DEF_FORMAT_INACTIVE; if (flags & VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE) domainflags |= VIR_DOMAIN_DEF_FORMAT_SECURE; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); virBufferEscapeString(buf, "%s\n", def->parent.name); if (def->parent.description) virBufferEscapeString(buf, "%s\n", def->parent.description); if (def->state) virBufferAsprintf(buf, "%s\n", virDomainSnapshotStateTypeToString(def->state)); if (def->parent.parent_name) { virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); virBufferEscapeString(buf, "%s\n", def->parent.parent_name); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } if (def->parent.creationTime) virBufferAsprintf(buf, "%lld\n", def->parent.creationTime); if (def->memory) { virBufferAsprintf(buf, "memory)); virBufferEscapeString(buf, " file='%s'", def->memorysnapshotfile); virBufferAddLit(buf, "/>\n"); } if (def->ndisks) { virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < def->ndisks; i++) { if (virDomainSnapshotDiskDefFormat(buf, &def->disks[i], xmlopt) < 0) return -1; } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } if (def->parent.dom) { if (virDomainDefFormatInternal(def->parent.dom, xmlopt, buf, domainflags) < 0) return -1; } else if (uuidstr) { virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); virBufferAsprintf(buf, "%s\n", uuidstr); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } if (def->parent.inactiveDom) { if (virDomainDefFormatInternalSetRootName(def->parent.inactiveDom, xmlopt, buf, "inactiveDomain", domainflags) < 0) return -1; } if (virSaveCookieFormatBuf(buf, def->cookie, virDomainXMLOptionGetSaveCookie(xmlopt)) < 0) return -1; if (flags & VIR_DOMAIN_SNAPSHOT_FORMAT_INTERNAL) virBufferAsprintf(buf, "%d\n", !!(flags & VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT)); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; } char * virDomainSnapshotDefFormat(const char *uuidstr, virDomainSnapshotDef *def, virDomainXMLOption *xmlopt, unsigned int flags) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; virCheckFlags(VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE | VIR_DOMAIN_SNAPSHOT_FORMAT_INTERNAL | VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT, NULL); if (virDomainSnapshotDefFormatInternal(&buf, uuidstr, def, xmlopt, flags) < 0) return NULL; return virBufferContentAndReset(&buf); } bool virDomainSnapshotDefIsExternal(virDomainSnapshotDef *def) { size_t i; if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) return true; for (i = 0; i < def->ndisks; i++) { if (def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) return true; } return false; } bool virDomainSnapshotIsExternal(virDomainMomentObj *snap) { virDomainSnapshotDef *def = virDomainSnapshotObjGetDef(snap); return virDomainSnapshotDefIsExternal(def); } int virDomainSnapshotRedefinePrep(virDomainObj *vm, virDomainSnapshotDef *snapdef, virDomainMomentObj **snap, virDomainXMLOption *xmlopt, unsigned int flags) { virDomainMomentObj *other; virDomainSnapshotDef *otherSnapDef = NULL; virDomainDef *otherDomDef = NULL; virDomainSnapshotLocation align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL; if (virDomainSnapshotCheckCycles(vm->snapshots, snapdef, vm->def->name) < 0) return -1; if ((other = virDomainSnapshotFindByName(vm->snapshots, snapdef->parent.name))) { otherSnapDef = virDomainSnapshotObjGetDef(other); otherDomDef = otherSnapDef->parent.dom; } *snap = other; if (virDomainSnapshotRedefineValidate(snapdef, vm->def->uuid, other, xmlopt, flags) < 0) return -1; if (snapdef->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT || virDomainSnapshotDefIsExternal(snapdef)) align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; if (virDomainSnapshotAlignDisks(snapdef, otherDomDef, align_location, true) < 0) return -1; return 0; }