diff mbox

[3/4] block/parallels: Don't update header until the first actual write

Message ID 2f0114c773e8fb917a7a54acccfd2e2adfb6e066.1509094209.git.jcody@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jeff Cody Oct. 27, 2017, 8:57 a.m. UTC
The on disk image format 'inuse' header field is updated blindly if the
image is opened RDWR.  This can cause problems if the QEMU runstate is
set to INMIGRATE, at which point the underlying file is set to INACTIVE.
This causes an assert in bdrv_co_pwritev().

Do something similar to what is done in VHDX; latch the first write, and
update the header the first time we modify the file.

Signed-off-by: Jeff Cody <jcody@redhat.com>
---
 block/parallels.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

Comments

Kevin Wolf Oct. 27, 2017, 9:09 a.m. UTC | #1
Am 27.10.2017 um 10:57 hat Jeff Cody geschrieben:
> The on disk image format 'inuse' header field is updated blindly if the
> image is opened RDWR.  This can cause problems if the QEMU runstate is
> set to INMIGRATE, at which point the underlying file is set to INACTIVE.
> This causes an assert in bdrv_co_pwritev().
> 
> Do something similar to what is done in VHDX; latch the first write, and
> update the header the first time we modify the file.
> 
> Signed-off-by: Jeff Cody <jcody@redhat.com>

For VHDX, it seems that we have to have the header update in the write
path anyway, so it might be justifiable, but I think for parallels, it's
just ugly.

The conservative approach to this would be doing the header write in
.bdrv_open() only if BDRV_O_INACTIVE is cleared, and otherwise do it
during .bdrv_invalidate_cache().

By the way, random design thought: It might make sense to change
.bdrv_open() so that it always opens inactive images and then call
.bdrv_invalidate_cache() (possibly renamed to .bdrv_activate())
unconditionally even without migration.

Kevin
Jeff Cody Oct. 27, 2017, 10:18 a.m. UTC | #2
On Fri, Oct 27, 2017 at 11:09:19AM +0200, Kevin Wolf wrote:
> Am 27.10.2017 um 10:57 hat Jeff Cody geschrieben:
> > The on disk image format 'inuse' header field is updated blindly if the
> > image is opened RDWR.  This can cause problems if the QEMU runstate is
> > set to INMIGRATE, at which point the underlying file is set to INACTIVE.
> > This causes an assert in bdrv_co_pwritev().
> > 
> > Do something similar to what is done in VHDX; latch the first write, and
> > update the header the first time we modify the file.
> > 
> > Signed-off-by: Jeff Cody <jcody@redhat.com>
> 
> For VHDX, it seems that we have to have the header update in the write
> path anyway, so it might be justifiable, but I think for parallels, it's
> just ugly.
> 

A bit ugly.  I think we could get around VHDX needing to do it as well; it
does it in the write path for two scenarios:

    * First normal write, or
    * Journal log replay, if dirty, on open (if r/w)

The log check happens early in vhdx_open().  If it does not write anything,
then we can just write the headers during the open like normal, if we are
R/W (and !BDRV_O_INACTIVE, of course).

> The conservative approach to this would be doing the header write in
> .bdrv_open() only if BDRV_O_INACTIVE is cleared, and otherwise do it
> during .bdrv_invalidate_cache().

What scenarios cause BDRV_O_INACTIVE to not be set on bs, but set on
bs->file-bs?

