Skip to content

Commit b0a9f6f

Browse files
committed
block: Let replace_child_noperm free children
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 <[email protected]> Message-Id: <[email protected]> Signed-off-by: Kevin Wolf <[email protected]> Message-Id: <[email protected]> Signed-off-by: Hanna Reitz <[email protected]>
1 parent 82b54cf commit b0a9f6f

File tree

1 file changed

+79
-23
lines changed

1 file changed

+79
-23
lines changed

block.c

+79-23
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ static BlockDriverState *bdrv_open_inherit(const char *filename,
8787
static bool bdrv_recurse_has_child(BlockDriverState *bs,
8888
BlockDriverState *child);
8989

90+
static void bdrv_child_free(BdrvChild *child);
9091
static void bdrv_replace_child_noperm(BdrvChild **child,
91-
BlockDriverState *new_bs);
92+
BlockDriverState *new_bs,
93+
bool free_empty_child);
9294
static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
9395
BdrvChild *child,
9496
Transaction *tran);
@@ -2256,12 +2258,16 @@ typedef struct BdrvReplaceChildState {
22562258
BdrvChild *child;
22572259
BdrvChild **childp;
22582260
BlockDriverState *old_bs;
2261+
bool free_empty_child;
22592262
} BdrvReplaceChildState;
22602263

22612264
static void bdrv_replace_child_commit(void *opaque)
22622265
{
22632266
BdrvReplaceChildState *s = opaque;
22642267

2268+
if (s->free_empty_child && !s->child->bs) {
2269+
bdrv_child_free(s->child);
2270+
}
22652271
bdrv_unref(s->old_bs);
22662272
}
22672273

@@ -2278,22 +2284,26 @@ static void bdrv_replace_child_abort(void *opaque)
22782284
* modify the BdrvChild * pointer we indirectly pass to it, i.e. it
22792285
* will not modify s->child. From that perspective, it does not matter
22802286
* whether we pass s->childp or &s->child.
2281-
* (TODO: Right now, bdrv_replace_child_noperm() never modifies that
2282-
* pointer anyway (though it will in the future), so at this point it
2283-
* absolutely does not matter whether we pass s->childp or &s->child.)
22842287
* (2) If new_bs is not NULL, s->childp will be NULL. We then cannot use
22852288
* it here.
22862289
* (3) If new_bs is NULL, *s->childp will have been NULLed by
22872290
* bdrv_replace_child_tran()'s bdrv_replace_child_noperm() call, and we
22882291
* must not pass a NULL *s->childp here.
2289-
* (TODO: In its current state, bdrv_replace_child_noperm() will not
2290-
* have NULLed *s->childp, so this does not apply yet. It will in the
2291-
* future.)
22922292
*
22932293
* So whether new_bs was NULL or not, we cannot pass s->childp here; and in
22942294
* any case, there is no reason to pass it anyway.
22952295
*/
2296-
bdrv_replace_child_noperm(&s->child, s->old_bs);
2296+
bdrv_replace_child_noperm(&s->child, s->old_bs, true);
2297+
/*
2298+
* The child was pre-existing, so s->old_bs must be non-NULL, and
2299+
* s->child thus must not have been freed
2300+
*/
2301+
assert(s->child != NULL);
2302+
if (!new_bs) {
2303+
/* As described above, *s->childp was cleared, so restore it */
2304+
assert(s->childp != NULL);
2305+
*s->childp = s->child;
2306+
}
22972307
bdrv_unref(new_bs);
22982308
}
22992309

