diff mbox series

[v4,06/13] block: Generalize should_update_child() rule

Message ID 20180411185425.2461-7-mreitz@redhat.com
State New
Headers show
Series block/mirror: Add active-sync mirroring | expand

Commit Message

Max Reitz April 11, 2018, 6:54 p.m. UTC
Currently, bdrv_replace_node() refuses to create loops from one BDS to
itself if the BDS to be replaced is the backing node of the BDS to
replace it: Say there is a node A and a node B.  Replacing B by A means
making all references to B point to A.  If B is a child of A (i.e. A has
a reference to B), that would mean we would have to make this reference
point to A itself -- so we'd create a loop.

bdrv_replace_node() (through should_update_child()) refuses to do so if
B is the backing node of A.  There is no reason why we should create
loops if B is not the backing node of A, though.  The BDS graph should
never contain loops, so we should always refuse to create them.

If B is a child of A and B is to be replaced by A, we should simply
leave B in place there because it is the most sensible choice.

A more specific argument would be: Putting filter drivers into the BDS
graph is basically the same as appending an overlay to a backing chain.
But the main child BDS of a filter driver is not "backing" but "file",
so restricting the no-loop rule to backing nodes would fail here.

Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Fam Zheng <famz@redhat.com>
---
 include/block/block_int.h |  2 ++
 block.c                   | 44 ++++++++++++++++++++++++++++++++++----------
 2 files changed, 36 insertions(+), 10 deletions(-)

Comments

Alberto Garcia April 19, 2018, 3:19 p.m. UTC | #1
On Wed 11 Apr 2018 08:54:18 PM CEST, Max Reitz wrote:
> Currently, bdrv_replace_node() refuses to create loops from one BDS to
> itself if the BDS to be replaced is the backing node of the BDS to
> replace it: Say there is a node A and a node B.  Replacing B by A means
> making all references to B point to A.  If B is a child of A (i.e. A has
> a reference to B), that would mean we would have to make this reference
> point to A itself -- so we'd create a loop.
>
> bdrv_replace_node() (through should_update_child()) refuses to do so if
> B is the backing node of A.  There is no reason why we should create
> loops if B is not the backing node of A, though.  The BDS graph should
> never contain loops, so we should always refuse to create them.
>
> If B is a child of A and B is to be replaced by A, we should simply
> leave B in place there because it is the most sensible choice.
>
> A more specific argument would be: Putting filter drivers into the BDS
> graph is basically the same as appending an overlay to a backing chain.
> But the main child BDS of a filter driver is not "backing" but "file",
> so restricting the no-loop rule to backing nodes would fail here.
>
> Signed-off-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Fam Zheng <famz@redhat.com>

Reviewed-by: Alberto Garcia <berto@igalia.com>

Berto
Alberto Garcia April 20, 2018, 8:36 a.m. UTC | #2
On Wed 11 Apr 2018 08:54:18 PM CEST, Max Reitz wrote:
> index c4dd1d4bb8..8d63a1b0c1 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -616,6 +616,8 @@ struct BdrvChild {
>      QLIST_ENTRY(BdrvChild) next_parent;
>  };
>  
> +typedef QLIST_HEAD(BdrvChildList, BdrvChild) BdrvChildList;
> +

I forgot to mention this in a previous e-mail, but what's this used for?

Berto
Max Reitz April 20, 2018, 1:29 p.m. UTC | #3
On 2018-04-20 10:36, Alberto Garcia wrote:
> On Wed 11 Apr 2018 08:54:18 PM CEST, Max Reitz wrote:
>> index c4dd1d4bb8..8d63a1b0c1 100644
>> --- a/include/block/block_int.h
>> +++ b/include/block/block_int.h
>> @@ -616,6 +616,8 @@ struct BdrvChild {
>>      QLIST_ENTRY(BdrvChild) next_parent;
>>  };
>>  
>> +typedef QLIST_HEAD(BdrvChildList, BdrvChild) BdrvChildList;
>> +
> 
> I forgot to mention this in a previous e-mail, but what's this used for?

Extremely good question.  grep says it isn't used for anything.  I'm
inclined to believe grep (especially considering the fact that
everything compiles without error after removing it).

I suppose I had some local version where I needed such a type (probably
because I didn't deem QLIST_FOREACH_SAFE() to be sufficiently safe, so I
needed to copy the list somewhere else, or I don't know...), and then I
found a way around it, removed the code, but forgot the type in the header.

Will remove in v5.


Thank you for reviewing!

Max
diff mbox series

Patch

diff --git a/include/block/block_int.h b/include/block/block_int.h
index c4dd1d4bb8..8d63a1b0c1 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -616,6 +616,8 @@  struct BdrvChild {
     QLIST_ENTRY(BdrvChild) next_parent;
 };
 
+typedef QLIST_HEAD(BdrvChildList, BdrvChild) BdrvChildList;
+
 /*
  * Note: the function bdrv_append() copies and swaps contents of
  * BlockDriverStates, so if you add new fields to this struct, please
diff --git a/block.c b/block.c
index a2caadf0a0..9294b89eb7 100644
--- a/block.c
+++ b/block.c
@@ -3383,16 +3383,39 @@  static bool should_update_child(BdrvChild *c, BlockDriverState *to)
         return false;
     }
 
-    if (c->role == &child_backing) {
-        /* If @from is a backing file of @to, ignore the child to avoid
-         * creating a loop. We only want to change the pointer of other
-         * parents. */
-        QLIST_FOREACH(to_c, &to->children, next) {
-            if (to_c == c) {
-                break;
-            }
-        }
-        if (to_c) {
+    /* If the child @c belongs to the BDS @to, replacing the current
+     * c->bs by @to would mean to create a loop.
+     *
+     * Such a case occurs when appending a BDS to a backing chain.
+     * For instance, imagine the following chain:
+     *
+     *   guest device -> node A -> further backing chain...
+     *
+     * Now we create a new BDS B which we want to put on top of this
+     * chain, so we first attach A as its backing node:
+     *
+     *                   node B
+     *                     |
+     *                     v
+     *   guest device -> node A -> further backing chain...
+     *
+     * Finally we want to replace A by B.  When doing that, we want to
+     * replace all pointers to A by pointers to B -- except for the
+     * pointer from B because (1) that would create a loop, and (2)
+     * that pointer should simply stay intact:
+     *
+     *   guest device -> node B
+     *                     |
+     *                     v
+     *                   node A -> further backing chain...
+     *
+     * In general, when replacing a node A (c->bs) by a node B (@to),
+     * if A is a child of B, that means we cannot replace A by B there
+     * because that would create a loop.  Silently detaching A from B
+     * is also not really an option.  So overall just leaving A in
+     * place there is the most sensible choice. */
+    QLIST_FOREACH(to_c, &to->children, next) {
+        if (to_c == c) {
             return false;
         }
     }
@@ -3418,6 +3441,7 @@  void bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
 
     /* Put all parents into @list and calculate their cumulative permissions */
     QLIST_FOREACH_SAFE(c, &from->parents, next_parent, next) {
+        assert(c->bs == from);
         if (!should_update_child(c, to)) {
             continue;
         }