X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/f11f6a5fff3d23e4ef3b3ef6fec53c5caf0378d5..72902672dc2ed6281cdb205259c1d52ecf01f6b2:/savevm.c diff --git a/savevm.c b/savevm.c index 7a363b64d5..6d83b0f4dc 100644 --- a/savevm.c +++ b/savevm.c @@ -42,26 +42,17 @@ #include #include #include -#if defined(__NetBSD__) -#include -#endif -#ifdef __linux__ -#include -#endif #include #include #include #include #ifdef CONFIG_BSD #include -#if defined(__FreeBSD__) || defined(__DragonFly__) +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) #include #else #include #endif -#elif defined (__GLIBC__) && defined (__FreeBSD_kernel__) -#include -#else #ifdef __linux__ #include #include @@ -81,62 +72,73 @@ #include "qemu-common.h" #include "hw/hw.h" +#include "hw/qdev.h" #include "net.h" #include "monitor.h" #include "sysemu.h" #include "qemu-timer.h" #include "qemu-char.h" -#include "block.h" #include "audio/audio.h" #include "migration.h" #include "qemu_socket.h" #include "qemu-queue.h" -/* point to the block driver where the snapshots are managed */ -static BlockDriverState *bs_snapshots; - #define SELF_ANNOUNCE_ROUNDS 5 -#define ETH_P_EXPERIMENTAL 0x01F1 /* just a number */ -//#define ETH_P_EXPERIMENTAL 0x0012 /* make it the size of the packet */ -#define EXPERIMENTAL_MAGIC 0xf1f23f4f -static int announce_self_create(uint8_t *buf, +#ifndef ETH_P_RARP +#define ETH_P_RARP 0x8035 +#endif +#define ARP_HTYPE_ETH 0x0001 +#define ARP_PTYPE_IP 0x0800 +#define ARP_OP_REQUEST_REV 0x3 + +static int announce_self_create(uint8_t *buf, uint8_t *mac_addr) { - uint32_t magic = EXPERIMENTAL_MAGIC; - uint16_t proto = htons(ETH_P_EXPERIMENTAL); + /* Ethernet header. */ + memset(buf, 0xff, 6); /* destination MAC addr */ + memcpy(buf + 6, mac_addr, 6); /* source MAC addr */ + *(uint16_t *)(buf + 12) = htons(ETH_P_RARP); /* ethertype */ + + /* RARP header. */ + *(uint16_t *)(buf + 14) = htons(ARP_HTYPE_ETH); /* hardware addr space */ + *(uint16_t *)(buf + 16) = htons(ARP_PTYPE_IP); /* protocol addr space */ + *(buf + 18) = 6; /* hardware addr length (ethernet) */ + *(buf + 19) = 4; /* protocol addr length (IPv4) */ + *(uint16_t *)(buf + 20) = htons(ARP_OP_REQUEST_REV); /* opcode */ + memcpy(buf + 22, mac_addr, 6); /* source hw addr */ + memset(buf + 28, 0x00, 4); /* source protocol addr */ + memcpy(buf + 32, mac_addr, 6); /* target hw addr */ + memset(buf + 38, 0x00, 4); /* target protocol addr */ + + /* Padding to get up to 60 bytes (ethernet min packet size, minus FCS). */ + memset(buf + 42, 0x00, 18); + + return 60; /* len (FCS will be added by hardware) */ +} - /* FIXME: should we send a different packet (arp/rarp/ping)? */ +static void qemu_announce_self_iter(NICState *nic, void *opaque) +{ + uint8_t buf[60]; + int len; - memset(buf, 0, 64); - memset(buf, 0xff, 6); /* h_dst */ - memcpy(buf + 6, mac_addr, 6); /* h_src */ - memcpy(buf + 12, &proto, 2); /* h_proto */ - memcpy(buf + 14, &magic, 4); /* magic */ + len = announce_self_create(buf, nic->conf->macaddr.a); - return 64; /* len */ + qemu_send_packet_raw(&nic->nc, buf, len); } + static void qemu_announce_self_once(void *opaque) { - int i, len; - VLANState *vlan; - VLANClientState *vc; - uint8_t buf[256]; static int count = SELF_ANNOUNCE_ROUNDS; QEMUTimer *timer = *(QEMUTimer **)opaque; - for (i = 0; i < MAX_NICS; i++) { - if (!nd_table[i].used) - continue; - len = announce_self_create(buf, nd_table[i].macaddr); - vlan = nd_table[i].vlan; - for(vc = vlan->first_client; vc != NULL; vc = vc->next) { - vc->receive(vc, buf, len); - } - } - if (count--) { - qemu_mod_timer(timer, qemu_get_clock(rt_clock) + 100); + qemu_foreach_nic(qemu_announce_self_iter, NULL); + + if (--count) { + /* delay 50ms, 150ms, 250ms, ... */ + qemu_mod_timer(timer, qemu_get_clock(rt_clock) + + 50 + (SELF_ANNOUNCE_ROUNDS - count - 1) * 100); } else { qemu_del_timer(timer); qemu_free_timer(timer); @@ -161,6 +163,7 @@ struct QEMUFile { QEMUFileCloseFunc *close; QEMUFileRateLimit *rate_limit; QEMUFileSetRateLimit *set_rate_limit; + QEMUFileGetRateLimit *get_rate_limit; void *opaque; int is_write; @@ -229,9 +232,10 @@ static int stdio_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) static int stdio_pclose(void *opaque) { QEMUFileStdio *s = opaque; - pclose(s->stdio_file); + int ret; + ret = pclose(s->stdio_file); qemu_free(s); - return 0; + return ret; } static int stdio_fclose(void *opaque) @@ -256,9 +260,11 @@ QEMUFile *qemu_popen(FILE *stdio_file, const char *mode) s->stdio_file = stdio_file; if(mode[0] == 'r') { - s->file = qemu_fopen_ops(s, NULL, stdio_get_buffer, stdio_pclose, NULL, NULL); + s->file = qemu_fopen_ops(s, NULL, stdio_get_buffer, stdio_pclose, + NULL, NULL, NULL); } else { - s->file = qemu_fopen_ops(s, stdio_put_buffer, NULL, stdio_pclose, NULL, NULL); + s->file = qemu_fopen_ops(s, stdio_put_buffer, NULL, stdio_pclose, + NULL, NULL, NULL); } return s->file; } @@ -303,9 +309,11 @@ QEMUFile *qemu_fdopen(int fd, const char *mode) goto fail; if(mode[0] == 'r') { - s->file = qemu_fopen_ops(s, NULL, stdio_get_buffer, stdio_fclose, NULL, NULL); + s->file = qemu_fopen_ops(s, NULL, stdio_get_buffer, stdio_fclose, + NULL, NULL, NULL); } else { - s->file = qemu_fopen_ops(s, stdio_put_buffer, NULL, stdio_fclose, NULL, NULL); + s->file = qemu_fopen_ops(s, stdio_put_buffer, NULL, stdio_fclose, + NULL, NULL, NULL); } return s->file; @@ -319,7 +327,8 @@ QEMUFile *qemu_fopen_socket(int fd) QEMUFileSocket *s = qemu_mallocz(sizeof(QEMUFileSocket)); s->fd = fd; - s->file = qemu_fopen_ops(s, NULL, socket_get_buffer, socket_close, NULL, NULL); + s->file = qemu_fopen_ops(s, NULL, socket_get_buffer, socket_close, + NULL, NULL, NULL); return s->file; } @@ -328,8 +337,7 @@ static int file_put_buffer(void *opaque, const uint8_t *buf, { QEMUFileStdio *s = opaque; fseek(s->stdio_file, pos, SEEK_SET); - fwrite(buf, 1, size, s->stdio_file); - return size; + return fwrite(buf, 1, size, s->stdio_file); } static int file_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) @@ -346,7 +354,7 @@ QEMUFile *qemu_fopen(const char *filename, const char *mode) if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || mode[1] != 'b' || mode[2] != 0) { - fprintf(stderr, "qemu_fdopen: Argument validity check failed\n"); + fprintf(stderr, "qemu_fopen: Argument validity check failed\n"); return NULL; } @@ -355,11 +363,13 @@ QEMUFile *qemu_fopen(const char *filename, const char *mode) s->stdio_file = fopen(filename, mode); if (!s->stdio_file) goto fail; - + if(mode[0] == 'w') { - s->file = qemu_fopen_ops(s, file_put_buffer, NULL, stdio_fclose, NULL, NULL); + s->file = qemu_fopen_ops(s, file_put_buffer, NULL, stdio_fclose, + NULL, NULL, NULL); } else { - s->file = qemu_fopen_ops(s, NULL, file_get_buffer, stdio_fclose, NULL, NULL); + s->file = qemu_fopen_ops(s, NULL, file_get_buffer, stdio_fclose, + NULL, NULL, NULL); } return s->file; fail: @@ -387,15 +397,17 @@ static int bdrv_fclose(void *opaque) static QEMUFile *qemu_fopen_bdrv(BlockDriverState *bs, int is_writable) { if (is_writable) - return qemu_fopen_ops(bs, block_put_buffer, NULL, bdrv_fclose, NULL, NULL); - return qemu_fopen_ops(bs, NULL, block_get_buffer, bdrv_fclose, NULL, NULL); + return qemu_fopen_ops(bs, block_put_buffer, NULL, bdrv_fclose, + NULL, NULL, NULL); + return qemu_fopen_ops(bs, NULL, block_get_buffer, bdrv_fclose, NULL, NULL, NULL); } QEMUFile *qemu_fopen_ops(void *opaque, QEMUFilePutBufferFunc *put_buffer, QEMUFileGetBufferFunc *get_buffer, QEMUFileCloseFunc *close, QEMUFileRateLimit *rate_limit, - QEMUFileSetRateLimit *set_rate_limit) + QEMUFileSetRateLimit *set_rate_limit, + QEMUFileGetRateLimit *get_rate_limit) { QEMUFile *f; @@ -407,6 +419,7 @@ QEMUFile *qemu_fopen_ops(void *opaque, QEMUFilePutBufferFunc *put_buffer, f->close = close; f->rate_limit = rate_limit; f->set_rate_limit = set_rate_limit; + f->get_rate_limit = get_rate_limit; f->is_write = 0; return f; @@ -537,6 +550,19 @@ int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size1) return size1 - size; } +static int qemu_peek_byte(QEMUFile *f) +{ + if (f->is_write) + abort(); + + if (f->buf_index >= f->buf_size) { + qemu_fill_buffer(f); + if (f->buf_index >= f->buf_size) + return 0; + } + return f->buf[f->buf_index]; +} + int qemu_get_byte(QEMUFile *f) { if (f->is_write) @@ -584,7 +610,15 @@ int qemu_file_rate_limit(QEMUFile *f) return 0; } -size_t qemu_file_set_rate_limit(QEMUFile *f, size_t new_rate) +int64_t qemu_file_get_rate_limit(QEMUFile *f) +{ + if (f->get_rate_limit) + return f->get_rate_limit(f->opaque); + + return 0; +} + +int64_t qemu_file_set_rate_limit(QEMUFile *f, int64_t new_rate) { /* any failed or completed migration keeps its state to allow probing of * migration data, but has no associated file anymore */ @@ -640,6 +674,27 @@ uint64_t qemu_get_be64(QEMUFile *f) return v; } +/* bool */ + +static int get_bool(QEMUFile *f, void *pv, size_t size) +{ + bool *v = pv; + *v = qemu_get_byte(f); + return 0; +} + +static void put_bool(QEMUFile *f, void *pv, size_t size) +{ + bool *v = pv; + qemu_put_byte(f, *v); +} + +const VMStateInfo vmstate_info_bool = { + .name = "bool", + .get = get_bool, + .put = put_bool, +}; + /* 8 bit int */ static int get_int8(QEMUFile *f, void *pv, size_t size) @@ -863,11 +918,31 @@ static int get_uint8_equal(QEMUFile *f, void *pv, size_t size) } const VMStateInfo vmstate_info_uint8_equal = { - .name = "int32 equal", + .name = "uint8 equal", .get = get_uint8_equal, .put = put_uint8, }; +/* 16 bit unsigned int int. See that the received value is the same than the one + in the field */ + +static int get_uint16_equal(QEMUFile *f, void *pv, size_t size) +{ + uint16_t *v = pv; + uint16_t v2; + qemu_get_be16s(f, &v2); + + if (*v == v2) + return 0; + return -EINVAL; +} + +const VMStateInfo vmstate_info_uint16_equal = { + .name = "uint16 equal", + .get = get_uint16_equal, + .put = put_uint16, +}; + /* timers */ static int get_timer(QEMUFile *f, void *pv, size_t size) @@ -910,19 +985,63 @@ const VMStateInfo vmstate_info_buffer = { .put = put_buffer, }; +/* unused buffers: space that was used for some fields that are + not usefull anymore */ + +static int get_unused_buffer(QEMUFile *f, void *pv, size_t size) +{ + uint8_t buf[1024]; + int block_len; + + while (size > 0) { + block_len = MIN(sizeof(buf), size); + size -= block_len; + qemu_get_buffer(f, buf, block_len); + } + return 0; +} + +static void put_unused_buffer(QEMUFile *f, void *pv, size_t size) +{ + static const uint8_t buf[1024]; + int block_len; + + while (size > 0) { + block_len = MIN(sizeof(buf), size); + size -= block_len; + qemu_put_buffer(f, buf, block_len); + } +} + +const VMStateInfo vmstate_info_unused_buffer = { + .name = "unused_buffer", + .get = get_unused_buffer, + .put = put_unused_buffer, +}; + +typedef struct CompatEntry { + char idstr[256]; + int instance_id; +} CompatEntry; + typedef struct SaveStateEntry { QTAILQ_ENTRY(SaveStateEntry) entry; char idstr[256]; int instance_id; + int alias_id; int version_id; int section_id; + SaveSetParamsHandler *set_params; SaveLiveStateHandler *save_live_state; SaveStateHandler *save_state; LoadStateHandler *load_state; const VMStateDescription *vmsd; void *opaque; + CompatEntry *compat; + int no_migrate; } SaveStateEntry; + static QTAILQ_HEAD(savevm_handlers, SaveStateEntry) savevm_handlers = QTAILQ_HEAD_INITIALIZER(savevm_handlers); static int global_section_id; @@ -941,13 +1060,32 @@ static int calculate_new_instance_id(const char *idstr) return instance_id; } +static int calculate_compat_instance_id(const char *idstr) +{ + SaveStateEntry *se; + int instance_id = 0; + + QTAILQ_FOREACH(se, &savevm_handlers, entry) { + if (!se->compat) + continue; + + if (strcmp(idstr, se->compat->idstr) == 0 + && instance_id <= se->compat->instance_id) { + instance_id = se->compat->instance_id + 1; + } + } + return instance_id; +} + /* TODO: Individual devices generally have very little idea about the rest of the system, so instance_id should be removed/replaced. Meanwhile pass -1 as instance_id if you do not already have a clearly distinguishing id for all instances of your device class. */ -int register_savevm_live(const char *idstr, +int register_savevm_live(DeviceState *dev, + const char *idstr, int instance_id, int version_id, + SaveSetParamsHandler *set_params, SaveLiveStateHandler *save_live_state, SaveStateHandler *save_state, LoadStateHandler *load_state, @@ -955,56 +1093,118 @@ int register_savevm_live(const char *idstr, { SaveStateEntry *se; - se = qemu_malloc(sizeof(SaveStateEntry)); - pstrcpy(se->idstr, sizeof(se->idstr), idstr); + se = qemu_mallocz(sizeof(SaveStateEntry)); se->version_id = version_id; se->section_id = global_section_id++; + se->set_params = set_params; se->save_live_state = save_live_state; se->save_state = save_state; se->load_state = load_state; se->opaque = opaque; se->vmsd = NULL; + se->no_migrate = 0; + + if (dev && dev->parent_bus && dev->parent_bus->info->get_dev_path) { + char *id = dev->parent_bus->info->get_dev_path(dev); + if (id) { + pstrcpy(se->idstr, sizeof(se->idstr), id); + pstrcat(se->idstr, sizeof(se->idstr), "/"); + qemu_free(id); + + se->compat = qemu_mallocz(sizeof(CompatEntry)); + pstrcpy(se->compat->idstr, sizeof(se->compat->idstr), idstr); + se->compat->instance_id = instance_id == -1 ? + calculate_compat_instance_id(idstr) : instance_id; + instance_id = -1; + } + } + pstrcat(se->idstr, sizeof(se->idstr), idstr); if (instance_id == -1) { - se->instance_id = calculate_new_instance_id(idstr); + se->instance_id = calculate_new_instance_id(se->idstr); } else { se->instance_id = instance_id; } + assert(!se->compat || se->instance_id == 0); /* add at the end of list */ QTAILQ_INSERT_TAIL(&savevm_handlers, se, entry); return 0; } -int register_savevm(const char *idstr, +int register_savevm(DeviceState *dev, + const char *idstr, int instance_id, int version_id, SaveStateHandler *save_state, LoadStateHandler *load_state, void *opaque) { - return register_savevm_live(idstr, instance_id, version_id, - NULL, save_state, load_state, opaque); + return register_savevm_live(dev, idstr, instance_id, version_id, + NULL, NULL, save_state, load_state, opaque); } -void unregister_savevm(const char *idstr, void *opaque) +void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque) { SaveStateEntry *se, *new_se; + char id[256] = ""; + + if (dev && dev->parent_bus && dev->parent_bus->info->get_dev_path) { + char *path = dev->parent_bus->info->get_dev_path(dev); + if (path) { + pstrcpy(id, sizeof(id), path); + pstrcat(id, sizeof(id), "/"); + qemu_free(path); + } + } + pstrcat(id, sizeof(id), idstr); QTAILQ_FOREACH_SAFE(se, &savevm_handlers, entry, new_se) { - if (strcmp(se->idstr, idstr) == 0 && se->opaque == opaque) { + if (strcmp(se->idstr, id) == 0 && se->opaque == opaque) { QTAILQ_REMOVE(&savevm_handlers, se, entry); + if (se->compat) { + qemu_free(se->compat); + } qemu_free(se); } } } -int vmstate_register(int instance_id, const VMStateDescription *vmsd, - void *opaque) +/* mark a device as not to be migrated, that is the device should be + unplugged before migration */ +void register_device_unmigratable(DeviceState *dev, const char *idstr, + void *opaque) { SaveStateEntry *se; + char id[256] = ""; + + if (dev && dev->parent_bus && dev->parent_bus->info->get_dev_path) { + char *path = dev->parent_bus->info->get_dev_path(dev); + if (path) { + pstrcpy(id, sizeof(id), path); + pstrcat(id, sizeof(id), "/"); + qemu_free(path); + } + } + pstrcat(id, sizeof(id), idstr); + + QTAILQ_FOREACH(se, &savevm_handlers, entry) { + if (strcmp(se->idstr, id) == 0 && se->opaque == opaque) { + se->no_migrate = 1; + } + } +} - se = qemu_malloc(sizeof(SaveStateEntry)); - pstrcpy(se->idstr, sizeof(se->idstr), vmsd->name); +int vmstate_register_with_alias_id(DeviceState *dev, int instance_id, + const VMStateDescription *vmsd, + void *opaque, int alias_id, + int required_for_version) +{ + SaveStateEntry *se; + + /* If this triggers, alias support can be dropped for the vmsd. */ + assert(alias_id == -1 || required_for_version >= vmsd->minimum_version_id); + + se = qemu_mallocz(sizeof(SaveStateEntry)); se->version_id = vmsd->version_id; se->section_id = global_section_id++; se->save_live_state = NULL; @@ -1012,33 +1212,68 @@ int vmstate_register(int instance_id, const VMStateDescription *vmsd, se->load_state = NULL; se->opaque = opaque; se->vmsd = vmsd; + se->alias_id = alias_id; + + if (dev && dev->parent_bus && dev->parent_bus->info->get_dev_path) { + char *id = dev->parent_bus->info->get_dev_path(dev); + if (id) { + pstrcpy(se->idstr, sizeof(se->idstr), id); + pstrcat(se->idstr, sizeof(se->idstr), "/"); + qemu_free(id); + + se->compat = qemu_mallocz(sizeof(CompatEntry)); + pstrcpy(se->compat->idstr, sizeof(se->compat->idstr), vmsd->name); + se->compat->instance_id = instance_id == -1 ? + calculate_compat_instance_id(vmsd->name) : instance_id; + instance_id = -1; + } + } + pstrcat(se->idstr, sizeof(se->idstr), vmsd->name); if (instance_id == -1) { - se->instance_id = calculate_new_instance_id(vmsd->name); + se->instance_id = calculate_new_instance_id(se->idstr); } else { se->instance_id = instance_id; } + assert(!se->compat || se->instance_id == 0); /* add at the end of list */ QTAILQ_INSERT_TAIL(&savevm_handlers, se, entry); return 0; } -void vmstate_unregister(const VMStateDescription *vmsd, void *opaque) +int vmstate_register(DeviceState *dev, int instance_id, + const VMStateDescription *vmsd, void *opaque) +{ + return vmstate_register_with_alias_id(dev, instance_id, vmsd, + opaque, -1, 0); +} + +void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd, + void *opaque) { SaveStateEntry *se, *new_se; QTAILQ_FOREACH_SAFE(se, &savevm_handlers, entry, new_se) { if (se->vmsd == vmsd && se->opaque == opaque) { QTAILQ_REMOVE(&savevm_handlers, se, entry); + if (se->compat) { + qemu_free(se->compat); + } qemu_free(se); } } } +static void vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque); +static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque); + int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, void *opaque, int version_id) { VMStateField *field = vmsd->fields; + int ret; if (version_id > vmsd->version_id) { return -EINVAL; @@ -1060,18 +1295,27 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, (!field->field_exists && field->version_id <= version_id)) { void *base_addr = opaque + field->offset; - int ret, i, n_elems = 1; + int i, n_elems = 1; + int size = field->size; + if (field->flags & VMS_VBUFFER) { + size = *(int32_t *)(opaque+field->size_offset); + if (field->flags & VMS_MULTIPLY) { + size *= field->size; + } + } if (field->flags & VMS_ARRAY) { n_elems = field->num; - } else if (field->flags & VMS_VARRAY) { - n_elems = *(size_t *)(opaque+field->num_offset); + } else if (field->flags & VMS_VARRAY_INT32) { + n_elems = *(int32_t *)(opaque+field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT16) { + n_elems = *(uint16_t *)(opaque+field->num_offset); } if (field->flags & VMS_POINTER) { - base_addr = *(void **)base_addr; + base_addr = *(void **)base_addr + field->start; } for (i = 0; i < n_elems; i++) { - void *addr = base_addr + field->size * i; + void *addr = base_addr + size * i; if (field->flags & VMS_ARRAY_OF_POINTER) { addr = *(void **)addr; @@ -1079,7 +1323,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, if (field->flags & VMS_STRUCT) { ret = vmstate_load_state(f, field->vmsd, addr, field->vmsd->version_id); } else { - ret = field->info->get(f, addr, field->size); + ret = field->info->get(f, addr, size); } if (ret < 0) { @@ -1089,6 +1333,10 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, } field++; } + ret = vmstate_subsection_load(f, vmsd, opaque); + if (ret != 0) { + return ret; + } if (vmsd->post_load) { return vmsd->post_load(opaque, version_id); } @@ -1108,30 +1356,40 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, field->field_exists(opaque, vmsd->version_id)) { void *base_addr = opaque + field->offset; int i, n_elems = 1; + int size = field->size; + if (field->flags & VMS_VBUFFER) { + size = *(int32_t *)(opaque+field->size_offset); + if (field->flags & VMS_MULTIPLY) { + size *= field->size; + } + } if (field->flags & VMS_ARRAY) { n_elems = field->num; - } else if (field->flags & VMS_VARRAY) { - n_elems = *(size_t *)(opaque+field->num_offset); + } else if (field->flags & VMS_VARRAY_INT32) { + n_elems = *(int32_t *)(opaque+field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT16) { + n_elems = *(uint16_t *)(opaque+field->num_offset); } if (field->flags & VMS_POINTER) { - base_addr = *(void **)base_addr; + base_addr = *(void **)base_addr + field->start; } for (i = 0; i < n_elems; i++) { - void *addr = base_addr + field->size * i; + void *addr = base_addr + size * i; + if (field->flags & VMS_ARRAY_OF_POINTER) { + addr = *(void **)addr; + } if (field->flags & VMS_STRUCT) { vmstate_save_state(f, field->vmsd, addr); } else { - field->info->put(f, addr, field->size); + field->info->put(f, addr, size); } } } field++; } - if (vmsd->post_save) { - vmsd->post_save(opaque); - } + vmstate_subsection_save(f, vmsd, opaque); } static int vmstate_load(QEMUFile *f, SaveStateEntry *se, int version_id) @@ -1160,11 +1418,34 @@ static void vmstate_save(QEMUFile *f, SaveStateEntry *se) #define QEMU_VM_SECTION_PART 0x02 #define QEMU_VM_SECTION_END 0x03 #define QEMU_VM_SECTION_FULL 0x04 +#define QEMU_VM_SUBSECTION 0x05 + +bool qemu_savevm_state_blocked(Monitor *mon) +{ + SaveStateEntry *se; + + QTAILQ_FOREACH(se, &savevm_handlers, entry) { + if (se->no_migrate) { + monitor_printf(mon, "state blocked by non-migratable device '%s'\n", + se->idstr); + return true; + } + } + return false; +} -int qemu_savevm_state_begin(QEMUFile *f) +int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable, + int shared) { SaveStateEntry *se; + QTAILQ_FOREACH(se, &savevm_handlers, entry) { + if(se->set_params == NULL) { + continue; + } + se->set_params(blk_enable, shared, se->opaque); + } + qemu_put_be32(f, QEMU_VM_FILE_MAGIC); qemu_put_be32(f, QEMU_VM_FILE_VERSION); @@ -1186,16 +1467,18 @@ int qemu_savevm_state_begin(QEMUFile *f) qemu_put_be32(f, se->instance_id); qemu_put_be32(f, se->version_id); - se->save_live_state(f, QEMU_VM_SECTION_START, se->opaque); + se->save_live_state(mon, f, QEMU_VM_SECTION_START, se->opaque); } - if (qemu_file_has_error(f)) + if (qemu_file_has_error(f)) { + qemu_savevm_state_cancel(mon, f); return -EIO; + } return 0; } -int qemu_savevm_state_iterate(QEMUFile *f) +int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f) { SaveStateEntry *se; int ret = 1; @@ -1208,22 +1491,33 @@ int qemu_savevm_state_iterate(QEMUFile *f) qemu_put_byte(f, QEMU_VM_SECTION_PART); qemu_put_be32(f, se->section_id); - ret &= !!se->save_live_state(f, QEMU_VM_SECTION_PART, se->opaque); + ret = se->save_live_state(mon, f, QEMU_VM_SECTION_PART, se->opaque); + if (!ret) { + /* Do not proceed to the next vmstate before this one reported + completion of the current stage. This serializes the migration + and reduces the probability that a faster changing state is + synchronized over and over again. */ + break; + } } if (ret) return 1; - if (qemu_file_has_error(f)) + if (qemu_file_has_error(f)) { + qemu_savevm_state_cancel(mon, f); return -EIO; + } return 0; } -int qemu_savevm_state_complete(QEMUFile *f) +int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f) { SaveStateEntry *se; + cpu_synchronize_all_states(); + QTAILQ_FOREACH(se, &savevm_handlers, entry) { if (se->save_live_state == NULL) continue; @@ -1232,7 +1526,7 @@ int qemu_savevm_state_complete(QEMUFile *f) qemu_put_byte(f, QEMU_VM_SECTION_END); qemu_put_be32(f, se->section_id); - se->save_live_state(f, QEMU_VM_SECTION_END, se->opaque); + se->save_live_state(mon, f, QEMU_VM_SECTION_END, se->opaque); } QTAILQ_FOREACH(se, &savevm_handlers, entry) { @@ -1264,7 +1558,18 @@ int qemu_savevm_state_complete(QEMUFile *f) return 0; } -int qemu_savevm_state(QEMUFile *f) +void qemu_savevm_state_cancel(Monitor *mon, QEMUFile *f) +{ + SaveStateEntry *se; + + QTAILQ_FOREACH(se, &savevm_handlers, entry) { + if (se->save_live_state) { + se->save_live_state(mon, f, -1, se->opaque); + } + } +} + +static int qemu_savevm_state(Monitor *mon, QEMUFile *f) { int saved_vm_running; int ret; @@ -1272,19 +1577,22 @@ int qemu_savevm_state(QEMUFile *f) saved_vm_running = vm_running; vm_stop(0); - bdrv_flush_all(); + if (qemu_savevm_state_blocked(mon)) { + ret = -EINVAL; + goto out; + } - ret = qemu_savevm_state_begin(f); + ret = qemu_savevm_state_begin(mon, f, 0, 0); if (ret < 0) goto out; do { - ret = qemu_savevm_state_iterate(f); + ret = qemu_savevm_state_iterate(mon, f); if (ret < 0) goto out; } while (ret == 0); - ret = qemu_savevm_state_complete(f); + ret = qemu_savevm_state_complete(mon, f); out: if (qemu_file_has_error(f)) @@ -1302,12 +1610,87 @@ static SaveStateEntry *find_se(const char *idstr, int instance_id) QTAILQ_FOREACH(se, &savevm_handlers, entry) { if (!strcmp(se->idstr, idstr) && - instance_id == se->instance_id) + (instance_id == se->instance_id || + instance_id == se->alias_id)) return se; + /* Migrating from an older version? */ + if (strstr(se->idstr, idstr) && se->compat) { + if (!strcmp(se->compat->idstr, idstr) && + (instance_id == se->compat->instance_id || + instance_id == se->alias_id)) + return se; + } + } + return NULL; +} + +static const VMStateDescription *vmstate_get_subsection(const VMStateSubsection *sub, char *idstr) +{ + while(sub && sub->needed) { + if (strcmp(idstr, sub->vmsd->name) == 0) { + return sub->vmsd; + } + sub++; } return NULL; } +static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque) +{ + const VMStateSubsection *sub = vmsd->subsections; + + if (!sub || !sub->needed) { + return 0; + } + + while (qemu_peek_byte(f) == QEMU_VM_SUBSECTION) { + char idstr[256]; + int ret; + uint8_t version_id, len; + const VMStateDescription *sub_vmsd; + + qemu_get_byte(f); /* subsection */ + len = qemu_get_byte(f); + qemu_get_buffer(f, (uint8_t *)idstr, len); + idstr[len] = 0; + version_id = qemu_get_be32(f); + + sub_vmsd = vmstate_get_subsection(sub, idstr); + if (sub_vmsd == NULL) { + return -ENOENT; + } + assert(!sub_vmsd->subsections); + ret = vmstate_load_state(f, sub_vmsd, opaque, version_id); + if (ret) { + return ret; + } + } + return 0; +} + +static void vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque) +{ + const VMStateSubsection *sub = vmsd->subsections; + + while (sub && sub->needed) { + if (sub->needed(opaque)) { + const VMStateDescription *vmsd = sub->vmsd; + uint8_t len; + + qemu_put_byte(f, QEMU_VM_SUBSECTION); + len = strlen(vmsd->name); + qemu_put_byte(f, len); + qemu_put_buffer(f, (uint8_t *)vmsd->name, len); + qemu_put_be32(f, vmsd->version_id); + assert(!vmsd->subsections); + vmstate_save_state(f, vmsd, opaque); + } + sub++; + } +} + typedef struct LoadStateEntry { QLIST_ENTRY(LoadStateEntry) entry; SaveStateEntry *se; @@ -1324,6 +1707,10 @@ int qemu_loadvm_state(QEMUFile *f) unsigned int v; int ret; + if (qemu_savevm_state_blocked(default_mon)) { + return -EINVAL; + } + v = qemu_get_be32(f); if (v != QEMU_VM_FILE_MAGIC) return -EINVAL; @@ -1413,6 +1800,8 @@ int qemu_loadvm_state(QEMUFile *f) } } + cpu_synchronize_all_post_init(); + ret = 0; out: @@ -1427,40 +1816,6 @@ out: return ret; } -/* device can contain snapshots */ -static int bdrv_can_snapshot(BlockDriverState *bs) -{ - return (bs && - !bdrv_is_removable(bs) && - !bdrv_is_read_only(bs)); -} - -/* device must be snapshots in order to have a reliable snapshot */ -static int bdrv_has_snapshot(BlockDriverState *bs) -{ - return (bs && - !bdrv_is_removable(bs) && - !bdrv_is_read_only(bs)); -} - -static BlockDriverState *get_bs_snapshots(void) -{ - BlockDriverState *bs; - DriveInfo *dinfo; - - if (bs_snapshots) - return bs_snapshots; - QTAILQ_FOREACH(dinfo, &drives, next) { - bs = dinfo->bdrv; - if (bdrv_can_snapshot(bs)) - goto ok; - } - return NULL; - ok: - bs_snapshots = bs; - return bs; -} - static int bdrv_snapshot_find(BlockDriverState *bs, QEMUSnapshotInfo *sn_info, const char *name) { @@ -1483,49 +1838,75 @@ static int bdrv_snapshot_find(BlockDriverState *bs, QEMUSnapshotInfo *sn_info, return ret; } +/* + * Deletes snapshots of a given name in all opened images. + */ +static int del_existing_snapshots(Monitor *mon, const char *name) +{ + BlockDriverState *bs; + QEMUSnapshotInfo sn1, *snapshot = &sn1; + int ret; + + bs = NULL; + while ((bs = bdrv_next(bs))) { + if (bdrv_can_snapshot(bs) && + bdrv_snapshot_find(bs, snapshot, name) >= 0) + { + ret = bdrv_snapshot_delete(bs, name); + if (ret < 0) { + monitor_printf(mon, + "Error while deleting snapshot on '%s'\n", + bdrv_get_device_name(bs)); + return -1; + } + } + } + + return 0; +} + void do_savevm(Monitor *mon, const QDict *qdict) { - DriveInfo *dinfo; BlockDriverState *bs, *bs1; QEMUSnapshotInfo sn1, *sn = &sn1, old_sn1, *old_sn = &old_sn1; - int must_delete, ret; + int ret; QEMUFile *f; int saved_vm_running; uint32_t vm_state_size; #ifdef _WIN32 struct _timeb tb; + struct tm *ptm; #else struct timeval tv; + struct tm tm; #endif const char *name = qdict_get_try_str(qdict, "name"); - bs = get_bs_snapshots(); + /* Verify if there is a device that doesn't support snapshots and is writable */ + bs = NULL; + while ((bs = bdrv_next(bs))) { + + if (bdrv_is_removable(bs) || bdrv_is_read_only(bs)) { + continue; + } + + if (!bdrv_can_snapshot(bs)) { + monitor_printf(mon, "Device '%s' is writable but does not support snapshots.\n", + bdrv_get_device_name(bs)); + return; + } + } + + bs = bdrv_snapshots(); if (!bs) { monitor_printf(mon, "No block device can accept snapshots\n"); return; } - /* ??? Should this occur after vm_stop? */ - qemu_aio_flush(); - saved_vm_running = vm_running; vm_stop(0); - must_delete = 0; - if (name) { - ret = bdrv_snapshot_find(bs, old_sn, name); - if (ret >= 0) { - must_delete = 1; - } - } memset(sn, 0, sizeof(*sn)); - if (must_delete) { - pstrcpy(sn->name, sizeof(sn->name), old_sn->name); - pstrcpy(sn->id_str, sizeof(sn->id_str), old_sn->id_str); - } else { - if (name) - pstrcpy(sn->name, sizeof(sn->name), name); - } /* fill auxiliary fields */ #ifdef _WIN32 @@ -1539,13 +1920,37 @@ void do_savevm(Monitor *mon, const QDict *qdict) #endif sn->vm_clock_nsec = qemu_get_clock(vm_clock); + if (name) { + ret = bdrv_snapshot_find(bs, old_sn, name); + if (ret >= 0) { + pstrcpy(sn->name, sizeof(sn->name), old_sn->name); + pstrcpy(sn->id_str, sizeof(sn->id_str), old_sn->id_str); + } else { + pstrcpy(sn->name, sizeof(sn->name), name); + } + } else { +#ifdef _WIN32 + ptm = localtime(&tb.time); + strftime(sn->name, sizeof(sn->name), "vm-%Y%m%d%H%M%S", ptm); +#else + /* cast below needed for OpenBSD where tv_sec is still 'long' */ + localtime_r((const time_t *)&tv.tv_sec, &tm); + strftime(sn->name, sizeof(sn->name), "vm-%Y%m%d%H%M%S", &tm); +#endif + } + + /* Delete old snapshots of the same name */ + if (name && del_existing_snapshots(mon, name) < 0) { + goto the_end; + } + /* save the VM state */ f = qemu_fopen_bdrv(bs, 1); if (!f) { monitor_printf(mon, "Could not open VM state file\n"); goto the_end; } - ret = qemu_savevm_state(f); + ret = qemu_savevm_state(mon, f); vm_state_size = qemu_ftell(f); qemu_fclose(f); if (ret < 0) { @@ -1555,17 +1960,9 @@ void do_savevm(Monitor *mon, const QDict *qdict) /* create the snapshots */ - QTAILQ_FOREACH(dinfo, &drives, next) { - bs1 = dinfo->bdrv; - if (bdrv_has_snapshot(bs1)) { - if (must_delete) { - ret = bdrv_snapshot_delete(bs1, old_sn->id_str); - if (ret < 0) { - monitor_printf(mon, - "Error while deleting snapshot on '%s'\n", - bdrv_get_device_name(bs1)); - } - } + bs1 = NULL; + while ((bs1 = bdrv_next(bs1))) { + if (bdrv_can_snapshot(bs1)) { /* Write VM state size only to the image that contains the state */ sn->vm_state_size = (bs == bs1 ? vm_state_size : 0); ret = bdrv_snapshot_create(bs1, sn); @@ -1581,89 +1978,98 @@ void do_savevm(Monitor *mon, const QDict *qdict) vm_start(); } -int load_vmstate(Monitor *mon, const char *name) +int load_vmstate(const char *name) { - DriveInfo *dinfo; - BlockDriverState *bs, *bs1; + BlockDriverState *bs, *bs_vm_state; QEMUSnapshotInfo sn; QEMUFile *f; int ret; - bs = get_bs_snapshots(); - if (!bs) { - monitor_printf(mon, "No block device supports snapshots\n"); + bs_vm_state = bdrv_snapshots(); + if (!bs_vm_state) { + error_report("No block device supports snapshots"); + return -ENOTSUP; + } + + /* Don't even try to load empty VM states */ + ret = bdrv_snapshot_find(bs_vm_state, &sn, name); + if (ret < 0) { + return ret; + } else if (sn.vm_state_size == 0) { return -EINVAL; } + /* Verify if there is any device that doesn't support snapshots and is + writable and check if the requested snapshot is available too. */ + bs = NULL; + while ((bs = bdrv_next(bs))) { + + if (bdrv_is_removable(bs) || bdrv_is_read_only(bs)) { + continue; + } + + if (!bdrv_can_snapshot(bs)) { + error_report("Device '%s' is writable but does not support snapshots.", + bdrv_get_device_name(bs)); + return -ENOTSUP; + } + + ret = bdrv_snapshot_find(bs, &sn, name); + if (ret < 0) { + error_report("Device '%s' does not have the requested snapshot '%s'", + bdrv_get_device_name(bs), name); + return ret; + } + } + /* Flush all IO requests so they don't interfere with the new state. */ qemu_aio_flush(); - QTAILQ_FOREACH(dinfo, &drives, next) { - bs1 = dinfo->bdrv; - if (bdrv_has_snapshot(bs1)) { - ret = bdrv_snapshot_goto(bs1, name); + bs = NULL; + while ((bs = bdrv_next(bs))) { + if (bdrv_can_snapshot(bs)) { + ret = bdrv_snapshot_goto(bs, name); if (ret < 0) { - if (bs != bs1) - monitor_printf(mon, "Warning: "); - switch(ret) { - case -ENOTSUP: - monitor_printf(mon, - "Snapshots not supported on device '%s'\n", - bdrv_get_device_name(bs1)); - break; - case -ENOENT: - monitor_printf(mon, "Could not find snapshot '%s' on " - "device '%s'\n", - name, bdrv_get_device_name(bs1)); - break; - default: - monitor_printf(mon, "Error %d while activating snapshot on" - " '%s'\n", ret, bdrv_get_device_name(bs1)); - break; - } - /* fatal on snapshot block device */ - if (bs == bs1) - return 0; + error_report("Error %d while activating snapshot '%s' on '%s'", + ret, name, bdrv_get_device_name(bs)); + return ret; } } } - /* Don't even try to load empty VM states */ - ret = bdrv_snapshot_find(bs, &sn, name); - if ((ret >= 0) && (sn.vm_state_size == 0)) - return -EINVAL; - /* restore the VM state */ - f = qemu_fopen_bdrv(bs, 0); + f = qemu_fopen_bdrv(bs_vm_state, 0); if (!f) { - monitor_printf(mon, "Could not open VM state file\n"); + error_report("Could not open VM state file"); return -EINVAL; } + ret = qemu_loadvm_state(f); + qemu_fclose(f); if (ret < 0) { - monitor_printf(mon, "Error %d while loading VM state\n", ret); + error_report("Error %d while loading VM state", ret); return ret; } + return 0; } void do_delvm(Monitor *mon, const QDict *qdict) { - DriveInfo *dinfo; BlockDriverState *bs, *bs1; int ret; const char *name = qdict_get_str(qdict, "name"); - bs = get_bs_snapshots(); + bs = bdrv_snapshots(); if (!bs) { monitor_printf(mon, "No block device supports snapshots\n"); return; } - QTAILQ_FOREACH(dinfo, &drives, next) { - bs1 = dinfo->bdrv; - if (bdrv_has_snapshot(bs1)) { + bs1 = NULL; + while ((bs1 = bdrv_next(bs1))) { + if (bdrv_can_snapshot(bs1)) { ret = bdrv_snapshot_delete(bs1, name); if (ret < 0) { if (ret == -ENOTSUP) @@ -1680,38 +2086,64 @@ void do_delvm(Monitor *mon, const QDict *qdict) void do_info_snapshots(Monitor *mon) { - DriveInfo *dinfo; BlockDriverState *bs, *bs1; - QEMUSnapshotInfo *sn_tab, *sn; - int nb_sns, i; + QEMUSnapshotInfo *sn_tab, *sn, s, *sn_info = &s; + int nb_sns, i, ret, available; + int total; + int *available_snapshots; char buf[256]; - bs = get_bs_snapshots(); + bs = bdrv_snapshots(); if (!bs) { monitor_printf(mon, "No available block device supports snapshots\n"); return; } - monitor_printf(mon, "Snapshot devices:"); - QTAILQ_FOREACH(dinfo, &drives, next) { - bs1 = dinfo->bdrv; - if (bdrv_has_snapshot(bs1)) { - if (bs == bs1) - monitor_printf(mon, " %s", bdrv_get_device_name(bs1)); - } - } - monitor_printf(mon, "\n"); nb_sns = bdrv_snapshot_list(bs, &sn_tab); if (nb_sns < 0) { monitor_printf(mon, "bdrv_snapshot_list: error %d\n", nb_sns); return; } - monitor_printf(mon, "Snapshot list (from %s):\n", - bdrv_get_device_name(bs)); - monitor_printf(mon, "%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL)); - for(i = 0; i < nb_sns; i++) { + + if (nb_sns == 0) { + monitor_printf(mon, "There is no snapshot available.\n"); + return; + } + + available_snapshots = qemu_mallocz(sizeof(int) * nb_sns); + total = 0; + for (i = 0; i < nb_sns; i++) { sn = &sn_tab[i]; - monitor_printf(mon, "%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn)); + available = 1; + bs1 = NULL; + + while ((bs1 = bdrv_next(bs1))) { + if (bdrv_can_snapshot(bs1) && bs1 != bs) { + ret = bdrv_snapshot_find(bs1, sn_info, sn->id_str); + if (ret < 0) { + available = 0; + break; + } + } + } + + if (available) { + available_snapshots[total] = i; + total++; + } } + + if (total > 0) { + monitor_printf(mon, "%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL)); + for (i = 0; i < total; i++) { + sn = &sn_tab[available_snapshots[i]]; + monitor_printf(mon, "%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn)); + } + } else { + monitor_printf(mon, "There is no suitable snapshot available\n"); + } + qemu_free(sn_tab); + qemu_free(available_snapshots); + }