]> git.proxmox.com Git - pve-qemu.git/blobdiff - debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch
update submodule and patches to QEMU 8.2.2
[pve-qemu.git] / debian / patches / pve / 0017-PVE-add-savevm-async-for-background-state-snapshots.patch
index c078a1166ada85f646d8fc2ea3cad3271afceb87..bd8837dd7524fcf4ea0fd590d6935b3ffbd11b4f 100644 (file)
@@ -21,28 +21,34 @@ still opened by QEMU.
 Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
 Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
 Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[improve aborting]
+[SR: improve aborting
+     register yank before migration_incoming_state_destroy]
 Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
+[FE: further improve aborting
+     adapt to removal of QEMUFileOps
+     improve condition for entering final stage
+     adapt to QAPI and other changes for 8.2]
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
 ---
  hmp-commands-info.hx         |  13 +
- hmp-commands.hx              |  33 ++
+ hmp-commands.hx              |  17 ++
  include/migration/snapshot.h |   2 +
- include/monitor/hmp.h        |   5 +
+ include/monitor/hmp.h        |   3 +
  migration/meson.build        |   1 +
- migration/savevm-async.c     | 598 +++++++++++++++++++++++++++++++++++
- monitor/hmp-cmds.c           |  57 ++++
- qapi/migration.json          |  34 ++
- qapi/misc.json               |  32 ++
+ migration/savevm-async.c     | 534 +++++++++++++++++++++++++++++++++++
+ monitor/hmp-cmds.c           |  38 +++
+ qapi/migration.json          |  34 +++
+ qapi/misc.json               |  16 ++
  qemu-options.hx              |  12 +
- softmmu/vl.c                 |  10 +
- 11 files changed, 797 insertions(+)
+ system/vl.c                  |  10 +
+ 11 files changed, 680 insertions(+)
  create mode 100644 migration/savevm-async.c
 
 diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index ab0c7aa5ee..c72db5f666 100644
+index f5b37eb74a..10fdd822e0 100644
 --- a/hmp-commands-info.hx
 +++ b/hmp-commands-info.hx
-@@ -567,6 +567,19 @@ SRST
+@@ -525,6 +525,19 @@ SRST
      Show current migration parameters.
  ERST
  
@@ -63,13 +69,13 @@ index ab0c7aa5ee..c72db5f666 100644
          .name       = "balloon",
          .args_type  = "",
 diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 435c591a1c..0b9d526937 100644
+index 765349ed14..893c3bd240 100644
 --- a/hmp-commands.hx
 +++ b/hmp-commands.hx
