block: Let replace_child_tran keep indirect pointer
As of a future commit, bdrv_replace_child_noperm() will clear the indirect BdrvChild pointer passed to it if the new child BDS is NULL. bdrv_replace_child_tran() will want to let it do that, but revert this change in its abort handler. For that, we need to have it receive a BdrvChild ** pointer, too, and keep it stored in the BdrvReplaceChildState object that we attach to the transaction. Note that we do not need to store it in the BdrvReplaceChildState when new_bs is not NULL, because then there is nothing to revert. This is important so that bdrv_replace_node_noperm() can pass a pointer to a loop-local variable to bdrv_replace_child_tran() without worrying that this pointer will outlive one loop iteration. (Of course, for that to work, bdrv_replace_node_noperm() and in turn bdrv_replace_node() and its relatives may not be called with a NULL @to node. Luckily, they already are not, but now we should assert this.) bdrv_remove_file_or_backing_child() on the other hand needs to ensure that the indirect pointer it passes will stay valid for the duration of the transaction. Ensure this by keeping a strong reference to the BDS whose &bs->backing or &bs->file it passes to bdrv_replace_child_tran(), and giving up that reference only in the transaction .clean() handler. Signed-off-by: Hanna Reitz <hreitz@redhat.com> Message-Id: <20211111120829.81329-9-hreitz@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com> Message-Id: <20211115145409.176785-9-kwolf@redhat.com> Signed-off-by: Hanna Reitz <hreitz@redhat.com>
This commit is contained in:
parent
079bff693b
commit
82b54cf516
83
block.c
83
block.c
@ -2254,6 +2254,7 @@ static int bdrv_drv_set_perm(BlockDriverState *bs, uint64_t perm,
|
|||||||
|
|
||||||
typedef struct BdrvReplaceChildState {
|
typedef struct BdrvReplaceChildState {
|
||||||
BdrvChild *child;
|
BdrvChild *child;
|
||||||
|
BdrvChild **childp;
|
||||||
BlockDriverState *old_bs;
|
BlockDriverState *old_bs;
|
||||||
} BdrvReplaceChildState;
|
} BdrvReplaceChildState;
|
||||||
|
|
||||||
@ -2269,7 +2270,29 @@ static void bdrv_replace_child_abort(void *opaque)
|
|||||||
BdrvReplaceChildState *s = opaque;
|
BdrvReplaceChildState *s = opaque;
|
||||||
BlockDriverState *new_bs = s->child->bs;
|
BlockDriverState *new_bs = s->child->bs;
|
||||||
|
|
||||||
/* old_bs reference is transparently moved from @s to @s->child */
|
/*
|
||||||
|
* old_bs reference is transparently moved from @s to s->child.
|
||||||
|
*
|
||||||
|
* Pass &s->child here instead of s->childp, because:
|
||||||
|
* (1) s->old_bs must be non-NULL, so bdrv_replace_child_noperm() will not
|
||||||
|
* modify the BdrvChild * pointer we indirectly pass to it, i.e. it
|
||||||
|
* will not modify s->child. From that perspective, it does not matter
|
||||||
|
* whether we pass s->childp or &s->child.
|
||||||
|
* (TODO: Right now, bdrv_replace_child_noperm() never modifies that
|
||||||
|
* pointer anyway (though it will in the future), so at this point it
|
||||||
|
* absolutely does not matter whether we pass s->childp or &s->child.)
|
||||||
|
* (2) If new_bs is not NULL, s->childp will be NULL. We then cannot use
|
||||||
|
* it here.
|
||||||
|
* (3) If new_bs is NULL, *s->childp will have been NULLed by
|
||||||
|
* bdrv_replace_child_tran()'s bdrv_replace_child_noperm() call, and we
|
||||||
|
* must not pass a NULL *s->childp here.
|
||||||
|
* (TODO: In its current state, bdrv_replace_child_noperm() will not
|
||||||
|
* have NULLed *s->childp, so this does not apply yet. It will in the
|
||||||
|
* future.)
|
||||||
|
*
|
||||||
|
* So whether new_bs was NULL or not, we cannot pass s->childp here; and in
|
||||||
|
* any case, there is no reason to pass it anyway.
|
||||||
|
*/
|
||||||
bdrv_replace_child_noperm(&s->child, s->old_bs);
|
bdrv_replace_child_noperm(&s->child, s->old_bs);
|
||||||
bdrv_unref(new_bs);
|
bdrv_unref(new_bs);
|
||||||
}
|
}
|
||||||
@ -2286,22 +2309,32 @@ static TransactionActionDrv bdrv_replace_child_drv = {
|
|||||||
* Note: real unref of old_bs is done only on commit.
|
* Note: real unref of old_bs is done only on commit.
|
||||||
*
|
*
|
||||||
* The function doesn't update permissions, caller is responsible for this.
|
* The function doesn't update permissions, caller is responsible for this.
|
||||||
|
*
|
||||||
|
* Note that if new_bs == NULL, @childp is stored in a state object attached
|
||||||
|
* to @tran, so that the old child can be reinstated in the abort handler.
|
||||||
|
* Therefore, if @new_bs can be NULL, @childp must stay valid until the
|
||||||
|
* transaction is committed or aborted.
|
||||||
|
*
|
||||||
|
* (TODO: The reinstating does not happen yet, but it will once
|
||||||
|
* bdrv_replace_child_noperm() NULLs *childp when new_bs is NULL.)
|
||||||
*/
|
*/
|
||||||
static void bdrv_replace_child_tran(BdrvChild *child, BlockDriverState *new_bs,
|
static void bdrv_replace_child_tran(BdrvChild **childp,
|
||||||
|
BlockDriverState *new_bs,
|
||||||
Transaction *tran)
|
Transaction *tran)
|
||||||
{
|
{
|
||||||
BdrvReplaceChildState *s = g_new(BdrvReplaceChildState, 1);
|
BdrvReplaceChildState *s = g_new(BdrvReplaceChildState, 1);
|
||||||
*s = (BdrvReplaceChildState) {
|
*s = (BdrvReplaceChildState) {
|
||||||
.child = child,
|
.child = *childp,
|
||||||
.old_bs = child->bs,
|
.childp = new_bs == NULL ? childp : NULL,
|
||||||
|
.old_bs = (*childp)->bs,
|
||||||
};
|
};
|
||||||
tran_add(tran, &bdrv_replace_child_drv, s);
|
tran_add(tran, &bdrv_replace_child_drv, s);
|
||||||
|
|
||||||
if (new_bs) {
|
if (new_bs) {
|
||||||
bdrv_ref(new_bs);
|
bdrv_ref(new_bs);
|
||||||
}
|
}
|
||||||
bdrv_replace_child_noperm(&child, new_bs);
|
bdrv_replace_child_noperm(childp, new_bs);
|
||||||
/* old_bs reference is transparently moved from @child to @s */
|
/* old_bs reference is transparently moved from *childp to @s */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4844,6 +4877,7 @@ static bool should_update_child(BdrvChild *c, BlockDriverState *to)
|
|||||||
|
|
||||||
typedef struct BdrvRemoveFilterOrCowChild {
|
typedef struct BdrvRemoveFilterOrCowChild {
|
||||||
BdrvChild *child;
|
BdrvChild *child;
|
||||||
|
BlockDriverState *bs;
|
||||||
bool is_backing;
|
bool is_backing;
|
||||||
} BdrvRemoveFilterOrCowChild;
|
} BdrvRemoveFilterOrCowChild;
|
||||||
|
|
||||||
@ -4873,10 +4907,19 @@ static void bdrv_remove_filter_or_cow_child_commit(void *opaque)
|
|||||||
bdrv_child_free(s->child);
|
bdrv_child_free(s->child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void bdrv_remove_filter_or_cow_child_clean(void *opaque)
|
||||||
|
{
|
||||||
|
BdrvRemoveFilterOrCowChild *s = opaque;
|
||||||
|
|
||||||
|
/* Drop the bs reference after the transaction is done */
|
||||||
|
bdrv_unref(s->bs);
|
||||||
|
g_free(s);
|
||||||
|
}
|
||||||
|
|
||||||
static TransactionActionDrv bdrv_remove_filter_or_cow_child_drv = {
|
static TransactionActionDrv bdrv_remove_filter_or_cow_child_drv = {
|
||||||
.abort = bdrv_remove_filter_or_cow_child_abort,
|
.abort = bdrv_remove_filter_or_cow_child_abort,
|
||||||
.commit = bdrv_remove_filter_or_cow_child_commit,
|
.commit = bdrv_remove_filter_or_cow_child_commit,
|
||||||
.clean = g_free,
|
.clean = bdrv_remove_filter_or_cow_child_clean,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4894,6 +4937,11 @@ static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep a reference to @bs so @childp will stay valid throughout the
|
||||||
|
* transaction (required by bdrv_replace_child_tran())
|
||||||
|
*/
|
||||||
|
bdrv_ref(bs);
|
||||||
if (child == bs->backing) {
|
if (child == bs->backing) {
|
||||||
childp = &bs->backing;
|
childp = &bs->backing;
|
||||||
} else if (child == bs->file) {
|
} else if (child == bs->file) {
|
||||||
@ -4903,12 +4951,13 @@ static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (child->bs) {
|
if (child->bs) {
|
||||||
bdrv_replace_child_tran(*childp, NULL, tran);
|
bdrv_replace_child_tran(childp, NULL, tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
s = g_new(BdrvRemoveFilterOrCowChild, 1);
|
s = g_new(BdrvRemoveFilterOrCowChild, 1);
|
||||||
*s = (BdrvRemoveFilterOrCowChild) {
|
*s = (BdrvRemoveFilterOrCowChild) {
|
||||||
.child = child,
|
.child = child,
|
||||||
|
.bs = bs,
|
||||||
.is_backing = (childp == &bs->backing),
|
.is_backing = (childp == &bs->backing),
|
||||||
};
|
};
|
||||||
tran_add(tran, &bdrv_remove_filter_or_cow_child_drv, s);
|
tran_add(tran, &bdrv_remove_filter_or_cow_child_drv, s);
|
||||||
@ -4934,6 +4983,8 @@ static int bdrv_replace_node_noperm(BlockDriverState *from,
|
|||||||
{
|
{
|
||||||
BdrvChild *c, *next;
|
BdrvChild *c, *next;
|
||||||
|
|
||||||
|
assert(to != NULL);
|
||||||
|
|
||||||
QLIST_FOREACH_SAFE(c, &from->parents, next_parent, next) {
|
QLIST_FOREACH_SAFE(c, &from->parents, next_parent, next) {
|
||||||
assert(c->bs == from);
|
assert(c->bs == from);
|
||||||
if (!should_update_child(c, to)) {
|
if (!should_update_child(c, to)) {
|
||||||
@ -4949,7 +5000,12 @@ static int bdrv_replace_node_noperm(BlockDriverState *from,
|
|||||||
c->name, from->node_name);
|
c->name, from->node_name);
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
}
|
}
|
||||||
bdrv_replace_child_tran(c, to, tran);
|
|
||||||
|
/*
|
||||||
|
* Passing a pointer to the local variable @c is fine here, because
|
||||||
|
* @to is not NULL, and so &c will not be attached to the transaction.
|
||||||
|
*/
|
||||||
|
bdrv_replace_child_tran(&c, to, tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -4964,6 +5020,8 @@ static int bdrv_replace_node_noperm(BlockDriverState *from,
|
|||||||
*
|
*
|
||||||
* With @detach_subchain=true @to must be in a backing chain of @from. In this
|
* With @detach_subchain=true @to must be in a backing chain of @from. In this
|
||||||
* case backing link of the cow-parent of @to is removed.
|
* case backing link of the cow-parent of @to is removed.
|
||||||
|
*
|
||||||
|
* @to must not be NULL.
|
||||||
*/
|
*/
|
||||||
static int bdrv_replace_node_common(BlockDriverState *from,
|
static int bdrv_replace_node_common(BlockDriverState *from,
|
||||||
BlockDriverState *to,
|
BlockDriverState *to,
|
||||||
@ -4976,6 +5034,8 @@ static int bdrv_replace_node_common(BlockDriverState *from,
|
|||||||
BlockDriverState *to_cow_parent = NULL;
|
BlockDriverState *to_cow_parent = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
assert(to != NULL);
|
||||||
|
|
||||||
if (detach_subchain) {
|
if (detach_subchain) {
|
||||||
assert(bdrv_chain_contains(from, to));
|
assert(bdrv_chain_contains(from, to));
|
||||||
assert(from != to);
|
assert(from != to);
|
||||||
@ -5031,6 +5091,9 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace node @from by @to (where neither may be NULL).
|
||||||
|
*/
|
||||||
int bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
|
int bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@ -5098,7 +5161,7 @@ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
|
|||||||
bdrv_drained_begin(old_bs);
|
bdrv_drained_begin(old_bs);
|
||||||
bdrv_drained_begin(new_bs);
|
bdrv_drained_begin(new_bs);
|
||||||
|
|
||||||
bdrv_replace_child_tran(child, new_bs, tran);
|
bdrv_replace_child_tran(&child, new_bs, tran);
|
||||||
|
|
||||||
found = g_hash_table_new(NULL, NULL);
|
found = g_hash_table_new(NULL, NULL);
|
||||||
refresh_list = bdrv_topological_dfs(refresh_list, found, old_bs);
|
refresh_list = bdrv_topological_dfs(refresh_list, found, old_bs);
|
||||||
|
Loading…
Reference in New Issue
Block a user