diff mbox

[v4,06/15] qmp: add block_stream command

Message ID 1325858501-25741-7-git-send-email-stefanha@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Hajnoczi Jan. 6, 2012, 2:01 p.m. UTC
Add the block_stream command, which starts copy backing file contents
into the image file.  Also add the BLOCK_JOB_COMPLETED QMP event which
is emitted when image streaming completes.  Later patches add control
over the background copy speed, cancelation, and querying running
streaming operations.

Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
---
 QMP/qmp-events.txt |   29 ++++++++++++++++++++++
 blockdev.c         |   67 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 hmp-commands.hx    |   13 ++++++++++
 hmp.c              |   11 ++++++++
 hmp.h              |    1 +
 monitor.c          |    3 ++
 monitor.h          |    1 +
 qapi-schema.json   |   32 ++++++++++++++++++++++++
 qerror.c           |    4 +++
 qerror.h           |    3 ++
 qmp-commands.hx    |    6 ++++
 trace-events       |    4 +++
 12 files changed, 174 insertions(+), 0 deletions(-)

Comments

Luiz Capitulino Jan. 11, 2012, 5:23 p.m. UTC | #1
On Fri,  6 Jan 2012 14:01:32 +0000
Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> wrote:

> Add the block_stream command, which starts copy backing file contents
> into the image file.  Also add the BLOCK_JOB_COMPLETED QMP event which
> is emitted when image streaming completes.  Later patches add control
> over the background copy speed, cancelation, and querying running
> streaming operations.
> 
> Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
> ---
>  QMP/qmp-events.txt |   29 ++++++++++++++++++++++
>  blockdev.c         |   67 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  hmp-commands.hx    |   13 ++++++++++
>  hmp.c              |   11 ++++++++
>  hmp.h              |    1 +
>  monitor.c          |    3 ++
>  monitor.h          |    1 +
>  qapi-schema.json   |   32 ++++++++++++++++++++++++
>  qerror.c           |    4 +++
>  qerror.h           |    3 ++
>  qmp-commands.hx    |    6 ++++
>  trace-events       |    4 +++
>  12 files changed, 174 insertions(+), 0 deletions(-)
> 
> diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt
> index af586ec..a80e604 100644
> --- a/QMP/qmp-events.txt
> +++ b/QMP/qmp-events.txt
> @@ -264,3 +264,32 @@ Example:
>  
>  Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
>  followed respectively by the RESET, SHUTDOWN, or STOP events.
> +
> +
> +BLOCK_JOB_COMPLETED
> +-------------------
> +
> +Emitted when a block job has completed.
> +
> +Data:
> +
> +- "type":     Job type ("stream" for image streaming, json-string)
> +- "device":   Device name (json-string)
> +- "len":      Maximum progress value (json-int)
> +- "offset":   Current progress value (json-int)
> +              On success this is equal to len.
> +              On failure this is less than len.
> +- "speed":    Rate limit, bytes per second (json-int)
> +- "error":    Error message (json-string)

"error" is optional, so it should be "(json-string, optional)"

