diff --git a/blockdev.c b/blockdev.c
index cd5e49c..e9bc577 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -52,6 +52,8 @@ static const int if_max_devs[IF_COUNT] = {
 };
 
 typedef struct StreamState {
+    MonitorCompletion *cancel_cb;
+    void *cancel_opaque;
     int64_t offset;             /* current position in block device */
     BlockDriverState *bs;
     QEMUTimer *timer;
@@ -90,6 +92,10 @@ static void stream_free(StreamState *s)
 {
     QLIST_REMOVE(s, list);
 
+    if (s->cancel_cb) {
+        s->cancel_cb(s->cancel_opaque, NULL);
+    }
+
     qemu_del_timer(s->timer);
     qemu_free_timer(s->timer);
     qemu_free(s);
@@ -115,6 +121,8 @@ static void stream_cb(void *opaque, int nb_sectors)
     if (s->offset == bdrv_getlength(s->bs)) {
         bdrv_change_backing_file(s->bs, NULL, NULL);
         stream_complete(s, 0);
+    } else if (s->cancel_cb) {
+        stream_free(s);
     } else {
         qemu_mod_timer(s->timer, qemu_get_clock_ns(rt_clock));
     }
@@ -176,6 +184,24 @@ static StreamState *stream_start(const char *device)
     return s;
 }
 
+static int stream_stop(const char *device, MonitorCompletion *cb, void *opaque)
+{
+    StreamState *s = stream_find(device);
+
+    if (!s) {
+        qerror_report(QERR_DEVICE_NOT_ACTIVE, device);
+        return -1;
+    }
+    if (s->cancel_cb) {
+        qerror_report(QERR_DEVICE_IN_USE, device);
+        return -1;
+    }
+
+    s->cancel_cb = cb;
+    s->cancel_opaque = opaque;
+    return 0;
+}
+
 /*
  * We automatically delete the drive when a device using it gets
  * unplugged.  Questionable feature, but we can't just drop it.
@@ -783,6 +809,14 @@ int do_block_stream(Monitor *mon, const QDict *params, QObject **ret_data)
     return stream_start(device) ? 0 : -1;
 }
 
+int do_block_job_cancel(Monitor *mon, const QDict *params,
+                        MonitorCompletion cb, void *opaque)
+{
+    const char *device = qdict_get_str(params, "device");
+
+    return stream_stop(device, cb, opaque);
+}
+
 static int eject_device(Monitor *mon, BlockDriverState *bs, int force)
 {
     if (!force) {
diff --git a/blockdev.h b/blockdev.h
index f475aa8..a3cde69 100644
--- a/blockdev.h
+++ b/blockdev.h
@@ -12,6 +12,7 @@
 
 #include "block.h"
 #include "qemu-queue.h"
+#include "monitor.h"
 
 void blockdev_mark_auto_del(BlockDriverState *bs);
 void blockdev_auto_del(BlockDriverState *bs);
@@ -66,5 +67,7 @@ int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data);
 int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data);
 int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data);
 int do_block_stream(Monitor *mon, const QDict *params, QObject **ret_data);
+int do_block_job_cancel(Monitor *mon, const QDict *params,
+                        MonitorCompletion cb, void *opaque);
 
 #endif
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 9bf1025..613eb76 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -52,6 +52,21 @@ Copy data from a backing file into a block device.
 ETEXI
 
     {
+        .name       = "block_job_cancel",
+        .args_type  = "device:B",
+        .params     = "device",
+        .help       = "Stop an active block streaming operation",
+        .mhandler.cmd_async = do_block_job_cancel,
+        .flags      = MONITOR_CMD_ASYNC,
+    },
+
+STEXI
+@item block_job_cancel
+@findex block_job_cancel
+Stop an active block streaming operation.
+ETEXI
+
+    {
         .name       = "q|quit",
         .args_type  = "",
         .params     = "",
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 80402c7..5ab15a4 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1010,6 +1010,47 @@ Examples:
 EQMP
 
     {
+        .name       = "block_job_cancel",
+        .args_type  = "device:B",
+        .params     = "device",
+        .help       = "Stop an active streaming operation on a block device",
+        .mhandler.cmd_async = do_block_job_cancel,
+        .flags      = MONITOR_CMD_ASYNC,
+    },
+
+SQMP
+
+block_job_cancel
+----------------
+
+Stop an active block streaming operation.
+
+This command returns once the active block streaming operation has been
+stopped.  It is an error to call this command if no operation is in progress.
+
+The image file retains its backing file unless the streaming operation happens
+to complete just as it is being cancelled.
+
+A new block streaming operation can be started at a later time to finish
+copying all data from the backing file.
+
+Arguments:
+
+- device: device name (json-string)
+
+Errors:
+
+DeviceNotActive: streaming is not active on this device
+DeviceInUse:     cancellation already in progress
+
+Examples:
+
+-> { "execute": "block_job_cancel", "arguments": { "device": "virtio0" } }
+<- { "return":  {} }
+
+EQMP
+
+    {
         .name       = "qmp_capabilities",
         .args_type  = "",
         .params     = "",
