Skip to contentSkip to conversation

block: Fix permissions after ro/rw reopen

PATCH Applied Kevin Wolf 3 participants 29 messages Sep 15, 2017 – Sep 22, 2017 Block layer

lore
Share & tools
lore

Kevin Wolf

author
#0 lore raw

cover block: Fix permissions after ro/rw reopen

bdrv_reopen() can switch nodes between read-only and read-write modes. This has implications for the required permissions on their child nodes. For example, a qcow2 node requests write permissions on bs->file only if it is writable itself.

This means that during bdrv_reopen(), the permissions need to be recalculated in order to prevent failures where the bs->file permissions don't match its actual read-only state (e.g. bs->file is a read-write node, but the permission still enforces read-only access).

Kevin Wolf (6):
qemu-io: Reset qemuio_blk permissions before each command
block: Add reopen_queue to bdrv_child_perm()
block: Add reopen queue to bdrv_check_perm()
block: Base permissions on rw state after reopen
block: reopen: Queue children after their parents
block: Fix permissions after bdrv_reopen()

 include/block/block.h      |   2 +-
 include/block/block_int.h  |   7 ++
 block.c                    | 191 +++++++++++++++++++++++++++++++++------------
 block/commit.c             |   1 +
 block/mirror.c             |   1 +
 block/replication.c        |   1 +
 block/vvfat.c              |   1 +
 qemu-io.c                  |  13 +++
 tests/qemu-iotests/187.out |   2 +-
 9 files changed, 169 insertions(+), 50 deletions(-)
Signature · 1 line
2.13.5
author
#1 lore raw

PATCH 1/6 qemu-io: Reset qemuio_blk permissions before each command

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

command() already makes sure to request any additional permissions that a qemu-io command requires, so just resetting the permissions to values that are safe for read-only images is enough to fix this.

As a side effect, this makes the output of qemu-iotests case 187 more consistent.

---
 qemu-io.c                  | 13 +++++++++++++
 tests/qemu-iotests/187.out |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
qemu-io.c +13 -0
index 265445ad89..9e031f0f8e 100644
@@ -366,6 +366,12 @@ static void command_loop(void)
     char *input;
 
     for (i = 0; !done && i < ncmdline; i++) {
+        /* Make sure that we start each command with clean permissions and only
+         * add whatever the specific cmdinfo_t describes */
+        if (qemuio_blk) {
+            blk_set_perm(qemuio_blk, BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL,
+                         &error_abort);
+        }
         done = qemuio_command(qemuio_blk, cmdline[i]);
     }
     if (cmdline) {
@@ -391,6 +397,13 @@ static void command_loop(void)
         if (input == NULL) {
             break;
         }
+
+        /* Make sure that we start each command with clean permissions and only
+         * add whatever the specific cmdinfo_t describes */
+        if (qemuio_blk) {
+            blk_set_perm(qemuio_blk, BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL,
+                         &error_abort);
+        }
         done = qemuio_command(qemuio_blk, input);
         g_free(input);
 
tests/qemu-iotests/187.out +1 -1
index 68fb944cd5..30b987f71f 100644
@@ -12,7 +12,7 @@ Start from read-write
 
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-write failed: Operation not permitted
+Block node is read-only
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 *** done
Signature · 1 line
2.13.5
Reviewed-by on v1 1/6
#2 lore raw

Re: [Qemu-devel] [PATCH 1/6] qemu-io: Reset qemuio_blk permissions before each command

Quoted 9 lines · Kevin Wolf

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

command() already makes sure to request any additional permissions that a qemu-io command requires, so just resetting the permissions to values that are safe for read-only images is enough to fix this.

Quoted 9 lines

As a side effect, this makes the output of qemu-iotests case 187 more consistent.

---
 qemu-io.c                  | 13 +++++++++++++
 tests/qemu-iotests/187.out |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
Reviewed-by on v1 1/6
#3 lore raw

Re: [Qemu-devel] [PATCH 1/6] qemu-io: Reset qemuio_blk permissions before each command

In reply to Kevin Wolf
Quoted 65 lines · Kevin Wolf

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

command() already makes sure to request any additional permissions that a qemu-io command requires, so just resetting the permissions to values that are safe for read-only images is enough to fix this.

As a side effect, this makes the output of qemu-iotests case 187 more consistent.

---
 qemu-io.c                  | 13 +++++++++++++
 tests/qemu-iotests/187.out |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
qemu-io.c +13 -0
index 265445ad89..9e031f0f8e 100644
@@ -366,6 +366,12 @@ static void command_loop(void)
     char *input;
 
     for (i = 0; !done && i < ncmdline; i++) {
+        /* Make sure that we start each command with clean permissions and only
+         * add whatever the specific cmdinfo_t describes */
+        if (qemuio_blk) {
+            blk_set_perm(qemuio_blk, BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL,
+                         &error_abort);
+        }
         done = qemuio_command(qemuio_blk, cmdline[i]);
     }
     if (cmdline) {
@@ -391,6 +397,13 @@ static void command_loop(void)
         if (input == NULL) {
             break;
         }
+
+        /* Make sure that we start each command with clean permissions and only
+         * add whatever the specific cmdinfo_t describes */
+        if (qemuio_blk) {
+            blk_set_perm(qemuio_blk, BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL,
+                         &error_abort);
+        }
         done = qemuio_command(qemuio_blk, input);
         g_free(input);
 
tests/qemu-iotests/187.out +1 -1
index 68fb944cd5..30b987f71f 100644
@@ -12,7 +12,7 @@ Start from read-write
 
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-write failed: Operation not permitted
+Block node is read-only
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 *** done
Signature · 1 line
2.13.5
author
#4 lore raw

Re: [Qemu-devel] [PATCH 1/6] qemu-io: Reset qemuio_blk permissions before each command

In reply to Kevin Wolf

Am 15.09.2017 um 12:10 hat Kevin Wolf geschrieben:

Quoted 5 lines

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

Quoted 8 lines

command() already makes sure to request any additional permissions that a qemu-io command requires, so just resetting the permissions to values that are safe for read-only images is enough to fix this.

As a side effect, this makes the output of qemu-iotests case 187 more consistent.

This seems to break qemu-iotests 077 and 081 for raw. I'll look into it tomorrow.

Kevin

author
#5 lore raw

Re: [Qemu-devel] [Qemu-block] [PATCH 1/6] qemu-io: Reset qemuio_blk permissions before each command

Am 21.09.2017 um 15:53 hat Kevin Wolf geschrieben:

Quoted 10 lines

Am 15.09.2017 um 12:10 hat Kevin Wolf geschrieben:

Quoted 9 lines

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

command() already makes sure to request any additional permissions that a qemu-io command requires, so just resetting the permissions to values that are safe for read-only images is enough to fix this.

Quoted 7 lines
Quoted 4 lines

As a side effect, this makes the output of qemu-iotests case 187 more consistent.

This seems to break qemu-iotests 077 and 081 for raw. I'll look into it tomorrow.

The problem seems to be related to 'aio_write': We already reset the permissions while the request is still in flight and might still start new internal requests that aren't allowed any more. We would have to drain blk around resetting the permissions, but this would obviously defeat the purpose of the aio_* commands.

I tried creating a separate temporary BB, but it doesn't seem to be that straightforward (still crashes during 'aio_flush'). It would also provide the wrong semantics, HMP 'qemu-io' commands are supposed to be executed on the exact BlockBackend that was given.

So unless someone has an idea how to salvage this patch, I'm afraid I'll just have to drop it. The original state isn't really correct either, but it errs on the side of having more permissions than necessary, so failure is less likely.

Kevin

author
#6 lore raw

PATCH v2 1/6 qemu-io: Drop write permissions before read-only reopen

In reply to Kevin Wolf

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

This requires a bdrv_drain() call because otherwise in-flight AIO write requests could issue new internal requests while the permission has already gone away, which would cause assertion failures. Draining the queue doesn't break AIO requests in any new way, bdrv_reopen() would drain it anyway only a few lines later.

---
 qemu-io-cmds.c             | 12 ++++++++++++
 tests/qemu-iotests/187.out |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)
qemu-io-cmds.c +12 -0
index 2811a89099..3727fb43f3 100644
@@ -2010,6 +2010,18 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
         return 0;
     }
 