> +              Only present on failure.  This field contains a human-readable
> +              error message.  There are no semantics other than that streaming
> +              has failed and clients should not try to interpret the error
> +              string.
> +
> +Example:
> +
> +{ "event": "BLOCK_JOB_COMPLETED",
> +     "data": { "type": "stream", "device": "virtio-disk0",
> +               "len": 10737418240, "offset": 10737418240,
> +               "speed": 0 },
> +     "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
> diff --git a/blockdev.c b/blockdev.c
> index 6d78b36..ba973b0 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -13,9 +13,11 @@
>  #include "qerror.h"
>  #include "qemu-option.h"
>  #include "qemu-config.h"
> +#include "qemu-objects.h"
>  #include "sysemu.h"
>  #include "block_int.h"
>  #include "qmp-commands.h"
> +#include "trace.h"
>  
>  static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
>  
> @@ -880,3 +882,68 @@ void qmp_block_resize(const char *device, int64_t size, Error **errp)
>          return;
>      }
>  }
> +
> +static QObject *qobject_from_block_job(BlockJob *job)
> +{
> +    return qobject_from_jsonf("{ 'type': %s,"
> +                              "'device': %s,"
> +                              "'len': %" PRId64 ","
> +                              "'offset': %" PRId64 ","
> +                              "'speed': %" PRId64 " }",
> +                              job->job_type->job_type,
> +                              bdrv_get_device_name(job->bs),
> +                              job->len,
> +                              job->offset,
> +                              job->speed);
> +}
> +
> +static void block_stream_cb(void *opaque, int ret)
> +{
> +    BlockDriverState *bs = opaque;
> +    QObject *obj;
> +
> +    trace_block_stream_cb(bs, bs->job, ret);
> +
> +    assert(bs->job);
> +    obj = qobject_from_block_job(bs->job);
> +    if (ret < 0) {
> +        QDict *dict = qobject_to_qdict(obj);
> +        qdict_put(dict, "error", qstring_from_str(strerror(-ret)));
> +    }
> +
> +    monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj);
> +    qobject_decref(obj);
> +}
> +
> +void qmp_block_stream(const char *device, bool has_base,
> +                      const char *base, Error **errp)
> +{
> +    BlockDriverState *bs;
> +    int ret;
> +
> +    bs = bdrv_find(device);
> +    if (!bs) {
> +        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
> +        return;
> +    }
> +
> +    /* Base device not supported */
> +    if (base) {
> +        error_set(errp, QERR_NOT_SUPPORTED);
> +        return;
> +    }
> +
> +    ret = stream_start(bs, NULL, block_stream_cb, bs);
> +    if (ret < 0) {
> +        switch (ret) {
> +        case -EBUSY:
> +            error_set(errp, QERR_DEVICE_IN_USE, device);
> +            return;
> +        default:
> +            error_set(errp, QERR_NOT_SUPPORTED);
> +            return;
> +        }
> +    }
> +
> +    trace_qmp_block_stream(bs, bs->job);
> +}
> diff --git a/hmp-commands.hx b/hmp-commands.hx
> index 14838b7..8d9dbd6 100644
> --- a/hmp-commands.hx
> +++ b/hmp-commands.hx
> @@ -69,6 +69,19 @@ but should be used with extreme caution.  Note that this command only
>  resizes image files, it can not resize block devices like LVM volumes.
>  ETEXI
>  
> +    {
> +        .name       = "block_stream",
> +        .args_type  = "device:B,base:s?",
> +        .params     = "device [base]",
> +        .help       = "copy data from a backing file into a block device",
> +        .mhandler.cmd = hmp_block_stream,
> +    },
> +
> +STEXI
> +@item block_stream
> +@findex block_stream
> +Copy data from a backing file into a block device.
> +ETEXI
>  
>      {
>          .name       = "eject",
> diff --git a/hmp.c b/hmp.c
> index e7659d5..b6e5913 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -679,3 +679,14 @@ void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict)
>      int64_t value = qdict_get_int(qdict, "value");
>      qmp_migrate_set_speed(value, NULL);
>  }
> +
> +void hmp_block_stream(Monitor *mon, const QDict *qdict)
> +{
> +    Error *error = NULL;
> +    const char *device = qdict_get_str(qdict, "device");
> +    const char *base = qdict_get_try_str(qdict, "base");
> +
> +    qmp_block_stream(device, base != NULL, base, &error);
> +
> +    hmp_handle_error(mon, &error);
> +}
> diff --git a/hmp.h b/hmp.h
> index 093242d..b55c295 100644
> --- a/hmp.h
> +++ b/hmp.h
> @@ -49,5 +49,6 @@ void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
>  void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
>  void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
>  void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
> +void hmp_block_stream(Monitor *mon, const QDict *qdict);
>  
>  #endif
> diff --git a/monitor.c b/monitor.c
> index 7334401..bb42580 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -479,6 +479,9 @@ void monitor_protocol_event(MonitorEvent event, QObject *data)
>          case QEVENT_SPICE_DISCONNECTED:
>              event_name = "SPICE_DISCONNECTED";
>              break;
> +        case QEVENT_BLOCK_JOB_COMPLETED:
> +            event_name = "BLOCK_JOB_COMPLETED";
> +            break;
>          default:
>              abort();
>              break;
> diff --git a/monitor.h b/monitor.h
> index cfa2f67..7324236 100644
> --- a/monitor.h
> +++ b/monitor.h
> @@ -35,6 +35,7 @@ typedef enum MonitorEvent {
>      QEVENT_SPICE_CONNECTED,
>      QEVENT_SPICE_INITIALIZED,
>      QEVENT_SPICE_DISCONNECTED,
> +    QEVENT_BLOCK_JOB_COMPLETED,
>      QEVENT_MAX,
>  } MonitorEvent;
>  
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 44cf764..2b1cc8c 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1275,3 +1275,35 @@
>  { 'command': 'qom-set',
>    'data': { 'path': 'str', 'property': 'str', 'value': 'visitor' },
>    'gen': 'no' }
> +
> +##
> +# @block_stream:
> +#
> +# Copy data from a backing file into a block device.
> +#
> +# The block streaming operation is performed in the background until the entire
> +# backing file has been copied.  This command returns immediately once streaming
> +# has started.  The status of ongoing block streaming operations can be checked
> +# with query-block-jobs.  The operation can be stopped before it has completed
> +# using the block_job_cancel command.
> +#
> +# If a base file is specified then sectors are not copied from that base file and
> +# its backing chain.  When streaming completes the image file will have the base
> +# file as its backing file.  This can be used to stream a subset of the backing
> +# file chain instead of flattening the entire image.
> +#
> +# On successful completion the image file is updated to drop the backing file
> +# and the BLOCK_JOB_COMPLETED event is emitted.
> +#
> +# @device: the device name
> +#
> +# @base:   the common backing file name