-@@ -1725,3 +1725,36 @@ ERST
-         .flags      = "p",
-     },
+@@ -1875,3 +1875,20 @@ SRST
+   List event channels in the guest
+ ERST
+ #endif
 +
 +    {
 +        .name       = "savevm-start",
@@ -80,22 +86,6 @@ index 435c591a1c..0b9d526937 100644
 +    },
 +
 +    {
-+        .name       = "snapshot-drive",
-+        .args_type  = "device:s,name:s",
-+        .params     = "device name",
-+        .help       = "Create internal snapshot.",
-+        .cmd = hmp_snapshot_drive,
-+    },
-+
-+    {
-+        .name       = "delete-drive-snapshot",
-+        .args_type  = "device:s,name:s",
-+        .params     = "device name",
-+        .help       = "Delete internal snapshot.",
-+        .cmd = hmp_delete_drive_snapshot,
-+    },
-+
-+    {
 +        .name       = "savevm-end",
 +        .args_type  = "",
 +        .params     = "",
@@ -115,10 +105,10 @@ index e72083b117..c846d37806 100644
 +
  #endif
 diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
-index 605d57287a..e287e071d8 100644
+index 13f9a2dedb..7a7def7530 100644
 --- a/include/monitor/hmp.h
 +++ b/include/monitor/hmp.h
-@@ -25,6 +25,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
+@@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
  void hmp_info_uuid(Monitor *mon, const QDict *qdict);
  void hmp_info_chardev(Monitor *mon, const QDict *qdict);
  void hmp_info_mice(Monitor *mon, const QDict *qdict);
@@ -126,37 +116,38 @@ index 605d57287a..e287e071d8 100644
  void hmp_info_migrate(Monitor *mon, const QDict *qdict);
  void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
  void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
-@@ -79,6 +80,10 @@ void hmp_netdev_add(Monitor *mon, const QDict *qdict);
- void hmp_netdev_del(Monitor *mon, const QDict *qdict);
- void hmp_getfd(Monitor *mon, const QDict *qdict);
- void hmp_closefd(Monitor *mon, const QDict *qdict);
+@@ -94,6 +95,8 @@ void hmp_closefd(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_move(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_button(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_set(Monitor *mon, const QDict *qdict);
 +void hmp_savevm_start(Monitor *mon, const QDict *qdict);
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict);
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict);
 +void hmp_savevm_end(Monitor *mon, const QDict *qdict);
  void hmp_sendkey(Monitor *mon, const QDict *qdict);
- void hmp_screendump(Monitor *mon, const QDict *qdict);
+ void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict);
  void hmp_chardev_add(Monitor *mon, const QDict *qdict);
 diff --git a/migration/meson.build b/migration/meson.build
-index 3ecedce94d..c8ae813a48 100644
+index 0e689eac09..8f9d122187 100644
 --- a/migration/meson.build
 +++ b/migration/meson.build
-@@ -23,6 +23,7 @@ softmmu_ss.add(files(
-   'multifd-zlib.c',
+@@ -27,6 +27,7 @@ system_ss.add(files(
+   'options.c',
    'postcopy-ram.c',
    'savevm.c',
 +  'savevm-async.c',
    'socket.c',
    'tls.c',
- ), gnutls)
+   'threadinfo.c',
 diff --git a/migration/savevm-async.c b/migration/savevm-async.c
 new file mode 100644
-index 0000000000..593a619088
+index 0000000000..8f63c4c637
 --- /dev/null
 +++ b/migration/savevm-async.c
-@@ -0,0 +1,598 @@
+@@ -0,0 +1,534 @@
 +#include "qemu/osdep.h"
++#include "migration/channel-savevm-async.h"
 +#include "migration/migration.h"
++#include "migration/migration-stats.h"
++#include "migration/options.h"
 +#include "migration/savevm.h"
 +#include "migration/snapshot.h"
 +#include "migration/global_state.h"
@@ -176,12 +167,10 @@ index 0000000000..593a619088
 +#include "qemu/timer.h"
 +#include "qemu/main-loop.h"
 +#include "qemu/rcu.h"
++#include "qemu/yank.h"
 +
 +/* #define DEBUG_SAVEVM_STATE */
 +
-+/* used while emulated sync operation in progress */
-+#define NOT_DONE -EINPROGRESS
-+
 +#ifdef DEBUG_SAVEVM_STATE
 +#define DPRINTF(fmt, ...) \
 +    do { printf("savevm-async: " fmt, ## __VA_ARGS__); } while (0)
@@ -210,7 +199,7 @@ index 0000000000..593a619088
 +    int64_t total_time;
 +    QEMUBH *finalize_bh;
 +    Coroutine *co;
-+    QemuCoSleepState *target_close_wait;
++    QemuCoSleep target_close_wait;
 +} snap_state;
 +
 +static bool savevm_aborted(void)
@@ -229,24 +218,20 @@ index 0000000000..593a619088
 +        info->bytes = s->bs_pos;
 +        switch (s->state) {
 +        case SAVE_STATE_ERROR:
-+            info->has_status = true;
 +            info->status = g_strdup("failed");
 +            info->has_total_time = true;
 +            info->total_time = s->total_time;
 +            if (s->error) {
-+                info->has_error = true;
 +                info->error = g_strdup(error_get_pretty(s->error));
 +            }
 +            break;
 +        case SAVE_STATE_ACTIVE:
-+            info->has_status = true;
 +            info->status = g_strdup("active");
 +            info->has_total_time = true;
 +            info->total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME)
 +                - s->total_time;
 +            break;
 +        case SAVE_STATE_COMPLETED:
-+            info->has_status = true;
 +            info->status = g_strdup("completed");
 +            info->has_total_time = true;
 +            info->total_time = s->total_time;
@@ -268,6 +253,7 @@ index 0000000000..593a619088
 +
 +    if (snap_state.file) {
 +        ret = qemu_fclose(snap_state.file);
++        snap_state.file = NULL;
 +    }
 +
 +    if (snap_state.target) {
@@ -285,15 +271,13 @@ index 0000000000..593a619088
 +        blk_unref(snap_state.target);
 +        snap_state.target = NULL;
 +
-+        if (snap_state.target_close_wait) {
-+            qemu_co_sleep_wake(snap_state.target_close_wait);
-+        }
++        qemu_co_sleep_wake(&snap_state.target_close_wait);
 +    }
 +
 +    return ret;
 +}
 +
-+static void save_snapshot_error(const char *fmt, ...)
++static void G_GNUC_PRINTF(1, 2) save_snapshot_error(const char *fmt, ...)
 +{
 +    va_list ap;
 +    char *msg;
@@ -313,60 +297,6 @@ index 0000000000..593a619088
 +    snap_state.state = SAVE_STATE_ERROR;
 +}
 +
-+static int block_state_close(void *opaque, Error **errp)
-+{
-+    snap_state.file = NULL;
-+    return blk_flush(snap_state.target);
-+}
-+
-+typedef struct BlkRwCo {
-+    int64_t offset;
-+    QEMUIOVector *qiov;
-+    ssize_t ret;
-+} BlkRwCo;
-+
-+static void coroutine_fn block_state_write_entry(void *opaque) {
-+    BlkRwCo *rwco = opaque;
-+    rwco->ret = blk_co_pwritev(snap_state.target, rwco->offset, rwco->qiov->size,
-+                               rwco->qiov, 0);
-+    aio_wait_kick();
-+}
-+
-+static ssize_t block_state_writev_buffer(void *opaque, struct iovec *iov,
-+                                         int iovcnt, int64_t pos, Error **errp)
-+{
-+    QEMUIOVector qiov;
-+    BlkRwCo rwco;
-+
-+    assert(pos == snap_state.bs_pos);
-+    rwco = (BlkRwCo) {
-+        .offset = pos,
-+        .qiov = &qiov,
-+        .ret = NOT_DONE,
-+    };
-+
-+    qemu_iovec_init_external(&qiov, iov, iovcnt);
-+
-+    if (qemu_in_coroutine()) {
-+        block_state_write_entry(&rwco);
-+    } else {
-+        Coroutine *co = qemu_coroutine_create(&block_state_write_entry, &rwco);
-+        bdrv_coroutine_enter(blk_bs(snap_state.target), co);
-+        BDRV_POLL_WHILE(blk_bs(snap_state.target), rwco.ret == NOT_DONE);
-+    }
-+    if (rwco.ret < 0) {
-+        return rwco.ret;
-+    }
-+
-+    snap_state.bs_pos += qiov.size;
-+    return qiov.size;
-+}
-+
-+static const QEMUFileOps block_file_ops = {
-+    .writev_buffer =  block_state_writev_buffer,
-+    .close =          block_state_close,
-+};
-+
 +static void process_savevm_finalize(void *opaque)
 +{
 +    int ret;
@@ -401,7 +331,7 @@ index 0000000000..593a619088
 +        (void)qemu_savevm_state_complete_precopy(snap_state.file, false, false);
 +        ret = qemu_file_get_error(snap_state.file);
 +        if (ret < 0) {
-+                save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
++            save_snapshot_error("qemu_savevm_state_complete_precopy error %d", ret);
 +        }
 +    }
 +
@@ -422,8 +352,11 @@ index 0000000000..593a619088
 +    } else if (snap_state.state == SAVE_STATE_ACTIVE) {
 +        snap_state.state = SAVE_STATE_COMPLETED;
 +    } else if (aborted) {
-+        save_snapshot_error("process_savevm_cleanup: found aborted state: %d",
-+                            snap_state.state);
++        /*
++         * If there was an error, there's no need to set a new one here.
++         * If the snapshot was canceled, leave setting the state to
++         * qmp_savevm_end(), which is waked by save_snapshot_cleanup().
++         */
 +    } else {
 +        save_snapshot_error("process_savevm_cleanup: invalid state: %d",
 +                            snap_state.state);
@@ -455,18 +388,32 @@ index 0000000000..593a619088
 +    }
 +
 +    while (snap_state.state == SAVE_STATE_ACTIVE) {
-+        uint64_t pending_size, pend_precopy, pend_compatible, pend_postcopy;
-+
-+        /* pending is expected to be called without iothread lock */
++        uint64_t pending_size, pend_precopy, pend_postcopy;
++        uint64_t threshold = 400 * 1000;
++
++        /*
++         * pending_{estimate,exact} are expected to be called without iothread
++         * lock. Similar to what is done in migration.c, call the exact variant
++         * only once pend_precopy in the estimate is below the threshold.
++         */
 +        qemu_mutex_unlock_iothread();
-+        qemu_savevm_state_pending(snap_state.file, 0, &pend_precopy, &pend_compatible, &pend_postcopy);
++        qemu_savevm_state_pending_estimate(&pend_precopy, &pend_postcopy);
++        if (pend_precopy <= threshold) {
++            qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
++        }
 +        qemu_mutex_lock_iothread();
-+
-+        pending_size = pend_precopy + pend_compatible + pend_postcopy;
-+
-+        maxlen = blk_getlength(snap_state.target) - 30*1024*1024;
-+
-+        if (pending_size > 400000 && snap_state.bs_pos + pending_size < maxlen) {
++        pending_size = pend_precopy + pend_postcopy;
++
++        /*
++         * A guest reaching this cutoff is dirtying lots of RAM. It should be
++         * large enough so that the guest can't dirty this much between the
++         * check and the guest actually being stopped, but it should be small
++         * enough to avoid long downtimes for non-hibernation snapshots.
++         */
++        maxlen = blk_getlength(snap_state.target) - 100*1024*1024;
++
++        /* Note that there is no progress for pend_postcopy when iterating */
++        if (pend_precopy > threshold && snap_state.bs_pos + pending_size < maxlen) {
 +            ret = qemu_savevm_state_iterate(snap_state.file, false);
 +            if (ret < 0) {
 +                save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
@@ -475,11 +422,7 @@ index 0000000000..593a619088
 +            DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret);
 +        } else {
 +            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
-+            ret = global_state_store();
-+            if (ret) {
-+                save_snapshot_error("global_state_store error %d", ret);
-+                break;
-+            }
++            global_state_store();
 +
 +            DPRINTF("savevm iterate complete\n");
 +            break;
@@ -498,19 +441,25 @@ index 0000000000..593a619088
 +     * so move there now and after every flush.
 +     */
 +    aio_co_reschedule_self(qemu_get_aio_context());
-+    for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
++    bdrv_graph_co_rdlock();
++    bs = bdrv_first(&it);
++    bdrv_graph_co_rdunlock();
++    while (bs) {
 +        /* target has BDRV_O_NO_FLUSH, no sense calling bdrv_flush on it */
-+        if (bs == blk_bs(snap_state.target)) {
-+            continue;
-+        }
-+
-+        AioContext *bs_ctx = bdrv_get_aio_context(bs);
-+        if (bs_ctx != qemu_get_aio_context()) {
-+            DPRINTF("savevm: async flushing drive %s\n", bs->filename);
-+            aio_co_reschedule_self(bs_ctx);
-+            bdrv_flush(bs);
-+            aio_co_reschedule_self(qemu_get_aio_context());
++        if (bs != blk_bs(snap_state.target)) {
++            AioContext *bs_ctx = bdrv_get_aio_context(bs);
++            if (bs_ctx != qemu_get_aio_context()) {
++                DPRINTF("savevm: async flushing drive %s\n", bs->filename);
++                aio_co_reschedule_self(bs_ctx);
++                bdrv_graph_co_rdlock();
++                bdrv_flush(bs);
++                bdrv_graph_co_rdunlock();
++                aio_co_reschedule_self(qemu_get_aio_context());
++            }
 +        }
++        bdrv_graph_co_rdlock();
++        bs = bdrv_next(&it);
++        bdrv_graph_co_rdunlock();
 +    }
 +
 +    DPRINTF("timing: async flushing took %ld ms\n",
@@ -519,7 +468,7 @@ index 0000000000..593a619088
 +    qemu_bh_schedule(snap_state.finalize_bh);
 +}
 +
-+void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp)
++void qmp_savevm_start(const char *statefile, Error **errp)
 +{
 +    Error *local_err = NULL;
 +    MigrationState *ms = migrate_get_current();
@@ -538,7 +487,7 @@ index 0000000000..593a619088
 +        return;
 +    }
 +
-+    if (migrate_use_block()) {
++    if (migrate_block()) {
 +        error_set(errp, ERROR_CLASS_GENERIC_ERROR,
 +                  "Block migration and snapshots are incompatible");
 +        return;
@@ -549,13 +498,14 @@ index 0000000000..593a619088
 +    snap_state.bs_pos = 0;
 +    snap_state.total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
 +    snap_state.blocker = NULL;
++    snap_state.target_close_wait = (QemuCoSleep){ .to_wake = NULL };
 +
 +    if (snap_state.error) {
 +        error_free(snap_state.error);
 +        snap_state.error = NULL;
 +    }
 +
-+    if (!has_statefile) {
++    if (!statefile) {
 +        vm_stop(RUN_STATE_SAVE_VM);
 +        snap_state.state = SAVE_STATE_COMPLETED;
 +        return;
@@ -575,7 +525,9 @@ index 0000000000..593a619088
 +        goto restart;
 +    }
 +
-+    snap_state.file = qemu_fopen_ops(&snap_state, &block_file_ops);
++    QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target,
++                                                               &snap_state.bs_pos));
++    snap_state.file = qemu_file_new_output(ioc);
 +
 +    if (!snap_state.file) {
 +        error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile);
@@ -587,8 +539,10 @@ index 0000000000..593a619088
 +     * State is cleared in process_savevm_co, but has to be initialized
 +     * here (blocking main thread, from QMP) to avoid race conditions.
 +     */
-+    migrate_init(ms);
-+    memset(&ram_counters, 0, sizeof(ram_counters));
++    if (migrate_init(ms, errp)) {
++        return;
++    }
++    memset(&mig_stats, 0, sizeof(mig_stats));
 +    ms->to_dst_file = snap_state.file;
 +
 +    error_setg(&snap_state.blocker, "block device is in use by savevm");
@@ -597,10 +551,8 @@ index 0000000000..593a619088
 +    snap_state.state = SAVE_STATE_ACTIVE;
 +    snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
 +    snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
-+    qemu_mutex_unlock_iothread();
 +    qemu_savevm_state_header(snap_state.file);
 +    qemu_savevm_state_setup(snap_state.file);
-+    qemu_mutex_lock_iothread();
 +
 +    /* Async processing from here on out happens in iohandler context, so let
 +     * the target bdrv have its home there.
@@ -653,9 +605,8 @@ index 0000000000..593a619088
 +     * call exits the statefile will be closed and can be removed immediately */
 +    DPRINTF("savevm-end: waiting for cleanup\n");
 +    timeout = 30L * 1000 * 1000 * 1000;
-+    qemu_co_sleep_ns_wakeable(QEMU_CLOCK_REALTIME, timeout,
-+                              &snap_state.target_close_wait);
-+    snap_state.target_close_wait = NULL;
++    qemu_co_sleep_ns_wakeable(&snap_state.target_close_wait,
++                              QEMU_CLOCK_REALTIME, timeout);
 +    if (snap_state.target) {
 +        save_snapshot_error("timeout waiting for target file close in "
 +                            "qmp_savevm_end");
@@ -664,45 +615,13 @@ index 0000000000..593a619088
 +        return;
 +    }
 +
-+    DPRINTF("savevm-end: cleanup done\n");
-+}
-+
-+// FIXME: Deprecated
-+void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
-+{
-+    // Compatibility to older qemu-server.
-+    qmp_blockdev_snapshot_internal_sync(device, name, errp);
-+}
-+
-+// FIXME: Deprecated
-+void qmp_delete_drive_snapshot(const char *device, const char *name,
-+                               Error **errp)
-+{
-+    // Compatibility to older qemu-server.
-+    (void)qmp_blockdev_snapshot_delete_internal_sync(device, false, NULL,
-+                                                     true, name, errp);
-+}
-+
-+static ssize_t loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos,
-+                                    size_t size, Error **errp)
-+{
-+    BlockBackend *be = opaque;
-+    int64_t maxlen = blk_getlength(be);
-+    if (pos > maxlen) {
-+        return -EIO;
-+    }
-+    if ((pos + size) > maxlen) {
-+        size = maxlen - pos - 1;
++    // File closed and no other error, so ensure next snapshot can be started.
++    if (snap_state.state != SAVE_STATE_ERROR) {
++        snap_state.state = SAVE_STATE_DONE;
 +    }
-+    if (size == 0) {
-+        return 0;
-+    }
-+    return blk_pread(be, pos, buf, size);
-+}
 +
-+static const QEMUFileOps loadstate_file_ops = {
-+    .get_buffer = loadstate_get_buffer,
-+};
++    DPRINTF("savevm-end: cleanup done\n");
++}
 +
 +int load_snapshot_from_blockdev(const char *filename, Error **errp)
 +{
@@ -711,6 +630,7 @@ index 0000000000..593a619088
 +    Error *blocker = NULL;
 +
 +    QEMUFile *f;
++    size_t bs_pos = 0;
 +    int ret = -EINVAL;
 +
 +    be = blk_new_open(filename, NULL, NULL, 0, &local_err);
@@ -724,7 +644,7 @@ index 0000000000..593a619088
 +    blk_op_block_all(be, blocker);
 +
 +    /* restore the VM state */
-+    f = qemu_fopen_ops(be, &loadstate_file_ops);
++    f = qemu_file_new_input(QIO_CHANNEL(qio_channel_savevm_async_new(be, &bs_pos)));
 +    if (!f) {
 +        error_setg(errp, "Could not open VM state file");
 +        goto the_end;
@@ -737,6 +657,10 @@ index 0000000000..593a619088
 +    dirty_bitmap_mig_before_vm_start();
 +
 +    qemu_fclose(f);
++
++    /* state_destroy assumes a real migration which would have added a yank */
++    yank_register_instance(MIGRATION_YANK_INSTANCE, &error_abort);
++
 +    migration_incoming_state_destroy();
 +    if (ret < 0) {
 +        error_setg_errno(errp, -ret, "Error while loading VM state");
@@ -754,39 +678,28 @@ index 0000000000..593a619088
 +    return ret;
 +}
 diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index 1116767275..948c9d9ff8 100644
+index 871898ac46..ef4634e5c1 100644
 --- a/monitor/hmp-cmds.c
 +++ b/monitor/hmp-cmds.c
-@@ -1904,6 +1904,63 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
-     hmp_handle_error(mon, err);
- }
+@@ -22,6 +22,7 @@
+ #include "monitor/monitor-internal.h"
+ #include "qapi/error.h"
+ #include "qapi/qapi-commands-control.h"
++#include "qapi/qapi-commands-migration.h"
+ #include "qapi/qapi-commands-misc.h"
+ #include "qapi/qmp/qdict.h"
+ #include "qemu/cutils.h"
+@@ -443,3 +444,40 @@ void hmp_info_mtree(Monitor *mon, const QDict *qdict)
  
+     mtree_info(flatview, dispatch_tree, owner, disabled);
+ }
++
 +void hmp_savevm_start(Monitor *mon, const QDict *qdict)
 +{
 +    Error *errp = NULL;
 +    const char *statefile = qdict_get_try_str(qdict, "statefile");
 +
-+    qmp_savevm_start(statefile != NULL, statefile, &errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+    const char *name = qdict_get_str(qdict, "name");
-+    const char *device = qdict_get_str(qdict, "device");
-+
-+    qmp_snapshot_drive(device, name, &errp);
-+    hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict)
-+{
-+    Error *errp = NULL;
-+    const char *name = qdict_get_str(qdict, "name");
-+    const char *device = qdict_get_str(qdict, "device");
-+
-+    qmp_delete_drive_snapshot(device, name, &errp);
++    qmp_savevm_start(statefile, &errp);
 +    hmp_handle_error(mon, errp);
 +}
 +
@@ -803,7 +716,7 @@ index 1116767275..948c9d9ff8 100644
 +    SaveVMInfo *info;
 +    info = qmp_query_savevm(NULL);
 +
-+    if (info->has_status) {
++    if (info->status) {
 +        monitor_printf(mon, "savevm status: %s\n", info->status);
 +        monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n",
 +                       info->total_time);
@@ -813,21 +726,17 @@ index 1116767275..948c9d9ff8 100644
 +    if (info->has_bytes) {
 +        monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes);
 +    }
-+    if (info->has_error) {
++    if (info->error) {
 +        monitor_printf(mon, "Error: %s\n", info->error);
 +    }
 +}
-+
- void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
- {
-     IOThreadInfoList *info_list = qmp_query_iothreads(NULL);
 diff --git a/qapi/migration.json b/qapi/migration.json
-index 0b17cce46b..db80401817 100644
+index 197d3faa43..b41465fbe9 100644
 --- a/qapi/migration.json
 +++ b/qapi/migration.json
-@@ -253,6 +253,40 @@
-            '*compression': 'CompressionStats',
-            '*socket-address': ['SocketAddress'] } }
+@@ -298,6 +298,40 @@
+            '*dirty-limit-throttle-time-per-round': 'uint64',
+            '*dirty-limit-ring-full-time': 'uint64'} }
  
 +##
 +# @SaveVMInfo:
@@ -867,10 +776,10 @@ index 0b17cce46b..db80401817 100644
  # @query-migrate:
  #
 diff --git a/qapi/misc.json b/qapi/misc.json
-index 156f98203e..ad6c7713e2 100644
+index cda2effa81..94a58bb0bf 100644
 --- a/qapi/misc.json
 +++ b/qapi/misc.json
-@@ -427,6 +427,38 @@
+@@ -456,6 +456,22 @@
  ##
  { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] }
  
@@ -883,22 +792,6 @@ index 156f98203e..ad6c7713e2 100644
 +{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
 +
 +##
-+# @snapshot-drive:
-+#
-+# Create an internal drive snapshot.
-+#
-+##
-+{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @delete-drive-snapshot:
-+#
-+# Delete a drive snapshot.
-+#
-+##
-+{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
 +# @savevm-end:
 +#
 +# Resume VM after a snapshot.
@@ -910,10 +803,10 @@ index 156f98203e..ad6c7713e2 100644
  # @CommandLineParameterType:
  #
 diff --git a/qemu-options.hx b/qemu-options.hx
-index fd21002bd6..83841e6703 100644
+index b6b4ad9e67..881b0b3c43 100644
 --- a/qemu-options.hx
 +++ b/qemu-options.hx
-@@ -4015,6 +4015,18 @@ SRST
+@@ -4590,6 +4590,18 @@ SRST
      Start right away with a saved state (``loadvm`` in monitor)
  ERST
  
@@ -932,22 +825,22 @@ index fd21002bd6..83841e6703 100644
  #ifndef _WIN32
  DEF("daemonize", 0, QEMU_OPTION_daemonize, \
      "-daemonize      daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
-diff --git a/softmmu/vl.c b/softmmu/vl.c
-index aadb526138..721196281c 100644
---- a/softmmu/vl.c
-+++ b/softmmu/vl.c
-@@ -145,6 +145,7 @@ static const char *cpu_option;
- static const char *mem_path;
- static const char *incoming;
- static const char *loadvm;
+diff --git a/system/vl.c b/system/vl.c
+index d2a3b3f457..57f7ba0525 100644
+--- a/system/vl.c
++++ b/system/vl.c
+@@ -163,6 +163,7 @@ static const char *accelerators;
+ static bool have_custom_ram_size;
+ static const char *ram_memdev_id;
+ static QDict *machine_opts_dict;
 +static const char *loadstate;
  static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts);
- static ram_addr_t maxram_size;
- static uint64_t ram_slots;
-@@ -2596,6 +2597,12 @@ void qmp_x_exit_preconfig(Error **errp)
-             autostart = 0;
-             exit(1);
-         }
+ static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts);
+ static int display_remote;
+@@ -2715,6 +2716,12 @@ void qmp_x_exit_preconfig(Error **errp)
+     if (loadvm) {
+         load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
 +    } else if (loadstate) {
 +        Error *local_err = NULL;
 +        if (load_snapshot_from_blockdev(loadstate, &local_err) < 0) {
@@ -957,7 +850,7 @@ index aadb526138..721196281c 100644
      }
      if (replay_mode != REPLAY_MODE_NONE) {
          replay_vmstate_init();
-@@ -3131,6 +3138,9 @@ void qemu_init(int argc, char **argv, char **envp)
+@@ -3265,6 +3272,9 @@ void qemu_init(int argc, char **argv)
              case QEMU_OPTION_loadvm:
                  loadvm = optarg;
                  break;