All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command
@ 2018-06-14 15:48 Alberto Garcia
  2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 01/10] file-posix: Forbid trying to change unsupported options during reopen Alberto Garcia
                   ` (9 more replies)
  0 siblings, 10 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

Hi,

here's my first attempt to implement a bdrv_reopen() QMP command. I'm
tagging this as RFC because there are still several important things
that need to be sorted out before I consider this stable enough. And
I'd still prefix the command with an "x-" for a while. But let's get
to the details.

The new command is called x-blockdev-reopen, and it's designed to be
similar to blockdev-add. It also takes BlockdevOptions as a
parameter. The "node-name" field of BlockdevOptions must be set, and
it is used to select the BlockDriverState to reopen.

In this example "hd0" is reopened with the given set of options:

    { "execute": "x-blockdev-reopen",
      "arguments": {
          "driver": "qcow2",
          "node-name": "hd0",
          "file": {
              "driver": "file",
              "filename": "hd0.qcow2",
              "node-name": "hd0f"
          },
          "l2-cache-size": 524288
       }
    }

Changing options
================
The general idea is quite straightforward, but the devil is in the
detail. Since this command tries to mimic blockdev-add, the state of
the block device after being reopened should generally be equivalent
to that of a block device added with the same set of options.

There are two important things with this:

  a) Some options cannot be changed (most drivers don't allow that, in
     fact).
  b) If an option is missing, it should be reset to its default value
     (rather than keeping its previous value).

Example: the default value of l2-cache-size is 1MB. If we set a
different value (2MB) on blockdev-add but then omit the option in
x-blockdev-reopen, then it should be reset back to 1MB. The current
bdrv_reopen() code keeps the previous value.

Trying to change an option that doesn't allow it is already being
handled by the code. The loop at the end of bdrv_reopen_prepare()
checks all options that were not handled by the block driver and sees
if any of them has been modified. There was at least one case where a
block driver silently ignores new values for some options (I fixed
that in the series) but otherwise this works generally well.

However, as I explained earlier in (b), omitting an option is supposed
to reset it to its default value, so it's also a way of changing
it. If the option cannot be changed then we should detect it and
return an error. What I'm doing in this series is making every driver
publish its list of run-time options, and which ones of them can be
modified.

Backing files
=============
This command allows reconfigurations in the node graph. Currently it
only allows changing an image's backing file, but it should be
possible to implement similar functionalities in drivers that have
other children, like blkverify or quorum.

Let's add an image with its backing file (hd1 <- hd0) like this:

    { "execute": "blockdev-add",
      "arguments": {
          "driver": "qcow2",
          "node-name": "hd0",
          "file": {
              "driver": "file",
              "filename": "hd0.qcow2",
              "node-name": "hd0f"
          },
          "backing": {
              "driver": "qcow2",
              "node-name": "hd1",
              "file": {
                  "driver": "file",
                  "filename": "hd1.qcow2",
                  "node-name": "hd1f"
              }
          }
       }
    }

Removing the backing file can be done by simply passing the option
{ ..., "backing": null } to x-blockdev-reopen.

Replacing it is not so straightforward. If we pass something like
{ ..., "backing": { "driver": "foo" ... } } then x-blockdev-reopen
will assume that we're trying to change the options of the existing
backing file (and it will fail in most cases because it'll likely
think that we're trying to change the node name -among other options).

So I decided to use a reference instead: { ..., "backing": "hd2" },
where "hd2" is of an existing node that has been added previously.

If the "backing" option is left out then we simply keep the existing
backing file. I have doubts about this, because it differs from what
blockdev-add would do (open the default backing file stored in the
image header), but I don't think we want to do that on reopen. Perhaps
we could force the user to specify "backing" in these cases? I haven't
explored this option much yet.

There are some tricky things with this: bs->options keeps the
effective set of options for a given BlockDriverState (*), including
the options of the backing file.

    (*) not quite, because that dict is not updated at all in
        bdrv_reopen_commit(). That looks like a bug to me and I'll
        probably need to fix that.

So on one hand the options for a given BDS can appear at least twice:
on the BDS itself an on its parent(s). If you reopen the child then
both sets are out of sync. On the other hand the graph can be
reconfigured by means other than x-blockdev-reopen: if you do
block-stream you are changing a BDS's backing file. That command
includes a call to bdrv_reopen(), keeping the same options, but now
some of those options are invalid because they refer to a BDS that is
no longer part of the backing chain.

Another tricky part is that we could be reopening a BDS while there's
an implicit node in the middle of the chain (e.g. a "commit_top"
filter node). I added some code to handle this, but I fear that it can
still give us some headaches.

As you can see in the patch series, I wrote a relatively large amount
of tests for many different scenarios, including some of the tricky
ones that I mentioned here (check the ones with the FIXME comments).
I'll need to write many more to make sure that all these non-trivial
cases work fine before we can add this new command, but I think this
series is a good starting point to discuss this feature and see if we
need to change something or reconsider any of the design decisions.

As usual, feedback is more than welcome.

Thanks,

Berto

Alberto Garcia (10):
  file-posix: Forbid trying to change unsupported options during reopen
  block: Allow changing 'discard' on reopen
  block: Allow changing 'detect-zeroes' on reopen
  block: Allow changing 'force-share' on reopen
  block: Add 'keep_old_opts' parameter to bdrv_reopen_queue()
  block: Allow changing the backing file on reopen
  block: Add 'runtime_opts' and 'mutable_opts' fields to BlockDriver
  block: Add bdrv_reset_options_allowed()
  block: Add a 'x-blockdev-reopen' QMP command
  qemu-iotests: Test the x-blockdev-reopen QMP command

 block.c                    | 241 +++++++++++++--
 block/blkdebug.c           |   1 +
 block/crypto.c             |   1 +
 block/file-posix.c         |  19 +-
 block/iscsi.c              |   2 +
 block/null.c               |   2 +
 block/nvme.c               |   1 +
 block/qcow.c               |   1 +
 block/rbd.c                |   1 +
 block/replication.c        |   4 +-
 block/sheepdog.c           |   2 +
 block/vpc.c                |   1 +
 blockdev.c                 |  57 ++++
 include/block/block.h      |   5 +-
 include/block/block_int.h  |   4 +
 qapi/block-core.json       |  29 ++
 qemu-io-cmds.c             |   2 +-
 tests/qemu-iotests/220     | 724 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/220.out |   5 +
 tests/qemu-iotests/group   |   1 +
 20 files changed, 1065 insertions(+), 38 deletions(-)
 create mode 100755 tests/qemu-iotests/220
 create mode 100644 tests/qemu-iotests/220.out

-- 
2.11.0

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 01/10] file-posix: Forbid trying to change unsupported options during reopen
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
@ 2018-06-14 15:48 ` Alberto Garcia
  2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 02/10] block: Allow changing 'discard' on reopen Alberto Garcia
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

The file-posix code is used for the "file", "host_device" and
"host_cdrom" drivers, and it allows reopening images. However the only
option that is actually processed is "x-check-cache-dropped", and
changes in all other options (e.g. "filename") are silently ignored.

While we could allow changing some of the other options, let's keep
things as they are for now but return an error if the user tries to
change any of them.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block/file-posix.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/block/file-posix.c b/block/file-posix.c
index 07bb061fe4..1511a4d69d 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -813,8 +813,13 @@ static int raw_reopen_prepare(BDRVReopenState *state,
         goto out;
     }
 
-    rs->check_cache_dropped = qemu_opt_get_bool(opts, "x-check-cache-dropped",
-                                                s->check_cache_dropped);
+    rs->check_cache_dropped =
+        qemu_opt_get_bool_del(opts, "x-check-cache-dropped", false);
+
+    /* This driver's reopen function doesn't currently allow changing
+     * other options, so let's put them back in the original QDict and
+     * bdrv_reopen_prepare() will detect changes and complain. */
+    qemu_opts_to_qdict(opts, state->options);
 
     if (s->type == FTYPE_CD) {
         rs->open_flags |= O_NONBLOCK;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 02/10] block: Allow changing 'discard' on reopen
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
  2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 01/10] file-posix: Forbid trying to change unsupported options during reopen Alberto Garcia
@ 2018-06-14 15:48 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 03/10] block: Allow changing 'detect-zeroes' " Alberto Garcia
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

'discard' is one of the basic BlockdevOptions available for all
drivers, but it's silently ignored by bdrv_reopen_prepare/commit(), so
the user cannot change it and doesn't get an error explaining that it
can't be changed.

Since there's no reason why we shouldn't allow changing it and the
implementation is trivial, let's just do it.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/block.c b/block.c
index 50887087f3..e470db7e5e 100644
--- a/block.c
+++ b/block.c
@@ -3139,6 +3139,15 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
 
     update_flags_from_options(&reopen_state->flags, opts);
 
+    value = qemu_opt_get(opts, "discard");
+    if (value != NULL) {
+        if (bdrv_parse_discard_flags(value, &reopen_state->flags) != 0) {
+            error_setg(errp, "Invalid discard option");
+            ret = -EINVAL;
+            goto error;
+        }
+    }
+
     /* node-name and driver must be unchanged. Put them back into the QDict, so
      * that they are checked at the end of this function. */
     value = qemu_opt_get(opts, "node-name");
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 03/10] block: Allow changing 'detect-zeroes' on reopen
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
  2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 01/10] file-posix: Forbid trying to change unsupported options during reopen Alberto Garcia
  2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 02/10] block: Allow changing 'discard' on reopen Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 04/10] block: Allow changing 'force-share' " Alberto Garcia
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

'detect-zeroes' is one of the basic BlockdevOptions available for all
drivers, but it's silently ignored by bdrv_reopen_prepare/commit(), so
the user cannot change it and doesn't get an error explaining that it
can't be changed.

Since there's no reason why we shouldn't allow changing it and the
implementation is trivial, let's just do it.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c               | 54 ++++++++++++++++++++++++++++++++++++---------------
 include/block/block.h |  1 +
 2 files changed, 39 insertions(+), 16 deletions(-)

diff --git a/block.c b/block.c
index e470db7e5e..4c186e54b6 100644
--- a/block.c
+++ b/block.c
@@ -759,6 +759,29 @@ static void bdrv_join_options(BlockDriverState *bs, QDict *options,
     }
 }
 
+static BlockdevDetectZeroesOptions bdrv_parse_detect_zeroes(const char *value,
+                                                            int open_flags,
+                                                            Error **errp)
+{
+    Error *local_err = NULL;
+    BlockdevDetectZeroesOptions detect_zeroes =
+        qapi_enum_parse(&BlockdevDetectZeroesOptions_lookup, value,
+                        BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return detect_zeroes;
+    }
+
+    if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP &&
+        !(open_flags & BDRV_O_UNMAP))
+    {
+        error_setg(errp, "setting detect-zeroes to unmap is not allowed "
+                   "without setting discard operation to unmap");
+    }
+
+    return detect_zeroes;
+}
+
 /**
  * Set open flags for a given discard mode
  *
@@ -1390,27 +1413,14 @@ static int bdrv_open_common(BlockDriverState *bs, BlockBackend *file,
 
     detect_zeroes = qemu_opt_get(opts, "detect-zeroes");
     if (detect_zeroes) {
-        BlockdevDetectZeroesOptions value =
-            qapi_enum_parse(&BlockdevDetectZeroesOptions_lookup,
-                            detect_zeroes,
-                            BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF,
-                            &local_err);
+        bs->detect_zeroes = bdrv_parse_detect_zeroes(detect_zeroes,
+                                                     bs->open_flags,
+                                                     &local_err);
         if (local_err) {
             error_propagate(errp, local_err);
             ret = -EINVAL;
             goto fail_opts;
         }
-
-        if (value == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP &&
-            !(bs->open_flags & BDRV_O_UNMAP))
-        {
-            error_setg(errp, "setting detect-zeroes to unmap is not allowed "
-                             "without setting discard operation to unmap");
-            ret = -EINVAL;
-            goto fail_opts;
-        }
-
-        bs->detect_zeroes = value;
     }
 
     if (filename != NULL) {
@@ -3148,6 +3158,17 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
         }
     }
 
+    value = qemu_opt_get(opts, "detect-zeroes");
+    if (value) {
+        reopen_state->detect_zeroes =
+            bdrv_parse_detect_zeroes(value, reopen_state->flags, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            ret = -EINVAL;
+            goto error;
+        }
+    }
+
     /* node-name and driver must be unchanged. Put them back into the QDict, so
      * that they are checked at the end of this function. */
     value = qemu_opt_get(opts, "node-name");
@@ -3278,6 +3299,7 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state)
     bs->explicit_options   = reopen_state->explicit_options;
     bs->open_flags         = reopen_state->flags;
     bs->read_only = !(reopen_state->flags & BDRV_O_RDWR);
+    bs->detect_zeroes      = reopen_state->detect_zeroes;
 
     bdrv_refresh_limits(bs, NULL);
 
diff --git a/include/block/block.h b/include/block/block.h
index e677080c4e..1e78587331 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -159,6 +159,7 @@ typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
 typedef struct BDRVReopenState {
     BlockDriverState *bs;
     int flags;
+    BlockdevDetectZeroesOptions detect_zeroes;
     uint64_t perm, shared_perm;
     QDict *options;
     QDict *explicit_options;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 04/10] block: Allow changing 'force-share' on reopen
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (2 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 03/10] block: Allow changing 'detect-zeroes' " Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue() Alberto Garcia
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

'force-share' is one of the basic BlockdevOptions available for all
drivers, but it's silently ignored by bdrv_reopen_prepare/commit(), so
the user cannot change it and doesn't get an error explaining that it
can't be changed.

Since there's no reason why we shouldn't allow changing it and the
implementation is trivial, let's just do it.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c               | 11 +++++++++++
 include/block/block.h |  1 +
 2 files changed, 12 insertions(+)

diff --git a/block.c b/block.c
index 4c186e54b6..a741300fae 100644
--- a/block.c
+++ b/block.c
@@ -3169,6 +3169,16 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
         }
     }
 
+    reopen_state->force_share = qemu_opt_get_bool(opts, BDRV_OPT_FORCE_SHARE,
+                                                  false);
+    if (reopen_state->force_share && (reopen_state->flags & BDRV_O_RDWR)) {
+        error_setg(errp,
+                   BDRV_OPT_FORCE_SHARE
+                   "=on can only be used with read-only images");
+        ret = -EINVAL;
+        goto error;
+    }
+
     /* node-name and driver must be unchanged. Put them back into the QDict, so
      * that they are checked at the end of this function. */
     value = qemu_opt_get(opts, "node-name");
@@ -3300,6 +3310,7 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state)
     bs->open_flags         = reopen_state->flags;
     bs->read_only = !(reopen_state->flags & BDRV_O_RDWR);
     bs->detect_zeroes      = reopen_state->detect_zeroes;
+    bs->force_share        = reopen_state->force_share;
 
     bdrv_refresh_limits(bs, NULL);
 
diff --git a/include/block/block.h b/include/block/block.h
index 1e78587331..9306c986ef 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -160,6 +160,7 @@ typedef struct BDRVReopenState {
     BlockDriverState *bs;
     int flags;
     BlockdevDetectZeroesOptions detect_zeroes;
+    bool force_share;
     uint64_t perm, shared_perm;
     QDict *options;
     QDict *explicit_options;
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue()
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (3 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 04/10] block: Allow changing 'force-share' " Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-18 14:15   ` Kevin Wolf
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen Alberto Garcia
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

The bdrv_reopen_queue() function is used to create a queue with the
BDSs that are going to be reopened and their new options. Once the
queue is ready bdrv_reopen_multiple() is called to perform the
operation.

The original options from each one of the BDSs are kept, with the new
options passed to bdrv_reopen_queue() applied on top of them.

For "x-blockdev-reopen" we want a function that behaves much like
"blockdev-add". We want to ignore the previous set of options so that
only the ones actually specified by the user are applied, with the
rest having their default values.

We can achieve this by adding a new parameter to bdrv_reopen_queue()
that specifies whether the old set of options is kept or discarded
when building the reopen queue. All current callers will set that
parameter to true, but x-blockdev-reopen will set it to false.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c               | 34 +++++++++++++++++++---------------
 block/replication.c   |  4 ++--
 include/block/block.h |  3 ++-
 qemu-io-cmds.c        |  2 +-
 4 files changed, 24 insertions(+), 19 deletions(-)

diff --git a/block.c b/block.c
index a741300fae..0b9268a48d 100644
--- a/block.c
+++ b/block.c
@@ -2850,7 +2850,8 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
                                                  int flags,
                                                  const BdrvChildRole *role,
                                                  QDict *parent_options,
-                                                 int parent_flags)
+                                                 int parent_flags,
+                                                 bool keep_old_opts)
 {
     assert(bs != NULL);
 
@@ -2899,13 +2900,13 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
     }
 
     /* Old explicitly set values (don't overwrite by inherited value) */
