From: Dietmar Maurer Date: Fri, 21 Sep 2012 11:44:09 +0000 (+0200) Subject: patch to save vmstate in background (not used for now) X-Git-Url: https://git.proxmox.com/?p=pve-qemu-kvm.git;a=commitdiff_plain;h=cd983153a607d56796820ed8a338607e0cf422cf patch to save vmstate in background (not used for now) --- diff --git a/debian/patches/internal-snapshot-async.patch b/debian/patches/internal-snapshot-async.patch new file mode 100644 index 0000000..6587ed8 --- /dev/null +++ b/debian/patches/internal-snapshot-async.patch @@ -0,0 +1,753 @@ +Index: new/qapi-schema.json +=================================================================== +--- new.orig/qapi-schema.json 2012-09-21 11:10:23.000000000 +0200 ++++ new/qapi-schema.json 2012-09-21 12:58:47.000000000 +0200 +@@ -358,6 +358,40 @@ + '*total-time': 'int'} } + + ## ++# @SaveVMInfo ++# ++# Information about current migration process. ++# ++# @status: #optional string describing the current savevm status. ++# This can be 'active', 'completed', 'failed'. ++# If this field is not returned, no savevm process ++# has been initiated ++# ++# @error: #optional string containing error message is status is failed. ++# ++# @total-time: #optional total amount of milliseconds since savevm started. ++# If savevm has ended, it returns the total save time ++# ++# @bytes: #optional total amount of data transfered ++# ++# Since: 1.3 ++## ++{ 'type': 'SaveVMInfo', ++ 'data': {'*status': 'str', '*error': 'str', ++ '*total-time': 'int', '*bytes': 'int'} } ++ ++## ++# @query-savevm ++# ++# Returns information about current savevm process. ++# ++# Returns: @SaveVMInfo ++# ++# Since: 1.3 ++## ++{ 'command': 'query-savevm', 'returns': 'SaveVMInfo' } ++ ++## + # @query-migrate + # + # Returns information about current migration process. +@@ -2493,3 +2527,12 @@ + # Since: 1.2.0 + ## + { 'command': 'query-target', 'returns': 'TargetInfo' } ++ ++ ++{ 'command': 'snapshot-start' 'data': { '*statefile': 'str' } } ++ ++{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } } ++ ++{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } } ++ ++{ 'command': 'snapshot-end' } +Index: new/qmp-commands.hx +=================================================================== +--- new.orig/qmp-commands.hx 2012-09-21 11:10:23.000000000 +0200 ++++ new/qmp-commands.hx 2012-09-21 11:11:22.000000000 +0200 +@@ -2514,3 +2514,27 @@ + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_target, + }, ++ ++ { ++ .name = "snapshot-start", ++ .args_type = "statefile:s?", ++ .mhandler.cmd_new = qmp_marshal_input_snapshot_start, ++ }, ++ ++ { ++ .name = "snapshot-drive", ++ .args_type = "device:s,name:s", ++ .mhandler.cmd_new = qmp_marshal_input_snapshot_drive, ++ }, ++ ++ { ++ .name = "delete-drive-snapshot", ++ .args_type = "device:s,name:s", ++ .mhandler.cmd_new = qmp_marshal_input_delete_drive_snapshot, ++ }, ++ ++ { ++ .name = "snapshot-end", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_snapshot_end, ++ }, +Index: new/hmp.c +=================================================================== +--- new.orig/hmp.c 2012-09-21 11:10:23.000000000 +0200 ++++ new/hmp.c 2012-09-21 12:59:44.000000000 +0200 +@@ -1102,3 +1102,60 @@ + qmp_closefd(fdname, &errp); + hmp_handle_error(mon, &errp); + } ++ ++void hmp_snapshot_start(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ const char *statefile = qdict_get_try_str(qdict, "statefile"); ++ ++ qmp_snapshot_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); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_snapshot_end(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ ++ qmp_snapshot_end(&errp); ++ hmp_handle_error(mon, &errp); ++} ++ ++void hmp_info_savevm(Monitor *mon) ++{ ++ SaveVMInfo *info; ++ info = qmp_query_savevm(NULL); ++ ++ if (info->has_status) { ++ monitor_printf(mon, "savevm status: %s\n", info->status); ++ monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n", ++ info->total_time); ++ } else { ++ monitor_printf(mon, "savevm status: not running\n"); ++ } ++ if (info->has_bytes) { ++ monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes); ++ } ++ if (info->has_error) { ++ monitor_printf(mon, "Error: %s\n", info->error); ++ } ++} +Index: new/hmp.h +=================================================================== +--- new.orig/hmp.h 2012-09-21 11:10:23.000000000 +0200 ++++ new/hmp.h 2012-09-21 12:35:32.000000000 +0200 +@@ -25,6 +25,7 @@ + void hmp_info_uuid(Monitor *mon); + void hmp_info_chardev(Monitor *mon); + void hmp_info_mice(Monitor *mon); ++void hmp_info_savevm(Monitor *mon); + void hmp_info_migrate(Monitor *mon); + void hmp_info_migrate_capabilities(Monitor *mon); + void hmp_info_migrate_cache_size(Monitor *mon); +@@ -71,5 +72,9 @@ + 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); ++void hmp_snapshot_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_snapshot_end(Monitor *mon, const QDict *qdict); + + #endif +Index: new/hmp-commands.hx +=================================================================== +--- new.orig/hmp-commands.hx 2012-09-21 11:10:23.000000000 +0200 ++++ new/hmp-commands.hx 2012-09-21 12:30:35.000000000 +0200 +@@ -1468,6 +1468,8 @@ + show current migration capabilities + @item info migrate_cache_size + show current migration XBZRLE cache size ++@item info savevm ++show savevm status + @item info balloon + show balloon information + @item info qtree +@@ -1494,3 +1496,35 @@ + STEXI + @end table + ETEXI ++ ++ { ++ .name = "snapshot-start", ++ .args_type = "statefile:s?", ++ .params = "[statefile]", ++ .help = "Prepare for snapshot and halt VM. Save VM state to statefile.", ++ .mhandler.cmd = hmp_snapshot_start, ++ }, ++ ++ { ++ .name = "snapshot-drive", ++ .args_type = "device:s,name:s", ++ .params = "device name", ++ .help = "Create internal snapshot.", ++ .mhandler.cmd = hmp_snapshot_drive, ++ }, ++ ++ { ++ .name = "delete-drive-snapshot", ++ .args_type = "device:s,name:s", ++ .params = "device name", ++ .help = "Delete internal snapshot.", ++ .mhandler.cmd = hmp_delete_drive_snapshot, ++ }, ++ ++ { ++ .name = "snapshot-end", ++ .args_type = "", ++ .params = "", ++ .help = "Resume VM after snaphot.", ++ .mhandler.cmd = hmp_snapshot_end, ++ }, +Index: new/savevm-async.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ new/savevm-async.c 2012-09-21 13:42:14.000000000 +0200 +@@ -0,0 +1,441 @@ ++#include "qemu-common.h" ++#include "qerror.h" ++#include "sysemu.h" ++#include "qmp-commands.h" ++#include "blockdev.h" ++#include "qemu/qom-qobject.h" ++#include "buffered_file.h" ++#include "migration.h" ++ ++//#define DEBUG_SAVEVM_STATE ++ ++#ifdef DEBUG_SAVEVM_STATE ++#define DPRINTF(fmt, ...) \ ++ do { printf("savevm-async: " fmt, ## __VA_ARGS__); } while (0) ++#else ++#define DPRINTF(fmt, ...) \ ++ do { } while (0) ++#endif ++ ++enum { ++ SAVE_STATE_DONE, ++ SAVE_STATE_ERROR, ++ SAVE_STATE_ACTIVE, ++ SAVE_STATE_COMPLETED, ++}; ++ ++static struct SnapshotState { ++ BlockDriverState *bs; ++ size_t bs_pos; ++ int state; ++ Error *error; ++ int saved_vm_running; ++ QEMUFile *file; ++ int64_t total_time; ++} snap_state; ++ ++SaveVMInfo *qmp_query_savevm(Error **errp) ++{ ++ SaveVMInfo *info = g_malloc0(sizeof(*info)); ++ struct SnapshotState *s = &snap_state; ++ ++ if (s->state != SAVE_STATE_DONE) { ++ info->has_bytes = true; ++ 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_get_clock_ms(rt_clock) ++ - 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; ++ break; ++ } ++ } ++ ++ return info; ++} ++ ++static int save_snapshot_cleanup(void) ++{ ++ int ret = 0; ++ ++ DPRINTF("save_snapshot_cleanup\n"); ++ ++ snap_state.total_time = qemu_get_clock_ms(rt_clock) - ++ snap_state.total_time; ++ ++ if (snap_state.file) { ++ ret = qemu_fclose(snap_state.file); ++ } ++ ++ if (snap_state.bs) { ++ // try to truncate, but ignore errors (will fail on block devices). ++ // note: bdrv_read() need whole blocks, so we round up ++ size_t size = (snap_state.bs_pos + BDRV_SECTOR_SIZE) & BDRV_SECTOR_MASK; ++ bdrv_truncate(snap_state.bs, size); ++ ++ bdrv_delete(snap_state.bs); ++ snap_state.bs = NULL; ++ } ++ ++ return ret; ++} ++ ++static void save_snapshot_error(const char *fmt, ...) ++{ ++ va_list ap; ++ char *msg; ++ ++ va_start(ap, fmt); ++ msg = g_strdup_vprintf(fmt, ap); ++ va_end(ap); ++ ++ DPRINTF("save_snapshot_error: %s\n", msg); ++ ++ if (!snap_state.error) { ++ error_set(&snap_state.error, ERROR_CLASS_GENERIC_ERROR, "%s", msg); ++ } ++ ++ 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) ++{ ++ snap_state.file = NULL; ++ return bdrv_flush(snap_state.bs); ++} ++ ++static ssize_t block_state_put_buffer(void *opaque, const void *buf, ++ size_t size) ++{ ++ int ret; ++ ++ if ((ret = bdrv_pwrite(snap_state.bs, snap_state.bs_pos, buf, size)) > 0) { ++ snap_state.bs_pos += ret; ++ } ++ ++ return ret; ++} ++ ++static void block_state_put_ready(void *opaque) ++{ ++ int ret; ++ ++ if (snap_state.state != SAVE_STATE_ACTIVE) { ++ save_snapshot_error("put_ready returning because of non-active state"); ++ return; ++ } ++ ++ if (!runstate_check(RUN_STATE_SAVE_VM)) { ++ save_snapshot_error("put_ready returning because of wrong run state"); ++ return; ++ } ++ ++ ret = qemu_savevm_state_iterate(snap_state.file); ++ if (ret < 0) { ++ save_snapshot_error("qemu_savevm_state_iterate error %d", ret); ++ return; ++ } else if (ret == 1) { ++ DPRINTF("savevm inerate finished\n"); ++ if ((ret = qemu_savevm_state_complete(snap_state.file)) < 0) { ++ save_snapshot_error("qemu_savevm_state_complete error %d", ret); ++ return; ++ } else { ++ DPRINTF("save complete\n"); ++ save_snapshot_completed(); ++ return; ++ } ++ } ++} ++ ++static void block_state_wait_for_unfreeze(void *opaque) ++{ ++ /* do nothing here - should not be called */ ++} ++ ++void qmp_snapshot_start(bool has_statefile, const char *statefile, Error **errp) ++{ ++ BlockDriver *drv = NULL; ++ int bdrv_oflags = BDRV_O_CACHE_WB | BDRV_O_RDWR; ++ MigrationParams params = { ++ .blk = 0, ++ .shared = 0 ++ }; ++ int ret; ++ ++ if (snap_state.state != SAVE_STATE_DONE) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "VM snapshot already started\n"); ++ return; ++ } ++ ++ /* initialize snapshot info */ ++ snap_state.saved_vm_running = runstate_is_running(); ++ snap_state.bs_pos = 0; ++ snap_state.total_time = qemu_get_clock_ms(rt_clock); ++ ++ if (snap_state.error) { ++ error_free(snap_state.error); ++ snap_state.error = NULL; ++ } ++ ++ /* stop the VM */ ++ vm_stop(RUN_STATE_SAVE_VM); ++ ++ if (!has_statefile) { ++ snap_state.state = SAVE_STATE_COMPLETED; ++ return; ++ } ++ ++ if (qemu_savevm_state_blocked(errp)) { ++ return; ++ } ++ ++ /* Open the image */ ++ snap_state.bs = bdrv_new("vmstate"); ++ ret = bdrv_open(snap_state.bs, statefile, bdrv_oflags, drv); ++ if (ret < 0) { ++ error_set(errp, QERR_OPEN_FILE_FAILED, statefile); ++ goto restart; ++ } ++ ++ snap_state.file = qemu_fopen_ops_buffered(&snap_state, 1000, //000000, ++ block_state_put_buffer, ++ block_state_put_ready, ++ block_state_wait_for_unfreeze, ++ block_state_close); ++ ++ if (!snap_state.file) { ++ error_set(errp, QERR_OPEN_FILE_FAILED, statefile); ++ goto restart; ++ } ++ ++ snap_state.state = SAVE_STATE_ACTIVE; ++ ++ ret = qemu_savevm_state_begin(snap_state.file, ¶ms); ++ if (ret < 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "qemu_savevm_state_begin failed\n"); ++ goto restart; ++ } ++ ++ block_state_put_ready(&snap_state); ++ ++ return; ++ ++restart: ++ ++ save_snapshot_error("setup failed"); ++ ++ if (snap_state.saved_vm_running) { ++ vm_start(); ++ } ++} ++ ++void qmp_snapshot_end(Error **errp) ++{ ++ if (snap_state.state == SAVE_STATE_DONE) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "VM snapshot not started\n"); ++ return; ++ } ++ ++ if (snap_state.saved_vm_running) { ++ vm_start(); ++ } ++ ++ snap_state.state = SAVE_STATE_DONE; ++} ++ ++void qmp_snapshot_drive(const char *device, const char *name, Error **errp) ++{ ++ 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; ++ } ++ ++ bs = bdrv_find(device); ++ if (!bs) { ++ error_set(errp, QERR_DEVICE_NOT_FOUND, device); ++ return; ++ } ++ ++ if (!bdrv_is_inserted(bs)) { ++ error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device); ++ return; ++ } ++ ++ if (bdrv_is_read_only(bs)) { ++ error_set(errp, QERR_DEVICE_IS_READ_ONLY, device); ++ return; ++ } ++ ++ if (!bdrv_can_snapshot(bs)) { ++ error_set(errp, QERR_NOT_SUPPORTED); ++ 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_get_clock_ns(vm_clock); ++ ++ 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; ++ } ++} ++ ++void qmp_delete_drive_snapshot(const char *device, const char *name, ++ Error **errp) ++{ ++ BlockDriverState *bs; ++ QEMUSnapshotInfo sn1, *sn = &sn1; ++ int ret; ++ ++ bs = bdrv_find(device); ++ if (!bs) { ++ error_set(errp, QERR_DEVICE_NOT_FOUND, device); ++ return; ++ } ++ if (bdrv_is_read_only(bs)) { ++ error_set(errp, QERR_DEVICE_IS_READ_ONLY, device); ++ return; ++ } ++ ++ if (!bdrv_can_snapshot(bs)) { ++ error_set(errp, QERR_NOT_SUPPORTED); ++ return; ++ } ++ ++ if (bdrv_snapshot_find(bs, sn, name) < 0) { ++ /* return success if snapshot does not exists */ ++ return; ++ } ++ ++ ret = bdrv_snapshot_delete(bs, name); ++ if (ret < 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "Error while deleting snapshot on '%s'\n", device); ++ return; ++ } ++} ++ ++static int loadstate_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) ++{ ++ BlockDriverState *bs = (BlockDriverState *)opaque; ++ int64_t maxlen = bdrv_getlength(bs); ++ if (pos > maxlen) { ++ return -EIO; ++ } ++ if ((pos + size) > maxlen) { ++ size = maxlen - pos - 1; ++ } ++ if (size == 0) { ++ return 0; ++ } ++ return bdrv_pread(bs, pos, buf, size); ++} ++ ++int load_state_from_blockdev(const char *filename) ++{ ++ BlockDriverState *bs = NULL; ++ BlockDriver *drv = NULL; ++ QEMUFile *f; ++ int ret = -1; ++ ++ bs = bdrv_new("vmstate"); ++ ret = bdrv_open(bs, filename, BDRV_O_CACHE_WB, drv); ++ if (ret < 0) { ++ error_report("Could not open VM state file"); ++ goto the_end; ++ } ++ ++ /* restore the VM state */ ++ f = qemu_fopen_ops(bs, NULL, loadstate_get_buffer, NULL, NULL, NULL, NULL); ++ if (!f) { ++ error_report("Could not open VM state file"); ++ ret = -EINVAL; ++ goto the_end; ++ } ++ ++ qemu_system_reset(VMRESET_SILENT); ++ ret = qemu_loadvm_state(f); ++ ++ qemu_fclose(f); ++ if (ret < 0) { ++ error_report("Error %d while loading VM state", ret); ++ goto the_end; ++ } ++ ++ ret = 0; ++ ++ the_end: ++ if (bs) { ++ bdrv_delete(bs); ++ } ++ return ret; ++} +Index: new/Makefile.objs +=================================================================== +--- new.orig/Makefile.objs 2012-09-21 11:10:23.000000000 +0200 ++++ new/Makefile.objs 2012-09-21 11:11:22.000000000 +0200 +@@ -78,6 +78,7 @@ + common-obj-y += pflib.o + common-obj-y += bitmap.o bitops.o + common-obj-y += page_cache.o ++common-obj-y += savevm-async.o + + common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o + common-obj-$(CONFIG_WIN32) += version.o +Index: new/sysemu.h +=================================================================== +--- new.orig/sysemu.h 2012-09-21 13:30:06.000000000 +0200 ++++ new/sysemu.h 2012-09-21 13:30:36.000000000 +0200 +@@ -72,6 +72,7 @@ + + void do_savevm(Monitor *mon, const QDict *qdict); + int load_vmstate(const char *name); ++int load_state_from_blockdev(const char *filename); + void do_delvm(Monitor *mon, const QDict *qdict); + void do_info_snapshots(Monitor *mon); + +Index: new/qemu-options.hx +=================================================================== +--- new.orig/qemu-options.hx 2012-09-21 13:31:18.000000000 +0200 ++++ new/qemu-options.hx 2012-09-21 13:32:02.000000000 +0200 +@@ -2477,6 +2477,19 @@ + Start right away with a saved state (@code{loadvm} in monitor) + ETEXI + ++DEF("loadstate", HAS_ARG, QEMU_OPTION_loadstate, \ ++ "-loadstate file\n" \ ++ " start right away with a saved state\n", ++ QEMU_ARCH_ALL) ++STEXI ++@item -loadstate @var{file} ++@findex -loadstate ++Start right away with a saved state. This option does not rollback ++disk state like @code{loadvm}, so user must make sure that disk ++have correct state. @var{file} can be any valid device URL. See the section ++for "Device URL Syntax" for more information. ++ETEXI ++ + #ifndef _WIN32 + DEF("daemonize", 0, QEMU_OPTION_daemonize, \ + "-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL) +Index: new/vl.c +=================================================================== +--- new.orig/vl.c 2012-09-21 13:32:39.000000000 +0200 ++++ new/vl.c 2012-09-21 13:34:30.000000000 +0200 +@@ -2364,6 +2364,7 @@ + int optind; + const char *optarg; + const char *loadvm = NULL; ++ const char *loadstate = NULL; + QEMUMachine *machine; + const char *cpu_model; + const char *vga_model = "none"; +@@ -2998,6 +2999,9 @@ + case QEMU_OPTION_loadvm: + loadvm = optarg; + break; ++ case QEMU_OPTION_loadstate: ++ loadstate = optarg; ++ break; + case QEMU_OPTION_full_screen: + full_screen = 1; + break; +@@ -3821,6 +3825,10 @@ + if (load_vmstate(loadvm) < 0) { + autostart = 0; + } ++ } else if (loadstate) { ++ if (load_state_from_blockdev(loadstate) < 0) { ++ autostart = 0; ++ } + } + + if (incoming) {