@@ -2310,30 +2320,44 @@ static TransactionActionDrv bdrv_replace_child_drv = {
23102320
*
23112321
* The function doesn't update permissions, caller is responsible for this.
23122322
*
2323+
* (*childp)->bs must not be NULL.
2324+
*
23132325
* Note that if new_bs == NULL, @childp is stored in a state object attached
23142326
* to @tran, so that the old child can be reinstated in the abort handler.
23152327
* Therefore, if @new_bs can be NULL, @childp must stay valid until the
23162328
* transaction is committed or aborted.
23172329
*
2318-
* (TODO: The reinstating does not happen yet, but it will once
2319-
* bdrv_replace_child_noperm() NULLs *childp when new_bs is NULL.)
2330+
* If @free_empty_child is true and @new_bs is NULL, the BdrvChild is
2331+
* freed (on commit). @free_empty_child should only be false if the
2332+
* caller will free the BDrvChild themselves (which may be important
2333+
* if this is in turn called in another transactional context).
23202334
*/
23212335
static void bdrv_replace_child_tran(BdrvChild **childp,
23222336
BlockDriverState *new_bs,
2323-
Transaction *tran)
2337+
Transaction *tran,
2338+
bool free_empty_child)
23242339
{
23252340
BdrvReplaceChildState *s = g_new(BdrvReplaceChildState, 1);
23262341
*s = (BdrvReplaceChildState) {
23272342
.child = *childp,
23282343
.childp = new_bs == NULL ? childp : NULL,
23292344
.old_bs = (*childp)->bs,
2345+
.free_empty_child = free_empty_child,
23302346
};
23312347
tran_add(tran, &bdrv_replace_child_drv, s);
23322348

2349+
/* The abort handler relies on this */
2350+
assert(s->old_bs != NULL);
2351+
23332352
if (new_bs) {
23342353
bdrv_ref(new_bs);
23352354
}
2336-
bdrv_replace_child_noperm(childp, new_bs);
2355+
/*
2356+
* Pass free_empty_child=false, we will free the child (if
2357+
* necessary) in bdrv_replace_child_commit() (if our
2358+
* @free_empty_child parameter was true).
2359+
*/
2360+
bdrv_replace_child_noperm(childp, new_bs, false);
23372361
/* old_bs reference is transparently moved from *childp to @s */
23382362
}
23392363

@@ -2705,8 +2729,22 @@ uint64_t bdrv_qapi_perm_to_blk_perm(BlockPermission qapi_perm)
27052729
return permissions[qapi_perm];
27062730
}
27072731

