libvirt/src/conf/snapshot_conf.c
Peter Krempa a1465e661e conf: snapshot: Introduce 'manual' mode for snapshot of a disk
The idea of the manual mode is to allow a synchronized snapshot in cases
when the storage is outsourced to an unmanaged storage provider which
requires cooperation with snapshotting.

The mode will instruct the hypervisor to pause along when the other
components are snapshotted and the 'manual' disk can be snapshotted
along. This increases latency of the snapshot but allows them in
otherwise impossible situations.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2022-03-22 10:32:43 +01:00

972 lines
33 KiB
C

/*
* 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
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#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 <domain>/<uuid>, 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,
"<max_size unit='bytes'>%llu</max_size>\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, "<domainsnapshot>\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "<name>%s</name>\n", def->parent.name);
if (def->parent.description)
virBufferEscapeString(buf, "<description>%s</description>\n",
def->parent.description);
if (def->state)
virBufferAsprintf(buf, "<state>%s</state>\n",
virDomainSnapshotStateTypeToString(def->state));
if (def->parent.parent_name) {
virBufferAddLit(buf, "<parent>\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "<name>%s</name>\n",
def->parent.parent_name);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</parent>\n");
}
if (def->parent.creationTime)
virBufferAsprintf(buf, "<creationTime>%lld</creationTime>\n",
def->parent.creationTime);
if (def->memory) {
virBufferAsprintf(buf, "<memory snapshot='%s'",
virDomainSnapshotLocationTypeToString(def->memory));
virBufferEscapeString(buf, " file='%s'", def->memorysnapshotfile);
virBufferAddLit(buf, "/>\n");
}
if (def->ndisks) {
virBufferAddLit(buf, "<disks>\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, "</disks>\n");
}
if (def->parent.dom) {
if (virDomainDefFormatInternal(def->parent.dom, xmlopt,
buf, domainflags) < 0)
return -1;
} else if (uuidstr) {
virBufferAddLit(buf, "<domain>\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<uuid>%s</uuid>\n", uuidstr);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</domain>\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, "<active>%d</active>\n",
!!(flags & VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</domainsnapshot>\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;
}