+    if (!(flags & BDRV_O_RDWR)) {
+        uint64_t orig_perm, orig_shared_perm;
+
+        bdrv_drain(bs);
+
+        blk_get_perm(blk, &orig_perm, &orig_shared_perm);
+        blk_set_perm(blk,
+                     orig_perm & ~(BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED),
+                     orig_shared_perm,
+                     &error_abort);
+    }
+
     qopts = qemu_opts_find(&reopen_opts, NULL);
     opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
     qemu_opts_reset(&reopen_opts);
tests/qemu-iotests/187.out +1 -1
index 68fb944cd5..30b987f71f 100644
@@ -12,7 +12,7 @@ Start from read-write
 
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-write failed: Operation not permitted
+Block node is read-only
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 *** done
Signature · 1 line
2.13.5
Reviewed-by on v2 1/6
#7 lore raw

Re: [Qemu-devel] [PATCH v2 1/6] qemu-io: Drop write permissions before read-only reopen

Quoted 56 lines · Kevin Wolf

qemu-io provides a 'reopen' command that allows switching from writable to read-only access. We need to make sure that we don't try to keep write permissions to a BlockBackend that becomes read-only, otherwise things are going to fail.

This requires a bdrv_drain() call because otherwise in-flight AIO write requests could issue new internal requests while the permission has already gone away, which would cause assertion failures. Draining the queue doesn't break AIO requests in any new way, bdrv_reopen() would drain it anyway only a few lines later.

---
 qemu-io-cmds.c             | 12 ++++++++++++
 tests/qemu-iotests/187.out |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)
qemu-io-cmds.c +12 -0
index 2811a89099..3727fb43f3 100644
@@ -2010,6 +2010,18 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
         return 0;
     }
 
+    if (!(flags & BDRV_O_RDWR)) {
+        uint64_t orig_perm, orig_shared_perm;
+
+        bdrv_drain(bs);
+
+        blk_get_perm(blk, &orig_perm, &orig_shared_perm);
+        blk_set_perm(blk,
+                     orig_perm & ~(BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED),
+                     orig_shared_perm,
+                     &error_abort);
+    }
+
     qopts = qemu_opts_find(&reopen_opts, NULL);
     opts = qopts ? qemu_opts_to_qdict(qopts, NULL) : NULL;
     qemu_opts_reset(&reopen_opts);
tests/qemu-iotests/187.out +1 -1
index 68fb944cd5..30b987f71f 100644
@@ -12,7 +12,7 @@ Start from read-write
 
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
-write failed: Operation not permitted
+Block node is read-only
 wrote 65536/65536 bytes at offset 0
 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
 *** done
Signature · 1 line
2.13.5
author
#8 lore raw

PATCH 2/6 block: Add reopen_queue to bdrv_child_perm()

In reply to Kevin Wolf

In the context of bdrv_reopen(), we'll have to look at the state of the graph as it will be after the reopen. This interface addition is in preparation for the change.

---
 include/block/block_int.h |  7 +++++++
 block.c                   | 19 ++++++++++++-------
 block/commit.c            |  1 +
 block/mirror.c            |  1 +
 block/replication.c       |  1 +
 block/vvfat.c             |  1 +
 6 files changed, 23 insertions(+), 7 deletions(-)