2732+
/**
2733+
* Replace (*childp)->bs by @new_bs.
2734+
*
2735+
* If @new_bs is NULL, *childp will be set to NULL, too: BDS parents
2736+
* generally cannot handle a BdrvChild with .bs == NULL, so clearing
2737+
* BdrvChild.bs should generally immediately be followed by the
2738+
* BdrvChild pointer being cleared as well.
2739+
*
2740+
* If @free_empty_child is true and @new_bs is NULL, the BdrvChild is
2741+
* freed. @free_empty_child should only be false if the caller will
2742+
* free the BdrvChild themselves (this may be important in a
2743+
* transactional context, where it may only be freed on commit).
2744+
*/
27082745
static void bdrv_replace_child_noperm(BdrvChild **childp,
2709-
BlockDriverState *new_bs)
2746+
BlockDriverState *new_bs,
2747+
bool free_empty_child)
27102748
{
27112749
BdrvChild *child = *childp;
27122750
BlockDriverState *old_bs = child->bs;
@@ -2743,6 +2781,9 @@ static void bdrv_replace_child_noperm(BdrvChild **childp,
27432781
}
27442782

27452783
child->bs = new_bs;
2784+
if (!new_bs) {
2785+
*childp = NULL;
2786+
}
27462787

27472788
if (new_bs) {
27482789
QLIST_INSERT_HEAD(&new_bs->parents, child, next_parent);
@@ -2772,6 +2813,10 @@ static void bdrv_replace_child_noperm(BdrvChild **childp,
27722813
bdrv_parent_drained_end_single(child);
27732814
drain_saldo++;
27742815
}
2816+
2817+
if (free_empty_child && !child->bs) {
2818+
bdrv_child_free(child);
2819+
}
27752820
}
27762821

27772822
/**
@@ -2801,7 +2846,14 @@ static void bdrv_attach_child_common_abort(void *opaque)
28012846
BdrvChild *child = *s->child;
28022847
BlockDriverState *bs = child->bs;
28032848

2804-
bdrv_replace_child_noperm(s->child, NULL);
2849+
/*
2850+
* Pass free_empty_child=false, because we still need the child
2851+
* for the AioContext operations on the parent below; those
2852+
* BdrvChildClass methods all work on a BdrvChild object, so we
2853+
* need to keep it as an empty shell (after this function, it will
2854+
* not be attached to any parent, and it will not have a .bs).
2855+
*/
2856+
bdrv_replace_child_noperm(s->child, NULL, false);
28052857

28062858
if (bdrv_get_aio_context(bs) != s->old_child_ctx) {
28072859
bdrv_try_set_aio_context(bs, s->old_child_ctx, &error_abort);
@@ -2823,7 +2875,6 @@ static void bdrv_attach_child_common_abort(void *opaque)
28232875

28242876
bdrv_unref(bs);
28252877
bdrv_child_free(child);
2826-
*s->child = NULL;
28272878
}
28282879

28292880
static TransactionActionDrv bdrv_attach_child_common_drv = {
@@ -2901,7 +2952,9 @@ static int bdrv_attach_child_common(BlockDriverState *child_bs,
29012952
}
29022953

29032954
bdrv_ref(child_bs);
2904-
bdrv_replace_child_noperm(&new_child, child_bs);
2955+
bdrv_replace_child_noperm(&new_child, child_bs, true);
2956+
/* child_bs was non-NULL, so new_child must not have been freed */
2957+
assert(new_child != NULL);
29052958

29062959
*child = new_child;
29072960

@@ -2960,8 +3013,7 @@ static void bdrv_detach_child(BdrvChild **childp)
29603013
{
29613014
BlockDriverState *old_bs = (*childp)->bs;
29623015

2963-
bdrv_replace_child_noperm(childp, NULL);
2964-
bdrv_child_free(*childp);
3016+
bdrv_replace_child_noperm(childp, NULL, true);
29653017

29663018
if (old_bs) {
29673019
/*
@@ -4951,7 +5003,11 @@ static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
49515003
}
49525004

49535005
if (child->bs) {
4954-
bdrv_replace_child_tran(childp, NULL, tran);
5006+
/*
5007+
* Pass free_empty_child=false, we will free the child in
5008+
* bdrv_remove_filter_or_cow_child_commit()
5009+
*/
5010+
bdrv_replace_child_tran(childp, NULL, tran, false);
49555011
}
49565012

49575013
s = g_new(BdrvRemoveFilterOrCowChild, 1);
@@ -4961,8 +5017,6 @@ static void bdrv_remove_file_or_backing_child(BlockDriverState *bs,
49615017
.is_backing = (childp == &bs->backing),
49625018
};
49635019
tran_add(tran, &bdrv_remove_filter_or_cow_child_drv, s);
4964-
4965-
*childp = NULL;
49665020
}
49675021

49685022
/*
@@ -5005,7 +5059,7 @@ static int bdrv_replace_node_noperm(BlockDriverState *from,
50055059
* Passing a pointer to the local variable @c is fine here, because
50065060
* @to is not NULL, and so &c will not be attached to the transaction.
50075061
*/
5008-
bdrv_replace_child_tran(&c, to, tran);
5062+
bdrv_replace_child_tran(&c, to, tran, true);
50095063
}
50105064

50115065
return 0;
@@ -5161,7 +5215,9 @@ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
51615215
bdrv_drained_begin(old_bs);
51625216
bdrv_drained_begin(new_bs);
51635217

5164-
bdrv_replace_child_tran(&child, new_bs, tran);
5218+
bdrv_replace_child_tran(&child, new_bs, tran, true);
5219+
/* @new_bs must have been non-NULL, so @child must not have been freed */
5220+
assert(child != NULL);
51655221

51665222
found = g_hash_table_new(NULL, NULL);
51675223
refresh_list = bdrv_topological_dfs(refresh_list, found, old_bs);

0 commit comments

Comments
 (0)