Makefile.objs | 1 +
hmp-commands-info.hx | 13 ++
hmp-commands.hx | 32 +++
- hmp.c | 57 +++++
+ hmp.c | 57 ++++++
hmp.h | 5 +
include/migration/snapshot.h | 1 +
- qapi/migration.json | 34 +++
+ qapi/migration.json | 34 ++++
qapi/misc.json | 32 +++
qemu-options.hx | 13 ++
- savevm-async.c | 528 +++++++++++++++++++++++++++++++++++++++++++
+ savevm-async.c | 460 +++++++++++++++++++++++++++++++++++++++++++
vl.c | 10 +
- 11 files changed, 726 insertions(+)
+ 11 files changed, 658 insertions(+)
create mode 100644 savevm-async.c
diff --git a/Makefile.objs b/Makefile.objs
"-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
diff --git a/savevm-async.c b/savevm-async.c
new file mode 100644
-index 0000000000..0bf830c906
+index 0000000000..73b7fe75ed
--- /dev/null
+++ b/savevm-async.c
-@@ -0,0 +1,528 @@
+@@ -0,0 +1,460 @@
+#include "qemu/osdep.h"
+#include "migration/migration.h"
+#include "migration/savevm.h"
+#include "migration/global_state.h"
+#include "migration/ram.h"
+#include "migration/qemu-file.h"
-+#include "qapi/qmp/qerror.h"
+#include "sysemu/sysemu.h"
-+#include "qmp-commands.h"
+#include "block/block.h"
+#include "sysemu/block-backend.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qapi-commands-migration.h"
+#include "qapi/qapi-commands-misc.h"
++#include "qapi/qapi-commands-block.h"
+#include "qemu/cutils.h"
+
+/* #define DEBUG_SAVEVM_STATE */
+
-+#define NOT_DONE 0x7fffffff /* used while emulated sync operation in progress */
++/* used while emulated sync operation in progress */
++#define NOT_DONE -EINPROGRESS
+
+#ifdef DEBUG_SAVEVM_STATE
+#define DPRINTF(fmt, ...) \
+ int saved_vm_running;
+ QEMUFile *file;
+ int64_t total_time;
++ QEMUBH *cleanup_bh;
++ QemuThread thread;
+} snap_state;
+
+SaveVMInfo *qmp_query_savevm(Error **errp)
+ g_free (msg);
+
+ snap_state.state = SAVE_STATE_ERROR;
-+
-+ save_snapshot_cleanup();
-+}
-+
-+static void save_snapshot_completed(void)
-+{
-+ DPRINTF("save_snapshot_completed\n");
-+
-+ if (save_snapshot_cleanup() < 0) {
-+ snap_state.state = SAVE_STATE_ERROR;
-+ } else {
-+ snap_state.state = SAVE_STATE_COMPLETED;
-+ }
+}
+
+static int block_state_close(void *opaque)
+ 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);
++}
++
+static ssize_t block_state_writev_buffer(void *opaque, struct iovec *iov,
+ int iovcnt, int64_t pos)
+{
-+ int ret;
+ 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);
-+ ret = blk_co_pwritev(snap_state.target, pos, qiov.size, &qiov, 0);
-+ if (ret < 0) {
-+ return ret;
++
++ 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 int store_and_stop(void) {
-+ if (global_state_store()) {
-+ save_snapshot_error("Error saving global state");
-+ return 1;
++static const QEMUFileOps block_file_ops = {
++ .writev_buffer = block_state_writev_buffer,
++ .close = block_state_close,
++};
++
++static void process_savevm_cleanup(void *opaque)
++{
++ int ret;
++ qemu_bh_delete(snap_state.cleanup_bh);
++ snap_state.cleanup_bh = NULL;
++ qemu_mutex_unlock_iothread();
++ qemu_thread_join(&snap_state.thread);
++ qemu_mutex_lock_iothread();
++ ret = save_snapshot_cleanup();
++ if (ret < 0) {
++ save_snapshot_error("save_snapshot_cleanup error %d", ret);
++ } else if (snap_state.state == SAVE_STATE_ACTIVE) {
++ snap_state.state = SAVE_STATE_COMPLETED;
++ } else {
++ save_snapshot_error("process_savevm_cleanup: invalid state: %d",
++ snap_state.state);
+ }
-+ if (runstate_is_running()) {
-+ vm_stop(RUN_STATE_SAVE_VM);
++ if (snap_state.saved_vm_running) {
++ vm_start();
++ snap_state.saved_vm_running = false;
+ }
-+ return 0;
+}
+
-+static void process_savevm_co(void *opaque)
++static void *process_savevm_thread(void *opaque)
+{
+ int ret;
+ int64_t maxlen;
+
-+ snap_state.state = SAVE_STATE_ACTIVE;
++ rcu_register_thread();
+
-+ qemu_mutex_unlock_iothread();
+ qemu_savevm_state_header(snap_state.file);
+ qemu_savevm_state_setup(snap_state.file);
+ ret = qemu_file_get_error(snap_state.file);
-+ qemu_mutex_lock_iothread();
+
+ if (ret < 0) {
+ save_snapshot_error("qemu_savevm_state_setup failed");
-+ return;
++ rcu_unregister_thread();
++ return NULL;
+ }
+
+ while (snap_state.state == SAVE_STATE_ACTIVE) {
-+ uint64_t pending_size, pend_post, pend_nonpost;
++ uint64_t pending_size, pend_precopy, pend_compatible, pend_postcopy;
+
-+ qemu_savevm_state_pending(snap_state.file, 0, &pend_nonpost, &pend_post);
-+ pending_size = pend_post + pend_nonpost;
++ qemu_savevm_state_pending(snap_state.file, 0, &pend_precopy, &pend_compatible, &pend_postcopy);
++ pending_size = pend_precopy + pend_compatible + pend_postcopy;
+
-+ if (pending_size) {
-+ ret = qemu_savevm_state_iterate(snap_state.file, false);
-+ if (ret < 0) {
-+ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
-+ break;
-+ }
-+ DPRINTF("savevm inerate pending size %lu ret %d\n", pending_size, ret);
++ maxlen = blk_getlength(snap_state.target) - 30*1024*1024;
++
++ if (pending_size > 400000 && snap_state.bs_pos + pending_size < maxlen) {
++ qemu_mutex_lock_iothread();
++ ret = qemu_savevm_state_iterate(snap_state.file, false);
++ if (ret < 0) {
++ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
++ break;
++ }
++ qemu_mutex_unlock_iothread();
++ DPRINTF("savevm inerate pending size %lu ret %d\n", pending_size, ret);
+ } else {
-+ DPRINTF("done iterating\n");
-+ if (store_and_stop())
++ qemu_mutex_lock_iothread();
++ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
++ ret = global_state_store();
++ if (ret) {
++ save_snapshot_error("global_state_store error %d", ret);
+ break;
++ }
++ ret = vm_stop_force_state(RUN_STATE_FINISH_MIGRATE);
++ if (ret < 0) {
++ save_snapshot_error("vm_stop_force_state error %d", ret);
++ break;
++ }
+ DPRINTF("savevm inerate finished\n");
+ /* upstream made the return value here inconsistent
+ * (-1 instead of 'ret' in one case and 0 after flush which can
+ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
+ break;
+ }
++ qemu_savevm_state_cleanup();
+ DPRINTF("save complete\n");
-+ save_snapshot_completed();
+ break;
+ }
-+
-+ /* stop the VM if we get to the end of available space,
-+ * or if pending_size is just a few MB
-+ */
-+ maxlen = blk_getlength(snap_state.target) - 30*1024*1024;
-+ if ((pending_size < 100000) ||
-+ ((snap_state.bs_pos + pending_size) >= maxlen)) {
-+ if (store_and_stop())
-+ break;
-+ }
+ }
+
-+ if(snap_state.state == SAVE_STATE_CANCELLED) {
-+ save_snapshot_completed();
-+ Error *errp = NULL;
-+ qmp_savevm_end(&errp);
-+ }
++ qemu_bh_schedule(snap_state.cleanup_bh);
++ qemu_mutex_unlock_iothread();
+
++ rcu_unregister_thread();
++ return NULL;
+}
+
-+static const QEMUFileOps block_file_ops = {
-+ .writev_buffer = block_state_writev_buffer,
-+ .close = block_state_close,
-+};
-+
-+
+void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp)
+{
+ Error *local_err = NULL;
+ error_setg(&snap_state.blocker, "block device is in use by savevm");
+ blk_op_block_all(snap_state.target, snap_state.blocker);
+
-+ Coroutine *co = qemu_coroutine_create(process_savevm_co, NULL);
-+ qemu_coroutine_enter(co);
++ snap_state.state = SAVE_STATE_ACTIVE;
++ snap_state.cleanup_bh = qemu_bh_new(process_savevm_cleanup, &snap_state);
++ qemu_thread_create(&snap_state.thread, "savevm-async", process_savevm_thread,
++ NULL, QEMU_THREAD_JOINABLE);
+
+ return;
+
+ snap_state.state = SAVE_STATE_DONE;
+}
+
++// FIXME: Deprecated
+void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
+{
-+ BlockBackend *blk;
-+ BlockDriverState *bs;
-+ QEMUSnapshotInfo sn1, *sn = &sn1;
-+ int ret;
-+#ifdef _WIN32
-+ struct _timeb tb;
-+#else
-+ struct timeval tv;
-+#endif
-+
-+ if (snap_state.state != SAVE_STATE_COMPLETED) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "VM snapshot not ready/started\n");
-+ return;
-+ }
-+
-+ blk = blk_by_name(device);
-+ if (!blk) {
-+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-+ "Device '%s' not found", device);
-+ return;
-+ }
-+
-+ bs = blk_bs(blk);
-+ if (!bdrv_is_inserted(bs)) {
-+ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
-+ return;
-+ }
-+
-+ if (bdrv_is_read_only(bs)) {
-+ error_setg(errp, "Node '%s' is read only", device);
-+ return;
-+ }
-+
-+ if (!bdrv_can_snapshot(bs)) {
-+ error_setg(errp, QERR_UNSUPPORTED);
-+ return;
-+ }
-+
-+ if (bdrv_snapshot_find(bs, sn, name) >= 0) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "snapshot '%s' already exists", name);
-+ return;
-+ }
-+
-+ sn = &sn1;
-+ memset(sn, 0, sizeof(*sn));
-+
-+#ifdef _WIN32
-+ _ftime(&tb);
-+ sn->date_sec = tb.time;
-+ sn->date_nsec = tb.millitm * 1000000;
-+#else
-+ gettimeofday(&tv, NULL);
-+ sn->date_sec = tv.tv_sec;
-+ sn->date_nsec = tv.tv_usec * 1000;
-+#endif
-+ sn->vm_clock_nsec = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-+
-+ pstrcpy(sn->name, sizeof(sn->name), name);
-+
-+ sn->vm_state_size = 0; /* do not save state */
-+
-+ ret = bdrv_snapshot_create(bs, sn);
-+ if (ret < 0) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "Error while creating snapshot on '%s'\n", device);
-+ return;
-+ }
++ // 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)
+{
-+ BlockBackend *blk;
-+ BlockDriverState *bs;
-+ QEMUSnapshotInfo sn1, *sn = &sn1;
-+ Error *local_err = NULL;
-+
-+ int ret;
-+
-+ blk = blk_by_name(device);
-+ if (!blk) {
-+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-+ "Device '%s' not found", device);
-+ return;
-+ }
-+
-+ bs = blk_bs(blk);
-+ if (bdrv_is_read_only(bs)) {
-+ error_setg(errp, "Node '%s' is read only", device);
-+ return;
-+ }
-+
-+ if (!bdrv_can_snapshot(bs)) {
-+ error_setg(errp, QERR_UNSUPPORTED);
-+ return;
-+ }
-+
-+ if (bdrv_snapshot_find(bs, sn, name) < 0) {
-+ /* return success if snapshot does not exists */
-+ return;
-+ }
-+
-+ ret = bdrv_snapshot_delete(bs, NULL, name, &local_err);
-+ if (ret < 0) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "Error while deleting snapshot on '%s'\n", device);
-+ return;
-+ }
++ // 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,
+ return ret;
+}
diff --git a/vl.c b/vl.c
-index c750b7c18e..b2e3e23724 100644
+index 9c3a41bfe2..63107d82a3 100644
--- a/vl.c
+++ b/vl.c
@@ -2927,6 +2927,7 @@ int main(int argc, char **argv, char **envp)
MachineClass *machine_class;
const char *cpu_model;
const char *vga_model = NULL;
-@@ -3528,6 +3529,9 @@ int main(int argc, char **argv, char **envp)
+@@ -3529,6 +3530,9 @@ int main(int argc, char **argv, char **envp)
case QEMU_OPTION_loadvm:
loadvm = optarg;
break;
case QEMU_OPTION_full_screen:
dpy.has_full_screen = true;
dpy.full_screen = true;
-@@ -4623,6 +4627,12 @@ int main(int argc, char **argv, char **envp)
+@@ -4624,6 +4628,12 @@ int main(int argc, char **argv, char **envp)
error_report_err(local_err);
autostart = 0;
}