2019-03-15 01:03:40 +00:00
|
|
|
/*
|
2019-03-22 04:46:26 +00:00
|
|
|
* virdomainmomentobjlist.c: handle snapshot/checkpoint objects
|
2019-03-15 01:03:40 +00:00
|
|
|
* (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"
|
2019-03-22 04:46:26 +00:00
|
|
|
#include "virdomainmomentobjlist.h"
|
2019-03-15 01:03:40 +00:00
|
|
|
#include "virlog.h"
|
|
|
|
#include "virerror.h"
|
2019-03-22 04:46:26 +00:00
|
|
|
#include "virstring.h"
|
|
|
|
#include "moment_conf.h"
|
2019-04-01 14:28:05 +00:00
|
|
|
#include "viralloc.h"
|
2019-03-15 01:03:40 +00:00
|
|
|
|
2019-03-22 05:39:02 +00:00
|
|
|
/* FIXME: using virObject would allow us to not need this */
|
|
|
|
#include "virdomainsnapshotobjlist.h"
|
backup: Allow for lists of checkpoint objects
Create a new file for managing a list of checkpoint objects, borrowing
heavily from existing virDomainSnapshotObjList paradigms.
Note that while snapshots definitely have a use case for multiple
children to a single parent (create a base snapshot, create a child
snapshot, revert to the base, then create another child snapshot),
it's harder to predict how checkpoints will play out with reverting to
prior points in time. Thus, in initial use, given a list of
checkpoints, you never have more than one child, and we can treat the
most-recent leaf node as the parent of the next node creation, without
having to expose a notion of a current node in XML or public API.
However, as the snapshot machinery is already generic, it is easier to
reuse the generic machinery that tracks relations between domain
moments than it is to open-code a new list-management scheme just for
checkpoints (hence, we still have internal functions related to a
current checkpoint, even though that has no observable effect
externally, as well as the addition of a function to easily find the
lone leaf in the list to use as the current checkpoint).
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-07-19 02:24:54 +00:00
|
|
|
#include "virdomaincheckpointobjlist.h"
|
2019-03-22 05:39:02 +00:00
|
|
|
|
2019-03-22 04:46:26 +00:00
|
|
|
#define VIR_FROM_THIS VIR_FROM_DOMAIN
|
2019-03-15 01:03:40 +00:00
|
|
|
|
2019-03-22 04:46:26 +00:00
|
|
|
VIR_LOG_INIT("conf.virdomainmomentobjlist");
|
2019-03-15 01:03:40 +00:00
|
|
|
|
2019-03-22 05:39:02 +00:00
|
|
|
/* Opaque struct */
|
|
|
|
struct _virDomainMomentObjList {
|
|
|
|
/* name string -> virDomainMomentObj mapping
|
|
|
|
* for O(1), lockless lookup-by-name */
|
|
|
|
virHashTable *objs;
|
|
|
|
|
|
|
|
virDomainMomentObj metaroot; /* Special parent of all root moments */
|
|
|
|
virDomainMomentObjPtr current; /* The current moment, if any */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
/* Run iter(data) on all direct children of moment, while ignoring all
|
|
|
|
* other entries in moments. Return the number of children
|
2019-03-15 01:03:40 +00:00
|
|
|
* visited. No particular ordering is guaranteed. */
|
|
|
|
int
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentForEachChild(virDomainMomentObjPtr moment,
|
|
|
|
virHashIterator iter,
|
|
|
|
void *data)
|
2019-03-15 01:03:40 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr child = moment->first_child;
|
2019-03-15 01:03:40 +00:00
|
|
|
|
|
|
|
while (child) {
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr next = child->sibling;
|
2019-03-22 04:44:33 +00:00
|
|
|
(iter)(child, child->def->name, data);
|
2019-03-15 01:03:40 +00:00
|
|
|
child = next;
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
return moment->nchildren;
|
2019-03-15 01:03:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
struct moment_act_on_descendant {
|
2019-03-15 01:03:40 +00:00
|
|
|
int number;
|
|
|
|
virHashIterator iter;
|
|
|
|
void *data;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentActOnDescendant(void *payload,
|
2020-10-21 11:31:16 +00:00
|
|
|
const char *name,
|
2019-03-22 04:45:25 +00:00
|
|
|
void *data)
|
2019-03-15 01:03:40 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
struct moment_act_on_descendant *curr = data;
|
snapshot: Fix use-after-free during snapshot delete
Commit b647d2195 introduced a use-after-free situation when the caller
is trying to delete a snapshot and its children: if the callback
function deletes the parent, it is no longer safe to query the parent
to learn which children also need to be deleted (where we previously
saved deleting the parent for last). To fix the problem, while still
maintaining support for topological visits of callback functions, we
have to stash off any information needed for later traversal prior to
using a callback function (virDomainMomentForEachChild already does
this, it is only virDomainMomentActOnDescendant that was running into
problems).
Sadly, the testsuite did not cover the problem at the time. Worse,
even though I later added commit 280a2b41e to catch problems like
this, and even though that test is indeed sufficient to detect the
problem when run under valgrind or suitable MALLOC_PERTURB_ settings,
I'm guilty of not running the test in such an environment. Thus,
v5.2.0 has a regression that could have been prevented had we used the
testsuite to its full power. On the bright side, deleting snapshots
requires ACL domain:snapshot, which is arguably as powerful as
domain:write, so I don't think this use-after-free forms a security
hole.
At some point, it would be nice to convert virDomainMomentObj into a
virObject, at which point, the solution is even simpler: add
virObjectRef/Unref around the callback. But as that will require
auditing even more places in the code, I went with the simplest patch
for the regression fix.
Fixes: b647d2195
Reported-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Tested-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
2019-04-08 16:45:47 +00:00
|
|
|
virDomainMomentObj tmp = *obj;
|
2019-03-15 01:03:40 +00:00
|
|
|
|
snapshot: Fix use-after-free during snapshot delete
Commit b647d2195 introduced a use-after-free situation when the caller
is trying to delete a snapshot and its children: if the callback
function deletes the parent, it is no longer safe to query the parent
to learn which children also need to be deleted (where we previously
saved deleting the parent for last). To fix the problem, while still
maintaining support for topological visits of callback functions, we
have to stash off any information needed for later traversal prior to
using a callback function (virDomainMomentForEachChild already does
this, it is only virDomainMomentActOnDescendant that was running into
problems).
Sadly, the testsuite did not cover the problem at the time. Worse,
even though I later added commit 280a2b41e to catch problems like
this, and even though that test is indeed sufficient to detect the
problem when run under valgrind or suitable MALLOC_PERTURB_ settings,
I'm guilty of not running the test in such an environment. Thus,
v5.2.0 has a regression that could have been prevented had we used the
testsuite to its full power. On the bright side, deleting snapshots
requires ACL domain:snapshot, which is arguably as powerful as
domain:write, so I don't think this use-after-free forms a security
hole.
At some point, it would be nice to convert virDomainMomentObj into a
virObject, at which point, the solution is even simpler: add
virObjectRef/Unref around the callback. But as that will require
auditing even more places in the code, I went with the simplest patch
for the regression fix.
Fixes: b647d2195
Reported-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Tested-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
2019-04-08 16:45:47 +00:00
|
|
|
/* Careful: curr->iter can delete obj, hence the need for tmp */
|
2019-03-15 01:03:40 +00:00
|
|
|
(curr->iter)(payload, name, curr->data);
|
snapshot: Fix use-after-free during snapshot delete
Commit b647d2195 introduced a use-after-free situation when the caller
is trying to delete a snapshot and its children: if the callback
function deletes the parent, it is no longer safe to query the parent
to learn which children also need to be deleted (where we previously
saved deleting the parent for last). To fix the problem, while still
maintaining support for topological visits of callback functions, we
have to stash off any information needed for later traversal prior to
using a callback function (virDomainMomentForEachChild already does
this, it is only virDomainMomentActOnDescendant that was running into
problems).
Sadly, the testsuite did not cover the problem at the time. Worse,
even though I later added commit 280a2b41e to catch problems like
this, and even though that test is indeed sufficient to detect the
problem when run under valgrind or suitable MALLOC_PERTURB_ settings,
I'm guilty of not running the test in such an environment. Thus,
v5.2.0 has a regression that could have been prevented had we used the
testsuite to its full power. On the bright side, deleting snapshots
requires ACL domain:snapshot, which is arguably as powerful as
domain:write, so I don't think this use-after-free forms a security
hole.
At some point, it would be nice to convert virDomainMomentObj into a
virObject, at which point, the solution is even simpler: add
virObjectRef/Unref around the callback. But as that will require
auditing even more places in the code, I went with the simplest patch
for the regression fix.
Fixes: b647d2195
Reported-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Tested-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
2019-04-08 16:45:47 +00:00
|
|
|
curr->number += 1 + virDomainMomentForEachDescendant(&tmp,
|
2019-03-22 04:46:26 +00:00
|
|
|
curr->iter,
|
|
|
|
curr->data);
|
2019-03-15 01:03:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
/* Run iter(data) on all descendants of moment, while ignoring all
|
|
|
|
* other entries in moments. Return the number of descendants
|
2019-03-15 01:03:40 +00:00
|
|
|
* visited. The visit is guaranteed to be topological, but no
|
|
|
|
* particular order between siblings is guaranteed. */
|
|
|
|
int
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentForEachDescendant(virDomainMomentObjPtr moment,
|
|
|
|
virHashIterator iter,
|
|
|
|
void *data)
|
2019-03-15 01:03:40 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
struct moment_act_on_descendant act;
|
2019-03-15 01:03:40 +00:00
|
|
|
|
|
|
|
act.number = 0;
|
|
|
|
act.iter = iter;
|
|
|
|
act.data = data;
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentForEachChild(moment,
|
|
|
|
virDomainMomentActOnDescendant, &act);
|
2019-03-15 01:03:40 +00:00
|
|
|
|
|
|
|
return act.number;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
/* Prepare to reparent or delete moment, by removing it from its
|
2019-03-15 01:03:40 +00:00
|
|
|
* current listed parent. Note that when bulk removing all children
|
|
|
|
* of a parent, it is faster to just 0 the count rather than calling
|
|
|
|
* this function on each child. */
|
|
|
|
void
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentDropParent(virDomainMomentObjPtr moment)
|
2019-03-15 01:03:40 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentObjPtr prev = NULL;
|
|
|
|
virDomainMomentObjPtr curr = NULL;
|
2019-03-15 01:03:40 +00:00
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
moment->parent->nchildren--;
|
|
|
|
curr = moment->parent->first_child;
|
|
|
|
while (curr != moment) {
|
2019-03-15 01:03:40 +00:00
|
|
|
if (!curr) {
|
2019-03-22 04:46:26 +00:00
|
|
|
VIR_WARN("inconsistent moment relations");
|
2019-03-15 01:03:40 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
prev = curr;
|
|
|
|
curr = curr->sibling;
|
|
|
|
}
|
|
|
|
if (prev)
|
2019-03-22 04:45:25 +00:00
|
|
|
prev->sibling = moment->sibling;
|
2019-03-15 01:03:40 +00:00
|
|
|
else
|
2019-03-22 04:45:25 +00:00
|
|
|
moment->parent->first_child = moment->sibling;
|
|
|
|
moment->parent = NULL;
|
|
|
|
moment->sibling = NULL;
|
2019-03-15 01:03:40 +00:00
|
|
|
}
|
2019-03-21 20:33:21 +00:00
|
|
|
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
/* Update @moment to no longer have children. */
|
2019-03-17 03:38:33 +00:00
|
|
|
void
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentDropChildren(virDomainMomentObjPtr moment)
|
2019-03-17 03:38:33 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
moment->nchildren = 0;
|
|
|
|
moment->first_child = NULL;
|
2019-03-17 03:38:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-22 04:45:25 +00:00
|
|
|
/* Add @moment to @parent's list of children. */
|
2019-07-24 03:26:05 +00:00
|
|
|
static void
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentSetParent(virDomainMomentObjPtr moment,
|
|
|
|
virDomainMomentObjPtr parent)
|
2019-03-17 03:38:33 +00:00
|
|
|
{
|
2019-03-22 04:45:25 +00:00
|
|
|
moment->parent = parent;
|
2019-03-17 03:38:33 +00:00
|
|
|
parent->nchildren++;
|
2019-03-22 04:45:25 +00:00
|
|
|
moment->sibling = parent->first_child;
|
|
|
|
parent->first_child = moment;
|
2019-03-17 03:38:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-24 03:26:05 +00:00
|
|
|
/* Add @moment to the appropriate parent's list of children. The
|
|
|
|
* caller must ensure that moment->def->parent_name is either NULL
|
|
|
|
* (for a new root) or set to an existing moment already in the
|
|
|
|
* list. */
|
|
|
|
void
|
|
|
|
virDomainMomentLinkParent(virDomainMomentObjListPtr moments,
|
|
|
|
virDomainMomentObjPtr moment)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr parent;
|
|
|
|
|
|
|
|
parent = virDomainMomentFindByName(moments, moment->def->parent_name);
|
|
|
|
if (!parent) {
|
|
|
|
parent = &moments->metaroot;
|
|
|
|
if (moment->def->parent_name)
|
|
|
|
VIR_WARN("moment %s lacks parent %s", moment->def->name,
|
|
|
|
moment->def->parent_name);
|
|
|
|
}
|
|
|
|
virDomainMomentSetParent(moment, parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-21 20:33:21 +00:00
|
|
|
/* Take all children of @from and convert them into children of @to. */
|
|
|
|
void
|
2019-03-22 04:45:25 +00:00
|
|
|
virDomainMomentMoveChildren(virDomainMomentObjPtr from,
|
|
|
|
virDomainMomentObjPtr to)
|
2019-03-21 20:33:21 +00:00
|
|
|
{
|
2019-03-28 14:00:59 +00:00
|
|
|
virDomainMomentObjPtr child = from->first_child;
|
2019-03-21 20:33:21 +00:00
|
|
|
|
2019-03-28 14:00:59 +00:00
|
|
|
if (!from->nchildren)
|
2019-03-21 20:33:21 +00:00
|
|
|
return;
|
2019-03-28 14:00:59 +00:00
|
|
|
while (child) {
|
2019-03-21 20:33:21 +00:00
|
|
|
child->parent = to;
|
2019-03-28 14:00:59 +00:00
|
|
|
if (!child->sibling) {
|
|
|
|
child->sibling = to->first_child;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
child = child->sibling;
|
2019-03-21 20:33:21 +00:00
|
|
|
}
|
|
|
|
to->nchildren += from->nchildren;
|
|
|
|
to->first_child = from->first_child;
|
|
|
|
from->nchildren = 0;
|
|
|
|
from->first_child = NULL;
|
|
|
|
}
|
2019-03-22 05:39:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
static virDomainMomentObjPtr
|
|
|
|
virDomainMomentObjNew(void)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr moment;
|
|
|
|
|
2020-10-07 19:15:50 +00:00
|
|
|
moment = g_new0(virDomainMomentObj, 1);
|
2019-03-22 05:39:02 +00:00
|
|
|
|
|
|
|
VIR_DEBUG("obj=%p", moment);
|
|
|
|
|
|
|
|
return moment;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
virDomainMomentObjFree(virDomainMomentObjPtr moment)
|
|
|
|
{
|
|
|
|
if (!moment)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_DEBUG("obj=%p", moment);
|
|
|
|
|
2019-05-09 14:59:06 +00:00
|
|
|
virObjectUnref(moment->def);
|
2019-03-22 05:39:02 +00:00
|
|
|
VIR_FREE(moment);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Add def to the list and return a matching object, or NULL on error */
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
virDomainMomentAssignDef(virDomainMomentObjListPtr moments,
|
|
|
|
virDomainMomentDefPtr def)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr moment;
|
|
|
|
|
|
|
|
if (virHashLookup(moments->objs, def->name) != NULL) {
|
2019-07-24 05:03:24 +00:00
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
|
|
_("domain moment %s already exists"),
|
2019-03-22 05:39:02 +00:00
|
|
|
def->name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(moment = virDomainMomentObjNew()))
|
|
|
|
return NULL;
|
|
|
|
|
2019-03-22 09:39:06 +00:00
|
|
|
if (virHashAddEntry(moments->objs, def->name, moment) < 0) {
|
2019-03-22 05:39:02 +00:00
|
|
|
VIR_FREE(moment);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
moment->def = def;
|
|
|
|
|
|
|
|
return moment;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2019-11-21 19:27:58 +00:00
|
|
|
virDomainMomentObjListDataFree(void *payload)
|
2019-03-22 05:39:02 +00:00
|
|
|
{
|
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
|
|
|
|
virDomainMomentObjFree(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virDomainMomentObjListPtr
|
|
|
|
virDomainMomentObjListNew(void)
|
|
|
|
{
|
|
|
|
virDomainMomentObjListPtr moments;
|
|
|
|
|
2020-10-07 19:15:50 +00:00
|
|
|
moments = g_new0(virDomainMomentObjList, 1);
|
2020-10-20 16:43:20 +00:00
|
|
|
moments->objs = virHashNew(virDomainMomentObjListDataFree);
|
2019-03-22 05:39:02 +00:00
|
|
|
if (!moments->objs) {
|
|
|
|
VIR_FREE(moments);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return moments;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
virDomainMomentObjListFree(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
if (!moments)
|
|
|
|
return;
|
|
|
|
virHashFree(moments->objs);
|
|
|
|
VIR_FREE(moments);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Struct and callback for collecting a list of names of moments that
|
|
|
|
* meet a particular filter. */
|
|
|
|
struct virDomainMomentNameData {
|
|
|
|
char **const names;
|
|
|
|
int maxnames;
|
|
|
|
unsigned int flags;
|
|
|
|
int count;
|
|
|
|
bool error;
|
|
|
|
virDomainMomentObjListFilter filter;
|
2019-03-22 04:17:01 +00:00
|
|
|
unsigned int filter_flags;
|
2019-03-22 05:39:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int virDomainMomentObjListCopyNames(void *payload,
|
2020-10-21 11:31:16 +00:00
|
|
|
const char *name G_GNUC_UNUSED,
|
2019-03-22 05:39:02 +00:00
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
struct virDomainMomentNameData *data = opaque;
|
|
|
|
|
|
|
|
if (data->error)
|
|
|
|
return 0;
|
|
|
|
/* Caller already sanitized flags. Filtering on DESCENDANTS was
|
|
|
|
* done by choice of iteration in the caller. */
|
2019-03-22 04:17:01 +00:00
|
|
|
if ((data->flags & VIR_DOMAIN_MOMENT_LIST_LEAVES) && obj->nchildren)
|
2019-03-22 05:39:02 +00:00
|
|
|
return 0;
|
2019-03-22 04:17:01 +00:00
|
|
|
if ((data->flags & VIR_DOMAIN_MOMENT_LIST_NO_LEAVES) && !obj->nchildren)
|
2019-03-22 05:39:02 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-03-22 04:17:01 +00:00
|
|
|
if (!data->filter(obj, data->filter_flags))
|
2019-03-22 05:39:02 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
if (data->names && data->count < data->maxnames)
|
|
|
|
data->names[data->count] = g_strdup(obj->def->name);
|
2019-03-22 05:39:02 +00:00
|
|
|
data->count++;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainMomentObjListGetNames(virDomainMomentObjListPtr moments,
|
|
|
|
virDomainMomentObjPtr from,
|
|
|
|
char **const names,
|
|
|
|
int maxnames,
|
|
|
|
unsigned int flags,
|
2019-03-22 04:17:01 +00:00
|
|
|
virDomainMomentObjListFilter filter,
|
|
|
|
unsigned int filter_flags)
|
2019-03-22 05:39:02 +00:00
|
|
|
{
|
|
|
|
struct virDomainMomentNameData data = { names, maxnames, flags, 0,
|
2019-03-22 04:17:01 +00:00
|
|
|
false, filter, filter_flags };
|
2019-03-22 05:39:02 +00:00
|
|
|
size_t i;
|
|
|
|
|
2019-03-22 04:17:01 +00:00
|
|
|
virCheckFlags(VIR_DOMAIN_MOMENT_FILTERS_ALL, -1);
|
2019-03-22 05:39:02 +00:00
|
|
|
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. */
|
2019-03-22 04:17:01 +00:00
|
|
|
flags ^= VIR_DOMAIN_MOMENT_LIST_ROOTS;
|
2019-03-22 05:39:02 +00:00
|
|
|
from = &moments->metaroot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We handle LIST_ROOT/LIST_DESCENDANTS and LIST_TOPOLOGICAL directly,
|
|
|
|
* mask those bits out to determine when we must use the filter callback. */
|
2019-03-22 04:17:01 +00:00
|
|
|
data.flags &= ~(VIR_DOMAIN_MOMENT_LIST_DESCENDANTS |
|
|
|
|
VIR_DOMAIN_MOMENT_LIST_TOPOLOGICAL);
|
2019-03-22 05:39:02 +00:00
|
|
|
|
|
|
|
/* If this common code is being used, we assume that all moments
|
|
|
|
* 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, in which case this check should move into the
|
|
|
|
* filter callback. */
|
2019-03-22 04:17:01 +00:00
|
|
|
if ((data.flags & VIR_DOMAIN_MOMENT_FILTERS_METADATA) ==
|
|
|
|
VIR_DOMAIN_MOMENT_LIST_NO_METADATA)
|
2019-03-22 05:39:02 +00:00
|
|
|
return 0;
|
2019-03-22 04:17:01 +00:00
|
|
|
data.flags &= ~VIR_DOMAIN_MOMENT_FILTERS_METADATA;
|
2019-03-22 05:39:02 +00:00
|
|
|
|
2019-03-22 04:17:01 +00:00
|
|
|
if (flags & VIR_DOMAIN_MOMENT_LIST_DESCENDANTS) {
|
2019-03-22 05:39:02 +00:00
|
|
|
/* 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 &&
|
2019-03-22 04:17:01 +00:00
|
|
|
(flags & VIR_DOMAIN_MOMENT_LIST_TOPOLOGICAL)))
|
2019-03-22 05:39:02 +00:00
|
|
|
virDomainMomentForEachDescendant(from,
|
|
|
|
virDomainMomentObjListCopyNames,
|
|
|
|
&data);
|
2019-03-22 04:17:01 +00:00
|
|
|
else if (names || data.flags || filter_flags)
|
2019-03-22 05:39:02 +00:00
|
|
|
virHashForEach(moments->objs, virDomainMomentObjListCopyNames,
|
|
|
|
&data);
|
|
|
|
else
|
|
|
|
data.count = virHashSize(moments->objs);
|
2019-03-22 04:17:01 +00:00
|
|
|
} else if (names || data.flags || filter_flags) {
|
2019-03-22 05:39:02 +00:00
|
|
|
virDomainMomentForEachChild(from,
|
|
|
|
virDomainMomentObjListCopyNames, &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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
virDomainMomentFindByName(virDomainMomentObjListPtr moments,
|
|
|
|
const char *name)
|
|
|
|
{
|
2019-07-24 03:26:05 +00:00
|
|
|
if (name)
|
|
|
|
return virHashLookup(moments->objs, name);
|
|
|
|
return NULL;
|
2019-03-22 05:39:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Return the current moment, or NULL */
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
virDomainMomentGetCurrent(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
return moments->current;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Return the current moment's name, or NULL */
|
|
|
|
const char *
|
|
|
|
virDomainMomentGetCurrentName(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
if (moments->current)
|
|
|
|
return moments->current->def->name;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Update the current moment, using NULL if no current remains */
|
|
|
|
void
|
|
|
|
virDomainMomentSetCurrent(virDomainMomentObjListPtr moments,
|
|
|
|
virDomainMomentObjPtr moment)
|
|
|
|
{
|
|
|
|
moments->current = moment;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Return the number of moments in the list */
|
|
|
|
int
|
|
|
|
virDomainMomentObjListSize(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
return virHashSize(moments->objs);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Remove moment from the list; return true if it was current */
|
|
|
|
bool
|
|
|
|
virDomainMomentObjListRemove(virDomainMomentObjListPtr moments,
|
|
|
|
virDomainMomentObjPtr moment)
|
|
|
|
{
|
|
|
|
bool ret = moments->current == moment;
|
|
|
|
|
|
|
|
virHashRemoveEntry(moments->objs, moment->def->name);
|
|
|
|
if (ret)
|
|
|
|
moments->current = NULL;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Remove all moments tracked in the list */
|
|
|
|
void
|
|
|
|
virDomainMomentObjListRemoveAll(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
virHashRemoveAll(moments->objs);
|
|
|
|
virDomainMomentDropChildren(&moments->metaroot);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Call iter on each member of the list, in unspecified order */
|
|
|
|
int
|
|
|
|
virDomainMomentForEach(virDomainMomentObjListPtr moments,
|
|
|
|
virHashIterator iter,
|
|
|
|
void *data)
|
|
|
|
{
|
2020-10-23 07:49:36 +00:00
|
|
|
return virHashForEachSafe(moments->objs, iter, data);
|
2019-03-22 05:39:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Struct and callback function used as a hash table callback; each call
|
2019-05-08 16:39:13 +00:00
|
|
|
* inspects the pre-existing moment->def->parent_name field, and adjusts
|
2019-03-22 05:39:02 +00:00
|
|
|
* the moment->parent field as well as the parent's child fields to
|
|
|
|
* wire up the hierarchical relations for the given moment. The error
|
|
|
|
* indicator gets set if a parent is missing or a requested parent would
|
|
|
|
* cause a circular parent chain. */
|
|
|
|
struct moment_set_relation {
|
|
|
|
virDomainMomentObjListPtr moments;
|
|
|
|
int err;
|
|
|
|
};
|
|
|
|
static int
|
|
|
|
virDomainMomentSetRelations(void *payload,
|
2020-10-21 11:31:16 +00:00
|
|
|
const char *name G_GNUC_UNUSED,
|
2019-03-22 05:39:02 +00:00
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr obj = payload;
|
|
|
|
struct moment_set_relation *curr = data;
|
|
|
|
virDomainMomentObjPtr tmp;
|
|
|
|
virDomainMomentObjPtr parent;
|
|
|
|
|
2019-05-08 16:39:13 +00:00
|
|
|
parent = virDomainMomentFindByName(curr->moments, obj->def->parent_name);
|
2019-03-22 05:39:02 +00:00
|
|
|
if (!parent) {
|
|
|
|
parent = &curr->moments->metaroot;
|
2019-07-24 03:26:05 +00:00
|
|
|
if (obj->def->parent_name) {
|
|
|
|
curr->err = -1;
|
|
|
|
VIR_WARN("moment %s lacks parent %s", obj->def->name,
|
|
|
|
obj->def->parent_name);
|
|
|
|
}
|
2019-03-22 05:39:02 +00:00
|
|
|
} else {
|
|
|
|
tmp = parent;
|
|
|
|
while (tmp && tmp->def) {
|
|
|
|
if (tmp == obj) {
|
|
|
|
curr->err = -1;
|
|
|
|
parent = &curr->moments->metaroot;
|
|
|
|
VIR_WARN("moment %s in circular chain", obj->def->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tmp = tmp->parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
virDomainMomentSetParent(obj, parent);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Populate parent link and child count of all moments, 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
|
|
|
|
virDomainMomentUpdateRelations(virDomainMomentObjListPtr moments)
|
|
|
|
{
|
|
|
|
struct moment_set_relation act = { moments, 0 };
|
|
|
|
|
|
|
|
virDomainMomentDropChildren(&moments->metaroot);
|
|
|
|
virHashForEach(moments->objs, virDomainMomentSetRelations, &act);
|
|
|
|
if (act.err)
|
|
|
|
moments->current = NULL;
|
|
|
|
return act.err;
|
|
|
|
}
|
2019-07-06 02:07:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* Check that inserting def into list would not create any impossible
|
|
|
|
* parent-child relationships (cycles or missing parents). Return 0
|
|
|
|
* on success, or report an error on behalf of domname before
|
|
|
|
* returning -1. */
|
|
|
|
int
|
|
|
|
virDomainMomentCheckCycles(virDomainMomentObjListPtr list,
|
|
|
|
virDomainMomentDefPtr def,
|
|
|
|
const char *domname)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr other;
|
|
|
|
|
|
|
|
if (def->parent_name) {
|
|
|
|
if (STREQ(def->name, def->parent_name)) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
|
|
_("cannot set moment %s as its own parent"),
|
|
|
|
def->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
other = virDomainMomentFindByName(list, def->parent_name);
|
|
|
|
if (!other) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
|
|
_("parent %s for moment %s not found"),
|
|
|
|
def->parent_name, def->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
while (other->def->parent_name) {
|
|
|
|
if (STREQ(other->def->parent_name, def->name)) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
|
|
_("parent %s would create cycle to %s"),
|
|
|
|
other->def->name, def->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
other = virDomainMomentFindByName(list, other->def->parent_name);
|
|
|
|
if (!other) {
|
|
|
|
VIR_WARN("moments are inconsistent for domain %s",
|
|
|
|
domname);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
backup: Allow for lists of checkpoint objects
Create a new file for managing a list of checkpoint objects, borrowing
heavily from existing virDomainSnapshotObjList paradigms.
Note that while snapshots definitely have a use case for multiple
children to a single parent (create a base snapshot, create a child
snapshot, revert to the base, then create another child snapshot),
it's harder to predict how checkpoints will play out with reverting to
prior points in time. Thus, in initial use, given a list of
checkpoints, you never have more than one child, and we can treat the
most-recent leaf node as the parent of the next node creation, without
having to expose a notion of a current node in XML or public API.
However, as the snapshot machinery is already generic, it is easier to
reuse the generic machinery that tracks relations between domain
moments than it is to open-code a new list-management scheme just for
checkpoints (hence, we still have internal functions related to a
current checkpoint, even though that has no observable effect
externally, as well as the addition of a function to easily find the
lone leaf in the list to use as the current checkpoint).
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-07-19 02:24:54 +00:00
|
|
|
|
|
|
|
/* If there is exactly one leaf node, return that node. */
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
virDomainMomentFindLeaf(virDomainMomentObjListPtr list)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr moment = &list->metaroot;
|
|
|
|
|
|
|
|
if (moment->nchildren != 1)
|
|
|
|
return NULL;
|
|
|
|
while (moment->nchildren == 1)
|
|
|
|
moment = moment->first_child;
|
|
|
|
if (moment->nchildren == 0)
|
|
|
|
return moment;
|
|
|
|
return NULL;
|
|
|
|
}
|