-    if (bs_entry) {
-        old_options = qdict_clone_shallow(bs_entry->state.explicit_options);
-    } else {
-        old_options = qdict_clone_shallow(bs->explicit_options);
+    if (bs_entry || keep_old_opts) {
+        old_options = qdict_clone_shallow(bs_entry ?
+                                          bs_entry->state.explicit_options :
+                                          bs->explicit_options);
+        bdrv_join_options(bs, options, old_options);
+        qobject_unref(old_options);
     }
-    bdrv_join_options(bs, options, old_options);
-    qobject_unref(old_options);
 
     explicit_options = qdict_clone_shallow(options);
 
@@ -2923,10 +2924,12 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
         qobject_unref(options_copy);
     }
 
-    /* Old values are used for options that aren't set yet */
-    old_options = qdict_clone_shallow(bs->options);
-    bdrv_join_options(bs, options, old_options);
-    qobject_unref(old_options);
+    if (keep_old_opts) {
+        /* Old values are used for options that aren't set yet */
+        old_options = qdict_clone_shallow(bs->options);
+        bdrv_join_options(bs, options, old_options);
+        qobject_unref(old_options);
+    }
 
     /* bdrv_open_inherit() sets and clears some additional flags internally */
     flags &= ~BDRV_O_PROTOCOL;
@@ -2967,7 +2970,7 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
         g_free(child_key_dot);
 
         bdrv_reopen_queue_child(bs_queue, child->bs, new_child_options, 0,
-                                child->role, options, flags);
+                                child->role, options, flags, keep_old_opts);
     }
 
     return bs_queue;
@@ -2975,10 +2978,11 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
 
 BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
                                     BlockDriverState *bs,
-                                    QDict *options, int flags)
+                                    QDict *options, int flags,
+                                    bool keep_old_opts)
 {
     return bdrv_reopen_queue_child(bs_queue, bs, options, flags,
-                                   NULL, NULL, 0);
+                                   NULL, NULL, 0, keep_old_opts);
 }
 
 /*
@@ -3049,7 +3053,7 @@ int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp)
 
     bdrv_subtree_drained_begin(bs);
 
-    queue = bdrv_reopen_queue(NULL, bs, NULL, bdrv_flags);
+    queue = bdrv_reopen_queue(NULL, bs, NULL, bdrv_flags, true);
     ret = bdrv_reopen_multiple(bdrv_get_aio_context(bs), queue, &local_err);
     if (local_err != NULL) {
         error_propagate(errp, local_err);
diff --git a/block/replication.c b/block/replication.c
index 826db7b304..af984c29dd 100644
--- a/block/replication.c
+++ b/block/replication.c
@@ -401,12 +401,12 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable,
 
     if (orig_hidden_flags != new_hidden_flags) {
         reopen_queue = bdrv_reopen_queue(reopen_queue, s->hidden_disk->bs, NULL,
-                                         new_hidden_flags);
+                                         new_hidden_flags, true);
     }
 
     if (!(orig_secondary_flags & BDRV_O_RDWR)) {
         reopen_queue = bdrv_reopen_queue(reopen_queue, s->secondary_disk->bs,
-                                         NULL, new_secondary_flags);
+                                         NULL, new_secondary_flags, true);
     }
 
     if (reopen_queue) {
diff --git a/include/block/block.h b/include/block/block.h
index 9306c986ef..c8654dde65 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -267,7 +267,8 @@ BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name,
                                        int flags, Error **errp);
 BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
                                     BlockDriverState *bs,
-                                    QDict *options, int flags);
+                                    QDict *options, int flags,
+                                    bool keep_old_opts);
 int bdrv_reopen_multiple(AioContext *ctx, BlockReopenQueue *bs_queue, Error **errp);
 int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp);
 int bdrv_reopen_prepare(BDRVReopenState *reopen_state,
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 5bf5f28178..4742358b1a 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -2050,7 +2050,7 @@ static int reopen_f(BlockBackend *blk, int argc, char **argv)
     qemu_opts_reset(&reopen_opts);
 
     bdrv_subtree_drained_begin(bs);
-    brq = bdrv_reopen_queue(NULL, bs, opts, flags);
+    brq = bdrv_reopen_queue(NULL, bs, opts, flags, true);
     bdrv_reopen_multiple(bdrv_get_aio_context(bs), brq, &local_err);
     bdrv_subtree_drained_end(bs);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (4 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue() Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-18 14:38   ` Kevin Wolf
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 07/10] block: Add 'runtime_opts' and 'mutable_opts' fields to BlockDriver Alberto Garcia
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

This patch allows the user to change the backing file of an image that
is being reopened. Here's what it does:

 - In bdrv_reopen_queue_child(): if the 'backing' option points to an
   image different from the current backing file then it means that
   the latter is going be detached so it must not be put in the reopen
   queue.

 - In bdrv_reopen_prepare(): check that the value of 'backing' points
   to an existing node or is null. If it points to an existing node it
   also needs to make sure that replacing the backing file will not
   create a cycle in the node graph (i.e. you cannot reach the parent
   from the new backing file).

 - In bdrv_reopen_commit(): perform the actual node replacement by
   calling bdrv_set_backing_hd(). It may happen that there are
   temporary implicit nodes between the BDS that we are reopening and
   the backing file that we want to replace (e.g. a commit fiter node),
   so we must skip them.

Although x-blockdev-reopen is meant to be used like blockdev-add,
there's two important things that must be taken into account:

1) The only way to set a new backing file is by using a reference to
   an existing node (previously added with e.g. blockdev-add).
   If 'backing' contains a dictionary with a new set of options
   ({"driver": "qcow2", "file": { ... }}) then it is interpreted that
   the _existing_ backing file must be reopened with those options.

2) If the 'backing' option is omitted altogether then the existing
   backing file (if any) is kept. Unlike blockdev-add this will _not_
   open the default backing file specified in the image header.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 84 insertions(+), 2 deletions(-)

diff --git a/block.c b/block.c
index 0b9268a48d..91fff6361f 100644
--- a/block.c
+++ b/block.c
@@ -2823,6 +2823,27 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference,
 }
 
 /*
+ * Returns true if @child can be reached recursively from @bs
+ */
+static bool bdrv_recurse_has_child(BlockDriverState *bs,
+                                   BlockDriverState *child)
+{
+    BdrvChild *c;
+
+    if (bs == child) {
+        return true;
+    }
+
+    QLIST_FOREACH(c, &bs->children, next) {
+        if (bdrv_recurse_has_child(c->bs, child)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/*
  * Adds a BlockDriverState to a simple queue for an atomic, transactional
  * reopen of multiple devices.
  *
@@ -2957,6 +2978,7 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
     QLIST_FOREACH(child, &bs->children, next) {
         QDict *new_child_options;
         char *child_key_dot;
+        bool child_keep_old = keep_old_opts;
 
         /* reopen can only change the options of block devices that were
          * implicitly created and inherited options. For other (referenced)
@@ -2965,12 +2987,28 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
             continue;
         }
 
+        /* If the 'backing' option is set and it points to a different
+         * node then it means that we want to replace the current one,
+         * so we shouldn't put it in the reopen queue */
+        if (child->role == &child_backing && qdict_haskey(options, "backing")) {
+            const char *backing = qdict_get_try_str(options, "backing");
+            if (g_strcmp0(backing, child->bs->node_name)) {
+                continue;
+            }
+        }
+
         child_key_dot = g_strdup_printf("%s.", child->name);
         qdict_extract_subqdict(options, &new_child_options, child_key_dot);
         g_free(child_key_dot);
 
+        /* If the 'backing' option was omitted then we keep the old
+         * set of options */
+        if (child->role == &child_backing && !qdict_size(new_child_options)) {
+            child_keep_old = true;
+        }
+
         bdrv_reopen_queue_child(bs_queue, child->bs, new_child_options, 0,
-                                child->role, options, flags, keep_old_opts);
+                                child->role, options, flags, child_keep_old);
     }
 
     return bs_queue;
@@ -3262,7 +3300,34 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
              * the user can simply omit options which cannot be changed anyway,
              * so they will stay unchanged.
              */
-            if (!qobject_is_equal(new, old)) {
+            /*
+             * Allow changing the 'backing' option. The new value can be
+             * either a reference to an existing node (using its node name)
+             * or NULL to simply detach the current backing file.
+             */
+            if (!strcmp(entry->key, "backing")) {
+                switch (qobject_type(new)) {
+                case QTYPE_QNULL:
+                    break;
+                case QTYPE_QSTRING: {
+                    const char *str = qobject_get_try_str(new);
+                    BlockDriverState *bs = bdrv_lookup_bs(NULL, str, errp);
+                    if (bs == NULL) {
+                        ret = -EINVAL;
+                        goto error;
+                    } else if (bdrv_recurse_has_child(bs, reopen_state->bs)) {
+                        error_setg(errp, "Making '%s' a backing file of '%s' "
+                                   "would create a cycle", str,
+                                   reopen_state->bs->node_name);
+                        ret = -EINVAL;
+                        goto error;
+                    }
+                    break;
+                }
+                default:
+                    g_assert_not_reached();
+                }
+            } else if (!qobject_is_equal(new, old)) {
                 error_setg(errp, "Cannot change the option '%s'", entry->key);
                 ret = -EINVAL;
                 goto error;
@@ -3293,6 +3358,7 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state)
     BlockDriver *drv;
     BlockDriverState *bs;
     bool old_can_write, new_can_write;
+    QObject *qobj;
 
     assert(reopen_state != NULL);
     bs = reopen_state->bs;
@@ -3307,6 +3373,22 @@ void bdrv_reopen_commit(BDRVReopenState *reopen_state)
         drv->bdrv_reopen_commit(reopen_state);
     }
 
+    /* Change the backing file if a new one was specified */
+    qobj = qdict_get(reopen_state->options, "backing");
+    if (qobj) {
+        const char *str = qobject_get_try_str(qobj);
+        BlockDriverState *new_backing = str ? bdrv_find_node(str) : NULL;
+        BlockDriverState *overlay = bs;
+        /* If there are implicit nodes between the current BDS and its
+         * "actual" backing file, skip them */
+        while (backing_bs(overlay) && backing_bs(overlay)->implicit) {
+            overlay = backing_bs(overlay);
+        }
+        if (new_backing != backing_bs(overlay)) {
+            bdrv_set_backing_hd(overlay, new_backing, NULL);
+        }
+    }
+
     /* set BDS specific flags now */
     qobject_unref(bs->explicit_options);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 07/10] block: Add 'runtime_opts' and 'mutable_opts' fields to BlockDriver
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (5 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 08/10] block: Add bdrv_reset_options_allowed() Alberto Garcia
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

This patch adds two new fields to BlockDriver:

   - runtime_opts: list of runtime options for a particular block
     driver. We'll use this list later to detect what options are
     missing when we try to reopen a block device.

   - mutable_opts: names of the runtime options that can be modified
     after a block device has been added. This way an option can not
     be changed unless we explicitly allow it.

This also sets runtime_opts (and mutable_opts where appropriate) in
all drivers that allow reopening. Most of those drivers don't actually
support changing any of their options. If the user specifies a new
value for an option that can't be changed then we already detect that
and forbid it (in bdrv_reopen_prepare()). But if the user omits an
option in order to try to reset it to its default value we need to
detect that, so we'll use these two new fields for that.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block/blkdebug.c          |  1 +
 block/crypto.c            |  1 +
 block/file-posix.c        | 10 ++++++++++
 block/iscsi.c             |  2 ++
 block/null.c              |  2 ++
 block/nvme.c              |  1 +
 block/qcow.c              |  1 +
 block/rbd.c               |  1 +
 block/sheepdog.c          |  2 ++
 block/vpc.c               |  1 +
 include/block/block_int.h |  4 ++++
 11 files changed, 26 insertions(+)