@base is optional, so it should be documented like this:

 @base:   #optional the common backing file name

> +#
> +# Returns: Nothing on success
> +#          If streaming is already active on this device, DeviceInUse
> +#          If @device is does not exist, DeviceNotFound
> +#          If image streaming is not supported by this device, NotSupported
> +#
> +# Since: 1.1
> +##
> +{ 'command': 'block_stream', 'data': { 'device': 'str', '*base': 'str' } }
> diff --git a/qerror.c b/qerror.c
> index 9a75d06..feb3d35 100644
> --- a/qerror.c
> +++ b/qerror.c
> @@ -182,6 +182,10 @@ static const QErrorStringTable qerror_table[] = {
>          .desc      = "No '%(bus)' bus found for device '%(device)'",
>      },
>      {
> +        .error_fmt = QERR_NOT_SUPPORTED,
> +        .desc      = "Not supported",
> +    },
> +    {
>          .error_fmt = QERR_OPEN_FILE_FAILED,
>          .desc      = "Could not open '%(filename)'",
>      },
> diff --git a/qerror.h b/qerror.h
> index efda232..095ba9d 100644
> --- a/qerror.h
> +++ b/qerror.h
> @@ -153,6 +153,9 @@ QError *qobject_to_qerror(const QObject *obj);
>  #define QERR_NO_BUS_FOR_DEVICE \
>      "{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }"
>  
> +#define QERR_NOT_SUPPORTED \
> +    "{ 'class': 'NotSupported', 'data': {} }"
> +
>  #define QERR_OPEN_FILE_FAILED \
>      "{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }"
>  
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index 7e3f4b9..b9ebb76 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -655,6 +655,12 @@ Example:
>  EQMP
>  
>      {
> +        .name       = "block_stream",
> +        .args_type  = "device:B,base:s?",
> +        .mhandler.cmd_new = qmp_marshal_input_block_stream,
> +    },
> +
> +    {
>          .name       = "blockdev-snapshot-sync",
>          .args_type  = "device:B,snapshot-file:s,format:s?",
>          .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
> diff --git a/trace-events b/trace-events
> index c5368fa..6ff0d43 100644
> --- a/trace-events
> +++ b/trace-events
> @@ -74,6 +74,10 @@ bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t clus
>  stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
>  stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base %p s %p co %p opaque %p"
>  
> +# blockdev.c
> +block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
> +qmp_block_stream(void *bs, void *job) "bs %p job %p"
> +
>  # hw/virtio-blk.c
>  virtio_blk_req_complete(void *req, int status) "req %p status %d"
>  virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"
Stefan Hajnoczi Jan. 12, 2012, 9:25 a.m. UTC | #2
On Wed, Jan 11, 2012 at 5:23 PM, Luiz Capitulino <lcapitulino@redhat.com> wrote:
> On Fri,  6 Jan 2012 14:01:32 +0000
> Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> wrote:
>> +- "error":    Error message (json-string)
>
> "error" is optional, so it should be "(json-string, optional)"
>

Ok.

>> +# @base:   the common backing file name
>
> @base is optional, so it should be documented like this:
>
>  @base:   #optional the common backing file name

Ok.
diff mbox

Patch

diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt
index af586ec..a80e604 100644
--- a/QMP/qmp-events.txt
+++ b/QMP/qmp-events.txt
@@ -264,3 +264,32 @@  Example:
 
 Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
 followed respectively by the RESET, SHUTDOWN, or STOP events.
+
+
+BLOCK_JOB_COMPLETED
+-------------------
+
+Emitted when a block job has completed.
+
+Data:
+
+- "type":     Job type ("stream" for image streaming, json-string)
+- "device":   Device name (json-string)
+- "len":      Maximum progress value (json-int)
+- "offset":   Current progress value (json-int)
+              On success this is equal to len.
+              On failure this is less than len.
+- "speed":    Rate limit, bytes per second (json-int)
+- "error":    Error message (json-string)
+              Only present on failure.  This field contains a human-readable
+              error message.  There are no semantics other than that streaming
+              has failed and clients should not try to interpret the error
+              string.
+
+Example:
+
+{ "event": "BLOCK_JOB_COMPLETED",
+     "data": { "type": "stream", "device": "virtio-disk0",
+               "len": 10737418240, "offset": 10737418240,
+               "speed": 0 },
+     "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
diff --git a/blockdev.c b/blockdev.c
index 6d78b36..ba973b0 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -13,9 +13,11 @@ 
 #include "qerror.h"
 #include "qemu-option.h"
 #include "qemu-config.h"
+#include "qemu-objects.h"
 #include "sysemu.h"
 #include "block_int.h"
 #include "qmp-commands.h"
+#include "trace.h"
 
 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
 
@@ -880,3 +882,68 @@  void qmp_block_resize(const char *device, int64_t size, Error **errp)
         return;
     }
 }
