2019-03-15 02:19:18 +00:00
|
|
|
/*
|
|
|
|
* 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 {
|
2019-03-22 04:45:25 +00:00
|
|
|
/* name string -> virDomainMomentObj mapping
|
2019-03-15 02:19:18 +00:00
|
|
|
* for O(1), lockless lookup-by-name */
|
|
|
|
virHashTable *objs;
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObj metaroot; /* Special parent of all root snapshots */
|
|
|
|
virDomainMomentObjPtr current; /* The current snapshot, if any */
|
2019-03-15 02:19:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-03-11 11:50:23 +00:00
|
|
|
/* 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.
|
2019-03-15 02:19:18 +00:00
|
|
|
*/
|
|
|
|
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);
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snap;
|
2019-03-15 02:19:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-03-17 03:38:33 +00:00
|
|
|
if (virDomainSnapshotObjListSize(snapshots) != 0) {
|
2019-03-15 02:19:18 +00:00
|
|
|
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;
|
|
|
|
|
snapshot: Drop virDomainSnapshotDef.current
The only use for the 'current' member of virDomainSnapshotDef was with
the PARSE/FORMAT_INTERNAL flag for controlling an internal-use
<active> element marking whether a particular snapshot definition was
current, and even then, only by the qemu driver on output, and by qemu
and test driver on input. But this duplicates vm->snapshot_current,
and gets in the way of potential simplifications to have qemu store a
single file for all snapshots rather than one file per snapshot. Get
rid of the member by adding a bool* parameter during parse (ignored if
the PARSE_INTERNAL flag is not set), and by adding a new flag during
format (if FORMAT_INTERNAL is set, the value printed in <active>
depends on the new FORMAT_CURRENT).
Then update the qemu driver accordingly, which involves hoisting
assignments to vm->current_snapshot to occur prior to any point where
a snapshot XML file is written (although qemu kept
vm->current_snapshot and snapshot->def_current in sync by the end of
the function, they were not always identical in the middle of
functions, so the shuffling gets a bit interesting). Later patches
will clean up some of that confusing churn to vm->current_snapshot.
Note: even if later patches refactor qemu to no longer use
FORMAT_INTERNAL for output (by storing bulk snapshot XML instead), we
will always need PARSE_INTERNAL for input (because on upgrade, a new
libvirt still has to parse XML left from a previous libvirt).
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: John Ferlan <jferlan@redhat.com>
2019-03-19 03:56:19 +00:00
|
|
|
def = virDomainSnapshotDefParseNode(xml, nodes[i], caps, xmlopt, NULL,
|
|
|
|
flags);
|
2019-03-15 02:19:18 +00:00
|
|
|
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) {
|
2019-03-21 20:00:08 +00:00
|
|
|
snap = virDomainSnapshotFindByName(snapshots, current);
|
|
|
|
if (!snap) {
|
2019-03-15 02:19:18 +00:00
|
|
|
virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT,
|
|
|
|
_("no snapshot matching current='%s'"), current);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2019-03-21 20:00:08 +00:00
|
|
|
virDomainSnapshotSetCurrent(snapshots, snap);
|
2019-03-15 02:19:18 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 11:50:23 +00:00
|
|
|
ret = n;
|
2019-03-15 02:19:18 +00:00
|
|
|
cleanup:
|
|
|
|
if (ret < 0) {
|
|
|
|
/* There were no snapshots before this call; so on error, just
|
|
|
|
* blindly delete anything created before the failure. */
|
2019-03-17 03:38:33 +00:00
|
|
|
virDomainSnapshotObjListRemoveAll(snapshots);
|
2019-03-15 02:19:18 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snap = payload;
|
2019-03-15 02:19:18 +00:00
|
|
|
struct virDomainSnapshotFormatData *data = opaque;
|
|
|
|
return virDomainSnapshotDefFormatInternal(data->buf, data->uuidstr,
|
2019-03-18 21:13:50 +00:00
|
|
|
virDomainSnapshotObjGetDef(snap),
|
|
|
|
data->caps, data->xmlopt,
|
|
|
|
data->flags);
|
2019-03-15 02:19:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-11 11:50:23 +00:00
|
|
|
/* Format the XML for all snapshots in the list into buf. @flags is
|
|
|
|
* virDomainSnapshotFormatFlags. On error, clear the buffer and return
|
|
|
|
* -1. */
|
2019-03-15 02:19:18 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2019-03-11 11:50:23 +00:00
|
|
|
virCheckFlags(VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE, -1);
|
2019-03-15 02:19:18 +00:00
|
|
|
virBufferAddLit(buf, "<snapshots");
|
2019-03-21 20:00:08 +00:00
|
|
|
virBufferEscapeString(buf, " current='%s'",
|
|
|
|
virDomainSnapshotGetCurrentName(snapshots));
|
2019-03-15 02:19:18 +00:00
|
|
|
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 */
|
2019-03-22 04:45:25 +00:00
|
|
|
static virDomainMomentObjPtr virDomainMomentObjNew(void)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snapshot;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
|
|
|
if (VIR_ALLOC(snapshot) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
VIR_DEBUG("obj=%p", snapshot);
|
|
|
|
|
|
|
|
return snapshot;
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
static void virDomainMomentObjFree(virDomainMomentObjPtr snapshot)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
|
|
|
if (!snapshot)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_DEBUG("obj=%p", snapshot);
|
|
|
|
|
2019-03-18 21:13:50 +00:00
|
|
|
virDomainSnapshotDefFree(virDomainSnapshotObjGetDef(snapshot));
|
2019-03-15 02:19:18 +00:00
|
|
|
VIR_FREE(snapshot);
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots,
|
|
|
|
virDomainSnapshotDefPtr def)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snap;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
2019-03-22 02:02:19 +00:00
|
|
|
if (virHashLookup(snapshots->objs, def->common.name) != NULL) {
|
2019-03-15 02:19:18 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unexpected domain snapshot %s already exists"),
|
2019-03-22 02:02:19 +00:00
|
|
|
def->common.name);
|
2019-03-15 02:19:18 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
if (!(snap = virDomainMomentObjNew()))
|
2019-03-15 02:19:18 +00:00
|
|
|
return NULL;
|
|
|
|
|
2019-03-22 04:44:33 +00:00
|
|
|
if (virHashAddEntry(snapshots->objs, snap->def->name, snap) < 0) {
|
2019-03-15 02:19:18 +00:00
|
|
|
VIR_FREE(snap);
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-03-22 04:44:33 +00:00
|
|
|
snap->def = &def->common;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
|
|
|
return snap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Snapshot Obj List functions */
|
2019-03-20 02:33:23 +00:00
|
|
|
static bool
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainSnapshotFilter(virDomainMomentObjPtr obj,
|
2019-03-20 02:33:23 +00:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
virDomainSnapshotDefPtr def = virDomainSnapshotObjGetDef(obj);
|
|
|
|
|
|
|
|
/* Caller has already sanitized flags and performed filtering on
|
|
|
|
* DESCENDANTS and LEAVES. */
|
|
|
|
if (flags & VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS) {
|
|
|
|
if (!(flags & VIR_DOMAIN_SNAPSHOT_LIST_INACTIVE) &&
|
|
|
|
def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF)
|
|
|
|
return false;
|
|
|
|
if (!(flags & VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY) &&
|
|
|
|
def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)
|
|
|
|
return false;
|
|
|
|
if (!(flags & VIR_DOMAIN_SNAPSHOT_LIST_ACTIVE) &&
|
|
|
|
def->state != VIR_DOMAIN_SNAPSHOT_SHUTOFF &&
|
|
|
|
def->state != VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL) &&
|
|
|
|
virDomainSnapshotIsExternal(obj))
|
|
|
|
return false;
|
|
|
|
if ((flags & VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL) &&
|
|
|
|
!virDomainSnapshotIsExternal(obj))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-15 02:19:18 +00:00
|
|
|
static void
|
|
|
|
virDomainSnapshotObjListDataFree(void *payload,
|
|
|
|
const void *name ATTRIBUTE_UNUSED)
|
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr obj = payload;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjFree(obj);
|
2019-03-15 02:19:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-03-20 02:33:23 +00:00
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
struct virDomainMomentNameData {
|
2019-03-15 02:19:18 +00:00
|
|
|
char **const names;
|
|
|
|
int maxnames;
|
|
|
|
unsigned int flags;
|
|
|
|
int count;
|
|
|
|
bool error;
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjListFilter filter;
|
2019-03-15 02:19:18 +00:00
|
|
|
};
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
static int virDomainMomentObjListCopyNames(void *payload,
|
|
|
|
const void *name ATTRIBUTE_UNUSED,
|
|
|
|
void *opaque)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
struct virDomainMomentNameData *data = opaque;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-03-20 02:33:23 +00:00
|
|
|
if (data->filter(obj, data->flags))
|
2019-03-15 02:19:18 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (data->names && data->count < data->maxnames &&
|
2019-03-22 04:44:33 +00:00
|
|
|
VIR_STRDUP(data->names[data->count], obj->def->name) < 0) {
|
2019-03-15 02:19:18 +00:00
|
|
|
data->error = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
data->count++;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots,
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr from,
|
2019-03-20 02:33:23 +00:00
|
|
|
char **const names,
|
|
|
|
int maxnames,
|
2019-03-15 02:19:18 +00:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
struct virDomainMomentNameData data = { names, maxnames, flags, 0,
|
|
|
|
false, virDomainSnapshotFilter };
|
2019-03-15 02:19:18 +00:00
|
|
|
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)))
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentForEachDescendant(from,
|
|
|
|
virDomainMomentObjListCopyNames,
|
|
|
|
&data);
|
2019-03-15 02:19:18 +00:00
|
|
|
else if (names || data.flags)
|
2019-03-22 04:45:25 +00:00
|
|
|
virHashForEach(snapshots->objs, virDomainMomentObjListCopyNames,
|
2019-03-15 02:19:18 +00:00
|
|
|
&data);
|
|
|
|
else
|
|
|
|
data.count = virHashSize(snapshots->objs);
|
|
|
|
} else if (names || data.flags) {
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentForEachChild(from,
|
|
|
|
virDomainMomentObjListCopyNames, &data);
|
2019-03-15 02:19:18 +00:00
|
|
|
} 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,
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr from,
|
2019-03-15 02:19:18 +00:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
return virDomainSnapshotObjListGetNames(snapshots, from, NULL, 0, flags);
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr
|
2019-03-15 02:19:18 +00:00
|
|
|
virDomainSnapshotFindByName(virDomainSnapshotObjListPtr snapshots,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
return name ? virHashLookup(snapshots->objs, name) : &snapshots->metaroot;
|
|
|
|
}
|
|
|
|
|
2019-03-21 20:00:08 +00:00
|
|
|
|
2019-03-17 03:38:33 +00:00
|
|
|
/* Return the number of objects currently in the list */
|
|
|
|
int
|
|
|
|
virDomainSnapshotObjListSize(virDomainSnapshotObjListPtr snapshots)
|
|
|
|
{
|
|
|
|
return virHashSize(snapshots->objs);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-21 20:00:08 +00:00
|
|
|
/* Return the current snapshot, or NULL */
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr
|
2019-03-21 20:00:08 +00:00
|
|
|
virDomainSnapshotGetCurrent(virDomainSnapshotObjListPtr snapshots)
|
|
|
|
{
|
|
|
|
return snapshots->current;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Return the current snapshot's name, or NULL */
|
|
|
|
const char *
|
|
|
|
virDomainSnapshotGetCurrentName(virDomainSnapshotObjListPtr snapshots)
|
|
|
|
{
|
|
|
|
if (snapshots->current)
|
2019-03-22 04:44:33 +00:00
|
|
|
return snapshots->current->def->name;
|
2019-03-21 20:00:08 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Return true if name matches the current snapshot */
|
|
|
|
bool
|
|
|
|
virDomainSnapshotIsCurrentName(virDomainSnapshotObjListPtr snapshots,
|
|
|
|
const char *name)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
2019-03-22 04:44:33 +00:00
|
|
|
return snapshots->current && STREQ(snapshots->current->def->name, name);
|
2019-03-21 20:00:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Update the current snapshot, using NULL if no current remains */
|
|
|
|
void
|
|
|
|
virDomainSnapshotSetCurrent(virDomainSnapshotObjListPtr snapshots,
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snapshot)
|
2019-03-21 20:00:08 +00:00
|
|
|
{
|
|
|
|
snapshots->current = snapshot;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Remove snapshot from the list; return true if it was current */
|
|
|
|
bool
|
|
|
|
virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots,
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr snapshot)
|
2019-03-21 20:00:08 +00:00
|
|
|
{
|
|
|
|
bool ret = snapshots->current == snapshot;
|
2019-03-22 04:44:33 +00:00
|
|
|
virHashRemoveEntry(snapshots->objs, snapshot->def->name);
|
2019-03-21 20:00:08 +00:00
|
|
|
if (ret)
|
|
|
|
snapshots->current = NULL;
|
|
|
|
return ret;
|
2019-03-15 02:19:18 +00:00
|
|
|
}
|
|
|
|
|
2019-03-17 03:38:33 +00:00
|
|
|
/* Remove all snapshots tracked in the list */
|
|
|
|
void
|
|
|
|
virDomainSnapshotObjListRemoveAll(virDomainSnapshotObjListPtr snapshots)
|
|
|
|
{
|
|
|
|
virHashRemoveAll(snapshots->objs);
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentDropChildren(&snapshots->metaroot);
|
2019-03-17 03:38:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-15 02:19:18 +00:00
|
|
|
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. */
|
2019-03-22 04:45:25 +00:00
|
|
|
struct moment_set_relation {
|
2019-03-15 02:19:18 +00:00
|
|
|
virDomainSnapshotObjListPtr snapshots;
|
|
|
|
int err;
|
|
|
|
};
|
|
|
|
static int
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentSetRelations(void *payload,
|
|
|
|
const void *name ATTRIBUTE_UNUSED,
|
|
|
|
void *data)
|
2019-03-15 02:19:18 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
struct moment_set_relation *curr = data;
|
|
|
|
virDomainMomentObjPtr tmp;
|
|
|
|
virDomainMomentObjPtr parent;
|
2019-03-15 02:19:18 +00:00
|
|
|
|
2019-03-22 04:44:33 +00:00
|
|
|
parent = virDomainSnapshotFindByName(curr->snapshots, obj->def->parent);
|
2019-03-17 03:38:33 +00:00
|
|
|
if (!parent) {
|
2019-03-15 02:19:18 +00:00
|
|
|
curr->err = -1;
|
2019-03-17 03:38:33 +00:00
|
|
|
parent = &curr->snapshots->metaroot;
|
2019-03-22 04:44:33 +00:00
|
|
|
VIR_WARN("snapshot %s lacks parent", obj->def->name);
|
2019-03-15 02:19:18 +00:00
|
|
|
} else {
|
2019-03-17 03:38:33 +00:00
|
|
|
tmp = parent;
|
2019-03-15 02:19:18 +00:00
|
|
|
while (tmp && tmp->def) {
|
|
|
|
if (tmp == obj) {
|
|
|
|
curr->err = -1;
|
2019-03-17 03:38:33 +00:00
|
|
|
parent = &curr->snapshots->metaroot;
|
2019-03-22 04:44:33 +00:00
|
|
|
VIR_WARN("snapshot %s in circular chain", obj->def->name);
|
2019-03-15 02:19:18 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
tmp = tmp->parent;
|
|
|
|
}
|
|
|
|
}
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentSetParent(obj, parent);
|
2019-03-15 02:19:18 +00:00
|
|
|
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)
|
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
struct moment_set_relation act = { snapshots, 0 };
|
2019-03-15 02:19:18 +00:00
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentDropChildren(&snapshots->metaroot);
|
|
|
|
virHashForEach(snapshots->objs, virDomainMomentSetRelations, &act);
|
2019-03-21 20:00:08 +00:00
|
|
|
if (act.err)
|
|
|
|
snapshots->current = NULL;
|
2019-03-15 02:19:18 +00:00
|
|
|
return act.err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots,
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr from,
|
2019-03-15 02:19:18 +00:00
|
|
|
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;
|
|
|
|
}
|