diff mbox

[v1,03/22] migration: ensure qemu_fflush() always writes full data amount

Message ID 1452599056-27357-4-git-send-email-berrange@redhat.com
State New
Headers show

Commit Message

Daniel P. Berrangé Jan. 12, 2016, 11:43 a.m. UTC
The QEMUFile writev_buffer / put_buffer functions are expected
to write out the full set of requested data, blocking until
complete. The qemu_fflush() caller does not expect to deal with
partial writes. Clarify the function comments and add a sanity
check to the code to catch mistaken implementations.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/migration/qemu-file.h |  6 ++++--
 migration/qemu-file.c         | 16 ++++++++++++----
 2 files changed, 16 insertions(+), 6 deletions(-)

Comments

Dr. David Alan Gilbert Jan. 28, 2016, 5:53 p.m. UTC | #1
* Daniel P. Berrange (berrange@redhat.com) wrote:
> The QEMUFile writev_buffer / put_buffer functions are expected
> to write out the full set of requested data, blocking until
> complete. The qemu_fflush() caller does not expect to deal with
> partial writes. Clarify the function comments and add a sanity
> check to the code to catch mistaken implementations.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/migration/qemu-file.h |  6 ++++--
>  migration/qemu-file.c         | 16 ++++++++++++----
>  2 files changed, 16 insertions(+), 6 deletions(-)
> 
> diff --git a/include/migration/qemu-file.h b/include/migration/qemu-file.h
> index b5d08d2..5debe8c 100644
> --- a/include/migration/qemu-file.h
> +++ b/include/migration/qemu-file.h
> @@ -29,7 +29,8 @@
>  
>  /* This function writes a chunk of data to a file at the given position.
>   * The pos argument can be ignored if the file is only being used for
> - * streaming.  The handler should try to write all of the data it can.
> + * streaming.  The handler must write all of the data or return a negative
> + * errno value.
>   */
>  typedef ssize_t (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf,
>                                          int64_t pos, size_t size);
> @@ -55,7 +56,8 @@ typedef int (QEMUFileCloseFunc)(void *opaque);
>  typedef int (QEMUFileGetFD)(void *opaque);
>  
>  /*
> - * This function writes an iovec to file.
> + * This function writes an iovec to file. The handler must write all
> + * of the data or return a negative errno value.
>   */
>  typedef ssize_t (QEMUFileWritevBufferFunc)(void *opaque, struct iovec *iov,
>                                             int iovcnt, int64_t pos);
> diff --git a/migration/qemu-file.c b/migration/qemu-file.c
> index 0bbd257..f89e64e 100644
> --- a/migration/qemu-file.c
> +++ b/migration/qemu-file.c
> @@ -107,11 +107,13 @@ bool qemu_file_is_writable(QEMUFile *f)
>   * Flushes QEMUFile buffer
>   *
>   * If there is writev_buffer QEMUFileOps it uses it otherwise uses
> - * put_buffer ops.
> + * put_buffer ops. This will flush all pending data. If data was
> + * only partially flushed, it will set an error state.
>   */
>  void qemu_fflush(QEMUFile *f)
>  {
>      ssize_t ret = 0;
> +    ssize_t expect = 0;
>  
>      if (!qemu_file_is_writable(f)) {
>          return;
> @@ -119,21 +121,27 @@ void qemu_fflush(QEMUFile *f)
>  
>      if (f->ops->writev_buffer) {
>          if (f->iovcnt > 0) {
> +            expect = iov_size(f->iov, f->iovcnt);
>              ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt, f->pos);
>          }
>      } else {
>          if (f->buf_index > 0) {
> +            expect = f->buf_index;
>              ret = f->ops->put_buffer(f->opaque, f->buf, f->pos, f->buf_index);
>          }
>      }
> +
>      if (ret >= 0) {
>          f->pos += ret;
>      }
> -    f->buf_index = 0;
> -    f->iovcnt = 0;
> -    if (ret < 0) {
> +    /* We expect the QEMUFile write impl to send the full
> +     * data set we requested, so sanity check that.
> +     */
> +    if (ret < 0 || ret != expect) {
>          qemu_file_set_error(f, ret);

You could simplify that to     if (ret != expect)     couldn't you?

In the case you're trying to guard against, the value past
to qemu_file_set_error is potentially truncated; which in the worst
case could make it appear as success; although I doubt that
can happen in our uses.

Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>

Dave

>      }
> +    f->buf_index = 0;
> +    f->iovcnt = 0;
>  }
>  
>  void ram_control_before_iterate(QEMUFile *f, uint64_t flags)
> -- 
> 2.5.0
> 
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
Daniel P. Berrangé Feb. 3, 2016, 1:31 p.m. UTC | #2
On Thu, Jan 28, 2016 at 05:53:46PM +0000, Dr. David Alan Gilbert wrote:
> * Daniel P. Berrange (berrange@redhat.com) wrote:
> > The QEMUFile writev_buffer / put_buffer functions are expected
> > to write out the full set of requested data, blocking until
> > complete. The qemu_fflush() caller does not expect to deal with
> > partial writes. Clarify the function comments and add a sanity
> > check to the code to catch mistaken implementations.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  include/migration/qemu-file.h |  6 ++++--
> >  migration/qemu-file.c         | 16 ++++++++++++----
> >  2 files changed, 16 insertions(+), 6 deletions(-)
> > 
> > diff --git a/include/migration/qemu-file.h b/include/migration/qemu-file.h
> > index b5d08d2..5debe8c 100644
> > --- a/include/migration/qemu-file.h
> > +++ b/include/migration/qemu-file.h
> > @@ -29,7 +29,8 @@
> >  
> >  /* This function writes a chunk of data to a file at the given position.
> >   * The pos argument can be ignored if the file is only being used for
> > - * streaming.  The handler should try to write all of the data it can.
> > + * streaming.  The handler must write all of the data or return a negative
> > + * errno value.
> >   */
> >  typedef ssize_t (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf,
> >                                          int64_t pos, size_t size);
> > @@ -55,7 +56,8 @@ typedef int (QEMUFileCloseFunc)(void *opaque);
> >  typedef int (QEMUFileGetFD)(void *opaque);
> >  
> >  /*
> > - * This function writes an iovec to file.
> > + * This function writes an iovec to file. The handler must write all
> > + * of the data or return a negative errno value.
> >   */
> >  typedef ssize_t (QEMUFileWritevBufferFunc)(void *opaque, struct iovec *iov,
> >                                             int iovcnt, int64_t pos);
> > diff --git a/migration/qemu-file.c b/migration/qemu-file.c
> > index 0bbd257..f89e64e 100644
> > --- a/migration/qemu-file.c
> > +++ b/migration/qemu-file.c
> > @@ -107,11 +107,13 @@ bool qemu_file_is_writable(QEMUFile *f)
> >   * Flushes QEMUFile buffer
> >   *
> >   * If there is writev_buffer QEMUFileOps it uses it otherwise uses
> > - * put_buffer ops.
> > + * put_buffer ops. This will flush all pending data. If data was
> > + * only partially flushed, it will set an error state.
> >   */
> >  void qemu_fflush(QEMUFile *f)
> >  {
> >      ssize_t ret = 0;
> > +    ssize_t expect = 0;
> >  
> >      if (!qemu_file_is_writable(f)) {
> >          return;
> > @@ -119,21 +121,27 @@ void qemu_fflush(QEMUFile *f)
> >  
> >      if (f->ops->writev_buffer) {
> >          if (f->iovcnt > 0) {
> > +            expect = iov_size(f->iov, f->iovcnt);
> >              ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt, f->pos);
> >          }
> >      } else {
> >          if (f->buf_index > 0) {
> > +            expect = f->buf_index;
> >              ret = f->ops->put_buffer(f->opaque, f->buf, f->pos, f->buf_index);
> >          }
> >      }
> > +
> >      if (ret >= 0) {
> >          f->pos += ret;
> >      }
> > -    f->buf_index = 0;
> > -    f->iovcnt = 0;
> > -    if (ret < 0) {
> > +    /* We expect the QEMUFile write impl to send the full
> > +     * data set we requested, so sanity check that.
> > +     */
> > +    if (ret < 0 || ret != expect) {
> >          qemu_file_set_error(f, ret);
> 
> You could simplify that to     if (ret != expect)     couldn't you?
> 
> In the case you're trying to guard against, the value past
> to qemu_file_set_error is potentially truncated; which in the worst
> case could make it appear as success; although I doubt that
> can happen in our uses.

qemu_file_set_error expects an errni, so in fact my code was
buggy if the ret != expect part got evaluated, so I'll change
this to

     if (ret != expect) {
        qemu_file_set_error(f, ret < 0 ? ret : -EIO);
     }


> 
> Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>

Regards,
Daniel
diff mbox

Patch

diff --git a/include/migration/qemu-file.h b/include/migration/qemu-file.h
index b5d08d2..5debe8c 100644
--- a/include/migration/qemu-file.h
+++ b/include/migration/qemu-file.h
@@ -29,7 +29,8 @@ 
 
 /* This function writes a chunk of data to a file at the given position.
  * The pos argument can be ignored if the file is only being used for
- * streaming.  The handler should try to write all of the data it can.
+ * streaming.  The handler must write all of the data or return a negative
+ * errno value.
  */
 typedef ssize_t (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf,
                                         int64_t pos, size_t size);
@@ -55,7 +56,8 @@  typedef int (QEMUFileCloseFunc)(void *opaque);
 typedef int (QEMUFileGetFD)(void *opaque);
 
 /*
- * This function writes an iovec to file.
+ * This function writes an iovec to file. The handler must write all
+ * of the data or return a negative errno value.
  */
 typedef ssize_t (QEMUFileWritevBufferFunc)(void *opaque, struct iovec *iov,
                                            int iovcnt, int64_t pos);
diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index 0bbd257..f89e64e 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -107,11 +107,13 @@  bool qemu_file_is_writable(QEMUFile *f)
  * Flushes QEMUFile buffer
  *
  * If there is writev_buffer QEMUFileOps it uses it otherwise uses
- * put_buffer ops.
+ * put_buffer ops. This will flush all pending data. If data was
+ * only partially flushed, it will set an error state.
  */
 void qemu_fflush(QEMUFile *f)
 {
     ssize_t ret = 0;
+    ssize_t expect = 0;
 
     if (!qemu_file_is_writable(f)) {
         return;
@@ -119,21 +121,27 @@  void qemu_fflush(QEMUFile *f)
 
     if (f->ops->writev_buffer) {
         if (f->iovcnt > 0) {
+            expect = iov_size(f->iov, f->iovcnt);
             ret = f->ops->writev_buffer(f->opaque, f->iov, f->iovcnt, f->pos);
         }
     } else {
         if (f->buf_index > 0) {
+            expect = f->buf_index;
             ret = f->ops->put_buffer(f->opaque, f->buf, f->pos, f->buf_index);
         }
     }
+
     if (ret >= 0) {
         f->pos += ret;
     }
-    f->buf_index = 0;
-    f->iovcnt = 0;
-    if (ret < 0) {
+    /* We expect the QEMUFile write impl to send the full
+     * data set we requested, so sanity check that.
+     */
+    if (ret < 0 || ret != expect) {
         qemu_file_set_error(f, ret);
     }
+    f->buf_index = 0;
+    f->iovcnt = 0;
 }
 
 void ram_control_before_iterate(QEMUFile *f, uint64_t flags)