diff --git a/block/blkdebug.c b/block/blkdebug.c
index 526af2a808..9b52465642 100644
--- a/block/blkdebug.c
+++ b/block/blkdebug.c
@@ -893,6 +893,7 @@ static BlockDriver bdrv_blkdebug = {
     .protocol_name          = "blkdebug",
     .instance_size          = sizeof(BDRVBlkdebugState),
     .is_filter              = true,
+    .runtime_opts           = &runtime_opts,
 
     .bdrv_parse_filename    = blkdebug_parse_filename,
     .bdrv_file_open         = blkdebug_open,
diff --git a/block/crypto.c b/block/crypto.c
index bc322b50f5..5da97799b2 100644
--- a/block/crypto.c
+++ b/block/crypto.c
@@ -696,6 +696,7 @@ BlockDriver bdrv_crypto_luks = {
     .bdrv_co_create_opts = block_crypto_co_create_opts_luks,
     .bdrv_truncate      = block_crypto_truncate,
     .create_opts        = &block_crypto_create_opts_luks,
+    .runtime_opts       = &block_crypto_runtime_opts_luks,
 
     .bdrv_reopen_prepare = block_crypto_reopen_prepare,
     .bdrv_refresh_limits = block_crypto_refresh_limits,
diff --git a/block/file-posix.c b/block/file-posix.c
index 1511a4d69d..d6fe8d17cf 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -429,6 +429,8 @@ static QemuOptsList raw_runtime_opts = {
     },
 };
 
+static const char *const mutable_opts[] = { "x-check-cache-dropped", NULL };
+
 static int raw_open_common(BlockDriverState *bs, QDict *options,
                            int bdrv_flags, int open_flags, Error **errp)
 {
@@ -2582,6 +2584,8 @@ BlockDriver bdrv_file = {
     .format_name = "file",
     .protocol_name = "file",
     .instance_size = sizeof(BDRVRawState),
+    .runtime_opts = &raw_runtime_opts,
+    .mutable_opts = mutable_opts,
     .bdrv_needs_filename = true,
     .bdrv_probe = NULL, /* no probe for protocols */
     .bdrv_parse_filename = raw_parse_filename,
@@ -3064,6 +3068,8 @@ static BlockDriver bdrv_host_device = {
     .format_name        = "host_device",
     .protocol_name        = "host_device",
     .instance_size      = sizeof(BDRVRawState),
+    .runtime_opts = &raw_runtime_opts,
+    .mutable_opts = mutable_opts,
     .bdrv_needs_filename = true,
     .bdrv_probe_device  = hdev_probe_device,
     .bdrv_parse_filename = hdev_parse_filename,
@@ -3189,6 +3195,8 @@ static BlockDriver bdrv_host_cdrom = {
     .format_name        = "host_cdrom",
     .protocol_name      = "host_cdrom",
     .instance_size      = sizeof(BDRVRawState),
+    .runtime_opts = &raw_runtime_opts,
+    .mutable_opts = mutable_opts,
     .bdrv_needs_filename = true,
     .bdrv_probe_device	= cdrom_probe_device,
     .bdrv_parse_filename = cdrom_parse_filename,
@@ -3321,6 +3329,8 @@ static BlockDriver bdrv_host_cdrom = {
     .format_name        = "host_cdrom",
     .protocol_name      = "host_cdrom",
     .instance_size      = sizeof(BDRVRawState),
+    .runtime_opts = &raw_runtime_opts,
+    .mutable_opts = mutable_opts,
     .bdrv_needs_filename = true,
     .bdrv_probe_device	= cdrom_probe_device,
     .bdrv_parse_filename = cdrom_parse_filename,
diff --git a/block/iscsi.c b/block/iscsi.c
index c2fbd8a8aa..5f68d11176 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -2443,6 +2443,7 @@ static BlockDriver bdrv_iscsi = {
     .bdrv_close             = iscsi_close,
     .bdrv_co_create_opts    = iscsi_co_create_opts,
     .create_opts            = &iscsi_create_opts,
+    .runtime_opts           = &runtime_opts,
     .bdrv_reopen_prepare    = iscsi_reopen_prepare,
     .bdrv_reopen_commit     = iscsi_reopen_commit,
     .bdrv_co_invalidate_cache = iscsi_co_invalidate_cache,
@@ -2480,6 +2481,7 @@ static BlockDriver bdrv_iser = {
     .bdrv_close             = iscsi_close,
     .bdrv_co_create_opts    = iscsi_co_create_opts,
     .create_opts            = &iscsi_create_opts,
+    .runtime_opts           = &runtime_opts,
     .bdrv_reopen_prepare    = iscsi_reopen_prepare,
     .bdrv_reopen_commit     = iscsi_reopen_commit,
     .bdrv_co_invalidate_cache  = iscsi_co_invalidate_cache,
diff --git a/block/null.c b/block/null.c
index 5d610fdfba..fff3b12099 100644
--- a/block/null.c
+++ b/block/null.c
@@ -260,6 +260,7 @@ static BlockDriver bdrv_null_co = {
     .format_name            = "null-co",
     .protocol_name          = "null-co",
     .instance_size          = sizeof(BDRVNullState),
+    .runtime_opts           = &runtime_opts,
 
     .bdrv_file_open         = null_file_open,
     .bdrv_parse_filename    = null_co_parse_filename,
@@ -280,6 +281,7 @@ static BlockDriver bdrv_null_aio = {
     .format_name            = "null-aio",
     .protocol_name          = "null-aio",
     .instance_size          = sizeof(BDRVNullState),
+    .runtime_opts           = &runtime_opts,
 
     .bdrv_file_open         = null_file_open,
     .bdrv_parse_filename    = null_aio_parse_filename,
diff --git a/block/nvme.c b/block/nvme.c
index 6f71122bf5..5c77f82adb 100644
--- a/block/nvme.c
+++ b/block/nvme.c
@@ -1158,6 +1158,7 @@ static BlockDriver bdrv_nvme = {
     .format_name              = "nvme",
     .protocol_name            = "nvme",
     .instance_size            = sizeof(BDRVNVMeState),
+    .runtime_opts             = &runtime_opts,
 
     .bdrv_parse_filename      = nvme_parse_filename,
     .bdrv_file_open           = nvme_file_open,
diff --git a/block/qcow.c b/block/qcow.c
index 1f866af0d3..1176d5dbef 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -1188,6 +1188,7 @@ static QemuOptsList qcow_create_opts = {
 static BlockDriver bdrv_qcow = {
     .format_name	= "qcow",
     .instance_size	= sizeof(BDRVQcowState),
+    .runtime_opts       = &qcow_runtime_opts,
     .bdrv_probe		= qcow_probe,
     .bdrv_open		= qcow_open,
     .bdrv_close		= qcow_close,
diff --git a/block/rbd.c b/block/rbd.c
index a16431e267..173d7a50b0 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -1160,6 +1160,7 @@ static QemuOptsList qemu_rbd_create_opts = {
 static BlockDriver bdrv_rbd = {
     .format_name            = "rbd",
     .instance_size          = sizeof(BDRVRBDState),
+    .runtime_opts           = &runtime_opts,
     .bdrv_parse_filename    = qemu_rbd_parse_filename,
     .bdrv_refresh_limits    = qemu_rbd_refresh_limits,
     .bdrv_file_open         = qemu_rbd_open,
diff --git a/block/sheepdog.c b/block/sheepdog.c
index 7b98725af7..6b27df169a 100644
--- a/block/sheepdog.c
+++ b/block/sheepdog.c
@@ -3301,6 +3301,7 @@ static BlockDriver bdrv_sheepdog_tcp = {
     .bdrv_attach_aio_context      = sd_attach_aio_context,
 
     .create_opts                  = &sd_create_opts,
+    .runtime_opts                 = &runtime_opts,
 };
 
 static BlockDriver bdrv_sheepdog_unix = {
@@ -3338,6 +3339,7 @@ static BlockDriver bdrv_sheepdog_unix = {
     .bdrv_attach_aio_context      = sd_attach_aio_context,
 
     .create_opts                  = &sd_create_opts,
+    .runtime_opts                 = &runtime_opts,
 };
 
 static void bdrv_sheepdog_init(void)
diff --git a/block/vpc.c b/block/vpc.c
index 0ebfcd3cc8..460212c261 100644
--- a/block/vpc.c
+++ b/block/vpc.c
@@ -1234,6 +1234,7 @@ static BlockDriver bdrv_vpc = {
     .bdrv_get_info          = vpc_get_info,
 
     .create_opts            = &vpc_create_opts,
+    .runtime_opts           = &vpc_runtime_opts,
     .bdrv_has_zero_init     = vpc_has_zero_init,
 };
 
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 327e478a73..eef3a55220 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -342,6 +342,10 @@ struct BlockDriver {
 
     /* List of options for creating images, terminated by name == NULL */
     QemuOptsList *create_opts;
+    /* Runtime options for a block device, terminated by name == NULL */
+    QemuOptsList *runtime_opts;
+    /* Names of the runtime options that can be modified, NULL-terminated */
+    const char *const *mutable_opts;
 
     /*
      * Returns 0 for completed check, -errno for internal errors.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 08/10] block: Add bdrv_reset_options_allowed()
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (6 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 07/10] block: Add 'runtime_opts' and 'mutable_opts' fields to BlockDriver Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 09/10] block: Add a 'x-blockdev-reopen' QMP command Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 10/10] qemu-iotests: Test the x-blockdev-reopen " Alberto Garcia
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

bdrv_reopen_prepare() receives a BDRVReopenState with (among other
things) a new set of options to be applied to that BlockDriverState.

If an option is missing then it means that we want to reset it to its
default value rather than keeping the previous one. This way the state
of the block device after being reopened is comparable to that of a
device added with "blockdev-add" using the same set of options.

Not all options from all drivers can be changed, however. If the user
attempts to reset an immutable option to its default value using this
method then we must forbid it.

This new function takes a QemuOptsList with the options of a block
driver and checks if there's any that was explicitly set but is not
present in the BDRVReopenState.

If the option is present in both sets we don't need to check that it
keeps the same value. The loop at the end of bdrv_reopen_prepare()
already takes care of that.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 block.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/block.c b/block.c
index 91fff6361f..4aa40f7f10 100644
--- a/block.c
+++ b/block.c
@@ -2823,6 +2823,43 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference,
 }
 
 /*
+ * For every option in @list, check that if it is present in
+ * @state->bs->explicit_options then it is also present in
+ * @state->options. Options present in @mutable_opts are skipped.
+ *
+ * @mutable_opts is either NULL or a NULL-terminated array of option
+ * names.
+ *
+ * Return 0 on success, -EINVAL otherwise.
+ */
+static int bdrv_reset_options_allowed(BDRVReopenState *state,
+                                      QemuOptsList *list,
+                                      const char *const mutable_opts[],
+                                      Error **errp)
+{
+    QemuOptDesc *desc;
+    for (desc = list->desc; desc && desc->name; desc++) {
+        unsigned i;
+        bool skip_option = false;
+        for (i = 0; mutable_opts && mutable_opts[i] != 0; i++) {
+            if (!strcmp(desc->name, mutable_opts[i])) {
+                skip_option = true;
+                break;
+            }
+        }
+
+        if (!skip_option && !qdict_haskey(state->options, desc->name) &&
+            qdict_haskey(state->bs->explicit_options, desc->name)) {
+            error_setg(errp, "Option '%s' can't be reset to its default value",
+                       desc->name);
+            return -EINVAL;
+        }
+    }
+
+    return 0;
+}
+
+/*
  * Returns true if @child can be reached recursively from @bs
  */
 static bool bdrv_recurse_has_child(BlockDriverState *bs,
@@ -3254,6 +3291,18 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
     }
 
     if (drv->bdrv_reopen_prepare) {
+        /* If a driver-specified option is missing, it means that we
+         * should reset it to its default value. Not all options can
+         * be modified, so we need to check that first */
+        if (drv->runtime_opts) {
+            ret = bdrv_reset_options_allowed(reopen_state, drv->runtime_opts,
+                                             drv->mutable_opts, &local_err);
+            if (ret) {
+                error_propagate(errp, local_err);
+                goto error;
+            }
+        }
+
         ret = drv->bdrv_reopen_prepare(reopen_state, queue, &local_err);
         if (ret) {
             if (local_err != NULL) {
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 09/10] block: Add a 'x-blockdev-reopen' QMP command
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (7 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 08/10] block: Add bdrv_reset_options_allowed() Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 10/10] qemu-iotests: Test the x-blockdev-reopen " Alberto Garcia
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

This command allows reopening an arbitrary BlockDriverState with a new
set of options. Some options (e.g node-name) cannot be changed and
some block drivers don't allow reopening, but otherwise this command
is modelled after 'blockdev-add' and the state of the reopened
BlockDriverState should generally be the same as if it had just been
added by 'blockdev-add' with the same set of options.

One notable exception is that when the 'backing' option is omitted
then 'blockdev-add' opens the image's default backing file, whereas
'x-blockdev-reopen' simply keeps the current one.

This command allows reconfiguring the graph by using the appropriate
options to change the children of a node. At the moment it's possible
to change a backing file by setting the 'backing' option to the name
of the new node, but it should also be possible to add a similar
functionality to other block drivers (e.g. Quorum, blkverify).

Although the API is unlikely to change, this command is marked
experimental for the time being so there's room to see if the
semantics need changes.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 blockdev.c           | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 qapi/block-core.json | 29 ++++++++++++++++++++++++++
 2 files changed, 86 insertions(+)

diff --git a/blockdev.c b/blockdev.c
index 4862323012..1502747483 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -4236,6 +4236,63 @@ fail:
     visit_free(v);
 }
 
+void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
+{
+    BlockDriverState *bs;
+    QObject *obj;
+    Visitor *v = qobject_output_visitor_new(&obj);
+    Error *local_err = NULL;
+    int flags;
+    BlockReopenQueue *queue;
+    QDict *qdict;
+
+    /* Check for the selected node name */
+    if (!options->has_node_name) {
+        error_setg(errp, "Node name not specified");
+        goto fail;
+    }
+
+    bs = bdrv_find_node(options->node_name);
+    if (!bs) {
+        error_setg(errp, "Cannot find node named '%s'", options->node_name);
+        goto fail;
+    }
+
+    /* Put all options in a QDict and flatten it */
+    visit_type_BlockdevOptions(v, NULL, &options, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto fail;
+    }
+
+    visit_complete(v, &obj);
+    qdict = qobject_to(QDict, obj);
+
+    qdict_flatten(qdict);
+
+    /* Set some default values */
+    qdict_put_bool(qdict, BDRV_OPT_CACHE_DIRECT,
+                   qdict_get_try_bool(qdict, BDRV_OPT_CACHE_DIRECT, false));
+    qdict_put_bool(qdict, BDRV_OPT_CACHE_NO_FLUSH,
+                   qdict_get_try_bool(qdict, BDRV_OPT_CACHE_NO_FLUSH, false));
+    qdict_put_bool(qdict, BDRV_OPT_READ_ONLY,
+                   qdict_get_try_bool(qdict, BDRV_OPT_READ_ONLY, false));
+
+    flags = 0;
+    if (!qdict_get_bool(qdict, BDRV_OPT_READ_ONLY)) {
+        flags |= (BDRV_O_RDWR | BDRV_O_ALLOW_RDWR);
+    }
+
+    /* Perform the reopen operation */
+    bdrv_subtree_drained_begin(bs);
+    queue = bdrv_reopen_queue(NULL, bs, qdict, flags, false);
+    bdrv_reopen_multiple(bdrv_get_aio_context(bs), queue, errp);
+    bdrv_subtree_drained_end(bs);
+
+fail:
+    visit_free(v);
+}
+
 void qmp_blockdev_del(const char *node_name, Error **errp)
 {
     AioContext *aio_context;
diff --git a/qapi/block-core.json b/qapi/block-core.json
index fff23fc82b..ee8bf7c0ea 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3651,6 +3651,35 @@
 { 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
 
 ##
+# @x-blockdev-reopen:
+#
+# Reopens a block device using the given set of options. Any option
+# not specified will be reset to its default value regardless of its
+# previous status. If an option cannot be changed or a particular
+# driver does not support reopening the command will return an error.
+#
+# The top-level @node-name option (from BlockdevOptions) must be
+# specified and is used to select the block device to be reopened.
+# Other @node-name options must be either omitted or set to the
+# current name of the appropriate node. This command won't change any
+# node name and any attempt to do it will result in an error.
+#
+# The @backing option can always be omitted: any block device can be
+# reopened by specifying only its own options, without having to
+# include any of its backing files.
+#
+# Unlike blockdev-add, leaving out the @backing option will simply
+# keep the current backing file for that image and will not try to
+# open the default one. The way to replace a backing file is by
+# passing a reference to the new one in the @backing parameter.
+# If @backing is null then the current backing file will be removed.
+#
+# Since: 2.13
+##
+{ 'command': 'x-blockdev-reopen',
+  'data': 'BlockdevOptions', 'boxed': true }
+
+##
 # @blockdev-del:
 #
 # Deletes a block device that has been added using blockdev-add.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [Qemu-devel] [RFC PATCH 10/10] qemu-iotests: Test the x-blockdev-reopen QMP command
  2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
                   ` (8 preceding siblings ...)
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 09/10] block: Add a 'x-blockdev-reopen' QMP command Alberto Garcia
@ 2018-06-14 15:49 ` Alberto Garcia
  9 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-06-14 15:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-block, Kevin Wolf, Max Reitz, Eric Blake, Markus Armbruster,
	Alberto Garcia

This patch adds several tests for the x-blockdev-reopen QMP command.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 tests/qemu-iotests/220     | 724 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/220.out |   5 +
 tests/qemu-iotests/group   |   1 +
 3 files changed, 730 insertions(+)
 create mode 100755 tests/qemu-iotests/220
 create mode 100644 tests/qemu-iotests/220.out

diff --git a/tests/qemu-iotests/220 b/tests/qemu-iotests/220
new file mode 100755
index 0000000000..ac737845d8
--- /dev/null
+++ b/tests/qemu-iotests/220
@@ -0,0 +1,724 @@
+#!/usr/bin/env python
+#
+# Test cases for the QMP 'x-blockdev-reopen' command
+#
+# Copyright (C) 2018 Igalia, S.L.
+# Author: Alberto Garcia <berto@igalia.com>
+#
+# 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/>.
+#
+
+import os
+import re
+import iotests
+import copy
+import json
+from iotests import qemu_img, qemu_io
+
+hd_path = [
+    os.path.join(iotests.test_dir, 'hd0.img'),
+    os.path.join(iotests.test_dir, 'hd1.img'),
+    os.path.join(iotests.test_dir, 'hd2.img')
+]
+
+def hd_opts(idx):
+    return {'driver': iotests.imgfmt,
+            'node-name': 'hd%d' % idx,
+            'file': {'driver': 'file',
+                     'node-name': 'hd%d-file' % idx,
+                     'filename':  hd_path[idx] } }
+
+class TestBlockdevReopen(iotests.QMPTestCase):
+    total_io_cmds = 0
+
+    def setUp(self):
+        qemu_img('create', '-f', iotests.imgfmt, hd_path[0], '3M')
+        qemu_img('create', '-f', iotests.imgfmt, '-b', hd_path[0], hd_path[1])
+        qemu_img('create', '-f', iotests.imgfmt, hd_path[2], '3M')
+        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa0  0 1M', hd_path[0])
+        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa1 1M 1M', hd_path[1])
+        qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0xa2 2M 1M', hd_path[2])
+        self.vm = iotests.VM()
+        self.vm.launch()
+
+    def tearDown(self):
+        self.vm.shutdown()
+        self.check_qemu_io_errors()
+        os.remove(hd_path[0])
+        os.remove(hd_path[1])
+        os.remove(hd_path[2])
+
+    # The output of qemu-io is not returned by vm.hmp_qemu_io() but
+    # it's stored in the log and can only be read when the VM has been
+    # shut down. This function runs qemu-io and keeps track of the
+    # number of times it's been called.
+    def run_qemu_io(self, img, cmd):
+        result = self.vm.hmp_qemu_io(img, cmd)
+        self.assert_qmp(result, 'return', '')
+        self.total_io_cmds += 1
+
+    # Once the VM is shut down we can parse the log and see if qemu-io
+    # ran without errors.
+    def check_qemu_io_errors(self):
+        self.assertFalse(self.vm.is_running())
+        found = 0
+        log = self.vm.get_log()
+        for line in log.split("\n"):
+            if line.startswith("Pattern verification failed"):
+                raise Exception("%s (command #%d)" % (line, found))
+            if re.match("read .*/.* bytes at offset", line):
+                found += 1
+        self.assertEqual(found, self.total_io_cmds,
+                         "Expected output of %d qemu-io commands, found %d" %
+                         (found, self.total_io_cmds))
+
+    # Run x-blockdev-reopen with 'opts' but applying 'newopts'
+    # on top of it. The original 'opts' dict is unmodified
+    def reopen(self, opts, newopts = {}, errmsg = None):
+        opts = copy.deepcopy(opts)
+
+        # Apply the changes from 'newopts' on top of 'opts'
+        for key in newopts:
+            value = newopts[key]
+            # If key has the form "foo.bar" then we need to do
+            # opts["foo"]["bar"] = value, not opts["foo.bar"] = value
+            subdict = opts
+            while key.find('.') != -1:
+                [prefix, key] = key.split('.', 1)
+                subdict = opts[prefix]
+            subdict[key] = value
+
+        result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts)
+        if errmsg:
+            self.assert_qmp(result, 'error/class', 'GenericError')
+            self.assert_qmp(result, 'error/desc', errmsg)
+        else:
+            self.assert_qmp(result, 'return', {})
+
+
+    # Run query-named-block-nodes and return the specified entry
+    def get_node(self, node_name):
+        result = self.vm.qmp('query-named-block-nodes')
+        for node in result['return']:
+            if node['node-name'] == node_name:
+                return node
+        return None
+
+    # Run 'query-named-block-nodes' and compare its output with the
+    # value passed by the user in 'graph'
+    def check_node_graph(self, graph):
+        result = self.vm.qmp('query-named-block-nodes')
+        self.assertEqual(json.dumps(graph,  sort_keys=True),
+                         json.dumps(result, sort_keys=True))
+
+    # This test opens one single disk image (without backing files)
+    # and tries to reopen it with illegal / incorrect parameters.
+    def test_incorrect_parameters_single_file(self):
+        # Open 'hd0' only (no backing files)
+        opts = hd_opts(0)
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+        original_graph = self.vm.qmp('query-named-block-nodes')
+
+        # We can reopen the image passing the same options
+        self.reopen(opts)
+
+        # We cannot change any of these
+        self.reopen(opts, {'node-name': 'not-found'}, "Cannot find node named 'not-found'")
+        self.reopen(opts, {'node-name': ''}, "Cannot find node named ''")
+        self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string")
+        self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
+        self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
+        self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string")
+        self.reopen(opts, {'file': 'not-found'}, "Cannot change the option 'file'")
+        self.reopen(opts, {'file': ''}, "Cannot change the option 'file'")
+        self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
+        self.reopen(opts, {'file': 'hd0-file'}, "Cannot change the option 'file'")
+        self.reopen(opts, {'file.node-name': 'newname'}, "Cannot change the option 'node-name'")
+        self.reopen(opts, {'file.driver': 'host_device'}, "Cannot change the option 'driver'")
+        self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
+        self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
+        self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
+        self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string")
+
+        # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
+        del opts['node-name']
+        self.reopen(opts, {}, "Node name not specified")
+
+        # Check that nothing has changed
+        self.check_node_graph(original_graph)
+
+        # Remove the node
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+    # This test opens an image with a backing file and tries to reopen
+    # it with illegal / incorrect parameters.
+    def test_incorrect_parameters_backing_file(self):
+        # Open hd1 omitting the backing options (hd0 will be opened
+        # with the default options)
+        opts = hd_opts(1)
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+        original_graph = self.vm.qmp('query-named-block-nodes')
+
+        # We can reopen the image passing the same options
+        self.reopen(opts)
+
+        # We can't use a non-existing or empty (non-NULL) node as the backing image
+        self.reopen(opts, {'backing': 'not-found'}, "Cannot find device= nor node_name=not-found")
+        self.reopen(opts, {'backing': ''}, "Cannot find device= nor node_name=")
+
+        # We can reopen the image just fine if we specify the backing options
+        opts['backing'] = {'driver': iotests.imgfmt,
+                           'file': {'driver': 'file',
+                                    'filename': hd_path[0]}}
+        self.reopen(opts)
+
+        # We cannot change any of these options
+        self.reopen(opts, {'backing.node-name': 'newname'}, "Cannot change the option 'node-name'")
+        self.reopen(opts, {'backing.driver': 'raw'}, "Cannot change the option 'driver'")
+        self.reopen(opts, {'backing.file.node-name': 'newname'}, "Cannot change the option 'node-name'")
+        self.reopen(opts, {'backing.file.driver': 'host_device'}, "Cannot change the option 'driver'")
+
+        # Check that nothing has changed since the beginning
+        self.check_node_graph(original_graph)
+
+        # Remove the node
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1')
+        self.assert_qmp(result, 'return', {})
+
+    def test_reopen(self):
+        # Open the hd1 image passing all backing options
+        opts = hd_opts(1)
+        opts['backing'] = hd_opts(0)
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+        original_graph = self.vm.qmp('query-named-block-nodes')
+
+        # We can reopen the image passing the same options
+        self.reopen(opts)
+
+        # Reopen in read-only mode
+        self.assert_qmp(self.get_node('hd1'), 'ro', False)
+
+        self.reopen(opts, {'read-only': True})
+        self.assert_qmp(self.get_node('hd1'), 'ro', True)
+        self.reopen(opts)
+        self.assert_qmp(self.get_node('hd1'), 'ro', False)
+
+        # Change the cache options
+        self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True)
+        self.assert_qmp(self.get_node('hd1'), 'cache/direct', False)
+        self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False)
+        self.reopen(opts, {'cache': { 'direct': True, 'no-flush': True }})
+        self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True)
+        self.assert_qmp(self.get_node('hd1'), 'cache/direct', True)
+        self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', True)
+
+        # Reopen again with the original options
+        self.reopen(opts)
+        self.assert_qmp(self.get_node('hd1'), 'cache/writeback', True)
+        self.assert_qmp(self.get_node('hd1'), 'cache/direct', False)
+        self.assert_qmp(self.get_node('hd1'), 'cache/no-flush', False)
+
+        # Change 'detect-zeroes' and 'discard'
+        self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off')
+        self.reopen(opts, {'detect-zeroes': 'on'})
+        self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on')
+        self.reopen(opts, {'detect-zeroes': 'unmap'},
+                    "setting detect-zeroes to unmap is not allowed " +
+                    "without setting discard operation to unmap")
+        self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'on')
+        self.reopen(opts, {'detect-zeroes': 'unmap', 'discard': 'unmap'})
+        self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'unmap')
+        self.reopen(opts)
+        self.assert_qmp(self.get_node('hd1'), 'detect_zeroes', 'off')
+
+        # Change 'force-share'
+        self.reopen(opts, {'force-share': True},
+                    "force-share=on can only be used with read-only images")
+        self.reopen(opts, {'force-share': True, 'read-only': True})
+        self.reopen(opts, {'force-share': False})
+
+        # Change some qcow2-specific options
+        # No way to test for success other than checking the return message
+        if iotests.imgfmt == 'qcow2':
+            self.reopen(opts, {'l2-cache-entry-size': 128 * 1024},
+                        "L2 cache entry size must be a power of two "+
+                        "between 512 and the cluster size (65536)")
+            self.reopen(opts, {'l2-cache-size': 1024 * 1024,
+                               'cache-size':     512 * 1024},
+                        "l2-cache-size may not exceed cache-size")
+            self.reopen(opts, {'l2-cache-size':        4 * 1024 * 1024,
+                               'refcount-cache-size':  4 * 1024 * 1024,
+                               'l2-cache-entry-size': 32 * 1024})
+            self.reopen(opts, {'pass-discard-request': True})
+
+        # Check that nothing has changed since the beginning
+        # (from the parameters that we can check)
+        self.check_node_graph(original_graph)
+
+        # Check that the node names (other than the top-level one) are optional
+        del opts['file']['node-name']
+        del opts['backing']['node-name']
+        del opts['backing']['file']['node-name']
+        self.reopen(opts)
+        self.check_node_graph(original_graph)
+
+        # Reopen setting backing = null, this removes the backing image from the chain
+        self.reopen(opts, {'backing': None})
+        self.assert_qmp_absent(self.get_node('hd1'), 'image/backing-image')
+
+        # Open the 'hd0' image
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **hd_opts(0))
+        self.assert_qmp(result, 'return', {})
+
+        # Reopen the hd1 image setting 'hd0' as its backing image
+        self.reopen(opts, {'backing': 'hd0'})
+        self.assert_qmp(self.get_node('hd1'), 'image/backing-image/filename', hd_path[0])
+
+        # Check that nothing has changed since the beginning
+        self.reopen(hd_opts(0), {'read-only': True})
+        self.check_node_graph(original_graph)
+
+        # FIXME: the backing file (hd0) is now a reference so should
+        # we still be able to change backing.* ?
+        self.reopen(opts)
+
+        # We can't remove 'hd0' while it's a backing image of 'hd1'
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'error/class', 'GenericError')
+        self.assert_qmp(result, 'error/desc', "Node 'hd0' is busy: node is used as backing hd of 'hd1'")
+
+        # But we can remove both nodes if done in the proper order
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd1')
+        self.assert_qmp(result, 'return', {})
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+    # Reopen a raw image and see the effect of changing the 'offset' option
+    def test_reopen_raw(self):
+        opts = {'driver': 'raw', 'node-name': 'hd0',
+                'file': { 'driver': 'file',
+                          'filename': hd_path[0],
+                          'node-name': 'hd0-file' } }
+
+        # First we create a 2MB raw file, and fill each half with a
+        # different value
+        qemu_img('create', '-f', 'raw', hd_path[0], '2M')
+        qemu_io('-f', 'raw', '-c', 'write -P 0xa0  0 1M', hd_path[0])
+        qemu_io('-f', 'raw', '-c', 'write -P 0xa1 1M 1M', hd_path[0])
+
+        # Open the raw file with QEMU
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # Read 1MB from offset 0
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+
+        # Reopen the image with a 1MB offset.
+        # Now the results are different
+        self.reopen(opts, {'offset': 1024*1024})
+        self.run_qemu_io("hd0", "read -P 0xa1  0 1M")
+
+        # Reopen again with the original options.
+        # We get the original results again
+        self.reopen(opts)
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+
+        # Remove the block device
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+    # Omitting an option should reset it to the default value, but if
+    # an option cannot be changed it shouldn't be possible to reset it
+    # to its default value either
+    def test_reset_default_values(self):
+        opts = {'driver': 'qcow2', 'node-name': 'hd0',
+                'file': { 'driver': 'file',
+                          'filename': hd_path[0],
+                          'x-check-cache-dropped': True, # This one can be changed
+                          'locking': 'off',              # This one can NOT be changed
+                          'node-name': 'hd0-file' } }
+
+        # Open the file with QEMU
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # file.x-check-cache-dropped can be changed...
+        self.reopen(opts, { 'file.x-check-cache-dropped': False })
+        # ...and dropped completely (resetting to the default value)
+        del opts['file']['x-check-cache-dropped']
+        self.reopen(opts)
+
+        # file.locking cannot be changed nor reset to the default value
+        self.reopen(opts, { 'file.locking': 'on' }, "Cannot change the option 'locking'")
+        del opts['file']['locking']
+        self.reopen(opts, {}, "Option 'locking' can't be reset to its default value")
+        # But we can reopen it if we maintain its previous value
+        self.reopen(opts, { 'file.locking': 'off' })
+
+        # Remove the block device
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+    # This test modifies the node graph a few times by changing the
+    # 'backing' option on reopen and verifies that the guest data that
+    # is read afterwards is consistent with the graph changes.
+    def test_io_with_graph_changes(self):
+        opts = []
+
+        # Open hd0, hd1 and hd2 without any backing image
+        for i in range(3):
+            opts.append(hd_opts(i))
+            opts[i]['backing'] = None
+            result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i])
+            self.assert_qmp(result, 'return', {})
+
+        # hd0
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+        self.run_qemu_io("hd0", "read -P 0    1M 1M")
+        self.run_qemu_io("hd0", "read -P 0    2M 1M")
+
+        # hd1 <- hd0
+        self.reopen(opts[0], {'backing': 'hd1'})
+
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+        self.run_qemu_io("hd0", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd0", "read -P 0    2M 1M")
+
+        # hd1 <- hd0 , hd1 <- hd2
+        self.reopen(opts[2], {'backing': 'hd1'})
+
+        self.run_qemu_io("hd2", "read -P 0     0 1M")
+        self.run_qemu_io("hd2", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd2", "read -P 0xa2 2M 1M")
+
+        # hd1 <- hd2 <- hd0
+        self.reopen(opts[0], {'backing': 'hd2'})
+
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+        self.run_qemu_io("hd0", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd0", "read -P 0xa2 2M 1M")
+
+        # hd2 <- hd0
+        self.reopen(opts[2], {'backing': None})
+
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+        self.run_qemu_io("hd0", "read -P 0    1M 1M")
+        self.run_qemu_io("hd0", "read -P 0xa2 2M 1M")
+
+        # hd2 <- hd1 <- hd0
+        self.reopen(opts[1], {'backing': 'hd2'})
+        self.reopen(opts[0], {'backing': 'hd1'})
+
+        self.run_qemu_io("hd0", "read -P 0xa0  0 1M")
+        self.run_qemu_io("hd0", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd0", "read -P 0xa2 2M 1M")
+
+        # Illegal operation: hd2 is a child of hd1
+        self.reopen(opts[2], {'backing': 'hd1'},
+                    "Making 'hd1' a backing file of 'hd2' would create a cycle")
+
+        # hd2 <- hd0, hd2 <- hd1
+        self.reopen(opts[0], {'backing': 'hd2'})
+
+        self.run_qemu_io("hd1", "read -P 0     0 1M")
+        self.run_qemu_io("hd1", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd1", "read -P 0xa2 2M 1M")
+
+        # More illegal operations
+        self.reopen(opts[2], {'backing': 'hd1'},
+                    "Making 'hd1' a backing file of 'hd2' would create a cycle")
+        self.reopen(opts[2], {'file': 'hd0-file'}, "Cannot change the option 'file'")
+
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd2')
+        self.assert_qmp(result, 'error/class', 'GenericError')
+        self.assert_qmp(result, 'error/desc', "Node 'hd2' is busy: node is used as backing hd of 'hd0'")
+
+        # hd1 doesn't have a backing file now
+        self.reopen(opts[1], {'backing': None})
+
+        self.run_qemu_io("hd1", "read -P 0     0 1M")
+        self.run_qemu_io("hd1", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd1", "read -P 0    2M 1M")
+
+        # Removing the 'backing' option doesn't open the default
+        # backing file specified in the image (hd0 in this case).
+        # TODO: change this behavior??
+        del opts[1]['backing']
+        self.reopen(opts[1])
+
+        self.run_qemu_io("hd1", "read -P 0     0 1M")
+        self.run_qemu_io("hd1", "read -P 0xa1 1M 1M")
+        self.run_qemu_io("hd1", "read -P 0    2M 1M")
+
+    # This test verifies that we can't change the children of a block
+    # device during a reopen operation in a way that would create
+    # cycles in the node graph
+    def test_graph_cycles(self):
+        opts = []
+
+        # Open all three images without backing file
+        for i in range(3):
+            opts.append(hd_opts(i))
+            opts[i]['backing'] = None
+            result = self.vm.qmp('blockdev-add', conv_keys = False, **opts[i])
+            self.assert_qmp(result, 'return', {})
+
+        # hd1 <- hd0, hd1 <- hd2
+        self.reopen(opts[0], {'backing': 'hd1'})
+        self.reopen(opts[2], {'backing': 'hd1'})
+
+        # Illegal: hd2 is backed by hd1
+        self.reopen(opts[1], {'backing': 'hd2'},
+                    "Making 'hd2' a backing file of 'hd1' would create a cycle")
+
+        # hd1 <- hd0 <- hd2
+        self.reopen(opts[2], {'backing': 'hd0'})
+
+        # Illegal: hd2 is backed by hd0, which is backed by hd1
+        self.reopen(opts[1], {'backing': 'hd2'},
+                    "Making 'hd2' a backing file of 'hd1' would create a cycle")
+
+        # Illegal: hd1 cannot point to itself
+        self.reopen(opts[1], {'backing': 'hd1'},
+                    "Making 'hd1' a backing file of 'hd1' would create a cycle")
+
+        # Remove all backing files
+        self.reopen(opts[0])
+        self.reopen(opts[2])
+
+        ##########################################
+        # Add a blkverify node using hd0 and hd1 #
+        ##########################################
+        bvopts = {'driver': 'blkverify',
+                  'node-name': 'bv',
+                  'test': 'hd0',
+                  'raw': 'hd1'}
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **bvopts)
+        self.assert_qmp(result, 'return', {})
+
+        # blkverify doesn't currently allow reopening. TODO: implement this
+        self.reopen(bvopts, {}, "Block format 'blkverify' used by node 'bv'" +
+                    " does not support reopening files")
+
+        # Illegal: hd0 is a child of the blkverify node
+        self.reopen(opts[0], {'backing': 'bv'},
+                    "Making 'bv' a backing file of 'hd0' would create a cycle")
+
+        # Delete the blkverify node
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bv')
+        self.assert_qmp(result, 'return', {})
+
+    # Misc reopen tests with different block drivers
+    def test_misc_drivers(self):
+        ####################
+        ###### quorum ######
+        ####################
+        for i in range(3):
+            opts = hd_opts(i)
+            # Open all three images without backing file
+            opts['backing'] = None
+            result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+            self.assert_qmp(result, 'return', {})
+
+        opts = {'driver': 'quorum',
+                'node-name': 'quorum0',
+                'children': ['hd0', 'hd1', 'hd2'],
+                'vote-threshold': 2}
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # Quorum doesn't currently allow reopening. TODO: implement this
+        self.reopen(opts, {}, "Block format 'quorum' used by node 'quorum0'" +
+                    " does not support reopening files")
+
+        # You can't make quorum0 a backing file of hd0:
+        # hd0 is already a child of quorum0.
+        self.reopen(hd_opts(0), {'backing': 'quorum0'},
+                    "Making 'quorum0' a backing file of 'hd0' would create a cycle")
+
+        # Delete quorum0
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'quorum0')
+        self.assert_qmp(result, 'return', {})
+
+        # Delete hd0, hd1 and hd2
+        for i in range(3):
+            result = self.vm.qmp('blockdev-del', conv_keys = True,
+                                 node_name = 'hd%d' % i)
+            self.assert_qmp(result, 'return', {})
+
+        ######################
+        ###### blkdebug ######
+        ######################
+        opts = {'driver': 'blkdebug',
+                'node-name': 'bd',
+                'config': '/dev/null',
+                'image': hd_opts(0)}
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # blkdebug allows reopening if we keep the same options
+        self.reopen(opts)
+
+        # but it currently does not allow changes
+        self.reopen(opts, {'image': 'hd1'}, "Cannot change the option 'image'")
+        self.reopen(opts, {'align': 33554432}, "Cannot change the option 'align'")
+        self.reopen(opts, {'config': '/non/existent'}, "Cannot change the option 'config'")
+        del opts['config']
+        self.reopen(opts, {}, "Option 'config' can't be reset to its default value")
+
+        # Delete the blkdebug node
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'bd')
+        self.assert_qmp(result, 'return', {})
+
+        ##################
+        ###### null ######
+        ##################
+        opts = {'driver': 'null-aio', 'node-name': 'root', 'size': 1024}
+
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # 1 << 30 is the default value, but we cannot change it explicitly
+        self.reopen(opts, {'size': (1 << 30)}, "Cannot change the option 'size'")
+
+        # We cannot change 'size' back to its default value either
+        del opts['size']
+        self.reopen(opts, {}, "Option 'size' can't be reset to its default value")
+
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'root')
+        self.assert_qmp(result, 'return', {})
+
+        ##################
+        ###### file ######
+        ##################
+        opts = hd_opts(0)
+        opts['file']['locking'] = 'on'
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # 'locking' cannot be changed
+        del opts['file']['locking']
+        self.reopen(opts, {'file.locking': 'on'})
+        self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
+        self.reopen(opts, {}, "Option 'locking' can't be reset to its default value")
+
+        # Trying to reopen the 'file' node directly does not make a difference
+        opts = opts['file']
+        self.reopen(opts, {'locking': 'on'})
+        self.reopen(opts, {'locking': 'off'}, "Cannot change the option 'locking'")
+        self.reopen(opts, {}, "Option 'locking' can't be reset to its default value")
+
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+        ######################
+        ###### throttle ######
+        ######################
+        opts = { 'qom-type': 'throttle-group', 'id': 'group0',
+                 'props': { 'limits': { 'iops-total': 1000 } } }
+        result = self.vm.qmp('object-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        opts = { 'qom-type': 'throttle-group', 'id': 'group1',
+                 'props': { 'limits': { 'iops-total': 2000 } } }
+        result = self.vm.qmp('object-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # Add a throttle filter with group = group0
+        opts = { 'driver': 'throttle', 'node-name': 'throttle0',
+                 'throttle-group': 'group0', 'file': hd_opts(0) }
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # We can reopen it if we keep the same options
+        self.reopen(opts)
+
+        # These are illegal
+        self.reopen(opts, {'file': 'hd0'}, "Cannot change the option 'file'")
+        self.reopen(opts, {'throttle-group': 'notfound'}, "Throttle group 'notfound' does not exist")
+
+        # But it's possible to change the group to group1
+        self.reopen(opts, {'throttle-group': 'group1'})
+
+        # Now group1 is in use, it cannot be deleted
+        result = self.vm.qmp('object-del', id = 'group1')
+        self.assert_qmp(result, 'error/class', 'GenericError')
+        self.assert_qmp(result, 'error/desc', "object 'group1' is in use, can not be deleted")
+
+        # Default options, this switches the group back to group0
+        self.reopen(opts)
+
+        # So now we cannot delete group0
+        result = self.vm.qmp('object-del', id = 'group0')
+        self.assert_qmp(result, 'error/class', 'GenericError')
+        self.assert_qmp(result, 'error/desc', "object 'group0' is in use, can not be deleted")
+
+        # But group1 is free this time, and it can be deleted
+        result = self.vm.qmp('object-del', id = 'group1')
+        self.assert_qmp(result, 'return', {})
+
+        # Let's delete the filter node
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'throttle0')
+        self.assert_qmp(result, 'return', {})
+
+        # And we can finally get rid of group0
+        result = self.vm.qmp('object-del', id = 'group0')
+        self.assert_qmp(result, 'return', {})
+
+    def test_missing_backing_file(self):
+        # hd1 <- hd0
+        opts = hd_opts(0)
+        opts['backing'] = hd_opts(1)
+        opts['backing']['backing'] = None
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # Remove hd1
+        self.reopen(opts, {'backing': None})
+
+        # FIXME: this should fail because we're specifying backing.*
+        # options but we have just removed the backing file
+        self.reopen(opts)
+
+    # Test what happens if the graph changes due to other operations
+    # such as block-stream
+    def test_block_stream(self):
+        # hd1 <- hd0
+        opts = hd_opts(0)
+        opts['backing'] = hd_opts(1)
+        opts['backing']['backing'] = None
+        result = self.vm.qmp('blockdev-add', conv_keys = False, **opts)
+        self.assert_qmp(result, 'return', {})
+
+        # Stream hd1 into hd0 and wait until it's done
+        result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0', device = 'hd0')
+        self.assert_qmp(result, 'return', {})
+        self.wait_until_completed(drive = 'stream0')
+
+        # Now we have only hd0
+        self.assertEqual(self.get_node('hd1'), None)
+
+        # FIXME: This should fail, we have backing.* options but
+        # there's no backing file anymore
+        self.reopen(opts)
+
+        result = self.vm.qmp('blockdev-del', conv_keys = True, node_name = 'hd0')
+        self.assert_qmp(result, 'return', {})
+
+if __name__ == '__main__':
+    iotests.main(supported_fmts=["qcow2"])
diff --git a/tests/qemu-iotests/220.out b/tests/qemu-iotests/220.out
new file mode 100644
index 0000000000..36376bed87
--- /dev/null
+++ b/tests/qemu-iotests/220.out
@@ -0,0 +1,5 @@
+..........
+----------------------------------------------------------------------
+Ran 10 tests
+
+OK
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 0914c922d7..7646f9b621 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -218,3 +218,4 @@
 217 rw auto quick
 218 rw auto quick
 219 rw auto
+220 rw auto quick
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue()
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue() Alberto Garcia
@ 2018-06-18 14:15   ` Kevin Wolf
  2018-06-18 15:28     ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-18 14:15 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 14.06.2018 um 17:49 hat Alberto Garcia geschrieben:
> The bdrv_reopen_queue() function is used to create a queue with the
> BDSs that are going to be reopened and their new options. Once the
> queue is ready bdrv_reopen_multiple() is called to perform the
> operation.
> 
> The original options from each one of the BDSs are kept, with the new
> options passed to bdrv_reopen_queue() applied on top of them.
> 
> For "x-blockdev-reopen" we want a function that behaves much like
> "blockdev-add". We want to ignore the previous set of options so that
> only the ones actually specified by the user are applied, with the
> rest having their default values.
> 
> We can achieve this by adding a new parameter to bdrv_reopen_queue()
> that specifies whether the old set of options is kept or discarded
> when building the reopen queue. All current callers will set that
> parameter to true, but x-blockdev-reopen will set it to false.
> 
> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>  block.c               | 34 +++++++++++++++++++---------------
>  block/replication.c   |  4 ++--
>  include/block/block.h |  3 ++-
>  qemu-io-cmds.c        |  2 +-
>  4 files changed, 24 insertions(+), 19 deletions(-)
> 
> diff --git a/block.c b/block.c
> index a741300fae..0b9268a48d 100644
> --- a/block.c
> +++ b/block.c
> @@ -2850,7 +2850,8 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
>                                                   int flags,
>                                                   const BdrvChildRole *role,
>                                                   QDict *parent_options,
> -                                                 int parent_flags)
> +                                                 int parent_flags,
> +                                                 bool keep_old_opts)

Can we change the semantics of keep_old_opts so that flags is completely
ignored for keep_old_opts=false?

Flags are one of the reasons why the behaviour of bdrv_reopen() is
rather complex and I'd prefer not having that complexity in a public
interface. To be honest, I wouldn't be sure that I could write a correct
documentation with it.

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen Alberto Garcia
@ 2018-06-18 14:38   ` Kevin Wolf
  2018-06-18 15:06     ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-18 14:38 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 14.06.2018 um 17:49 hat Alberto Garcia geschrieben:
> This patch allows the user to change the backing file of an image that
> is being reopened. Here's what it does:
> 
>  - In bdrv_reopen_queue_child(): if the 'backing' option points to an
>    image different from the current backing file then it means that
>    the latter is going be detached so it must not be put in the reopen
>    queue.
> 
>  - In bdrv_reopen_prepare(): check that the value of 'backing' points
>    to an existing node or is null. If it points to an existing node it
>    also needs to make sure that replacing the backing file will not
>    create a cycle in the node graph (i.e. you cannot reach the parent
>    from the new backing file).
> 
>  - In bdrv_reopen_commit(): perform the actual node replacement by
>    calling bdrv_set_backing_hd(). It may happen that there are
>    temporary implicit nodes between the BDS that we are reopening and
>    the backing file that we want to replace (e.g. a commit fiter node),
>    so we must skip them.

I think we shouldn't do this. blockdev-reopen is an advanced command
that requires knowledge of the graph at the node level. Therefore we can
expect the management tool to consider filter nodes when it reconfigures
the graph. This is important because it makes a difference whether a
new node is inserted above or below the filter node.

> Although x-blockdev-reopen is meant to be used like blockdev-add,
> there's two important things that must be taken into account:
> 
> 1) The only way to set a new backing file is by using a reference to
>    an existing node (previously added with e.g. blockdev-add).
>    If 'backing' contains a dictionary with a new set of options
>    ({"driver": "qcow2", "file": { ... }}) then it is interpreted that
>    the _existing_ backing file must be reopened with those options.

This sounds a bit odd at first, but it makes perfect sense in fact.

Maybe we should enforce that child nodes that were openend by reference
first must be reopened with a reference, and only those that were defined
inline (and therefore inherit options from the parent) can be reopened
inline?

> 2) If the 'backing' option is omitted altogether then the existing
>    backing file (if any) is kept. Unlike blockdev-add this will _not_
>    open the default backing file specified in the image header.

Hm... This is certainly an inconsistency. Is it unavoidable?

> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>  block.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 84 insertions(+), 2 deletions(-)
> 
> diff --git a/block.c b/block.c
> index 0b9268a48d..91fff6361f 100644
> --- a/block.c
> +++ b/block.c
> @@ -2823,6 +2823,27 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference,
>  }
>  
>  /*
> + * Returns true if @child can be reached recursively from @bs
> + */
> +static bool bdrv_recurse_has_child(BlockDriverState *bs,
> +                                   BlockDriverState *child)
> +{
> +    BdrvChild *c;
> +
> +    if (bs == child) {
> +        return true;
> +    }
> +
> +    QLIST_FOREACH(c, &bs->children, next) {
> +        if (bdrv_recurse_has_child(c->bs, child)) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
> +/*
>   * Adds a BlockDriverState to a simple queue for an atomic, transactional
>   * reopen of multiple devices.
>   *
> @@ -2957,6 +2978,7 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
>      QLIST_FOREACH(child, &bs->children, next) {
>          QDict *new_child_options;
>          char *child_key_dot;
> +        bool child_keep_old = keep_old_opts;
>  
>          /* reopen can only change the options of block devices that were
>           * implicitly created and inherited options. For other (referenced)
> @@ -2965,12 +2987,28 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
>              continue;
>          }
>  
> +        /* If the 'backing' option is set and it points to a different
> +         * node then it means that we want to replace the current one,
> +         * so we shouldn't put it in the reopen queue */
> +        if (child->role == &child_backing && qdict_haskey(options, "backing")) {

I think checking child->name for "backing" makes more sense when we also
look for the "backing" key in the options QDict. This would make
generalising it for other children easier (which we probably should do,
whether in this patch or a follow-up).

> +            const char *backing = qdict_get_try_str(options, "backing");
> +            if (g_strcmp0(backing, child->bs->node_name)) {
> +                continue;
> +            }
> +        }
> +
>          child_key_dot = g_strdup_printf("%s.", child->name);
>          qdict_extract_subqdict(options, &new_child_options, child_key_dot);
>          g_free(child_key_dot);
>  
> +        /* If the 'backing' option was omitted then we keep the old
> +         * set of options */
> +        if (child->role == &child_backing && !qdict_size(new_child_options)) {
> +            child_keep_old = true;
> +        }
> +
>          bdrv_reopen_queue_child(bs_queue, child->bs, new_child_options, 0,
> -                                child->role, options, flags, keep_old_opts);
> +                                child->role, options, flags, child_keep_old);
>      }
>  
>      return bs_queue;
> @@ -3262,7 +3300,34 @@ int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
>               * the user can simply omit options which cannot be changed anyway,
>               * so they will stay unchanged.
>               */
> -            if (!qobject_is_equal(new, old)) {
> +            /*
> +             * Allow changing the 'backing' option. The new value can be
> +             * either a reference to an existing node (using its node name)
> +             * or NULL to simply detach the current backing file.
> +             */
> +            if (!strcmp(entry->key, "backing")) {
> +                switch (qobject_type(new)) {
> +                case QTYPE_QNULL:
> +                    break;
> +                case QTYPE_QSTRING: {
> +                    const char *str = qobject_get_try_str(new);
> +                    BlockDriverState *bs = bdrv_lookup_bs(NULL, str, errp);
> +                    if (bs == NULL) {
> +                        ret = -EINVAL;
> +                        goto error;
> +                    } else if (bdrv_recurse_has_child(bs, reopen_state->bs)) {
> +                        error_setg(errp, "Making '%s' a backing file of '%s' "
> +                                   "would create a cycle", str,
> +                                   reopen_state->bs->node_name);
> +                        ret = -EINVAL;
> +                        goto error;
> +                    }
> +                    break;
> +                }
> +                default:
> +                    g_assert_not_reached();
> +                }

Do we need some way for e.g. block jobs to forbid changing the backing
file of the subchain they are operating on?

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-18 14:38   ` Kevin Wolf
@ 2018-06-18 15:06     ` Alberto Garcia
  2018-06-18 16:12       ` Kevin Wolf
  0 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-18 15:06 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

On Mon 18 Jun 2018 04:38:01 PM CEST, Kevin Wolf wrote:
> Am 14.06.2018 um 17:49 hat Alberto Garcia geschrieben:
>> This patch allows the user to change the backing file of an image that
>> is being reopened. Here's what it does:
>> 
>>  - In bdrv_reopen_queue_child(): if the 'backing' option points to an
>>    image different from the current backing file then it means that
>>    the latter is going be detached so it must not be put in the reopen
>>    queue.
>> 
>>  - In bdrv_reopen_prepare(): check that the value of 'backing' points
>>    to an existing node or is null. If it points to an existing node it
>>    also needs to make sure that replacing the backing file will not
>>    create a cycle in the node graph (i.e. you cannot reach the parent
>>    from the new backing file).
>> 
>>  - In bdrv_reopen_commit(): perform the actual node replacement by
>>    calling bdrv_set_backing_hd(). It may happen that there are
>>    temporary implicit nodes between the BDS that we are reopening and
>>    the backing file that we want to replace (e.g. a commit fiter node),
>>    so we must skip them.
>
> I think we shouldn't do this. blockdev-reopen is an advanced command
> that requires knowledge of the graph at the node level. Therefore we can
> expect the management tool to consider filter nodes when it reconfigures
> the graph. This is important because it makes a difference whether a
> new node is inserted above or below the filter node.

But those nodes that the management tool knows about would not be
implicit, or would they?

The reason why I did this is because there's several places in the QEMU
codebase where bdrv_reopen() is called while there's a temporary
implicit node. For example, on exit bdrv_commit() needs to call
bdrv_set_backing_hd() to remove intermediate nodes from the chain. This
happens while bdrv_mirror_top is still there, so if we don't skip it
then QEMU crashes.

>> Although x-blockdev-reopen is meant to be used like blockdev-add,
>> there's two important things that must be taken into account:
>> 
>> 1) The only way to set a new backing file is by using a reference to
>>    an existing node (previously added with e.g. blockdev-add).
>>    If 'backing' contains a dictionary with a new set of options
>>    ({"driver": "qcow2", "file": { ... }}) then it is interpreted that
>>    the _existing_ backing file must be reopened with those options.
>
> This sounds a bit odd at first, but it makes perfect sense in fact.
>
> Maybe we should enforce that child nodes that were openend by reference
> first must be reopened with a reference, and only those that were defined
> inline (and therefore inherit options from the parent) can be reopened
> inline?

I think that should be doable, yes.

>> 2) If the 'backing' option is omitted altogether then the existing
>>    backing file (if any) is kept. Unlike blockdev-add this will _not_
>>    open the default backing file specified in the image header.
>
> Hm... This is certainly an inconsistency. Is it unavoidable?

I don't think we want to open new nodes on reopen(), but one easy way to
avoid this behavior is simply to return an error if the user omits the
'backing' option.

But this raises a few questions. Let's say you have an image with a
backing file and you open it with blockdev-add omitting the 'backing'
option. That means the default backing file is opened.

  - Do you still have to specify 'backing' on reopen? I suppose you
    don't have to.

  - If you don't have to, can QEMU tell if bs->backing is the original
    backing file or a new one? I suppose it can, by checking for
    'backing / backing.*' in bs->options.

  - If you omit 'backing', does the backing file still get reopened? And
    more importantly, does it keep its current options or are they
    supposed to be reset to their default values?

>> +        /* If the 'backing' option is set and it points to a different
>> +         * node then it means that we want to replace the current one,
>> +         * so we shouldn't put it in the reopen queue */
>> +        if (child->role == &child_backing && qdict_haskey(options, "backing")) {
>
> I think checking child->name for "backing" makes more sense when we
> also look for the "backing" key in the options QDict. This would make
> generalising it for other children easier (which we probably should
> do, whether in this patch or a follow-up).

Sounds reasonable.

> Do we need some way for e.g. block jobs to forbid changing the backing
> file of the subchain they are operating on?

Are you thinking of any particular scenario?

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue()
  2018-06-18 14:15   ` Kevin Wolf
@ 2018-06-18 15:28     ` Alberto Garcia
  2018-06-18 16:15       ` Kevin Wolf
  0 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-18 15:28 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

On Mon 18 Jun 2018 04:15:07 PM CEST, Kevin Wolf wrote:

>> @@ -2850,7 +2850,8 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
>>                                                   int flags,
>>                                                   const BdrvChildRole *role,
>>                                                   QDict *parent_options,
>> -                                                 int parent_flags)
>> +                                                 int parent_flags,
>> +                                                 bool keep_old_opts)
>
> Can we change the semantics of keep_old_opts so that flags is completely
> ignored for keep_old_opts=false?
>
> Flags are one of the reasons why the behaviour of bdrv_reopen() is
> rather complex and I'd prefer not having that complexity in a public
> interface. To be honest, I wouldn't be sure that I could write a
> correct documentation with it.

I think so. In this series if keep_old_opts is false the only possible
flags are either 0 or BDRV_O_RDWR | BDRV_O_ALLOW_RDWR, depending on the
value of "read-only" option.

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-18 15:06     ` Alberto Garcia
@ 2018-06-18 16:12       ` Kevin Wolf
  2018-06-19 12:27         ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-18 16:12 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 18.06.2018 um 17:06 hat Alberto Garcia geschrieben:
> On Mon 18 Jun 2018 04:38:01 PM CEST, Kevin Wolf wrote:
> > Am 14.06.2018 um 17:49 hat Alberto Garcia geschrieben:
> >> This patch allows the user to change the backing file of an image that
> >> is being reopened. Here's what it does:
> >> 
> >>  - In bdrv_reopen_queue_child(): if the 'backing' option points to an
> >>    image different from the current backing file then it means that
> >>    the latter is going be detached so it must not be put in the reopen
> >>    queue.
> >> 
> >>  - In bdrv_reopen_prepare(): check that the value of 'backing' points
> >>    to an existing node or is null. If it points to an existing node it
> >>    also needs to make sure that replacing the backing file will not
> >>    create a cycle in the node graph (i.e. you cannot reach the parent
> >>    from the new backing file).
> >> 
> >>  - In bdrv_reopen_commit(): perform the actual node replacement by
> >>    calling bdrv_set_backing_hd(). It may happen that there are
> >>    temporary implicit nodes between the BDS that we are reopening and
> >>    the backing file that we want to replace (e.g. a commit fiter node),
> >>    so we must skip them.
> >
> > I think we shouldn't do this. blockdev-reopen is an advanced command
> > that requires knowledge of the graph at the node level. Therefore we can
> > expect the management tool to consider filter nodes when it reconfigures
> > the graph. This is important because it makes a difference whether a
> > new node is inserted above or below the filter node.
> 
> But those nodes that the management tool knows about would not be
> implicit, or would they?

Hm, you're right, they are only implicit if no node name was given. So
it's not as bad as I thought.

I still think of bs->implicit as a hack to maintain compatibility for
legacy commands rather than something to use in new commands.

> The reason why I did this is because there's several places in the QEMU
> codebase where bdrv_reopen() is called while there's a temporary
> implicit node. For example, on exit bdrv_commit() needs to call
> bdrv_set_backing_hd() to remove intermediate nodes from the chain. This
> happens while bdrv_mirror_top is still there, so if we don't skip it
> then QEMU crashes.

But isn't that bdrv_set_backing_hd() a separate operation? I would
understand that it matters if you changed the code so that it would use
bdrv_reopen() in order to change the backing file, but that's not what
it does, is it?

> >> Although x-blockdev-reopen is meant to be used like blockdev-add,
> >> there's two important things that must be taken into account:
> >> 
> >> 1) The only way to set a new backing file is by using a reference to
> >>    an existing node (previously added with e.g. blockdev-add).
> >>    If 'backing' contains a dictionary with a new set of options
> >>    ({"driver": "qcow2", "file": { ... }}) then it is interpreted that
> >>    the _existing_ backing file must be reopened with those options.
> >
> > This sounds a bit odd at first, but it makes perfect sense in fact.
> >
> > Maybe we should enforce that child nodes that were openend by reference
> > first must be reopened with a reference, and only those that were defined
> > inline (and therefore inherit options from the parent) can be reopened
> > inline?
> 
> I think that should be doable, yes.
> 
> >> 2) If the 'backing' option is omitted altogether then the existing
> >>    backing file (if any) is kept. Unlike blockdev-add this will _not_
> >>    open the default backing file specified in the image header.
> >
> > Hm... This is certainly an inconsistency. Is it unavoidable?
> 
> I don't think we want to open new nodes on reopen(), but one easy way to
> avoid this behavior is simply to return an error if the user omits the
> 'backing' option.

Hm, yes, not opening new nodes is a good point.

As long as find a good solution that can be consistently applied to all
BlockdevRef, it should be okay. So I don't necessarily object to the
special casing and just leaving them as they are by default.

> But this raises a few questions. Let's say you have an image with a
> backing file and you open it with blockdev-add omitting the 'backing'
> option. That means the default backing file is opened.
> 
>   - Do you still have to specify 'backing' on reopen? I suppose you
>     don't have to.

You would have to. I agree it's ugly (not the least because you probably
didn't assign an explicit node name, though -drive allows specifying
only the node name, but still using the filename from the image file).

>   - If you don't have to, can QEMU tell if bs->backing is the original
>     backing file or a new one? I suppose it can, by checking for
>     'backing / backing.*' in bs->options.
> 
>   - If you omit 'backing', does the backing file still get reopened? And
>     more importantly, does it keep its current options or are they
>     supposed to be reset to their default values?

If you make omitting it an error, then that's not really a question?

> >> +        /* If the 'backing' option is set and it points to a different
> >> +         * node then it means that we want to replace the current one,
> >> +         * so we shouldn't put it in the reopen queue */
> >> +        if (child->role == &child_backing && qdict_haskey(options, "backing")) {
> >
> > I think checking child->name for "backing" makes more sense when we
> > also look for the "backing" key in the options QDict. This would make
> > generalising it for other children easier (which we probably should
> > do, whether in this patch or a follow-up).
> 
> Sounds reasonable.
> 
> > Do we need some way for e.g. block jobs to forbid changing the backing
> > file of the subchain they are operating on?
> 
> Are you thinking of any particular scenario?

Not specifically, but I think e.g. the commit job might get confused if
you break its backing chain because it assumes that base is somewhere in
the backing chain of top, and also that it called block_job_add_bdrv()
on everything in the subchain it is working on.

It just feels like we'd allow to break any such assumptions if we don't
block graph changes there.

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue()
  2018-06-18 15:28     ` Alberto Garcia
@ 2018-06-18 16:15       ` Kevin Wolf
  0 siblings, 0 replies; 24+ messages in thread
From: Kevin Wolf @ 2018-06-18 16:15 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 18.06.2018 um 17:28 hat Alberto Garcia geschrieben:
> On Mon 18 Jun 2018 04:15:07 PM CEST, Kevin Wolf wrote:
> 
> >> @@ -2850,7 +2850,8 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue,
> >>                                                   int flags,
> >>                                                   const BdrvChildRole *role,
> >>                                                   QDict *parent_options,
> >> -                                                 int parent_flags)
> >> +                                                 int parent_flags,
> >> +                                                 bool keep_old_opts)
> >
> > Can we change the semantics of keep_old_opts so that flags is completely
> > ignored for keep_old_opts=false?
> >
> > Flags are one of the reasons why the behaviour of bdrv_reopen() is
> > rather complex and I'd prefer not having that complexity in a public
> > interface. To be honest, I wouldn't be sure that I could write a
> > correct documentation with it.
> 
> I think so. In this series if keep_old_opts is false the only possible
> flags are either 0 or BDRV_O_RDWR | BDRV_O_ALLOW_RDWR, depending on the
> value of "read-only" option.

I assume the default options that you have to set in
qmp_x_blockdev_reopen() are only because update_options_from_flags()
would break things otherwise. So if we get rid of the latter, maybe we
can simplify qmp_x_blockdev_reopen(), too.

Well, or maybe my assumption is just wrong, of course.

KEvin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-18 16:12       ` Kevin Wolf
@ 2018-06-19 12:27         ` Alberto Garcia
  2018-06-19 13:05           ` Kevin Wolf
  0 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-19 12:27 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

On Mon 18 Jun 2018 06:12:29 PM CEST, Kevin Wolf wrote:
>> >> This patch allows the user to change the backing file of an image
>> >> that is being reopened. Here's what it does:
>> >> 
>> >>  - In bdrv_reopen_queue_child(): if the 'backing' option points to an
>> >>    image different from the current backing file then it means that
>> >>    the latter is going be detached so it must not be put in the reopen
>> >>    queue.
>> >> 
>> >>  - In bdrv_reopen_prepare(): check that the value of 'backing' points
>> >>    to an existing node or is null. If it points to an existing node it
>> >>    also needs to make sure that replacing the backing file will not
>> >>    create a cycle in the node graph (i.e. you cannot reach the parent
>> >>    from the new backing file).
>> >> 
>> >>  - In bdrv_reopen_commit(): perform the actual node replacement by
>> >>    calling bdrv_set_backing_hd(). It may happen that there are
>> >>    temporary implicit nodes between the BDS that we are reopening and
>> >>    the backing file that we want to replace (e.g. a commit fiter node),
>> >>    so we must skip them.
>> >
>> > I think we shouldn't do this. blockdev-reopen is an advanced command
>> > that requires knowledge of the graph at the node level. Therefore we can
>> > expect the management tool to consider filter nodes when it reconfigures
>> > the graph. This is important because it makes a difference whether a
>> > new node is inserted above or below the filter node.
>> 
>> But those nodes that the management tool knows about would not be
>> implicit, or would they?
>
> Hm, you're right, they are only implicit if no node name was given. So
> it's not as bad as I thought.
>
> I still think of bs->implicit as a hack to maintain compatibility for
> legacy commands rather than something to use in new commands.

Yes, and I'm still not 100% convinced that skipping the implicit nodes
as I'm doing here is the proper solution, so maybe I'll need to come up
with something else.

>> The reason why I did this is because there's several places in the
>> QEMU codebase where bdrv_reopen() is called while there's a temporary
>> implicit node. For example, on exit bdrv_commit() needs to call
>> bdrv_set_backing_hd() to remove intermediate nodes from the
>> chain. This happens while bdrv_mirror_top is still there, so if we
>> don't skip it then QEMU crashes.
>
> But isn't that bdrv_set_backing_hd() a separate operation? I would
> understand that it matters if you changed the code so that it would
> use bdrv_reopen() in order to change the backing file, but that's not
> what it does, is it?

Wait, I think the description I gave is inaccurate:

commit_complete() calls bdrv_drop_intermediate(), and that updates the
backing image name (c->role->update_filename()). If we're doing this in
an intermediate node then it needs to be reopened in read-write mode,
while keeping the rest of the options.

But then bdrv_reopen_commit() realizes that the backing file (from
reopen_state->options) is not the current one (because there's a
bdrv_mirror_top implicit filter) and attempts to remove it. And that's
the root cause of the crash.

So my first impression is that we should not try to change backing files
if a reopen was not requested by the user (blockdev-reopen) and perhaps
we should forbid it when there are implicit nodes involved.

>> >> 2) If the 'backing' option is omitted altogether then the existing
>> >> backing file (if any) is kept. Unlike blockdev-add this will _not_
>> >> open the default backing file specified in the image header.
>> >
>> > Hm... This is certainly an inconsistency. Is it unavoidable?
>> 
>> I don't think we want to open new nodes on reopen(), but one easy way
>> to avoid this behavior is simply to return an error if the user omits
>> the 'backing' option.
>
> Hm, yes, not opening new nodes is a good point.
>
> As long as find a good solution that can be consistently applied to
> all BlockdevRef, it should be okay. So I don't necessarily object to
> the special casing and just leaving them as they are by default.
>
>> But this raises a few questions. Let's say you have an image with a
>> backing file and you open it with blockdev-add omitting the 'backing'
>> option. That means the default backing file is opened.
>> 
>>   - Do you still have to specify 'backing' on reopen? I suppose you
>>     don't have to.
>
> You would have to. I agree it's ugly (not the least because you probably
> didn't assign an explicit node name, though -drive allows specifying
> only the node name, but still using the filename from the image file).

Yes it's a bit ugly, but we wouldn't be having a special case with the
'backing' option.

>>   - If you don't have to, can QEMU tell if bs->backing is the original
>>     backing file or a new one? I suppose it can, by checking for
>>     'backing / backing.*' in bs->options.
>> 
>>   - If you omit 'backing', does the backing file still get reopened? And
>>     more importantly, does it keep its current options or are they
>>     supposed to be reset to their default values?
>
> If you make omitting it an error, then that's not really a question?

No, if you are forced to specify 'backing' then this is not a problem.

>> >> +        /* If the 'backing' option is set and it points to a different
>> >> +         * node then it means that we want to replace the current one,
>> >> +         * so we shouldn't put it in the reopen queue */
>> >> +        if (child->role == &child_backing && qdict_haskey(options, "backing")) {
>> >
>> > I think checking child->name for "backing" makes more sense when we
>> > also look for the "backing" key in the options QDict. This would make
>> > generalising it for other children easier (which we probably should
>> > do, whether in this patch or a follow-up).
>> 
>> Sounds reasonable.
>> 
>> > Do we need some way for e.g. block jobs to forbid changing the backing
>> > file of the subchain they are operating on?
>> 
>> Are you thinking of any particular scenario?
>
> Not specifically, but I think e.g. the commit job might get confused
> if you break its backing chain because it assumes that base is
> somewhere in the backing chain of top, and also that it called
> block_job_add_bdrv() on everything in the subchain it is working on.
>
> It just feels like we'd allow to break any such assumptions if we
> don't block graph changes there.

Ah, ok, I think that's related to the crash that I mentioned earlier
with block-commit. Yes, it makes sense that we forbid that. I suppose
that we can do this already with BLK_PERM_GRAPH_MOD ?

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-19 12:27         ` Alberto Garcia
@ 2018-06-19 13:05           ` Kevin Wolf
  2018-06-19 14:20             ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-19 13:05 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 19.06.2018 um 14:27 hat Alberto Garcia geschrieben:
> On Mon 18 Jun 2018 06:12:29 PM CEST, Kevin Wolf wrote:
> >> >> This patch allows the user to change the backing file of an image
> >> >> that is being reopened. Here's what it does:
> >> >> 
> >> >>  - In bdrv_reopen_queue_child(): if the 'backing' option points to an
> >> >>    image different from the current backing file then it means that
> >> >>    the latter is going be detached so it must not be put in the reopen
> >> >>    queue.
> >> >> 
> >> >>  - In bdrv_reopen_prepare(): check that the value of 'backing' points
> >> >>    to an existing node or is null. If it points to an existing node it
> >> >>    also needs to make sure that replacing the backing file will not
> >> >>    create a cycle in the node graph (i.e. you cannot reach the parent
> >> >>    from the new backing file).
> >> >> 
> >> >>  - In bdrv_reopen_commit(): perform the actual node replacement by
> >> >>    calling bdrv_set_backing_hd(). It may happen that there are
> >> >>    temporary implicit nodes between the BDS that we are reopening and
> >> >>    the backing file that we want to replace (e.g. a commit fiter node),
> >> >>    so we must skip them.
> >> >
> >> > I think we shouldn't do this. blockdev-reopen is an advanced command
> >> > that requires knowledge of the graph at the node level. Therefore we can
> >> > expect the management tool to consider filter nodes when it reconfigures
> >> > the graph. This is important because it makes a difference whether a
> >> > new node is inserted above or below the filter node.
> >> 
> >> But those nodes that the management tool knows about would not be
> >> implicit, or would they?
> >
> > Hm, you're right, they are only implicit if no node name was given. So
> > it's not as bad as I thought.
> >
> > I still think of bs->implicit as a hack to maintain compatibility for
> > legacy commands rather than something to use in new commands.
> 
> Yes, and I'm still not 100% convinced that skipping the implicit nodes
> as I'm doing here is the proper solution, so maybe I'll need to come up
> with something else.
> 
> >> The reason why I did this is because there's several places in the
> >> QEMU codebase where bdrv_reopen() is called while there's a temporary
> >> implicit node. For example, on exit bdrv_commit() needs to call
> >> bdrv_set_backing_hd() to remove intermediate nodes from the
> >> chain. This happens while bdrv_mirror_top is still there, so if we
> >> don't skip it then QEMU crashes.
> >
> > But isn't that bdrv_set_backing_hd() a separate operation? I would
> > understand that it matters if you changed the code so that it would
> > use bdrv_reopen() in order to change the backing file, but that's not
> > what it does, is it?
> 
> Wait, I think the description I gave is inaccurate:
> 
> commit_complete() calls bdrv_drop_intermediate(), and that updates the
> backing image name (c->role->update_filename()). If we're doing this in
> an intermediate node then it needs to be reopened in read-write mode,
> while keeping the rest of the options.
> 
> But then bdrv_reopen_commit() realizes that the backing file (from
> reopen_state->options) is not the current one (because there's a
> bdrv_mirror_top implicit filter) and attempts to remove it. And that's
> the root cause of the crash.

How did the other (the real?) backing file get into
reopen_state->options? I don't think bdrv_drop_intermediate() wants to
change anything except the read-only flag, so we should surely have the
node name of bdrv_mirror_top there?

> So my first impression is that we should not try to change backing files
> if a reopen was not requested by the user (blockdev-reopen) and perhaps
> we should forbid it when there are implicit nodes involved.

Changing the behaviour depending on who the caller is sounds like a hack
that relies on coincidence and may break sooner or later.

> >> >> 2) If the 'backing' option is omitted altogether then the existing
> >> >> backing file (if any) is kept. Unlike blockdev-add this will _not_
> >> >> open the default backing file specified in the image header.
> >> >
> >> > Hm... This is certainly an inconsistency. Is it unavoidable?
> >> 
> >> I don't think we want to open new nodes on reopen(), but one easy way
> >> to avoid this behavior is simply to return an error if the user omits
> >> the 'backing' option.
> >
> > Hm, yes, not opening new nodes is a good point.
> >
> > As long as find a good solution that can be consistently applied to
> > all BlockdevRef, it should be okay. So I don't necessarily object to
> > the special casing and just leaving them as they are by default.
> >
> >> But this raises a few questions. Let's say you have an image with a
> >> backing file and you open it with blockdev-add omitting the 'backing'
> >> option. That means the default backing file is opened.
> >> 
> >>   - Do you still have to specify 'backing' on reopen? I suppose you
> >>     don't have to.
> >
> > You would have to. I agree it's ugly (not the least because you probably
> > didn't assign an explicit node name, though -drive allows specifying
> > only the node name, but still using the filename from the image file).
> 
> Yes it's a bit ugly, but we wouldn't be having a special case with the
> 'backing' option.

I think files I'm meanwhile tending towards your current solution (i.e.
default is leaving the reference as it is and you must use null to get
rid of a backing file).

> >>   - If you don't have to, can QEMU tell if bs->backing is the original
> >>     backing file or a new one? I suppose it can, by checking for
> >>     'backing / backing.*' in bs->options.
> >> 
> >>   - If you omit 'backing', does the backing file still get reopened? And
> >>     more importantly, does it keep its current options or are they
> >>     supposed to be reset to their default values?
> >
> > If you make omitting it an error, then that's not really a question?
> 
> No, if you are forced to specify 'backing' then this is not a problem.
> 
> >> >> +        /* If the 'backing' option is set and it points to a different
> >> >> +         * node then it means that we want to replace the current one,
> >> >> +         * so we shouldn't put it in the reopen queue */
> >> >> +        if (child->role == &child_backing && qdict_haskey(options, "backing")) {
> >> >
> >> > I think checking child->name for "backing" makes more sense when we
> >> > also look for the "backing" key in the options QDict. This would make
> >> > generalising it for other children easier (which we probably should
> >> > do, whether in this patch or a follow-up).
> >> 
> >> Sounds reasonable.
> >> 
> >> > Do we need some way for e.g. block jobs to forbid changing the backing
> >> > file of the subchain they are operating on?
> >> 
> >> Are you thinking of any particular scenario?
> >
> > Not specifically, but I think e.g. the commit job might get confused
> > if you break its backing chain because it assumes that base is
> > somewhere in the backing chain of top, and also that it called
> > block_job_add_bdrv() on everything in the subchain it is working on.
> >
> > It just feels like we'd allow to break any such assumptions if we
> > don't block graph changes there.
> 
> Ah, ok, I think that's related to the crash that I mentioned earlier
> with block-commit. Yes, it makes sense that we forbid that. I suppose
> that we can do this already with BLK_PERM_GRAPH_MOD ?

Possibly. The thing with BLK_PERM_GRAPH_MOD is that nobody knows what
its meaning has to be so that it's actually useful, so we're not using
it in any meaningful way yet.

I suspect BLK_PERM_GRAPH_MOD is the wrong tool because what we actually
want to protect against modification is not a BDS, but a BdrvChild. But
I may be wrong there.

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-19 13:05           ` Kevin Wolf
@ 2018-06-19 14:20             ` Alberto Garcia
  2018-06-20 10:58               ` Kevin Wolf
  0 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-19 14:20 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

>> Wait, I think the description I gave is inaccurate:
>> 
>> commit_complete() calls bdrv_drop_intermediate(), and that updates the
>> backing image name (c->role->update_filename()). If we're doing this in
>> an intermediate node then it needs to be reopened in read-write mode,
>> while keeping the rest of the options.
>> 
>> But then bdrv_reopen_commit() realizes that the backing file (from
>> reopen_state->options) is not the current one (because there's a
>> bdrv_mirror_top implicit filter) and attempts to remove it. And that's
>> the root cause of the crash.
>
> How did the other (the real?) backing file get into
> reopen_state->options? I don't think bdrv_drop_intermediate() wants to
> change anything except the read-only flag, so we should surely have
> the node name of bdrv_mirror_top there?

No, it doesn't (try to) change anything else. It cannot do it:
bdrv_reopen() only takes flags, but keeps the current options. And the
current options have 'backing' set to a thing different from the
bdrv_mirror_top node.

> > So my first impression is that we should not try to change backing
> > files if a reopen was not requested by the user (blockdev-reopen)
> > and perhaps we should forbid it when there are implicit nodes
> > involved.
> Changing the behaviour depending on who the caller is sounds like a
> hack that relies on coincidence and may break sooner or later.

The main difference between the user calling blockdev-reopen and the
code doing bdrv_reopen() internally is that in the former case all
existing options are ignored (keep_old_opts = false) and in the latter
they are kept.

This latter case can have unintended consequences, and I think they're
all related to the fact that bs->explicit_options also keeps the options
of its children. So if you have

   C <- B <- A

and A contains 'backing.driver=qcow2, backing.node-name=foo, ...', and
you reopen A (keeping its options) then everything goes fine. However if
you remove B from the chain (using e.g. block-stream) then you can't
reopen A anymore because backing.* no longer matches the current backing
file.

I suppose that we would need to remove the children options from
bs->explicit_options in that case? If this all happens because of the
user calling blockdev-reopen then we don't need to worry about it becase
bs->explicit_options are ignored.

>> >> >> 2) If the 'backing' option is omitted altogether then the existing
>> >> >> backing file (if any) is kept. Unlike blockdev-add this will _not_
>> >> >> open the default backing file specified in the image header.
>> >> >
>> >> > Hm... This is certainly an inconsistency. Is it unavoidable?
>> >> 
>> >> I don't think we want to open new nodes on reopen(), but one easy way
>> >> to avoid this behavior is simply to return an error if the user omits
>> >> the 'backing' option.
>> >
>> > Hm, yes, not opening new nodes is a good point.
>> >
>> > As long as find a good solution that can be consistently applied to
>> > all BlockdevRef, it should be okay. So I don't necessarily object to
>> > the special casing and just leaving them as they are by default.
>> >
>> >> But this raises a few questions. Let's say you have an image with a
>> >> backing file and you open it with blockdev-add omitting the 'backing'
>> >> option. That means the default backing file is opened.
>> >> 
>> >>   - Do you still have to specify 'backing' on reopen? I suppose you
>> >>     don't have to.
>> >
>> > You would have to. I agree it's ugly (not the least because you probably
>> > didn't assign an explicit node name, though -drive allows specifying
>> > only the node name, but still using the filename from the image file).
>> 
>> Yes it's a bit ugly, but we wouldn't be having a special case with the
>> 'backing' option.
>
> I think files I'm meanwhile tending towards your current solution
> (i.e.  default is leaving the reference as it is and you must use null
> to get rid of a backing file).

It's not necessarily a bad option. The only one that I don't like is
opening the default backing file if 'backing' is omitted.

>> >> > Do we need some way for e.g. block jobs to forbid changing the
>> >> > backing file of the subchain they are operating on?
>> >> 
>> >> Are you thinking of any particular scenario?
>> >
>> > Not specifically, but I think e.g. the commit job might get confused
>> > if you break its backing chain because it assumes that base is
>> > somewhere in the backing chain of top, and also that it called
>> > block_job_add_bdrv() on everything in the subchain it is working on.
>> >
>> > It just feels like we'd allow to break any such assumptions if we
>> > don't block graph changes there.
>> 
>> Ah, ok, I think that's related to the crash that I mentioned earlier
>> with block-commit. Yes, it makes sense that we forbid that. I suppose
>> that we can do this already with BLK_PERM_GRAPH_MOD ?
>
> Possibly. The thing with BLK_PERM_GRAPH_MOD is that nobody knows what
> its meaning has to be so that it's actually useful, so we're not using
> it in any meaningful way yet.
>
> I suspect BLK_PERM_GRAPH_MOD is the wrong tool because what we
> actually want to protect against modification is not a BDS, but a
> BdrvChild. But I may be wrong there.

I'll take a closer look and see what I come up with.

At the moment there's 

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-19 14:20             ` Alberto Garcia
@ 2018-06-20 10:58               ` Kevin Wolf
  2018-06-20 12:35                 ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-20 10:58 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 19.06.2018 um 16:20 hat Alberto Garcia geschrieben:
> >> Wait, I think the description I gave is inaccurate:
> >> 
> >> commit_complete() calls bdrv_drop_intermediate(), and that updates the
> >> backing image name (c->role->update_filename()). If we're doing this in
> >> an intermediate node then it needs to be reopened in read-write mode,
> >> while keeping the rest of the options.
> >> 
> >> But then bdrv_reopen_commit() realizes that the backing file (from
> >> reopen_state->options) is not the current one (because there's a
> >> bdrv_mirror_top implicit filter) and attempts to remove it. And that's
> >> the root cause of the crash.
> >
> > How did the other (the real?) backing file get into
> > reopen_state->options? I don't think bdrv_drop_intermediate() wants to
> > change anything except the read-only flag, so we should surely have
> > the node name of bdrv_mirror_top there?
> 
> No, it doesn't (try to) change anything else. It cannot do it:
> bdrv_reopen() only takes flags, but keeps the current options. And the
> current options have 'backing' set to a thing different from the
> bdrv_mirror_top node.

Okay, so in theory this is expected to just work.

Actually, do we ever use bdrv_reopen() for flags other than read-only?
Maybe we should get rid of that flags nonsense and simply make it a
bdrv_reopen_set_readonly() taking a boolean.

> > > So my first impression is that we should not try to change backing
> > > files if a reopen was not requested by the user (blockdev-reopen)
> > > and perhaps we should forbid it when there are implicit nodes
> > > involved.
> > Changing the behaviour depending on who the caller is sounds like a
> > hack that relies on coincidence and may break sooner or later.
> 
> The main difference between the user calling blockdev-reopen and the
> code doing bdrv_reopen() internally is that in the former case all
> existing options are ignored (keep_old_opts = false) and in the latter
> they are kept.
> 
> This latter case can have unintended consequences, and I think they're
> all related to the fact that bs->explicit_options also keeps the options
> of its children. So if you have
> 
>    C <- B <- A
> 
> and A contains 'backing.driver=qcow2, backing.node-name=foo, ...', and
> you reopen A (keeping its options) then everything goes fine. However if
> you remove B from the chain (using e.g. block-stream) then you can't
> reopen A anymore because backing.* no longer matches the current backing
> file.
> 
> I suppose that we would need to remove the children options from
> bs->explicit_options in that case? If this all happens because of the
> user calling blockdev-reopen then we don't need to worry about it becase
> bs->explicit_options are ignored.

So the problem is that bs->explicit_options (and probably bs->options)
aren't consistent with the actual state of the graph. The fix for that
is likely not in bdrv_reopen().

I think we should already remove nested options of children from the
dicts in bdrv_open_inherit(). I'm less sure about node-name references.
Maybe instead of keeing the dicts up-to-date each time we modify the
graph, we should just get rid of those in the dicts as well, and instead
add a function that reconstructs the full dict from bs->options and the
currently attached children. This requires that the child name and the
option name match, but I think that's true. (Mostly at least - what
about quorum? But the child node handling of quorum is broken anyway.)

I'm almost sure Max has an opinion about this, too. :-)

> >> Ah, ok, I think that's related to the crash that I mentioned earlier
> >> with block-commit. Yes, it makes sense that we forbid that. I suppose
> >> that we can do this already with BLK_PERM_GRAPH_MOD ?
> >
> > Possibly. The thing with BLK_PERM_GRAPH_MOD is that nobody knows what
> > its meaning has to be so that it's actually useful, so we're not using
> > it in any meaningful way yet.
> >
> > I suspect BLK_PERM_GRAPH_MOD is the wrong tool because what we
> > actually want to protect against modification is not a BDS, but a
> > BdrvChild. But I may be wrong there.
> 
> I'll take a closer look and see what I come up with.

Okay. Maybe don't implement anything yet, but just try to come up with a
concept for discussion.

> At the moment there's 
> 
> Berto

And it's very good to have a Berto at the moment, but I think you wanted
to add something else there? ;-)

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-20 10:58               ` Kevin Wolf
@ 2018-06-20 12:35                 ` Alberto Garcia
  2018-06-21 13:06                   ` Kevin Wolf
  0 siblings, 1 reply; 24+ messages in thread
From: Alberto Garcia @ 2018-06-20 12:35 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

On Wed 20 Jun 2018 12:58:55 PM CEST, Kevin Wolf wrote:
> Am 19.06.2018 um 16:20 hat Alberto Garcia geschrieben:
>> >> Wait, I think the description I gave is inaccurate:
>> >> 
>> >> commit_complete() calls bdrv_drop_intermediate(), and that updates the
>> >> backing image name (c->role->update_filename()). If we're doing this in
>> >> an intermediate node then it needs to be reopened in read-write mode,
>> >> while keeping the rest of the options.
>> >> 
>> >> But then bdrv_reopen_commit() realizes that the backing file (from
>> >> reopen_state->options) is not the current one (because there's a
>> >> bdrv_mirror_top implicit filter) and attempts to remove it. And that's
>> >> the root cause of the crash.
>> >
>> > How did the other (the real?) backing file get into
>> > reopen_state->options? I don't think bdrv_drop_intermediate() wants to
>> > change anything except the read-only flag, so we should surely have
>> > the node name of bdrv_mirror_top there?
>> 
>> No, it doesn't (try to) change anything else. It cannot do it:
>> bdrv_reopen() only takes flags, but keeps the current options. And the
>> current options have 'backing' set to a thing different from the
>> bdrv_mirror_top node.
>
> Okay, so in theory this is expected to just work.
>
> Actually, do we ever use bdrv_reopen() for flags other than read-only?
> Maybe we should get rid of that flags nonsense and simply make it a
> bdrv_reopen_set_readonly() taking a boolean.

I think that's a good idea. There's however one case where other flags
are changed: reopen_backing_file() in block/replication.c that also
clears BDRV_O_INACTIVE. I'd need to take a closer look to that code to
see what we can do about it.

And there's of course qemu-io's "reopen" command, which does take
options but keeps the previous values.

>> > > So my first impression is that we should not try to change backing
>> > > files if a reopen was not requested by the user (blockdev-reopen)
>> > > and perhaps we should forbid it when there are implicit nodes
>> > > involved.
>> > Changing the behaviour depending on who the caller is sounds like a
>> > hack that relies on coincidence and may break sooner or later.
>> 
>> The main difference between the user calling blockdev-reopen and the
>> code doing bdrv_reopen() internally is that in the former case all
>> existing options are ignored (keep_old_opts = false) and in the latter
>> they are kept.
>> 
>> This latter case can have unintended consequences, and I think they're
>> all related to the fact that bs->explicit_options also keeps the options
>> of its children. So if you have
>> 
>>    C <- B <- A
>> 
>> and A contains 'backing.driver=qcow2, backing.node-name=foo, ...', and
>> you reopen A (keeping its options) then everything goes fine. However if
>> you remove B from the chain (using e.g. block-stream) then you can't
>> reopen A anymore because backing.* no longer matches the current backing
>> file.
>> 
>> I suppose that we would need to remove the children options from
>> bs->explicit_options in that case? If this all happens because of the
>> user calling blockdev-reopen then we don't need to worry about it becase
>> bs->explicit_options are ignored.
>
> So the problem is that bs->explicit_options (and probably bs->options)
> aren't consistent with the actual state of the graph. The fix for that
> is likely not in bdrv_reopen().

Probably not because the graph can be changed by other means (e.g
block-stream as I just said).

> I think we should already remove nested options of children from the
> dicts in bdrv_open_inherit(). I'm less sure about node-name
> references.  Maybe instead of keeing the dicts up-to-date each time we
> modify the graph, we should just get rid of those in the dicts as
> well, and instead add a function that reconstructs the full dict from
> bs->options and the currently attached children. This requires that
> the child name and the option name match, but I think that's
> true. (Mostly at least - what about quorum? But the child node
> handling of quorum is broken anyway.)

Yes, removing nested options sounds like a good idea. In what cases do
we need the full qdict, though?

>> At the moment there's 
>> 
>> Berto
>
> And it's very good to have a Berto at the moment, but I think you
> wanted to add something else there? ;-)

I think it was just a leftover from a previous paragraph :-)

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-20 12:35                 ` Alberto Garcia
@ 2018-06-21 13:06                   ` Kevin Wolf
  2018-09-12 15:09                     ` Alberto Garcia
  0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2018-06-21 13:06 UTC (permalink / raw)
  To: Alberto Garcia
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster

Am 20.06.2018 um 14:35 hat Alberto Garcia geschrieben:
> On Wed 20 Jun 2018 12:58:55 PM CEST, Kevin Wolf wrote:
> > Am 19.06.2018 um 16:20 hat Alberto Garcia geschrieben:
> >> >> Wait, I think the description I gave is inaccurate:
> >> >> 
> >> >> commit_complete() calls bdrv_drop_intermediate(), and that updates the
> >> >> backing image name (c->role->update_filename()). If we're doing this in
> >> >> an intermediate node then it needs to be reopened in read-write mode,
> >> >> while keeping the rest of the options.
> >> >> 
> >> >> But then bdrv_reopen_commit() realizes that the backing file (from
> >> >> reopen_state->options) is not the current one (because there's a
> >> >> bdrv_mirror_top implicit filter) and attempts to remove it. And that's
> >> >> the root cause of the crash.
> >> >
> >> > How did the other (the real?) backing file get into
> >> > reopen_state->options? I don't think bdrv_drop_intermediate() wants to
> >> > change anything except the read-only flag, so we should surely have
> >> > the node name of bdrv_mirror_top there?
> >> 
> >> No, it doesn't (try to) change anything else. It cannot do it:
> >> bdrv_reopen() only takes flags, but keeps the current options. And the
> >> current options have 'backing' set to a thing different from the
> >> bdrv_mirror_top node.
> >
> > Okay, so in theory this is expected to just work.
> >
> > Actually, do we ever use bdrv_reopen() for flags other than read-only?
> > Maybe we should get rid of that flags nonsense and simply make it a
> > bdrv_reopen_set_readonly() taking a boolean.
> 
> I think that's a good idea. There's however one case where other flags
> are changed: reopen_backing_file() in block/replication.c that also
> clears BDRV_O_INACTIVE. I'd need to take a closer look to that code to
> see what we can do about it.

