Re: [PATCH v2 09/10] block: Let replace_child_noperm free children

2021-11-16 Thread Vladimir Sementsov-Ogievskiy

15.11.2021 16:04, Hanna Reitz wrote:

On 12.11.21 17:10, Vladimir Sementsov-Ogievskiy wrote:

11.11.2021 15:08, Hanna Reitz wrote:

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 
---
  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 >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 >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.)


What I don't like about this patch is that it does two different things: 
zeroing the pointer and clearing the object. And if we look at the latter in 
separate, it seems that it's not needed:

Look: bdrv_replace_child_tran(): new parameter is set to true in two places, in 
both of them we are sure (and do assertion and comment) that new bs is not NULL 
and nothing will be freed.

Similarly, bdrv_replace_child_noperm() is called with true in two places where 
we sure that new bs is not NULL.

and only one place where new parameter set to true really do something:


@@ -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) {
  /*


And it doesn't worth the whole complexity of new parameters for two functions.

In this place we can simply do something like

BdrvChild *child = *childp;

bdrv_replace_child_noperm(childp, NULL);

bdrv_child_free(child);


I understand the idea: it seems good and intuitive to do zeroing the pointer and clearing 
the object in one shot. But this patch itself shows that we just can't do it in 90% of 
cases. So, I think better is not do it and live with only "zeroing the pointer" 
part of this patch.


I see your point, but I don’t find it too complex.  Passing `true` is the 
default and then calling the function is 

Re: [PATCH v2 09/10] block: Let replace_child_noperm free children

2021-11-15 Thread Hanna Reitz

On 12.11.21 17:10, Vladimir Sementsov-Ogievskiy wrote:

11.11.2021 15:08, Hanna Reitz wrote:

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 
---
  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 >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 
>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.)


What I don't like about this patch is that it does two different 
things: zeroing the pointer and clearing the object. And if we look at 
the latter in separate, it seems that it's not needed:


Look: bdrv_replace_child_tran(): new parameter is set to true in two 
places, in both of them we are sure (and do assertion and comment) 
that new bs is not NULL and nothing will be freed.


Similarly, bdrv_replace_child_noperm() is called with true in two 
places where we sure that new bs is not NULL.


and only one place where new parameter set to true really do something:


@@ -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) {
  /*


And it doesn't worth the whole complexity of new parameters for two 
functions.


In this place we can simply do something like

BdrvChild *child = *childp;

bdrv_replace_child_noperm(childp, NULL);

bdrv_child_free(child);


I understand the idea: it seems good and intuitive to do zeroing the 
pointer and clearing the object in one shot. But this patch itself 
shows that we just can't do it in 90% of cases. So, I think better is 
not do it and live with only "zeroing the pointer" part of this patch.


I see your point, but I don’t find it too complex.  Passing `true` is 
the default and then calling the function is easy.  Passing 

Re: [PATCH v2 09/10] block: Let replace_child_noperm free children

2021-11-12 Thread Vladimir Sementsov-Ogievskiy

11.11.2021 15:08, Hanna Reitz wrote:

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 
---
  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 >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 >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.)


What I don't like about this patch is that it does two different things: 
zeroing the pointer and clearing the object. And if we look at the latter in 
separate, it seems that it's not needed:

Look: bdrv_replace_child_tran(): new parameter is set to true in two places, in 
both of them we are sure (and do assertion and comment) that new bs is not NULL 
and nothing will be freed.

Similarly, bdrv_replace_child_noperm() is called with true in two places where 
we sure that new bs is not NULL.

and only one place where new parameter set to true really do something:


@@ -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) {

  /*


And it doesn't worth the whole complexity of new parameters for two functions.

In this place we can simply do something like

BdrvChild *child = *childp;

bdrv_replace_child_noperm(childp, NULL);

bdrv_child_free(child);


I understand the idea: it seems good and intuitive to do zeroing the pointer and clearing 
the object in one shot. But this patch itself shows that we just can't do it in 90% of 
cases. So, I think better is not do it and live with only "zeroing the pointer" 
part of this patch.





Another idea that come to my mind while reviewing this series: did you consider zeroing 
bs->file / bs->backing in .detach, like you do with bs->children list at start of 
the series?  We can 

[PATCH v2 09/10] block: Let replace_child_noperm free children

2021-11-11 Thread Hanna Reitz
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 
---
 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 >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 >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(>child, s->old_bs);
+bdrv_replace_child_noperm(>child, s->old_bs, true);
+/*
+ * The child was pre-existing, so s->old_bs must be non-NULL, and
+ * s->child thus must not have been freed
+ */
+assert(s->child != NULL);
+if (!new_bs) {
+/* As described above, *s->childp was cleared, so restore it */
+assert(s->childp != NULL);
+*s->childp = s->child;
+}
 bdrv_unref(new_bs);
 }
 
@@ -2310,30 +2320,44 @@ static TransactionActionDrv bdrv_replace_child_drv = {
  *
  * The function doesn't update permissions, caller is responsible for this.
  *
+ * (*childp)->bs must not be NULL.
+ *
  * 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.)
+ * If @free_empty_child is true and @new_bs is NULL, the BdrvChild is
+ * freed (on commit).  @free_empty_child should only be false if the
+ * caller will free the BDrvChild themselves (which may be important
+ * if this is in turn called in another transactional context).
  */
 static void bdrv_replace_child_tran(BdrvChild **childp,
 BlockDriverState *new_bs,
-