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
.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",
+ },
+
+ {
-+ .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 = "",
+
#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);
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"
+#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)
+ int64_t total_time;
+ QEMUBH *finalize_bh;
+ Coroutine *co;
-+ QemuCoSleepState *target_close_wait;
++ QemuCoSleep target_close_wait;
+} snap_state;
+
+static bool savevm_aborted(void)
+ 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;
+
+ if (snap_state.file) {
+ ret = qemu_fclose(snap_state.file);
++ snap_state.file = NULL;
+ }
+
+ if (snap_state.target) {
+ 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;
+ 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;
+ (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);
+ }
+ }
+
+ } 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);
+ }
+
+ 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);
+ 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;
+ * 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",
+ 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();
+ return;
+ }
+
-+ if (migrate_use_block()) {
++ if (migrate_block()) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "Block migration and snapshots are incompatible");
+ return;
+ 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;
+ 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);
+ * 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");
+ 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.
+ * 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");
+ 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)
+{
+ Error *blocker = NULL;
+
+ QEMUFile *f;
++ size_t bs_pos = 0;
+ int ret = -EINVAL;
+
+ be = blk_new_open(filename, NULL, NULL, 0, &local_err);
+ 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;
+ 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");
+ 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);
+}
+
+ 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);
+ 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:
# @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'] }
+{ '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.
# @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
#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) {
}
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;