In most of the block layer, especially when traversing down from other
BlockDriverStates, we assume that BdrvChild.bs can never be NULL. When
it becomes NULL, it is expected that the corresponding BdrvChild pointer
also becomes NULL and the BdrvChild object is freed.
Therefore, once bdrv_replace_child_noperm() sets the BdrvChild.bs
pointer to NULL, it should also immediately set the corresponding
BdrvChild pointer (like bs->file or bs->backing) to NULL.
In that context, it also makes sense for this function to free the
child. Sometimes we cannot do so, though, because it is called in a
transactional context where the caller might still want to reinstate the
child in the abort branch (and free it only on commit), so this behavior
has to remain optional.
In bdrv_replace_child_tran()'s abort handler, we now rely on the fact
that the BdrvChild passed to bdrv_replace_child_tran() must have had a
non-NULL .bs pointer initially. Make a note of that and assert it.
Signed-off-by: Hanna Reitz <hre...@redhat.com>
---
block.c | 102 +++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 79 insertions(+), 23 deletions(-)
diff --git a/block.c b/block.c
index a40027161c..0ac5b163d2 100644
--- a/block.c
+++ b/block.c
@@ -87,8 +87,10 @@ static BlockDriverState *bdrv_open_inherit(const
char *filename,
static bool bdrv_recurse_has_child(BlockDriverState *bs,
BlockDriverState *child);
+static void bdrv_child_free(BdrvChild *child);
static void bdrv_replace_child_noperm(BdrvChild **child,
- BlockDriverState *new_bs);
+ BlockDriverState *new_bs,
+ bool free_empty_child);
static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
BdrvChild *child,
Transaction *tran);
@@ -2256,12 +2258,16 @@ typedef struct BdrvReplaceChildState {
BdrvChild *child;
BdrvChild **childp;
BlockDriverState *old_bs;
+ bool free_empty_child;
} BdrvReplaceChildState;
static void bdrv_replace_child_commit(void *opaque)
{
BdrvReplaceChildState *s = opaque;
+ if (s->free_empty_child && !s->child->bs) {
+ bdrv_child_free(s->child);
+ }
bdrv_unref(s->old_bs);
}
@@ -2278,22 +2284,26 @@ static void bdrv_replace_child_abort(void
*opaque)
* 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.)
@@ -2960,8 +3013,7 @@ static void bdrv_detach_child(BdrvChild **childp)
{
BlockDriverState *old_bs = (*childp)->bs;
- bdrv_replace_child_noperm(childp, NULL);
- bdrv_child_free(*childp);
+ bdrv_replace_child_noperm(childp, NULL, true);
if (old_bs) {
/*