All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>, Alberto Garcia <berto@igalia.com>,
	qemu-devel@nongnu.org, Max Reitz <mreitz@redhat.com>
Subject: [PATCH 15/22] mirror: Prevent loops
Date: Fri, 20 Sep 2019 17:27:57 +0200	[thread overview]
Message-ID: <20190920152804.12875-16-mreitz@redhat.com> (raw)
In-Reply-To: <20190920152804.12875-1-mreitz@redhat.com>

While bdrv_replace_node() will not follow through with it, a specific
@replaces asks the mirror job to create a loop.

For example, say both the source and the target share a child where the
source is a filter; by letting @replaces point to the common child, you
ask for a loop.

Or if you use @replaces in drive-mirror with sync=none and
mode=absolute-paths, you generally ask for a loop (@replaces must point
to a child of the source, and sync=none makes the source the backing
file of the target after the job).

bdrv_replace_node() will not create those loops, but it by doing so, it
ignores the user-requested configuration, which is not ideally either.
(In the first example above, the target's child will remain what it was,
which may still be reasonable.  But in the second example, the target
will just not become a child of the source, which is precisely what was
requested with @replaces.)

So prevent such configurations, both before the job, and before it
actually completes.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 include/block/block_int.h |  3 +++
 block.c                   | 30 ++++++++++++++++++++++++
 block/mirror.c            | 19 +++++++++++++++-
 blockdev.c                | 48 ++++++++++++++++++++++++++++++++++++++-
 4 files changed, 98 insertions(+), 2 deletions(-)

