]> git.proxmox.com Git - pve-qemu-kvm.git/commitdiff
patch to save vmstate in background (not used for now)
authorDietmar Maurer <dietmar@proxmox.com>
Fri, 21 Sep 2012 11:44:09 +0000 (13:44 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 21 Sep 2012 11:44:09 +0000 (13:44 +0200)
debian/patches/internal-snapshot-async.patch [new file with mode: 0644]

diff --git a/debian/patches/internal-snapshot-async.patch b/debian/patches/internal-snapshot-async.patch
new file mode 100644 (file)
index 0000000..6587ed8
--- /dev/null
@@ -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, &params);
++    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) {