> By the way, random design thought: It might make sense to change
> .bdrv_open() so that it always opens inactive images and then call
> .bdrv_invalidate_cache() (possibly renamed to .bdrv_activate())
> unconditionally even without migration.
> 
> Kevin
Denis V. Lunev Oct. 27, 2017, 12:13 p.m. UTC | #3
On 10/27/2017 10:57 AM, Jeff Cody wrote:
> The on disk image format 'inuse' header field is updated blindly if the
> image is opened RDWR.  This can cause problems if the QEMU runstate is
> set to INMIGRATE, at which point the underlying file is set to INACTIVE.
> This causes an assert in bdrv_co_pwritev().
>
> Do something similar to what is done in VHDX; latch the first write, and
> update the header the first time we modify the file.
>
> Signed-off-by: Jeff Cody <jcody@redhat.com>
> ---
>  block/parallels.c | 25 ++++++++++++++++++++-----
>  1 file changed, 20 insertions(+), 5 deletions(-)
>
> diff --git a/block/parallels.c b/block/parallels.c
> index fed199eccd..c560e2fcf2 100644
> --- a/block/parallels.c
> +++ b/block/parallels.c
> @@ -100,6 +100,8 @@ typedef struct BDRVParallelsState {
>      unsigned int tracks;
>  
>      unsigned int off_multiplier;
> +
> +    bool first_write_latch;
>  } BDRVParallelsState;
>  
>  
> @@ -317,6 +319,16 @@ static coroutine_fn int parallels_co_writev(BlockDriverState *bs,
>      QEMUIOVector hd_qiov;
>      int ret = 0;
>  
> +    if (s->first_write_latch) {
> +        s->first_write_latch = false;
> +        qemu_co_mutex_lock(&s->lock);
> +        ret = parallels_update_header(bs);
> +        qemu_co_mutex_unlock(&s->lock);
> +    }
> +    if (ret < 0) {
> +        return ret;
> +    }
> +
>      qemu_iovec_init(&hd_qiov, qiov->niov);
>  
>      while (nb_sectors > 0) {
> @@ -416,6 +428,9 @@ static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
>              /* parallels_close will do the job right */
>              res->corruptions_fixed++;
>              s->header_unclean = false;
> +            /* set that a write has occurred, so that parallels_close() will
> +             * update the inuse field in the header */
> +            s->first_write_latch = false;
>          }
>      }
>  
> @@ -597,6 +612,8 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
>      Error *local_err = NULL;
>      char *buf;
>  
> +    s->first_write_latch = true;
> +
>      bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
>                                 false, errp);
>      if (!bs->file) {
> @@ -710,10 +727,6 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
>  
>      if (flags & BDRV_O_RDWR) {
>          s->header->inuse = cpu_to_le32(HEADER_INUSE_MAGIC);
> -        ret = parallels_update_header(bs);
> -        if (ret < 0) {
> -            goto fail;
> -        }
>      }
>  
>      s->bat_dirty_block = 4 * getpagesize();
> @@ -741,7 +754,9 @@ static void parallels_close(BlockDriverState *bs)
>  {
>      BDRVParallelsState *s = bs->opaque;
>  
> -    if (bs->open_flags & BDRV_O_RDWR) {
> +    /* Only need to update the header, if we ever actually wrote to the
> +     * image at all */
> +    if ((bs->open_flags & BDRV_O_RDWR) && !s->first_write_latch) {
>          s->header->inuse = 0;
>          parallels_update_header(bs);
>      }
Reviewed-by: Denis V. Lunev <den@openvz.org>
Kevin Wolf Oct. 29, 2017, 8:59 a.m. UTC | #4
Am 27.10.2017 um 12:18 hat Jeff Cody geschrieben:
> On Fri, Oct 27, 2017 at 11:09:19AM +0200, Kevin Wolf wrote:
> > Am 27.10.2017 um 10:57 hat Jeff Cody geschrieben:
> > > The on disk image format 'inuse' header field is updated blindly if the
> > > image is opened RDWR.  This can cause problems if the QEMU runstate is
> > > set to INMIGRATE, at which point the underlying file is set to INACTIVE.
> > > This causes an assert in bdrv_co_pwritev().
> > > 
> > > Do something similar to what is done in VHDX; latch the first write, and
> > > update the header the first time we modify the file.
> > > 
> > > Signed-off-by: Jeff Cody <jcody@redhat.com>
> > 
> > For VHDX, it seems that we have to have the header update in the write
> > path anyway, so it might be justifiable, but I think for parallels, it's
> > just ugly.
> > 
> 
> A bit ugly.  I think we could get around VHDX needing to do it as well; it
> does it in the write path for two scenarios:
> 
>     * First normal write, or
>     * Journal log replay, if dirty, on open (if r/w)
> 
> The log check happens early in vhdx_open().  If it does not write anything,
> then we can just write the headers during the open like normal, if we are
> R/W (and !BDRV_O_INACTIVE, of course).

Technically, you can do the same as I suggest for parallels, the
difference is just that by the letter, the VHDX spec seems to require
that you update the header on the first write (going by the comment in
our VHDX driver...).

> > The conservative approach to this would be doing the header write in
> > .bdrv_open() only if BDRV_O_INACTIVE is cleared, and otherwise do it
> > during .bdrv_invalidate_cache().
> 
> What scenarios cause BDRV_O_INACTIVE to not be set on bs, but set on
> bs->file-bs?

Bugs? :-)

Kevin

> 
> > By the way, random design thought: It might make sense to change
> > .bdrv_open() so that it always opens inactive images and then call
> > .bdrv_invalidate_cache() (possibly renamed to .bdrv_activate())
> > unconditionally even without migration.
> > 
> > Kevin
diff mbox

Patch

diff --git a/block/parallels.c b/block/parallels.c
index fed199eccd..c560e2fcf2 100644
--- a/block/parallels.c
+++ b/block/parallels.c
@@ -100,6 +100,8 @@  typedef struct BDRVParallelsState {
     unsigned int tracks;
 
     unsigned int off_multiplier;
+
+    bool first_write_latch;
 } BDRVParallelsState;
 
 
@@ -317,6 +319,16 @@  static coroutine_fn int parallels_co_writev(BlockDriverState *bs,
     QEMUIOVector hd_qiov;
     int ret = 0;
 
+    if (s->first_write_latch) {
+        s->first_write_latch = false;
+        qemu_co_mutex_lock(&s->lock);
+        ret = parallels_update_header(bs);
+        qemu_co_mutex_unlock(&s->lock);
+    }
+    if (ret < 0) {
+        return ret;
+    }
+
     qemu_iovec_init(&hd_qiov, qiov->niov);
 
     while (nb_sectors > 0) {
@@ -416,6 +428,9 @@  static int parallels_check(BlockDriverState *bs, BdrvCheckResult *res,
             /* parallels_close will do the job right */
             res->corruptions_fixed++;
             s->header_unclean = false;
+            /* set that a write has occurred, so that parallels_close() will
+             * update the inuse field in the header */
+            s->first_write_latch = false;
         }
     }
 
@@ -597,6 +612,8 @@  static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
     Error *local_err = NULL;
     char *buf;
 
+    s->first_write_latch = true;
+
     bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
                                false, errp);
     if (!bs->file) {
@@ -710,10 +727,6 @@  static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
 
     if (flags & BDRV_O_RDWR) {
         s->header->inuse = cpu_to_le32(HEADER_INUSE_MAGIC);
-        ret = parallels_update_header(bs);
-        if (ret < 0) {
-            goto fail;
-        }
     }
 
     s->bat_dirty_block = 4 * getpagesize();
@@ -741,7 +754,9 @@  static void parallels_close(BlockDriverState *bs)
 {
     BDRVParallelsState *s = bs->opaque;
 
-    if (bs->open_flags & BDRV_O_RDWR) {
+    /* Only need to update the header, if we ever actually wrote to the
+     * image at all */
+    if ((bs->open_flags & BDRV_O_RDWR) && !s->first_write_latch) {
         s->header->inuse = 0;
         parallels_update_header(bs);
     }