+
+static QObject *qobject_from_block_job(BlockJob *job)
+{
+    return qobject_from_jsonf("{ 'type': %s,"
+                              "'device': %s,"
+                              "'len': %" PRId64 ","
+                              "'offset': %" PRId64 ","
+                              "'speed': %" PRId64 " }",
+                              job->job_type->job_type,
+                              bdrv_get_device_name(job->bs),
+                              job->len,
+                              job->offset,
+                              job->speed);
+}
+
+static void block_stream_cb(void *opaque, int ret)
+{
+    BlockDriverState *bs = opaque;
+    QObject *obj;
+
+    trace_block_stream_cb(bs, bs->job, ret);
+
+    assert(bs->job);
+    obj = qobject_from_block_job(bs->job);
+    if (ret < 0) {
+        QDict *dict = qobject_to_qdict(obj);
+        qdict_put(dict, "error", qstring_from_str(strerror(-ret)));
+    }
+
+    monitor_protocol_event(QEVENT_BLOCK_JOB_COMPLETED, obj);
+    qobject_decref(obj);
+}
+
+void qmp_block_stream(const char *device, bool has_base,
+                      const char *base, Error **errp)
+{
+    BlockDriverState *bs;
+    int ret;
+
+    bs = bdrv_find(device);
+    if (!bs) {
+        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+        return;
+    }
+
+    /* Base device not supported */
+    if (base) {
+        error_set(errp, QERR_NOT_SUPPORTED);
+        return;
+    }
+
+    ret = stream_start(bs, NULL, block_stream_cb, bs);
+    if (ret < 0) {
+        switch (ret) {
+        case -EBUSY:
+            error_set(errp, QERR_DEVICE_IN_USE, device);
+            return;
+        default:
+            error_set(errp, QERR_NOT_SUPPORTED);
+            return;
+        }
+    }
+
+    trace_qmp_block_stream(bs, bs->job);
+}
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 14838b7..8d9dbd6 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -69,6 +69,19 @@  but should be used with extreme caution.  Note that this command only
 resizes image files, it can not resize block devices like LVM volumes.
 ETEXI
 
+    {
+        .name       = "block_stream",
+        .args_type  = "device:B,base:s?",
+        .params     = "device [base]",
+        .help       = "copy data from a backing file into a block device",
+        .mhandler.cmd = hmp_block_stream,
+    },
+
+STEXI
+@item block_stream
+@findex block_stream
+Copy data from a backing file into a block device.
+ETEXI
 
     {
         .name       = "eject",
diff --git a/hmp.c b/hmp.c
index e7659d5..b6e5913 100644
--- a/hmp.c
+++ b/hmp.c
@@ -679,3 +679,14 @@  void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict)
     int64_t value = qdict_get_int(qdict, "value");
     qmp_migrate_set_speed(value, NULL);
 }
+
+void hmp_block_stream(Monitor *mon, const QDict *qdict)
+{
+    Error *error = NULL;
+    const char *device = qdict_get_str(qdict, "device");
+    const char *base = qdict_get_try_str(qdict, "base");
+
+    qmp_block_stream(device, base != NULL, base, &error);
+
+    hmp_handle_error(mon, &error);
+}
diff --git a/hmp.h b/hmp.h
index 093242d..b55c295 100644
--- a/hmp.h
+++ b/hmp.h
@@ -49,5 +49,6 @@  void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
 void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
+void hmp_block_stream(Monitor *mon, const QDict *qdict);
 
 #endif