Uh, and that works?

Clearing BDRV_O_INACTIVE with bdrv_reopen() (which means, without
calling bdrv_invalidate_cache()) is surely suspicious. Probably this
code is buggy.

Another reason for removing flags from the interface: We didn't do any
sanity checks for the flag changes.

> And there's of course qemu-io's "reopen" command, which does take
> options but keeps the previous values.

It provides -r/-w for changing readonly and -c to change the cache mode
flags. Both should be easy to convert to QDict options.

> > I think we should already remove nested options of children from the
> > dicts in bdrv_open_inherit(). I'm less sure about node-name
> > references.  Maybe instead of keeing the dicts up-to-date each time we
> > modify the graph, we should just get rid of those in the dicts as
> > well, and instead add a function that reconstructs the full dict from
> > bs->options and the currently attached children. This requires that
> > the child name and the option name match, but I think that's
> > true. (Mostly at least - what about quorum? But the child node
> > handling of quorum is broken anyway.)
> 
> Yes, removing nested options sounds like a good idea. In what cases do
> we need the full qdict, though?

Not sure, maybe we don't even need them now that we decided that the
default is leaving the reference unchanged.

There's the creation of json: filenames, maybe we need it there. We'd
also certainly need to get the full QDict if we wanted to convert the
options to a QAPI object before we pass them to the drivers.