diff --git a/include/block/block_int.h b/include/block/block_int.h
index 70f26530c9..8a26a0d88a 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -1256,6 +1256,9 @@ void bdrv_format_default_perms(BlockDriverState *bs, BdrvChild *c,
 bool bdrv_recurse_can_replace(BlockDriverState *bs,
                               BlockDriverState *to_replace);
 
+bool bdrv_is_child_of(BlockDriverState *child, BlockDriverState *parent,
+                      int min_level);
+
 /*
  * Default implementation for drivers to pass bdrv_co_block_status() to
  * their file.
diff --git a/block.c b/block.c
index 0d9b3de98f..332191fb47 100644
--- a/block.c
+++ b/block.c
@@ -6260,6 +6260,36 @@ out:
     return to_replace_bs;
 }
 
+/*
+ * Return true iff @child is a (recursive) child of @parent, with at
+ * least @min_level edges between them.
+ *
+ * (If @min_level == 0, return true if @child == @parent.  For
+ * @min_level == 1, @child needs to be at least a real child; for
+ * @min_level == 2, it needs to be at least a grand-child; and so on.)
+ */
+bool bdrv_is_child_of(BlockDriverState *child, BlockDriverState *parent,
+                      int min_level)
+{
+    BdrvChild *c;
+
+    if (child == parent && min_level <= 0) {
+        return true;
+    }
+
+    if (!parent) {
+        return false;
+    }
+
+    QLIST_FOREACH(c, &parent->children, next) {
+        if (bdrv_is_child_of(child, c->bs, min_level - 1)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 /**
  * Iterates through the list of runtime option keys that are said to
  * be "strong" for a BDS.  An option is called "strong" if it changes
diff --git a/block/mirror.c b/block/mirror.c
index d877637e1e..f30a6933d8 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -701,7 +701,24 @@ static int mirror_exit_common(Job *job)
          * there.
          */
         if (bdrv_recurse_can_replace(src, to_replace)) {
-            bdrv_replace_node(to_replace, target_bs, &local_err);
+            /*
+             * It is OK for @to_replace to be an immediate child of
+             * @target_bs, because that is what happens with
+             * drive-mirror sync=none mode=absolute-paths: target_bs's
+             * backing file will be the source node, which is also
+             * to_replace (by default).
+             * bdrv_replace_node() handles this case by not letting
+             * target_bs->backing point to itself, but to the source
+             * still.
+             */
+            if (!bdrv_is_child_of(to_replace, target_bs, 2)) {
+                bdrv_replace_node(to_replace, target_bs, &local_err);
+            } else {
+                error_setg(&local_err, "Can no longer replace '%s' by '%s', "
+                           "because the former is now a child of the latter, "
+                           "and doing so would thus create a loop",
+                           to_replace->node_name, target_bs->node_name);
+            }
         } else {
             error_setg(&local_err, "Can no longer replace '%s' by '%s', "
                        "because it can no longer be guaranteed that doing so "
diff --git a/blockdev.c b/blockdev.c
index 0420bc29be..27344247d5 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3845,7 +3845,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
     }
 
     if (has_replaces) {
-        BlockDriverState *to_replace_bs;
+        BlockDriverState *to_replace_bs, *target_backing_bs;
         AioContext *replace_aio_context;
         int64_t bs_size, replace_size;
 
@@ -3860,6 +3860,52 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
             return;
         }
 
+        if (bdrv_is_child_of(to_replace_bs, target, 1)) {
+            error_setg(errp, "Replacing %s by %s would result in a loop, "
+                       "because the former is a child of the latter",
+                       to_replace_bs->node_name, target->node_name);
+            return;
+        }
+
+        if (backing_mode == MIRROR_SOURCE_BACKING_CHAIN ||
+            backing_mode == MIRROR_OPEN_BACKING_CHAIN)
+        {
+            /*
+             * While we do not quite know what OPEN_BACKING_CHAIN
+             * (used for mode=existing) will yield, it is probably
+             * best to restrict it exactly like SOURCE_BACKING_CHAIN,
+             * because that is our best guess.
+             */
+            switch (sync) {
+            case MIRROR_SYNC_MODE_FULL:
+                target_backing_bs = NULL;
+                break;
+
+            case MIRROR_SYNC_MODE_TOP:
+                target_backing_bs = backing_bs(bs);
+                break;
+
+            case MIRROR_SYNC_MODE_NONE:
+                target_backing_bs = bs;
+                break;
+
+            default:
+                abort();
+            }
+        } else {
+            assert(backing_mode == MIRROR_LEAVE_BACKING_CHAIN);
+            target_backing_bs = backing_bs(target);
+        }
+
+        if (bdrv_is_child_of(to_replace_bs, target_backing_bs, 0)) {
+            error_setg(errp, "Replacing '%s' by '%s' with this sync mode would "
+                       "result in a loop, because the former would be a child "
+                       "of the latter's backing file ('%s') after the mirror "
+                       "job", to_replace_bs->node_name, target->node_name,
+                       target_backing_bs->node_name);
+            return;
+        }
+
         replace_aio_context = bdrv_get_aio_context(to_replace_bs);
         aio_context_acquire(replace_aio_context);
         replace_size = bdrv_getlength(to_replace_bs);
-- 
2.21.0



  parent reply	other threads:[~2019-09-20 15:46 UTC|newest]

Thread overview: 58+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-09-20 15:27 [PATCH 00/22] block: Fix check_to_replace_node() Max Reitz
2019-09-20 15:27 ` [PATCH 01/22] blockdev: Allow external snapshots everywhere Max Reitz
2019-09-25 11:45   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 02/22] blockdev: Allow resizing everywhere Max Reitz
2019-09-25 11:46   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 03/22] block: Drop bdrv_is_first_non_filter() Max Reitz
2019-09-25 11:46   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 04/22] iotests: Let 041 use -blockdev for quorum children Max Reitz
2019-09-25 11:51   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 05/22] quorum: Fix child permissions Max Reitz
2019-09-25 11:56   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:02     ` Max Reitz
2019-09-20 15:27 ` [PATCH 06/22] block: Add bdrv_recurse_can_replace() Max Reitz
2019-09-25 12:39   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:03     ` Max Reitz
2019-09-20 15:27 ` [PATCH 07/22] blkverify: Implement .bdrv_recurse_can_replace() Max Reitz
2019-09-25 12:56   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:06     ` Max Reitz
2019-09-20 15:27 ` [PATCH 08/22] quorum: Store children in own structure Max Reitz
2019-09-25 13:21   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:07     ` Max Reitz
2019-09-20 15:27 ` [PATCH 09/22] quorum: Add QuorumChild.to_be_replaced Max Reitz
2019-09-25 13:45   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:13     ` Max Reitz
2019-09-20 15:27 ` [PATCH 10/22] quorum: Implement .bdrv_recurse_can_replace() Max Reitz
2019-09-25 13:39   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:12     ` Max Reitz
2019-09-25 14:12   ` Vladimir Sementsov-Ogievskiy
2019-09-26 11:17     ` Max Reitz
2019-09-25 14:14   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 11/22] block: Use bdrv_recurse_can_replace() Max Reitz
2019-09-20 15:27 ` [PATCH 12/22] block: Remove bdrv_recurse_is_first_non_filter() Max Reitz
2019-09-25 14:16   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 13/22] mirror: Double-check immediately before replacing Max Reitz
2019-09-20 15:27 ` [PATCH 14/22] quorum: Stop marking it as a filter Max Reitz
2019-09-26 12:43   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` Max Reitz [this message]
2019-09-26 13:08   ` [PATCH 15/22] mirror: Prevent loops Vladimir Sementsov-Ogievskiy
2019-10-02 12:36     ` Max Reitz
2019-09-20 15:27 ` [PATCH 16/22] iotests: Use complete_and_wait() in 155 Max Reitz
2019-09-26 13:11   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:27 ` [PATCH 17/22] iotests: Add VM.assert_block_path() Max Reitz
2019-09-26 14:07   ` Vladimir Sementsov-Ogievskiy
2019-10-02 12:40     ` Max Reitz
2019-10-02 13:51       ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:28 ` [PATCH 18/22] iotests: Resolve TODOs in 041 Max Reitz
2019-09-26 14:09   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:28 ` [PATCH 19/22] iotests: Use self.image_len in TestRepairQuorum Max Reitz
2019-09-26 14:13   ` Vladimir Sementsov-Ogievskiy
2019-10-02 12:42     ` Max Reitz
2019-09-20 15:28 ` [PATCH 20/22] iotests: Add tests for invalid Quorum @replaces Max Reitz
2019-09-26 14:30   ` Vladimir Sementsov-Ogievskiy
2019-10-02 12:43     ` Max Reitz
2019-09-20 15:28 ` [PATCH 21/22] iotests: Check that @replaces can replace filters Max Reitz
2019-09-26 14:42   ` Vladimir Sementsov-Ogievskiy
2019-09-20 15:28 ` [PATCH 22/22] iotests: Mirror must not attempt to create loops Max Reitz
2019-09-26 15:03   ` Vladimir Sementsov-Ogievskiy
2019-10-02 12:46     ` Max Reitz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190920152804.12875-16-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=berto@igalia.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.