mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-10-31 10:23:09 +00:00
02c4e24db7
Rather than allowing a leaky abstraction where multiple drivers have to open-code operations that update the relations in a virDomainSnapshotObjList, it is better to add accessor functions so that updates to relations are maintained closer to the internals. This patch finishes the job started in the previous patch, by getting rid of all direct access to nchildren, first_child, or sibling outside of the lowest level functions, making it easier to refactor later on. The lone new caller to virDomainSnapshotObjListSize() checks for a return != 0, because it wants to handles errors (-1, only possible if the hash table wasn't allocated) and existing snapshots (> 0) in the same manner; we can drop the check for a current snapshot on the grounds that there shouldn't be one if there are no snapshots. Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: John Ferlan <jferlan@redhat.com>
609 lines
19 KiB
C
609 lines
19 KiB
C
/*
|
|
* virdomainsnapshotobjlist.c: handle a tree of snapshot objects
|
|
* (derived from snapshot_conf.c)
|
|
*
|
|
* 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 "internal.h"
|
|
#include "virdomainsnapshotobjlist.h"
|
|
#include "snapshot_conf.h"
|
|
#include "virlog.h"
|
|
#include "virerror.h"
|
|
#include "datatypes.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_DOMAIN_SNAPSHOT
|
|
|
|
VIR_LOG_INIT("conf.virdomainsnapshotobjlist");
|
|
|
|
struct _virDomainSnapshotObjList {
|
|
/* name string -> virDomainSnapshotObj mapping
|
|
* for O(1), lockless lookup-by-name */
|
|
virHashTable *objs;
|
|
|
|
virDomainSnapshotObj metaroot; /* Special parent of all root snapshots */
|
|
virDomainSnapshotObjPtr current; /* The current snapshot, if any */
|
|
};
|
|
|
|
|
|
/* Parse a <snapshots> XML entry into snapshots, which must start
|
|
* empty. Any <domain> sub-elements of a <domainsnapshot> must match
|
|
* domain_uuid. @flags is virDomainSnapshotParseFlags. Return the
|
|
* number of snapshots parsed, or -1 on error.
|
|
*/
|
|
int
|
|
virDomainSnapshotObjListParse(const char *xmlStr,
|
|
const unsigned char *domain_uuid,
|
|
virDomainSnapshotObjListPtr snapshots,
|
|
virCapsPtr caps,
|
|
virDomainXMLOptionPtr xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
int ret = -1;
|
|
xmlDocPtr xml;
|
|
xmlNodePtr root;
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
int n;
|
|
size_t i;
|
|
int keepBlanksDefault = xmlKeepBlanksDefault(0);
|
|
virDomainSnapshotObjPtr snap;
|
|
VIR_AUTOFREE(xmlNodePtr *) nodes = NULL;
|
|
VIR_AUTOFREE(char *) current = NULL;
|
|
|
|
if (!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) ||
|
|
(flags & VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("incorrect flags for bulk parse"));
|
|
return -1;
|
|
}
|
|
if (virDomainSnapshotObjListSize(snapshots) != 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("bulk define of snapshots only possible with "
|
|
"no existing snapshot"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(xml = virXMLParse(NULL, xmlStr, _("(domain_snapshot)"))))
|
|
return -1;
|
|
|
|
root = xmlDocGetRootElement(xml);
|
|
if (!virXMLNodeNameEqual(root, "snapshots")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unexpected root element <%s>, "
|
|
"expecting <snapshots>"), root->name);
|
|
goto cleanup;
|
|
}
|
|
ctxt = xmlXPathNewContext(xml);
|
|
if (ctxt == NULL) {
|
|
virReportOOMError();
|
|
goto cleanup;
|
|
}
|
|
ctxt->node = root;
|
|
current = virXMLPropString(root, "current");
|
|
|
|
if ((n = virXPathNodeSet("./domainsnapshot", ctxt, &nodes)) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainSnapshotDefPtr def;
|
|
|
|
def = virDomainSnapshotDefParseNode(xml, nodes[i], caps, xmlopt, NULL,
|
|
flags);
|
|
if (!def)
|
|
goto cleanup;
|
|
if (!(snap = virDomainSnapshotAssignDef(snapshots, def))) {
|
|
virDomainSnapshotDefFree(def);
|
|
goto cleanup;
|
|
}
|
|
if (virDomainSnapshotRedefineValidate(def, domain_uuid, NULL, NULL,
|
|
flags) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virDomainSnapshotUpdateRelations(snapshots) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("<snapshots> contains inconsistent parent-child "
|
|
"relationships"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (current) {
|
|
snap = virDomainSnapshotFindByName(snapshots, current);
|
|
if (!snap) {
|
|
virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
|
|
_("no snapshot matching current='%s'"), current);
|
|
goto cleanup;
|
|
}
|
|
virDomainSnapshotSetCurrent(snapshots, snap);
|
|
}
|
|
|
|
ret = n;
|
|
cleanup:
|
|
if (ret < 0) {
|
|
/* There were no snapshots before this call; so on error, just
|
|
* blindly delete anything created before the failure. */
|
|
virDomainSnapshotObjListRemoveAll(snapshots);
|
|
}
|
|
xmlXPathFreeContext(ctxt);
|
|
xmlFreeDoc(xml);
|
|
xmlKeepBlanksDefault(keepBlanksDefault);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Struct and callback function used as a hash table callback; each call
|
|
* appends another snapshot XML to buf, with the caller clearing the
|
|
* buffer if any callback fails. */
|
|
struct virDomainSnapshotFormatData {
|
|
virBufferPtr buf;
|
|
const char *uuidstr;
|
|
virCapsPtr caps;
|
|
virDomainXMLOptionPtr xmlopt;
|
|
unsigned int flags;
|
|
};
|
|
|
|
static int
|
|
virDomainSnapshotFormatOne(void *payload,
|
|
const void *name ATTRIBUTE_UNUSED,
|
|
void *opaque)
|
|
{
|
|
virDomainSnapshotObjPtr snap = payload;
|
|
struct virDomainSnapshotFormatData *data = opaque;
|
|
return virDomainSnapshotDefFormatInternal(data->buf, data->uuidstr,
|
|
snap->def, data->caps,
|
|
data->xmlopt, data->flags);
|
|
}
|
|
|
|
|
|
/* Format the XML for all snapshots in the list into buf. @flags is
|
|
* virDomainSnapshotFormatFlags. On error, clear the buffer and return
|
|
* -1. */
|
|
int
|
|
virDomainSnapshotObjListFormat(virBufferPtr buf,
|
|
const char *uuidstr,
|
|
virDomainSnapshotObjListPtr snapshots,
|
|
virCapsPtr caps,
|
|
virDomainXMLOptionPtr xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
struct virDomainSnapshotFormatData data = {
|
|
.buf = buf,
|
|
.uuidstr = uuidstr,
|
|
.caps = caps,
|
|
.xmlopt = xmlopt,
|
|
.flags = flags,
|
|
};
|
|
|
|
virCheckFlags(VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE, -1);
|
|
virBufferAddLit(buf, "<snapshots");
|
|
virBufferEscapeString(buf, " current='%s'",
|
|
virDomainSnapshotGetCurrentName(snapshots));
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (virDomainSnapshotForEach(snapshots, virDomainSnapshotFormatOne,
|
|
&data) < 0) {
|
|
virBufferFreeAndReset(buf);
|
|
return -1;
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</snapshots>\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Snapshot Obj functions */
|
|
static virDomainSnapshotObjPtr virDomainSnapshotObjNew(void)
|
|
{
|
|
virDomainSnapshotObjPtr snapshot;
|
|
|
|
if (VIR_ALLOC(snapshot) < 0)
|
|
return NULL;
|
|
|
|
VIR_DEBUG("obj=%p", snapshot);
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
static void virDomainSnapshotObjFree(virDomainSnapshotObjPtr snapshot)
|
|
{
|
|
if (!snapshot)
|
|
return;
|
|
|
|
VIR_DEBUG("obj=%p", snapshot);
|
|
|
|
virDomainSnapshotDefFree(snapshot->def);
|
|
VIR_FREE(snapshot);
|
|
}
|
|
|
|
virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotDefPtr def)
|
|
{
|
|
virDomainSnapshotObjPtr snap;
|
|
|
|
if (virHashLookup(snapshots->objs, def->name) != NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected domain snapshot %s already exists"),
|
|
def->name);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(snap = virDomainSnapshotObjNew()))
|
|
return NULL;
|
|
|
|
if (virHashAddEntry(snapshots->objs, snap->def->name, snap) < 0) {
|
|
VIR_FREE(snap);
|
|
return NULL;
|
|
}
|
|
snap->def = def;
|
|
|
|
return snap;
|
|
}
|
|
|
|
/* Snapshot Obj List functions */
|
|
static void
|
|
virDomainSnapshotObjListDataFree(void *payload,
|
|
const void *name ATTRIBUTE_UNUSED)
|
|
{
|
|
virDomainSnapshotObjPtr obj = payload;
|
|
|
|
virDomainSnapshotObjFree(obj);
|
|
}
|
|
|
|
virDomainSnapshotObjListPtr
|
|
virDomainSnapshotObjListNew(void)
|
|
{
|
|
virDomainSnapshotObjListPtr snapshots;
|
|
if (VIR_ALLOC(snapshots) < 0)
|
|
return NULL;
|
|
snapshots->objs = virHashCreate(50, virDomainSnapshotObjListDataFree);
|
|
if (!snapshots->objs) {
|
|
VIR_FREE(snapshots);
|
|
return NULL;
|
|
}
|
|
return snapshots;
|
|
}
|
|
|
|
void
|
|
virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
if (!snapshots)
|
|
return;
|
|
virHashFree(snapshots->objs);
|
|
VIR_FREE(snapshots);
|
|
}
|
|
|
|
struct virDomainSnapshotNameData {
|
|
char **const names;
|
|
int maxnames;
|
|
unsigned int flags;
|
|
int count;
|
|
bool error;
|
|
};
|
|
|
|
static int virDomainSnapshotObjListCopyNames(void *payload,
|
|
const void *name ATTRIBUTE_UNUSED,
|
|
void *opaque)
|
|
{
|
|
virDomainSnapshotObjPtr obj = payload;
|
|
struct virDomainSnapshotNameData *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_SNAPSHOT_LIST_LEAVES) && obj->nchildren)
|
|
return 0;
|
|
if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES) && !obj->nchildren)
|
|
return 0;
|
|
|
|
if (data->flags & VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS) {
|
|
if (!(data->flags & VIR_DOMAIN_SNAPSHOT_LIST_INACTIVE) &&
|
|
obj->def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF)
|
|
return 0;
|
|
if (!(data->flags & VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY) &&
|
|
obj->def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)
|
|
return 0;
|
|
if (!(data->flags & VIR_DOMAIN_SNAPSHOT_LIST_ACTIVE) &&
|
|
obj->def->state != VIR_DOMAIN_SNAPSHOT_SHUTOFF &&
|
|
obj->def->state != VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)
|
|
return 0;
|
|
}
|
|
|
|
if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL) &&
|
|
virDomainSnapshotIsExternal(obj))
|
|
return 0;
|
|
if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL) &&
|
|
!virDomainSnapshotIsExternal(obj))
|
|
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;
|
|
}
|
|
|
|
int
|
|
virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotObjPtr from,
|
|
char **const names, int maxnames,
|
|
unsigned int flags)
|
|
{
|
|
struct virDomainSnapshotNameData 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_SNAPSHOT_LIST_ROOTS;
|
|
from = &snapshots->metaroot;
|
|
}
|
|
|
|
/* We handle LIST_ROOT/LIST_DESCENDANTS and LIST_TOPOLOGICAL directly,
|
|
* mask those bits out to determine when we must use the filter callback. */
|
|
data.flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS |
|
|
VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL);
|
|
|
|
/* If this common code is being used, we assume that all snapshots
|
|
* 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 internal snapshots without the
|
|
* use of metadata. */
|
|
if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA) ==
|
|
VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA)
|
|
return 0;
|
|
data.flags &= ~VIR_DOMAIN_SNAPSHOT_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_SNAPSHOT_FILTERS_LEAVES) ==
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES)
|
|
data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES;
|
|
if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS) ==
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS)
|
|
data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS;
|
|
if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION) ==
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION)
|
|
data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION;
|
|
|
|
if (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) {
|
|
/* We could just always do a topological visit; but it is
|
|
* possible to optimize for less stack usage and time when a
|
|
* simpler full hashtable visit or counter will do. */
|
|
if (from->def || (names &&
|
|
(flags & VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL)))
|
|
virDomainSnapshotForEachDescendant(from,
|
|
virDomainSnapshotObjListCopyNames,
|
|
&data);
|
|
else if (names || data.flags)
|
|
virHashForEach(snapshots->objs, virDomainSnapshotObjListCopyNames,
|
|
&data);
|
|
else
|
|
data.count = virHashSize(snapshots->objs);
|
|
} else if (names || data.flags) {
|
|
virDomainSnapshotForEachChild(from,
|
|
virDomainSnapshotObjListCopyNames, &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;
|
|
}
|
|
|
|
int
|
|
virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotObjPtr from,
|
|
unsigned int flags)
|
|
{
|
|
return virDomainSnapshotObjListGetNames(snapshots, from, NULL, 0, flags);
|
|
}
|
|
|
|
virDomainSnapshotObjPtr
|
|
virDomainSnapshotFindByName(virDomainSnapshotObjListPtr snapshots,
|
|
const char *name)
|
|
{
|
|
return name ? virHashLookup(snapshots->objs, name) : &snapshots->metaroot;
|
|
}
|
|
|
|
|
|
/* Return the number of objects currently in the list */
|
|
int
|
|
virDomainSnapshotObjListSize(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
return virHashSize(snapshots->objs);
|
|
}
|
|
|
|
|
|
/* Return the current snapshot, or NULL */
|
|
virDomainSnapshotObjPtr
|
|
virDomainSnapshotGetCurrent(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
return snapshots->current;
|
|
}
|
|
|
|
|
|
/* Return the current snapshot's name, or NULL */
|
|
const char *
|
|
virDomainSnapshotGetCurrentName(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
if (snapshots->current)
|
|
return snapshots->current->def->name;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Return true if name matches the current snapshot */
|
|
bool
|
|
virDomainSnapshotIsCurrentName(virDomainSnapshotObjListPtr snapshots,
|
|
const char *name)
|
|
{
|
|
return snapshots->current && STREQ(snapshots->current->def->name, name);
|
|
}
|
|
|
|
|
|
/* Update the current snapshot, using NULL if no current remains */
|
|
void
|
|
virDomainSnapshotSetCurrent(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotObjPtr snapshot)
|
|
{
|
|
snapshots->current = snapshot;
|
|
}
|
|
|
|
|
|
/* Remove snapshot from the list; return true if it was current */
|
|
bool
|
|
virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotObjPtr snapshot)
|
|
{
|
|
bool ret = snapshots->current == snapshot;
|
|
virHashRemoveEntry(snapshots->objs, snapshot->def->name);
|
|
if (ret)
|
|
snapshots->current = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/* Remove all snapshots tracked in the list */
|
|
void
|
|
virDomainSnapshotObjListRemoveAll(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
virHashRemoveAll(snapshots->objs);
|
|
virDomainSnapshotDropChildren(&snapshots->metaroot);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots,
|
|
virHashIterator iter,
|
|
void *data)
|
|
{
|
|
return virHashForEach(snapshots->objs, iter, data);
|
|
}
|
|
|
|
|
|
/* Struct and callback function used as a hash table callback; each call
|
|
* inspects the pre-existing snapshot->def->parent field, and adjusts
|
|
* the snapshot->parent field as well as the parent's child fields to
|
|
* wire up the hierarchical relations for the given snapshot. The error
|
|
* indicator gets set if a parent is missing or a requested parent would
|
|
* cause a circular parent chain. */
|
|
struct snapshot_set_relation {
|
|
virDomainSnapshotObjListPtr snapshots;
|
|
int err;
|
|
};
|
|
static int
|
|
virDomainSnapshotSetRelations(void *payload,
|
|
const void *name ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
virDomainSnapshotObjPtr obj = payload;
|
|
struct snapshot_set_relation *curr = data;
|
|
virDomainSnapshotObjPtr tmp;
|
|
virDomainSnapshotObjPtr parent;
|
|
|
|
parent = virDomainSnapshotFindByName(curr->snapshots, obj->def->parent);
|
|
if (!parent) {
|
|
curr->err = -1;
|
|
parent = &curr->snapshots->metaroot;
|
|
VIR_WARN("snapshot %s lacks parent", obj->def->name);
|
|
} else {
|
|
tmp = parent;
|
|
while (tmp && tmp->def) {
|
|
if (tmp == obj) {
|
|
curr->err = -1;
|
|
parent = &curr->snapshots->metaroot;
|
|
VIR_WARN("snapshot %s in circular chain", obj->def->name);
|
|
break;
|
|
}
|
|
tmp = tmp->parent;
|
|
}
|
|
}
|
|
virDomainSnapshotSetParent(obj, parent);
|
|
return 0;
|
|
}
|
|
|
|
/* Populate parent link and child count of all snapshots, with all
|
|
* assigned defs having relations starting as 0/NULL. Return 0 on
|
|
* success, -1 if a parent is missing or if a circular relationship
|
|
* was requested. */
|
|
int
|
|
virDomainSnapshotUpdateRelations(virDomainSnapshotObjListPtr snapshots)
|
|
{
|
|
struct snapshot_set_relation act = { snapshots, 0 };
|
|
|
|
virDomainSnapshotDropChildren(&snapshots->metaroot);
|
|
virHashForEach(snapshots->objs, virDomainSnapshotSetRelations, &act);
|
|
if (act.err)
|
|
snapshots->current = NULL;
|
|
return act.err;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots,
|
|
virDomainSnapshotObjPtr from,
|
|
virDomainPtr dom,
|
|
virDomainSnapshotPtr **snaps,
|
|
unsigned int flags)
|
|
{
|
|
int count = virDomainSnapshotObjListNum(snapshots, from, flags);
|
|
virDomainSnapshotPtr *list = NULL;
|
|
char **names;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (!snaps || count < 0)
|
|
return count;
|
|
if (VIR_ALLOC_N(names, count) < 0 ||
|
|
VIR_ALLOC_N(list, count + 1) < 0)
|
|
goto cleanup;
|
|
|
|
if (virDomainSnapshotObjListGetNames(snapshots, from, names, count,
|
|
flags) < 0)
|
|
goto cleanup;
|
|
for (i = 0; i < count; i++)
|
|
if ((list[i] = virGetDomainSnapshot(dom, names[i])) == NULL)
|
|
goto cleanup;
|
|
|
|
ret = count;
|
|
*snaps = 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;
|
|
}
|