Kevin

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen
  2018-06-21 13:06                   ` Kevin Wolf
@ 2018-09-12 15:09                     ` Alberto Garcia
  0 siblings, 0 replies; 24+ messages in thread
From: Alberto Garcia @ 2018-09-12 15:09 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, qemu-block, Max Reitz, Eric Blake, Markus Armbruster,
	Changlong Xie, Gonglei, Wang WeiWei, Wen Congyang, Zhang Chen,
	zhanghailiang

On Thu 21 Jun 2018 03:06:22 PM CEST, Kevin Wolf wrote:
>> > Actually, do we ever use bdrv_reopen() for flags other than
>> > read-only?  Maybe we should get rid of that flags nonsense and
>> > simply make it a bdrv_reopen_set_readonly() taking a boolean.
>> 
>> I think that's a good idea. There's however one case where other
>> flags are changed: reopen_backing_file() in block/replication.c that
>> also clears BDRV_O_INACTIVE. I'd need to take a closer look to that
>> code to see what we can do about it.
>
> Uh, and that works?
>
> Clearing BDRV_O_INACTIVE with bdrv_reopen() (which means, without
> calling bdrv_invalidate_cache()) is surely suspicious. Probably this
> code is buggy.
>
> Another reason for removing flags from the interface: We didn't do any
> sanity checks for the flag changes.