include/block/block_int.h +7 -0
index ba4c383393..99abe2ce74 100644
@@ -411,9 +411,14 @@ struct BlockDriver {
      *
      * If @c is NULL, return the permissions for attaching a new child for the
      * given @role.
+     *
+     * If @reopen_queue is non-NULL, don't return the currently needed
+     * permissions, but those that will be needed after applying the
+     * @reopen_queue.
      */
      void (*bdrv_child_perm)(BlockDriverState *bs, BdrvChild *c,
                              const BdrvChildRole *role,
+                             BlockReopenQueue *reopen_queue,
                              uint64_t parent_perm, uint64_t parent_shared,
                              uint64_t *nperm, uint64_t *nshared);
 
@@ -983,6 +988,7 @@ int bdrv_child_try_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
  * all children */
 void bdrv_filter_default_perms(BlockDriverState *bs, BdrvChild *c,
                                const BdrvChildRole *role,
+                               BlockReopenQueue *reopen_queue,
                                uint64_t perm, uint64_t shared,
                                uint64_t *nperm, uint64_t *nshared);
 
@@ -992,6 +998,7 @@ void bdrv_filter_default_perms(BlockDriverState *bs, BdrvChild *c,
  * CONSISTENT_READ and doesn't share WRITE. */
 void bdrv_format_default_perms(BlockDriverState *bs, BdrvChild *c,
                                const BdrvChildRole *role,
+                               BlockReopenQueue *reopen_queue,
                                uint64_t perm, uint64_t shared,
                                uint64_t *nperm, uint64_t *nshared);
 
block.c +12 -7
index 6dd47e414e..c7724c85e3 100644
@@ -1537,16 +1537,17 @@ static void bdrv_child_abort_perm_update(BdrvChild *c);
 static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared);
 
 static void bdrv_child_perm(BlockDriverState *bs, BlockDriverState *child_bs,
-                            BdrvChild *c,
-                            const BdrvChildRole *role,
+                            BdrvChild *c, const BdrvChildRole *role,
+                            BlockReopenQueue *reopen_queue,
                             uint64_t parent_perm, uint64_t parent_shared,
                             uint64_t *nperm, uint64_t *nshared)
 {
     if (bs->drv && bs->drv->bdrv_child_perm) {
-        bs->drv->bdrv_child_perm(bs, c, role,
+        bs->drv->bdrv_child_perm(bs, c, role, reopen_queue,
                                  parent_perm, parent_shared,
                                  nperm, nshared);
     }
+    /* TODO Take force_share from reopen_queue */
     if (child_bs && child_bs->force_share) {
         *nshared = BLK_PERM_ALL;
     }
@@ -1596,7 +1597,7 @@ static int bdrv_check_perm(BlockDriverState *bs, uint64_t cumulative_perms,
     /* Check all children */
     QLIST_FOREACH(c, &bs->children, next) {
         uint64_t cur_perm, cur_shared;
-        bdrv_child_perm(bs, c->bs, c, c->role,
+        bdrv_child_perm(bs, c->bs, c, c->role, NULL,
                         cumulative_perms, cumulative_shared_perms,
                         &cur_perm, &cur_shared);
         ret = bdrv_child_check_perm(c, cur_perm, cur_shared, ignore_children,
@@ -1658,7 +1659,7 @@ static void bdrv_set_perm(BlockDriverState *bs, uint64_t cumulative_perms,
     /* Update all children */
     QLIST_FOREACH(c, &bs->children, next) {
         uint64_t cur_perm, cur_shared;
-        bdrv_child_perm(bs, c->bs, c, c->role,
+        bdrv_child_perm(bs, c->bs, c, c->role, NULL,
                         cumulative_perms, cumulative_shared_perms,
                         &cur_perm, &cur_shared);
         bdrv_child_set_perm(c, cur_perm, cur_shared);
@@ -1827,6 +1828,7 @@ int bdrv_child_try_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
 
 void bdrv_filter_default_perms(BlockDriverState *bs, BdrvChild *c,
                                const BdrvChildRole *role,
+                               BlockReopenQueue *reopen_queue,
                                uint64_t perm, uint64_t shared,
                                uint64_t *nperm, uint64_t *nshared)
 {
@@ -1844,6 +1846,7 @@ void bdrv_filter_default_perms(BlockDriverState *bs, BdrvChild *c,
 
 void bdrv_format_default_perms(BlockDriverState *bs, BdrvChild *c,
                                const BdrvChildRole *role,
+                               BlockReopenQueue *reopen_queue,
                                uint64_t perm, uint64_t shared,
                                uint64_t *nperm, uint64_t *nshared)
 {
@@ -1853,9 +1856,11 @@ void bdrv_format_default_perms(BlockDriverState *bs, BdrvChild *c,
     if (!backing) {
         /* Apart from the modifications below, the same permissions are
          * forwarded and left alone as for filters */
-        bdrv_filter_default_perms(bs, c, role, perm, shared, &perm, &shared);
+        bdrv_filter_default_perms(bs, c, role, reopen_queue, perm, shared,
+                                  &perm, &shared);
 
         /* Format drivers may touch metadata even if the guest doesn't write */
+        /* TODO Take flags from reopen_queue */
         if (bdrv_is_writable(bs)) {
             perm |= BLK_PERM_WRITE | BLK_PERM_RESIZE;
         }
@@ -1999,7 +2004,7 @@ BdrvChild *bdrv_attach_child(BlockDriverState *parent_bs,
 
     assert(parent_bs->drv);
     assert(bdrv_get_aio_context(parent_bs) == bdrv_get_aio_context(child_bs));
-    bdrv_child_perm(parent_bs, child_bs, NULL, child_role,
+    bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL,
                     perm, shared_perm, &perm, &shared_perm);
 
     child = bdrv_root_attach_child(child_bs, child_name, child_role,
block/commit.c +1 -0
index 898d91f653..8f0e83578a 100644
@@ -257,6 +257,7 @@ static void bdrv_commit_top_close(BlockDriverState *bs)
 
 static void bdrv_commit_top_child_perm(BlockDriverState *bs, BdrvChild *c,
                                        const BdrvChildRole *role,
+                                       BlockReopenQueue *reopen_queue,
                                        uint64_t perm, uint64_t shared,
                                        uint64_t *nperm, uint64_t *nshared)
 {
block/mirror.c +1 -0
index 6531652d73..6f5cb9f26c 100644
@@ -1084,6 +1084,7 @@ static void bdrv_mirror_top_close(BlockDriverState *bs)
 
 static void bdrv_mirror_top_child_perm(BlockDriverState *bs, BdrvChild *c,
                                        const BdrvChildRole *role,
+                                       BlockReopenQueue *reopen_queue,
                                        uint64_t perm, uint64_t shared,
                                        uint64_t *nperm, uint64_t *nshared)
 {
block/replication.c +1 -0
index bf4462c8e7..3a4e6822e4 100644
@@ -157,6 +157,7 @@ static void replication_close(BlockDriverState *bs)
 
 static void replication_child_perm(BlockDriverState *bs, BdrvChild *c,
                                    const BdrvChildRole *role,
+                                   BlockReopenQueue *reopen_queue,
                                    uint64_t perm, uint64_t shared,
                                    uint64_t *nperm, uint64_t *nshared)
 {
block/vvfat.c +1 -0
index c54fa94651..ee894bbe98 100644
@@ -3210,6 +3210,7 @@ err:
 
 static void vvfat_child_perm(BlockDriverState *bs, BdrvChild *c,
                              const BdrvChildRole *role,
+                             BlockReopenQueue *reopen_queue,
                              uint64_t perm, uint64_t shared,
                              uint64_t *nperm, uint64_t *nshared)
 {
Signature · 1 line
2.13.5
Reviewed-by on v1 2/6
#9 lore raw

Re: [Qemu-devel] [PATCH 2/6] block: Add reopen_queue to bdrv_child_perm()

Quoted 5 lines · Kevin Wolf

In the context of bdrv_reopen(), we'll have to look at the state of the graph as it will be after the reopen. This interface addition is in preparation for the change.

Quoted 9 lines
---
 include/block/block_int.h |  7 +++++++
 block.c                   | 19 ++++++++++++-------
 block/commit.c            |  1 +
 block/mirror.c            |  1 +
 block/replication.c       |  1 +
 block/vvfat.c             |  1 +
 6 files changed, 23 insertions(+), 7 deletions(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#10 lore raw

PATCH 3/6 block: Add reopen queue to bdrv_check_perm()

In reply to Kevin Wolf

In the context of bdrv_reopen(), we'll have to look at the state of the graph as it will be after the reopen. This interface addition is in preparation for the change.

---
 block.c | 34 +++++++++++++++++++---------------
 1 file changed, 19 insertions(+), 15 deletions(-)
block.c +19 -15
index c7724c85e3..0b499fda4c 100644
@@ -1531,7 +1531,8 @@ static int bdrv_fill_options(QDict **options, const char *filename,
     return 0;
 }
 
-static int bdrv_child_check_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
+static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q,
+                                 uint64_t perm, uint64_t shared,
                                  GSList *ignore_children, Error **errp);
 static void bdrv_child_abort_perm_update(BdrvChild *c);
 static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared);
@@ -1562,7 +1563,8 @@ static void bdrv_child_perm(BlockDriverState *bs, BlockDriverState *child_bs,
  * A call to this function must always be followed by a call to bdrv_set_perm()
  * or bdrv_abort_perm_update().
  */
-static int bdrv_check_perm(BlockDriverState *bs, uint64_t cumulative_perms,
+static int bdrv_check_perm(BlockDriverState *bs, BlockReopenQueue *q,
+                           uint64_t cumulative_perms,
                            uint64_t cumulative_shared_perms,
                            GSList *ignore_children, Error **errp)
 {
@@ -1597,11 +1599,11 @@ static int bdrv_check_perm(BlockDriverState *bs, uint64_t cumulative_perms,
     /* Check all children */
     QLIST_FOREACH(c, &bs->children, next) {
         uint64_t cur_perm, cur_shared;
-        bdrv_child_perm(bs, c->bs, c, c->role, NULL,
+        bdrv_child_perm(bs, c->bs, c, c->role, q,
                         cumulative_perms, cumulative_shared_perms,
                         &cur_perm, &cur_shared);
-        ret = bdrv_child_check_perm(c, cur_perm, cur_shared, ignore_children,
-                                    errp);
+        ret = bdrv_child_check_perm(c, q, cur_perm, cur_shared,
+                                    ignore_children, errp);
         if (ret < 0) {
             return ret;
         }
@@ -1727,7 +1729,8 @@ char *bdrv_perm_names(uint64_t perm)
  *
  * Needs to be followed by a call to either bdrv_set_perm() or
  * bdrv_abort_perm_update(). */
-static int bdrv_check_update_perm(BlockDriverState *bs, uint64_t new_used_perm,
+static int bdrv_check_update_perm(BlockDriverState *bs, BlockReopenQueue *q,
+                                  uint64_t new_used_perm,
                                   uint64_t new_shared_perm,
                                   GSList *ignore_children, Error **errp)
 {
@@ -1769,19 +1772,20 @@ static int bdrv_check_update_perm(BlockDriverState *bs, uint64_t new_used_perm,
         cumulative_shared_perms &= c->shared_perm;
     }
 
-    return bdrv_check_perm(bs, cumulative_perms, cumulative_shared_perms,
+    return bdrv_check_perm(bs, q, cumulative_perms, cumulative_shared_perms,
                            ignore_children, errp);
 }
 
 /* Needs to be followed by a call to either bdrv_child_set_perm() or
  * bdrv_child_abort_perm_update(). */
-static int bdrv_child_check_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
+static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q,
+                                 uint64_t perm, uint64_t shared,
                                  GSList *ignore_children, Error **errp)
 {
     int ret;
 
     ignore_children = g_slist_prepend(g_slist_copy(ignore_children), c);
-    ret = bdrv_check_update_perm(c->bs, perm, shared, ignore_children, errp);
+    ret = bdrv_check_update_perm(c->bs, q, perm, shared, ignore_children, errp);
     g_slist_free(ignore_children);
 
     return ret;
@@ -1809,7 +1813,7 @@ int bdrv_child_try_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
 {
     int ret;
 
-    ret = bdrv_child_check_perm(c, perm, shared, NULL, errp);
+    ret = bdrv_child_check_perm(c, NULL, perm, shared, NULL, errp);
     if (ret < 0) {
         bdrv_child_abort_perm_update(c);
         return ret;
@@ -1950,7 +1954,7 @@ static void bdrv_replace_child(BdrvChild *child, BlockDriverState *new_bs)
          * because we're just taking a parent away, so we're loosening
          * restrictions. */
         bdrv_get_cumulative_perm(old_bs, &perm, &shared_perm);
-        bdrv_check_perm(old_bs, perm, shared_perm, NULL, &error_abort);
+        bdrv_check_perm(old_bs, NULL, perm, shared_perm, NULL, &error_abort);
         bdrv_set_perm(old_bs, perm, shared_perm);
     }
 
@@ -1969,7 +1973,7 @@ BdrvChild *bdrv_root_attach_child(BlockDriverState *child_bs,
     BdrvChild *child;
     int ret;
 
-    ret = bdrv_check_update_perm(child_bs, perm, shared_perm, NULL, errp);
+    ret = bdrv_check_update_perm(child_bs, NULL, perm, shared_perm, NULL, errp);
     if (ret < 0) {
         bdrv_abort_perm_update(child_bs);
         return NULL;
@@ -3184,7 +3188,7 @@ void bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
 
     /* Check whether the required permissions can be granted on @to, ignoring
      * all BdrvChild in @list so that they can't block themselves. */
-    ret = bdrv_check_update_perm(to, perm, shared, list, errp);
+    ret = bdrv_check_update_perm(to, NULL, perm, shared, list, errp);
     if (ret < 0) {
         bdrv_abort_perm_update(to);
         goto out;
@@ -4054,7 +4058,7 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
 
     /* Update permissions, they may differ for inactive nodes */
     bdrv_get_cumulative_perm(bs, &perm, &shared_perm);
-    ret = bdrv_check_perm(bs, perm, shared_perm, NULL, &local_err);
+    ret = bdrv_check_perm(bs, NULL, perm, shared_perm, NULL, &local_err);
     if (ret < 0) {
         bs->open_flags |= BDRV_O_INACTIVE;
         error_propagate(errp, local_err);
@@ -4121,7 +4125,7 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs,
 
         /* Update permissions, they may differ for inactive nodes */
         bdrv_get_cumulative_perm(bs, &perm, &shared_perm);
-        bdrv_check_perm(bs, perm, shared_perm, NULL, &error_abort);
+        bdrv_check_perm(bs, NULL, perm, shared_perm, NULL, &error_abort);
         bdrv_set_perm(bs, perm, shared_perm);
     }
 
Signature · 1 line
2.13.5
Reviewed-by on v1 3/6
#11 lore raw

Re: [Qemu-devel] [PATCH 3/6] block: Add reopen queue to bdrv_check_perm()

Quoted 9 lines · Kevin Wolf

In the context of bdrv_reopen(), we'll have to look at the state of the graph as it will be after the reopen. This interface addition is in preparation for the change.

---
 block.c | 34 +++++++++++++++++++---------------
 1 file changed, 19 insertions(+), 15 deletions(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#12 lore raw

PATCH 4/6 block: Base permissions on rw state after reopen

In reply to Kevin Wolf

When new permissions are calculated during bdrv_reopen(), they need to be based on the state of the graph as it will be after the reopen has completed, not on the current state of the involved nodes.

This patch makes bdrv_is_writable() optionally accept a BlockReopenQueue from which the new flags are taken. This is then used for determining the new bs->file permissions of format drivers as soon as we add the code to actually pass a non-NULL reopen queue to the .bdrv_child_perm callbacks.

While moving bdrv_is_writable(), make it static. It isn't used outside block.c.

---
 include/block/block.h |  1 -
 block.c               | 52 ++++++++++++++++++++++++++++++++++++---------------
 2 files changed, 37 insertions(+), 16 deletions(-)
include/block/block.h +0 -1
index 2ad18775af..082eb2cd9c 100644
@@ -435,7 +435,6 @@ int bdrv_is_allocated_above(BlockDriverState *top, BlockDriverState *base,
                             int64_t offset, int64_t bytes, int64_t *pnum);
 
 bool bdrv_is_read_only(BlockDriverState *bs);
-bool bdrv_is_writable(BlockDriverState *bs);
 int bdrv_can_set_read_only(BlockDriverState *bs, bool read_only,
                            bool ignore_allow_rdw, Error **errp);
 int bdrv_set_read_only(BlockDriverState *bs, bool read_only, Error **errp);
block.c +37 -15

82 lines not on this page · raw message

Signature · 1 line
2.13.5
Reviewed-by on v1 4/6
#13 lore raw

Re: [Qemu-devel] [PATCH 4/6] block: Base permissions on rw state after reopen

Quoted 24 lines · Kevin Wolf

When new permissions are calculated during bdrv_reopen(), they need to be based on the state of the graph as it will be after the reopen has completed, not on the current state of the involved nodes.

This patch makes bdrv_is_writable() optionally accept a BlockReopenQueue from which the new flags are taken. This is then used for determining the new bs->file permissions of format drivers as soon as we add the code to actually pass a non-NULL reopen queue to the .bdrv_child_perm callbacks.

While moving bdrv_is_writable(), make it static. It isn't used outside block.c.

---
 include/block/block.h |  1 -
 block.c               | 52 ++++++++++++++++++++++++++++++++++++---------------
 2 files changed, 37 insertions(+), 16 deletions(-)

+ * Return the flags that @bs will have after the reopens in @q have
+ * successfully completed. If @q is NULL (or @bs is not contained in @q),
+ * return the current flags.
+ */

block.c static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q, · line 1562

Quoted 5 lines
block.c +4 -0
+static int bdrv_reopen_get_flags(BlockReopenQueue *q, BlockDriverState *bs)
 
+/* Returns whether the image file can be written to after the reopen queue @q
+ * has been successfully applied, or right now if @q is NULL. */
+static bool bdrv_is_writable(BlockDriverState *bs, BlockReopenQueue *q)

Is it worth having both functions with arguments in the same order?

block.c static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q, · line 1565

Quoted 3 lines
block.c +3 -0
+{
+    int flags = bdrv_reopen_get_flags(q, bs);
+

No real semantic impact to leave it as is, but it would avoid the odd swap of arguments here. So either way,

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#14 lore raw

PATCH 5/6 block: reopen: Queue children after their parents

In reply to Kevin Wolf

We will calculate the required new permissions in the prepare stage of a reopen. Required permissions of children can be influenced by the changes made to their parents, but parents are independent from their children. This means that permissions need to be calculated top-down. In order to achieve this, queue parents before their children rather than queuing the children first.

---
 block.c | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)
block.c +13 -13
index ed8d51dd42..204cbb46c7 100644
@@ -2768,6 +2768,19 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
         flags |= BDRV_O_ALLOW_RDWR;
     }
 
+    if (!bs_entry) {
+        bs_entry = g_new0(BlockReopenQueueEntry, 1);
+        QSIMPLEQ_INSERT_TAIL(bs_queue, bs_entry, entry);
+    } else {
+        QDECREF(bs_entry->state.options);
+        QDECREF(bs_entry->state.explicit_options);
+    }
+
+    bs_entry->state.bs = bs;
+    bs_entry->state.options = options;
+    bs_entry->state.explicit_options = explicit_options;
+    bs_entry->state.flags = flags;
+
     QLIST_FOREACH(child, &bs->children, next) {
         QDict *new_child_options;
         char *child_key_dot;
@@ -2787,19 +2800,6 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
                                 child->role, options, flags);
     }
 
-    if (!bs_entry) {
-        bs_entry = g_new0(BlockReopenQueueEntry, 1);
-        QSIMPLEQ_INSERT_TAIL(bs_queue, bs_entry, entry);
-    } else {
-        QDECREF(bs_entry->state.options);
-        QDECREF(bs_entry->state.explicit_options);
-    }
-
-    bs_entry->state.bs = bs;
-    bs_entry->state.options = options;
-    bs_entry->state.explicit_options = explicit_options;
-    bs_entry->state.flags = flags;
-
     return bs_queue;
 }
 
Signature · 1 line
2.13.5
Reviewed-by on v1 5/6
#15 lore raw

Re: [Qemu-devel] [PATCH 5/6] block: reopen: Queue children after their parents

Quoted 7 lines · Kevin Wolf

We will calculate the required new permissions in the prepare stage of a reopen. Required permissions of children can be influenced by the changes made to their parents, but parents are independent from their children. This means that permissions need to be calculated top-down. In order to achieve this, queue parents before their children rather than queuing the children first.

Quoted 5 lines
---
 block.c | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#16 lore raw

PATCH 6/6 block: Fix permissions after bdrv_reopen()

In reply to Kevin Wolf

If we switch between read-only and read-write, the permissions that image format drivers need on bs->file change, too. Make sure to update the permissions during bdrv_reopen().

---
 include/block/block.h |  1 +
 block.c               | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
include/block/block.h +1 -0
index 082eb2cd9c..3c3af462e4 100644
@@ -166,6 +166,7 @@ typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
 typedef struct BDRVReopenState {
     BlockDriverState *bs;
     int flags;
+    uint64_t perm, shared_perm;
     QDict *options;
     QDict *explicit_options;
     void *opaque;
block.c +64 -0

100 lines not on this page · raw message

Signature · 1 line
2.13.5
Reviewed-by on v1 6/6
#17 lore raw

Re: [Qemu-devel] [PATCH 6/6] block: Fix permissions after bdrv_reopen()

Quoted 13 lines · Kevin Wolf

If we switch between read-only and read-write, the permissions that image format drivers need on bs->file change, too. Make sure to update the permissions during bdrv_reopen().

---
 include/block/block.h |  1 +
 block.c               | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
block.c +2 -0
+static BlockReopenQueueEntry *find_parent_in_reopen_queue(BlockReopenQueue *q,
+                                                          BdrvChild *c)

block.c int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp) · line 2905

Quoted 10 lines
block.c +10 -0
+{
+    BlockReopenQueueEntry *entry;
+
+    QSIMPLEQ_FOREACH(entry, q, entry) {
+        BlockDriverState *bs = entry->state.bs;
+        BdrvChild *child;
+
+        QLIST_FOREACH(child, &bs->children, next) {
+            if (child == c) {
+                return entry;

An O(n^2) loop. Is it going to bite us at any point in the future, or are we generally dealing with a small enough queue size and BDS graph to not worry about it?

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
author
#18 lore raw

Re: [Qemu-devel] [PATCH 6/6] block: Fix permissions after bdrv_reopen()

Am 15.09.2017 um 21:06 hat Eric Blake geschrieben:

Quoted 12 lines · Kevin Wolf
Quoted 10 lines

If we switch between read-only and read-write, the permissions that image format drivers need on bs->file change, too. Make sure to update the permissions during bdrv_reopen().

---
 include/block/block.h |  1 +
 block.c               | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
Quoted 16 lines

block.c int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp) · line 2905

Quoted 12 lines
block.c +12 -0
+static BlockReopenQueueEntry *find_parent_in_reopen_queue(BlockReopenQueue *q,
+                                                          BdrvChild *c)
+{
+    BlockReopenQueueEntry *entry;
+
+    QSIMPLEQ_FOREACH(entry, q, entry) {
+        BlockDriverState *bs = entry->state.bs;
+        BdrvChild *child;
+
+        QLIST_FOREACH(child, &bs->children, next) {
+            if (child == c) {
+                return entry;

An O(n^2) loop. Is it going to bite us at any point in the future, or are we generally dealing with a small enough queue size and BDS graph to not worry about it?

The loops you're quoting aren't O(n^2), they don't loop over the same thing. This part is O(n) in terms of BdrvChild elements looked at.

The thing that worried me a bit more is the caller:

+ QLIST_FOREACH(c, &bs->parents, next_parent) {
+ parent = find_parent_in_reopen_queue(q, c);

This is indeed O(n^2) (again with n = number of BdrvChild elements) in the pathological worst case of a quorum node where all children point to the same node.

As soon as all parents of the node are distinct - and I don't see any reason why they wouldn't in practice - we're back to O(n) because each BdrvChild belongs to only one parent. Even if we ever introduce a driver where having the same node as a child in a constant number of different roles makes sense for a parent (i.e. anything that doesn't involve an (unbounded) array of children), we would still be O(n) with an additional small constant factor.

So I think in practice we should be okay.

Kevin

#19 lore raw

Re: [Qemu-devel] [PATCH 6/6] block: Fix permissions after bdrv_reopen()

In reply to Kevin Wolf
Quoted 27 lines · Kevin Wolf

If we switch between read-only and read-write, the permissions that image format drivers need on bs->file change, too. Make sure to update the permissions during bdrv_reopen().

---
 include/block/block.h |  1 +
 block.c               | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
include/block/block.h +1 -0
index 082eb2cd9c..3c3af462e4 100644
@@ -166,6 +166,7 @@ typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
 typedef struct BDRVReopenState {
     BlockDriverState *bs;
     int flags;
+    uint64_t perm, shared_perm;
     QDict *options;
     QDict *explicit_options;
     void *opaque;
block.c +0 -0
index 204cbb46c7..5c65fac672 100644

block.c static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, · line 2785

Quoted 5 lines
block.c +2 -0
@@ -2781,6 +2781,10 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
     bs_entry->state.explicit_options = explicit_options;
     bs_entry->state.flags = flags;
 
+    /* This needs to be overwritten in bdrv_reopen_prepare() */
+    bs_entry->state.perm = UINT64_MAX;

Probably doesn't matter because as the comment says it will be overwritten soon, but is BLK_PERM_ALL more appropriate?

Quoted 103 lines
block.c +62 -0
+    bs_entry->state.shared_perm = 0;
+
     QLIST_FOREACH(child, &bs->children, next) {
         QDict *new_child_options;
         char *child_key_dot;
@@ -2887,6 +2891,52 @@ int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp)
     return ret;
 }
 
+static BlockReopenQueueEntry *find_parent_in_reopen_queue(BlockReopenQueue *q,
+                                                          BdrvChild *c)
+{
+    BlockReopenQueueEntry *entry;
+
+    QSIMPLEQ_FOREACH(entry, q, entry) {
+        BlockDriverState *bs = entry->state.bs;
+        BdrvChild *child;
+
+        QLIST_FOREACH(child, &bs->children, next) {
+            if (child == c) {
+                return entry;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static void bdrv_reopen_perm(BlockReopenQueue *q, BlockDriverState *bs,
+                             uint64_t *perm, uint64_t *shared)
+{
+    BdrvChild *c;
+    BlockReopenQueueEntry *parent;
+    uint64_t cumulative_perms = 0;
+    uint64_t cumulative_shared_perms = BLK_PERM_ALL;
+
+    QLIST_FOREACH(c, &bs->parents, next_parent) {
+        parent = find_parent_in_reopen_queue(q, c);
+        if (!parent) {
+            cumulative_perms |= c->perm;
+            cumulative_shared_perms &= c->shared_perm;
+        } else {
+            uint64_t nperm, nshared;
+
+            bdrv_child_perm(parent->state.bs, bs, c, c->role, q,
+                            parent->state.perm, parent->state.shared_perm,
+                            &nperm, &nshared);
+
+            cumulative_perms |= nperm;
+            cumulative_shared_perms &= nshared;
+        }
+    }
+    *perm = cumulative_perms;
+    *shared = cumulative_shared_perms;
+}
 
 /*
  * Prepares a BlockDriverState for reopen. All changes are staged in the
@@ -2952,6 +3002,9 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
         goto error;
     }
 
+    /* Calculate required permissions after reopening */
+    bdrv_reopen_perm(queue, reopen_state->bs,
+                     &reopen_state->perm, &reopen_state->shared_perm);
 
     ret = bdrv_flush(reopen_state->bs);
     if (ret) {
@@ -3007,6 +3060,12 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
         } while ((entry = qdict_next(reopen_state->options, entry)));
     }
 
+    ret = bdrv_check_perm(reopen_state->bs, queue, reopen_state->perm,
+                          reopen_state->shared_perm, NULL, errp);
+    if (ret < 0) {
+        goto error;
+    }
+
     ret = 0;
 
 error:
@@ -3047,6 +3106,9 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state)
 
     bdrv_refresh_limits(bs, NULL);
 
+    bdrv_set_perm(reopen_state->bs, reopen_state->perm,
+                  reopen_state->shared_perm);
+
     new_can_write =
         !bdrv_is_read_only(bs) && !(bdrv_get_flags(bs) & BDRV_O_INACTIVE);
     if (!old_can_write && new_can_write && drv->bdrv_reopen_bitmaps_rw) {
@@ -3080,6 +3142,8 @@ void bdrv_reopen_abort(BDRVReopenState *reopen_state)
     }
 
     QDECREF(reopen_state->explicit_options);
+
+    bdrv_abort_perm_update(reopen_state->bs);
 }
 
 
Signature · 1 line
2.13.5
author
#20 lore raw

Re: [Qemu-devel] [PATCH 6/6] block: Fix permissions after bdrv_reopen()

Am 18.09.2017 um 09:37 hat Fam Zheng geschrieben:

Quoted 31 lines · Kevin Wolf
Quoted 30 lines

If we switch between read-only and read-write, the permissions that image format drivers need on bs->file change, too. Make sure to update the permissions during bdrv_reopen().

---
 include/block/block.h |  1 +
 block.c               | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
include/block/block.h +1 -0
index 082eb2cd9c..3c3af462e4 100644
@@ -166,6 +166,7 @@ typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
 typedef struct BDRVReopenState {
     BlockDriverState *bs;
     int flags;
+    uint64_t perm, shared_perm;
     QDict *options;
     QDict *explicit_options;
     void *opaque;
block.c +0 -0
index 204cbb46c7..5c65fac672 100644
@@ -2781,6 +2781,10 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
     bs_entry->state.explicit_options = explicit_options;
     bs_entry->state.flags = flags;
 
Quoted 5 lines

block.c static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, · line 2785

Quoted 2 lines
block.c +2 -0
+    /* This needs to be overwritten in bdrv_reopen_prepare() */
+    bs_entry->state.perm = UINT64_MAX;

Probably doesn't matter because as the comment says it will be overwritten soon, but is BLK_PERM_ALL more appropriate?

I had BLK_PERM_ALL at first, but after debugging some assertion failures in gdb, I came to the conclusion that UINT64_MAX is easier to identify as uninitialised than BLK_PERM_ALL, which could be a valid result of the permission calculation.

Kevin

author
#21 lore raw

[Qemu-devel] [PATCH 7/6] qemu-iotests: Test change-backing-file command

In reply to Kevin Wolf

This involves a temporary read-write reopen if the backing file link in the middle of a backing file chain should be changed and is therefore a good test for the latest bdrv_reopen() vs. op blockers fixes.

---

I actually managed to find a simple case that reproduces the bug that is fixed in this series, but outside of my commit job improvements that originally led me to this work.

 tests/qemu-iotests/195     | 92 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/195.out | 78 +++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |  1 +
 3 files changed, 171 insertions(+)
 create mode 100755 tests/qemu-iotests/195
 create mode 100644 tests/qemu-iotests/195.out
tests/qemu-iotests/195 new file +92 -0
new file mode 100755
index 0000000000..05a239cbf5
@@ -0,0 +1,92 @@
+#!/bin/bash
+#
+# Test change-backing-file command
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
[email protected]
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+status=1 # failure is the default!
+
+_cleanup()
+{
+    _cleanup_test_img
+    rm -f "$TEST_IMG.mid"
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt qcow2
+_supported_proto file
+_supported_os Linux
+
+function do_run_qemu()
+{
+    echo Testing: "$@" | _filter_imgfmt
+    $QEMU -nographic -qmp-pretty stdio -serial none "$@"
+    echo
+}
+
+function run_qemu()
+{
+    do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp \
+                          | _filter_qemu_io | _filter_generated_node_ids
+}
+
+size=64M
+TEST_IMG="$TEST_IMG.base" _make_test_img $size
+TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base"
+_make_test_img -b "$TEST_IMG.mid"
+
+echo
+echo "Change backing file of mid (opened read-only)"
+echo
+
+run_qemu -drive if=none,file="$TEST_IMG",backing.node-name=mid <<EOF
+{"execute":"qmp_capabilities"}
+{"execute":"change-backing-file", "arguments":{"device":"none0","image-node-name":"mid","backing-file":"/dev/null"}}
+{"execute":"quit"}
+EOF
+
+TEST_IMG="$TEST_IMG.mid" _img_info
+
+echo
+echo "Change backing file of top (opened writable)"
+echo
+
+TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base"
+
+run_qemu -drive if=none,file="$TEST_IMG",node-name=top <<EOF
+{"execute":"qmp_capabilities"}
+{"execute":"change-backing-file", "arguments":{"device":"none0","image-node-name":"top","backing-file":"/dev/null"}}
+{"execute":"quit"}
+EOF
+
+_img_info
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
tests/qemu-iotests/195.out new file +78 -0

78 lines not on this page · raw message

tests/qemu-iotests/group +1 -0
index 94e764865a..108339cd03 100644
@@ -189,3 +189,4 @@
 190 rw auto quick
 192 rw auto quick
 194 rw auto migration quick
+195 rw auto quick
Signature · 1 line
2.13.5
#22 lore raw

Re: [Qemu-devel] [PATCH 7/6] qemu-iotests: Test change-backing-file command

Quoted 7 lines · Kevin Wolf

This involves a temporary read-write reopen if the backing file link in the middle of a backing file chain should be changed and is therefore a good test for the latest bdrv_reopen() vs. op blockers fixes.

---

Quoted 7 lines

I actually managed to find a simple case that reproduces the bug that is fixed in this series, but outside of my commit job improvements that originally led me to this work.

 tests/qemu-iotests/195     | 92 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/195.out | 78 +++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |  1 +

The usual collision in test numbering ;)

tests/qemu-iotests/195 line 35

Quoted 6 lines
tests/qemu-iotests/195 new file +6 -0
+_cleanup()
+{
+    _cleanup_test_img
+    rm -f "$TEST_IMG.mid"
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15

Semantic conflict with Jeff's work to add './check -s' to save intermediate files in a per-test temp directory.

tests/qemu-iotests/195 line 69

Quoted 8 lines
tests/qemu-iotests/195 new file +8 -0
+
+echo
+echo "Change backing file of mid (opened read-only)"
+echo
+
+run_qemu -drive if=none,file="$TEST_IMG",backing.node-name=mid <<EOF
+{"execute":"qmp_capabilities"}
+{"execute":"change-backing-file", "arguments":{"device":"none0","image-node-name":"mid","backing-file":"/dev/null"}}

Since the images don't contain any content, I guess this works. But another possible change would be rewriting the backing file to alternate between absolute and relative (or even between 'file' and './file') (so that it's pointing to the same file at all times, just by different names)

Quoted 5 lines
tests/qemu-iotests/195 new file +5 -0
+{"execute":"quit"}
+EOF
+
+TEST_IMG="$TEST_IMG.mid" _img_info
+

tests/qemu-iotests/195 line 84

Quoted 10 lines
tests/qemu-iotests/195 new file +10 -0
+echo
+echo "Change backing file of top (opened writable)"
+echo
+
+TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base"
+
+run_qemu -drive if=none,file="$TEST_IMG",node-name=top <<EOF
+{"execute":"qmp_capabilities"}
+{"execute":"change-backing-file", "arguments":{"device":"none0","image-node-name":"top","backing-file":"/dev/null"}}
+{"execute":"quit"}

You've quit qemu between runs. Would we get any further coverage by doing:

change mid
change active
change mid

all within a single run? (That is, make SURE that actions on one part of the graph don't interfere with later actions elsewhere)

Signature · 3 lines
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
#23 lore raw
In reply to Kevin Wolf
Quoted 9 lines · Kevin Wolf

bdrv_reopen() can switch nodes between read-only and read-write modes. This has implications for the required permissions on their child nodes. For example, a qcow2 node requests write permissions on bs->file only if it is writable itself.

This means that during bdrv_reopen(), the permissions need to be recalculated in order to prevent failures where the bs->file permissions don't match its actual read-only state (e.g. bs->file is a read-write node, but the permission still enforces read-only access).

Passing reopen_queue around makes the interface and implementations complicated. I wonder if any of the alternatives make sense:

1) Don't pass reopen_queue as a parameter, just pass the one interesting BDRVReopenState pointer. So that callees don't need to call bdrv_reopen_get_flags().

2) Don't change the prototypes at all, just change .bdrv_reopen_prepare contract so that after it returns, .bdrv_child_perm/.bdrv_check_perm should comply to the new state that would be commited once .bdrv_reopen_commit() is called, or reverted if .bdrv_reopen_abort().

3) Don't change the prototypes at all, track the reopen progress in block.c generically, (e.g. ignore conflicts and voilations) and update the permissions only after bdrv_reopen_commit().

Fam

Quoted 23 lines

Kevin Wolf (6):
qemu-io: Reset qemuio_blk permissions before each command
block: Add reopen_queue to bdrv_child_perm()
block: Add reopen queue to bdrv_check_perm()
block: Base permissions on rw state after reopen
block: reopen: Queue children after their parents
block: Fix permissions after bdrv_reopen()

 include/block/block.h      |   2 +-
 include/block/block_int.h  |   7 ++
 block.c                    | 191 +++++++++++++++++++++++++++++++++------------
 block/commit.c             |   1 +
 block/mirror.c             |   1 +
 block/replication.c        |   1 +
 block/vvfat.c              |   1 +
 qemu-io.c                  |  13 +++
 tests/qemu-iotests/187.out |   2 +-
 9 files changed, 169 insertions(+), 50 deletions(-)
Signature · 1 line
2.13.5
author
#24 lore raw

Am 18.09.2017 um 09:51 hat Fam Zheng geschrieben:

Quoted 6 lines · Kevin Wolf
Quoted 5 lines

bdrv_reopen() can switch nodes between read-only and read-write modes. This has implications for the required permissions on their child nodes. For example, a qcow2 node requests write permissions on bs->file only if it is writable itself.

Quoted 7 lines
Quoted 4 lines

This means that during bdrv_reopen(), the permissions need to be recalculated in order to prevent failures where the bs->file permissions don't match its actual read-only state (e.g. bs->file is a read-write node, but the permission still enforces read-only access).

Passing reopen_queue around makes the interface and implementations complicated.

Yes. I don't like it, but I couldn't find any easier way.

Quoted 5 lines

I wonder if any of the alternatives make sense:

1) Don't pass reopen_queue as a parameter, just pass the one interesting BDRVReopenState pointer. So that callees don't need to call bdrv_reopen_get_flags().

There isn't a single interesting BDRVReopenState. The subset that a single BDS needs to determine its new state is the the BDRVReopenState of all of its parents. This can be an arbitrary number.

Quoted 4 lines

2) Don't change the prototypes at all, just change .bdrv_reopen_prepare contract so that after it returns, .bdrv_child_perm/.bdrv_check_perm should comply to the new state that would be commited once .bdrv_reopen_commit() is called, or reverted if .bdrv_reopen_abort().

Hm, .bdrv_reopen_prepare already gets the whole queue passed, so I guess this could technically work. I'm not sure if it is a good idea, though.

Such a change would still make .bdrv_child_perm depend on the reopen queue, just without actually passing it as a parameter. I like such hidden data flows even less than adding an explicit one.

It would also mean that each block driver would have to save the queue in its local bs->opaque structure so that .bdrv_child_perm can access it later. Alternatively, bdrv_reopen_prepare could already store the new cumulative parent permissions, but it would still involve two new fields in bs->opaque for storing something of a rather temporary nature.

Though maybe I'm just missing another way to implement this that you had in mind?

Quoted 3 lines

3) Don't change the prototypes at all, track the reopen progress in block.c generically, (e.g. ignore conflicts and voilations) and update the permissions only after bdrv_reopen_commit().

Both permission updates and reopen are transactional. You need to do both prepare stages first before you can do commits. If you only start doing the prepare stage of permissions during the commit stage of reopen, you break the error cases.

Kevin

#25 lore raw
Quoted 12 lines · Kevin Wolf
Quoted 4 lines

2) Don't change the prototypes at all, just change .bdrv_reopen_prepare contract so that after it returns, .bdrv_child_perm/.bdrv_check_perm should comply to the new state that would be commited once .bdrv_reopen_commit() is called, or reverted if .bdrv_reopen_abort().

Hm, .bdrv_reopen_prepare already gets the whole queue passed, so I guess this could technically work. I'm not sure if it is a good idea, though.

Such a change would still make .bdrv_child_perm depend on the reopen queue, just without actually passing it as a parameter. I like such hidden data flows even less than adding an explicit one.

Quoted 5 lines

It would also mean that each block driver would have to save the queue in its local bs->opaque structure so that .bdrv_child_perm can access it later. Alternatively, bdrv_reopen_prepare could already store the new cumulative parent permissions, but it would still involve two new fields in bs->opaque for storing something of a rather temporary nature.

What about this?

1) drv->bdrv_reopen_prepare() saves the desired new perms in BDRVReopenState.
2) bdrv_reopen_prepare() checks the new perms after drv->bdrv_reopen_prepare() returns.
3) bdrv_reopen_commit() updates the bs to new perms.

Fam

author
#26 lore raw

Am 18.09.2017 um 13:53 hat Fam Zheng geschrieben:

Quoted 19 lines · Kevin Wolf
Quoted 12 lines
Quoted 4 lines

2) Don't change the prototypes at all, just change .bdrv_reopen_prepare contract so that after it returns, .bdrv_child_perm/.bdrv_check_perm should comply to the new state that would be commited once .bdrv_reopen_commit() is called, or reverted if .bdrv_reopen_abort().

Hm, .bdrv_reopen_prepare already gets the whole queue passed, so I guess this could technically work. I'm not sure if it is a good idea, though.

Such a change would still make .bdrv_child_perm depend on the reopen queue, just without actually passing it as a parameter. I like such hidden data flows even less than adding an explicit one.

Quoted 5 lines

It would also mean that each block driver would have to save the queue in its local bs->opaque structure so that .bdrv_child_perm can access it later. Alternatively, bdrv_reopen_prepare could already store the new cumulative parent permissions, but it would still involve two new fields in bs->opaque for storing something of a rather temporary nature.

Quoted 4 lines

What about this?

1) drv->bdrv_reopen_prepare() saves the desired new perms in BDRVReopenState.

But how does it determine the desired new perms? Either you duplicate the logic of the .bdrv_child_perm implementation into a new set of functions that does the same thing, but based on the new state; or you extend the existing function with a BlockReopenQueue parameter. The latter is basically this series, except with an additional unnecessary detour through the driver code instead of doing it in common code.

Also note that storing it in BDRVReopenState would have to involve a new list of an additional data structure because permissions are per BdrvChild, not per BlockDriverState.

Quoted 3 lines

2) bdrv_reopen_prepare() checks the new perms after drv->bdrv_reopen_prepare() returns.
3) bdrv_reopen_commit() updates the bs to new perms.

2) and 3) are already implemented in this series like you suggest.

Kevin

#27 lore raw
Quoted 10 lines · Kevin Wolf

But how does it determine the desired new perms? Either you duplicate the logic of the .bdrv_child_perm implementation into a new set of functions that does the same thing, but based on the new state; or you extend the existing function with a BlockReopenQueue parameter. The latter is basically this series, except with an additional unnecessary detour through the driver code instead of doing it in common code.

Also note that storing it in BDRVReopenState would have to involve a new list of an additional data structure because permissions are per BdrvChild, not per BlockDriverState.

Indeed, this is not going to remove any complexity. :(

Fam

author Applied
#28 lore raw
In reply to Kevin Wolf

Am 15.09.2017 um 12:10 hat Kevin Wolf geschrieben:

Quoted 9 lines

bdrv_reopen() can switch nodes between read-only and read-write modes. This has implications for the required permissions on their child nodes. For example, a qcow2 node requests write permissions on bs->file only if it is writable itself.

This means that during bdrv_reopen(), the permissions need to be recalculated in order to prevent failures where the bs->file permissions don't match its actual read-only state (e.g. bs->file is a read-write node, but the permission still enforces read-only access).

Applied to the block branch.

Kevin