diff --git a/monitor.c b/monitor.c
index 7334401..bb42580 100644
--- a/monitor.c
+++ b/monitor.c
@@ -479,6 +479,9 @@  void monitor_protocol_event(MonitorEvent event, QObject *data)
         case QEVENT_SPICE_DISCONNECTED:
             event_name = "SPICE_DISCONNECTED";
             break;
+        case QEVENT_BLOCK_JOB_COMPLETED:
+            event_name = "BLOCK_JOB_COMPLETED";
+            break;
         default:
             abort();
             break;
diff --git a/monitor.h b/monitor.h
index cfa2f67..7324236 100644
--- a/monitor.h
+++ b/monitor.h
@@ -35,6 +35,7 @@  typedef enum MonitorEvent {
     QEVENT_SPICE_CONNECTED,
     QEVENT_SPICE_INITIALIZED,
     QEVENT_SPICE_DISCONNECTED,
+    QEVENT_BLOCK_JOB_COMPLETED,
     QEVENT_MAX,
 } MonitorEvent;
 
diff --git a/qapi-schema.json b/qapi-schema.json
index 44cf764..2b1cc8c 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1275,3 +1275,35 @@ 
 { 'command': 'qom-set',
   'data': { 'path': 'str', 'property': 'str', 'value': 'visitor' },
   'gen': 'no' }
+
+##
+# @block_stream:
+#
+# Copy data from a backing file into a block device.
+#
+# The block streaming operation is performed in the background until the entire
+# backing file has been copied.  This command returns immediately once streaming
+# has started.  The status of ongoing block streaming operations can be checked
+# with query-block-jobs.  The operation can be stopped before it has completed
+# using the block_job_cancel command.
+#
+# If a base file is specified then sectors are not copied from that base file and
+# its backing chain.  When streaming completes the image file will have the base
+# file as its backing file.  This can be used to stream a subset of the backing
+# file chain instead of flattening the entire image.
+#
+# On successful completion the image file is updated to drop the backing file
+# and the BLOCK_JOB_COMPLETED event is emitted.
+#
+# @device: the device name
+#
+# @base:   the common backing file name
+#
+# Returns: Nothing on success
+#          If streaming is already active on this device, DeviceInUse
+#          If @device is does not exist, DeviceNotFound
+#          If image streaming is not supported by this device, NotSupported
+#
+# Since: 1.1
+##
+{ 'command': 'block_stream', 'data': { 'device': 'str', '*base': 'str' } }
diff --git a/qerror.c b/qerror.c
index 9a75d06..feb3d35 100644
--- a/qerror.c
+++ b/qerror.c
@@ -182,6 +182,10 @@  static const QErrorStringTable qerror_table[] = {
         .desc      = "No '%(bus)' bus found for device '%(device)'",
     },
     {
+        .error_fmt = QERR_NOT_SUPPORTED,
+        .desc      = "Not supported",
+    },
+    {
         .error_fmt = QERR_OPEN_FILE_FAILED,
         .desc      = "Could not open '%(filename)'",
     },
diff --git a/qerror.h b/qerror.h
index efda232..095ba9d 100644
--- a/qerror.h
+++ b/qerror.h
@@ -153,6 +153,9 @@  QError *qobject_to_qerror(const QObject *obj);
 #define QERR_NO_BUS_FOR_DEVICE \
     "{ 'class': 'NoBusForDevice', 'data': { 'device': %s, 'bus': %s } }"
 
+#define QERR_NOT_SUPPORTED \
+    "{ 'class': 'NotSupported', 'data': {} }"
+
 #define QERR_OPEN_FILE_FAILED \
     "{ 'class': 'OpenFileFailed', 'data': { 'filename': %s } }"
 
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 7e3f4b9..b9ebb76 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -655,6 +655,12 @@  Example:
 EQMP
 
     {
+        .name       = "block_stream",
+        .args_type  = "device:B,base:s?",
+        .mhandler.cmd_new = qmp_marshal_input_block_stream,
+    },
+
+    {
         .name       = "blockdev-snapshot-sync",
         .args_type  = "device:B,snapshot-file:s,format:s?",
         .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
diff --git a/trace-events b/trace-events
index c5368fa..6ff0d43 100644
--- a/trace-events
+++ b/trace-events
@@ -74,6 +74,10 @@  bdrv_co_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t clus
 stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
 stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base %p s %p co %p opaque %p"
 
+# blockdev.c
+block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
+qmp_block_stream(void *bs, void *job) "bs %p job %p"
+
 # hw/virtio-blk.c
 virtio_blk_req_complete(void *req, int status) "req %p status %d"
 virtio_blk_rw_complete(void *req, int ret) "req %p ret %d"