I'm working on removing the flags parameter from bdrv_reopen() and
friends, and I think this is the only clearly strange thing on the way.

The documentation is in https://wiki.qemu.org/Features/BlockReplication
or in docs/block-replication.txt.

As far as I can see there's the following backing chain:

  [secondary] <- [hidden] <- [active]

There's an NBD server writing on [secondary], a backup job copying data
from [secondary] to [hidden], and the VM writing to [active].

The reopen_backing_file() function in question simply puts the two
backing images in read-write mode (clearing BDRV_O_INACTIVE along the
way) so the NBD server and the backup job can write to them.

This is done by replication_start(), which can only be reached with the
QMP command "xen-set-replication". There's a comment that says "The
caller of the function MUST make sure vm stopped" but no one seems to
check that the vm is indeed stopped (?). This API was btw added half a
year after the replication driver, and before this point the only user
of replication_start() was a unit test.

Without having tested the COLO / replication code before, I don't see
any reason why it would make sense to clear the BDRV_O_INACTIVE flag on
the backing files, or why that flag would be there in the first place
(and I'm not even talking about whether that flag is supposed to be
set/cleared with a simple bdrv_reopen()). So I'm thinking to simply
remove it from there.

If anyone else has more information I'd be happy to hear it.

Berto

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2018-09-12 15:10 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-14 15:48 [Qemu-devel] [RFC PATCH 00/10] Add a 'x-blockdev-reopen' QMP command Alberto Garcia
2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 01/10] file-posix: Forbid trying to change unsupported options during reopen Alberto Garcia
2018-06-14 15:48 ` [Qemu-devel] [RFC PATCH 02/10] block: Allow changing 'discard' on reopen Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 03/10] block: Allow changing 'detect-zeroes' " Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 04/10] block: Allow changing 'force-share' " Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 05/10] block: Add 'keep_old_opts' parameter to bdrv_reopen_queue() Alberto Garcia
2018-06-18 14:15   ` Kevin Wolf
2018-06-18 15:28     ` Alberto Garcia
2018-06-18 16:15       ` Kevin Wolf
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 06/10] block: Allow changing the backing file on reopen Alberto Garcia
2018-06-18 14:38   ` Kevin Wolf
2018-06-18 15:06     ` Alberto Garcia
2018-06-18 16:12       ` Kevin Wolf
2018-06-19 12:27         ` Alberto Garcia
2018-06-19 13:05           ` Kevin Wolf
2018-06-19 14:20             ` Alberto Garcia
2018-06-20 10:58               ` Kevin Wolf
2018-06-20 12:35                 ` Alberto Garcia
2018-06-21 13:06                   ` Kevin Wolf
2018-09-12 15:09                     ` Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 07/10] block: Add 'runtime_opts' and 'mutable_opts' fields to BlockDriver Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 08/10] block: Add bdrv_reset_options_allowed() Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 09/10] block: Add a 'x-blockdev-reopen' QMP command Alberto Garcia
2018-06-14 15:49 ` [Qemu-devel] [RFC PATCH 10/10] qemu-iotests: Test the x-blockdev-reopen " Alberto Garcia

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.