From 3e92c2d49cb0e8752ce8c9a3c879c84ac3299061 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Tue, 11 Oct 2011 03:02:41 +0200 Subject: Fix snapshot crash - Add snapshot load/save support to QEMU Pipes This adds the ability to save and load QEMU Pipe connections with snapshots. Note that by default, all loaded pipe client connections are force-fully closed on load. We don't have a good way to save the state of network connections to persistent storage. Properly implements snapshot save / load for qemu pipe clients. Change-Id: Ie5767f8ce40c8341b958cc5844e724dd4fc1ed2b --- android/hw-pipe-net.c | 6 ++ android/hw-qemud.c | 203 +++++++++++++++++++++++++++++++++++++------ hw/goldfish_pipe.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++---- hw/goldfish_pipe.h | 21 +++++ hw/hw.h | 5 ++ savevm.c | 40 ++++++++- 6 files changed, 464 insertions(+), 43 deletions(-) diff --git a/android/hw-pipe-net.c b/android/hw-pipe-net.c index 193d60b..c2a7e9e 100644 --- a/android/hw-pipe-net.c +++ b/android/hw-pipe-net.c @@ -442,6 +442,8 @@ static const GoldfishPipeFuncs netPipeTcp_funcs = { netPipe_recvBuffers, netPipe_poll, netPipe_wakeOn, + NULL, /* we can't save these */ + NULL, /* we can't load these */ }; #ifndef _WIN32 @@ -452,6 +454,8 @@ static const GoldfishPipeFuncs netPipeUnix_funcs = { netPipe_recvBuffers, netPipe_poll, netPipe_wakeOn, + NULL, /* we can't save these */ + NULL, /* we can't load these */ }; #endif @@ -517,6 +521,8 @@ static const GoldfishPipeFuncs openglesPipe_funcs = { netPipe_recvBuffers, netPipe_poll, netPipe_wakeOn, + NULL, /* we can't save these */ + NULL, /* we can't load these */ }; void diff --git a/android/hw-qemud.c b/android/hw-qemud.c index 0820e4c..df9ab21 100644 --- a/android/hw-qemud.c +++ b/android/hw-qemud.c @@ -20,6 +20,7 @@ #include "qemu-char.h" #include "charpipe.h" #include "cbuffer.h" +#include "utils/panic.h" #define D(...) VERBOSE_PRINT(qemud,__VA_ARGS__) #define D_ACTIVE VERBOSE_CHECK(qemud) @@ -927,16 +928,16 @@ static QemudClient* qemud_service_connect_client( QemudService *sv, const char* client_param); /* Saves the client state needed to re-establish connections on load. + * Note that we save only serial clients here. The pipe clients will be + * saved along with the pipe to which they are attached. */ static void -qemud_client_save(QEMUFile* f, QemudClient* c) +qemud_serial_client_save(QEMUFile* f, QemudClient* c) { /* save generic information */ qemud_service_save_name(f, c->service); - qemu_put_be32(f, c->protocol); - if (!_is_pipe_client(c)) { - qemu_put_be32(f, c->ProtocolSelector.Serial.channel); - } + qemu_put_string(f, c->param); + qemu_put_be32(f, c->ProtocolSelector.Serial.channel); /* save client-specific state */ if (c->clie_save) @@ -957,14 +958,16 @@ qemud_client_save(QEMUFile* f, QemudClient* c) /* Loads client state from file, then starts a new client connected to the * corresponding service. + * Note that we load only serial clients here. The pipe clients will be + * loaded along with the pipe to which they were attached. */ static int -qemud_client_load(QEMUFile* f, QemudService* current_services, int version ) +qemud_serial_client_load(QEMUFile* f, QemudService* current_services, int version ) { char *service_name = qemud_service_load_name(f); if (service_name == NULL) return -EIO; - + char* param = qemu_get_string(f); /* get current service instance */ QemudService *sv = qemud_service_find(current_services, service_name); if (sv == NULL) { @@ -973,18 +976,7 @@ qemud_client_load(QEMUFile* f, QemudService* current_services, int version ) return -EIO; } - int channel = -1; - - if (version >= 2) { - /* get protocol. */ - QemudProtocol protocol = qemu_get_be32(f); - /* get channel id */ - if (protocol == QEMUD_PROTOCOL_SERIAL) { - channel = qemu_get_be32(f); - } - } else { - channel = qemu_get_be32(f); - } + int channel = qemu_get_be32(f); if (channel == 0) { D("%s: illegal snapshot: client for control channel must no be saved\n", @@ -993,7 +985,7 @@ qemud_client_load(QEMUFile* f, QemudService* current_services, int version ) } /* re-connect client */ - QemudClient* c = qemud_service_connect_client(sv, channel, NULL); + QemudClient* c = qemud_service_connect_client(sv, channel, param); if(c == NULL) return -EIO; @@ -1717,8 +1709,9 @@ qemud_client_save_count(QEMUFile* f, QemudClient* c) { unsigned int client_count = 0; for( ; c; c = c->next) // walk over linked list - // skip control channel, which is not saved - if (_is_pipe_client(c) || c->ProtocolSelector.Serial.channel > 0) + /* skip control channel, which is not saved, and pipe channels that + * are saved along with the pipe. */ + if (!_is_pipe_client(c) && c->ProtocolSelector.Serial.channel > 0) client_count++; qemu_put_be32(f, client_count); @@ -1760,9 +1753,9 @@ qemud_save(QEMUFile* f, void* opaque) qemud_client_save_count(f, m->clients); QemudClient *c; for (c = m->clients; c; c = c->next) { - /* skip control channel client */ - if (_is_pipe_client(c) || c->ProtocolSelector.Serial.channel > 0) { - qemud_client_save(f, c); + /* skip control channel, and pipe clients */ + if (!_is_pipe_client(c) && c->ProtocolSelector.Serial.channel > 0) { + qemud_serial_client_save(f, c); } } @@ -1806,7 +1799,7 @@ qemud_load_clients(QEMUFile* f, QemudMultiplexer* m, int version ) int client_count = qemu_get_be32(f); int i, ret; for (i = 0; i < client_count; i++) { - if ((ret = qemud_client_load(f, m->services, version))) { + if ((ret = qemud_serial_client_load(f, m->services, version))) { return ret; } } @@ -1839,6 +1832,46 @@ qemud_load(QEMUFile *f, void* opaque, int version) * * ----------------------------------------------------------------------------*/ +/* Saves pending pipe message to the snapshot file. */ +static void +_save_pipe_message(QEMUFile* f, QemudPipeMessage* msg) +{ + qemu_put_be32(f, msg->size); + qemu_put_be32(f, msg->offset); + qemu_put_buffer(f, msg->message, msg->size); +} + +/* Loads pending pipe messages from the snapshot file. + * Return: + * List of pending pipe messages loaded from snapshot, or NULL if snapshot didn't + * contain saved messages. + */ +static QemudPipeMessage* +_load_pipe_message(QEMUFile* f) +{ + QemudPipeMessage* ret = NULL; + QemudPipeMessage** next = &ret; + + uint32_t size = qemu_get_be32(f); + while (size != 0) { + QemudPipeMessage* wrk; + ANEW0(wrk); + *next = wrk; + wrk->size = size; + wrk->offset = qemu_get_be32(f); + wrk->message = malloc(wrk->size); + if (wrk->message == NULL) { + APANIC("Unable to allocate buffer for pipe's pending message."); + } + qemu_get_buffer(f, wrk->message, wrk->size); + next = &wrk->next; + *next = NULL; + size = qemu_get_be32(f); + } + + return ret; +} + /* This is a callback that gets invoked when guest is connecting to the service. * * Here we will create a new client as well as pipe descriptor representing new @@ -2039,6 +2072,122 @@ _qemudPipe_wakeOn(void* opaque, int flags) D("%s: -> %X", __FUNCTION__, flags); } +static void +_qemudPipe_save(void* opaque, QEMUFile* f ) +{ + QemudPipe* qemud_pipe = (QemudPipe*)opaque; + QemudClient* c = qemud_pipe->client; + QemudPipeMessage* msg = c->ProtocolSelector.Pipe.messages; + + /* save generic information */ + qemud_service_save_name(f, c->service); + qemu_put_string(f, c->param); + + /* Save pending messages. */ + while (msg != NULL) { + _save_pipe_message(f, msg); + msg = msg->next; + } + /* End of pending messages. */ + qemu_put_be32(f, 0); + + /* save client-specific state */ + if (c->clie_save) + c->clie_save(f, c, c->clie_opaque); + + /* save framing configuration */ + qemu_put_be32(f, c->framing); + if (c->framing) { + qemu_put_be32(f, c->need_header); + /* header sink always connected to c->header0, no need to save */ + qemu_put_be32(f, FRAME_HEADER_SIZE); + qemu_put_buffer(f, c->header0, FRAME_HEADER_SIZE); + /* payload sink */ + qemud_sink_save(f, c->payload); + qemu_put_buffer(f, c->payload->buff, c->payload->size); + } +} + +static void* +_qemudPipe_load(void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* f) +{ + QemudPipe* qemud_pipe = NULL; + char* param; + char *service_name = qemud_service_load_name(f); + if (service_name == NULL) + return NULL; + /* get service instance for the loading client*/ + QemudService *sv = qemud_service_find(_multiplexer->services, service_name); + if (sv == NULL) { + D("%s: load failed: unknown service \"%s\"\n", + __FUNCTION__, service_name); + return NULL; + } + + /* Load saved parameters. */ + param = qemu_get_string(f); + + /* re-connect client */ + QemudClient* c = qemud_service_connect_client(sv, -1, param); + if(c == NULL) + return NULL; + + /* Load pending messages. */ + c->ProtocolSelector.Pipe.messages = _load_pipe_message(f); + + /* load client-specific state */ + if (c->clie_load && c->clie_load(f, c, c->clie_opaque)) { + /* load failure */ + return NULL; + } + + /* load framing configuration */ + c->framing = qemu_get_be32(f); + if (c->framing) { + + /* header buffer */ + c->need_header = qemu_get_be32(f); + int header_size = qemu_get_be32(f); + if (header_size > FRAME_HEADER_SIZE) { + D("%s: load failed: payload buffer requires %d bytes, %d available\n", + __FUNCTION__, header_size, FRAME_HEADER_SIZE); + return NULL; + } + int ret; + if ((ret = qemu_get_buffer(f, c->header0, header_size)) != header_size) { + D("%s: frame header buffer load failed: expected %d bytes, got %d\n", + __FUNCTION__, header_size, ret); + return NULL; + } + + /* payload sink */ + if ((ret = qemud_sink_load(f, c->payload))) + return NULL; + + /* replace payload buffer by saved data */ + if (c->payload->buff) { + AFREE(c->payload->buff); + } + AARRAY_NEW(c->payload->buff, c->payload->size+1); /* +1 for terminating zero */ + if ((ret = qemu_get_buffer(f, c->payload->buff, c->payload->size)) != c->payload->size) { + D("%s: frame payload buffer load failed: expected %d bytes, got %d\n", + __FUNCTION__, c->payload->size, ret); + AFREE(c->payload->buff); + return NULL; + } + } + + /* Associate the client with the pipe. */ + ANEW0(qemud_pipe); + qemud_pipe->hwpipe = hwpipe; + qemud_pipe->looper = pipeOpaque; + qemud_pipe->service = sv; + qemud_pipe->client = c; + c->ProtocolSelector.Pipe.qemud_pipe = qemud_pipe; + + return qemud_pipe; +} + /* QEMUD pipe functions. */ static const GoldfishPipeFuncs _qemudPipe_funcs = { @@ -2048,6 +2197,8 @@ static const GoldfishPipeFuncs _qemudPipe_funcs = { _qemudPipe_recvBuffers, _qemudPipe_poll, _qemudPipe_wakeOn, + _qemudPipe_save, + _qemudPipe_load, }; /* Initializes QEMUD pipe interface. diff --git a/hw/goldfish_pipe.c b/hw/goldfish_pipe.c index b3c6975..3c076f1 100644 --- a/hw/goldfish_pipe.c +++ b/hw/goldfish_pipe.c @@ -52,6 +52,11 @@ /* Set to 1 to enable the 'throttle' pipe type, useful for debugging */ #define DEBUG_THROTTLE_PIPE 1 +/* Maximum length of pipe service name, in characters (excluding final 0) */ +#define MAX_PIPE_SERVICE_NAME_SIZE 255 + +#define GOLDFISH_PIPE_SAVE_VERSION 1 + /*********************************************************************** *********************************************************************** ***** @@ -85,6 +90,10 @@ goldfish_pipe_add_type(const char* pipeName, APANIC("Too many goldfish pipe services (%d)", count); } + if (strlen(pipeName) > MAX_PIPE_SERVICE_NAME_SIZE) { + APANIC("Pipe service name too long: '%s'", pipeName); + } + list->services[count].name = pipeName; list->services[count].opaque = pipeOpaque; list->services[count].funcs = pipeFuncs[0]; @@ -120,24 +129,33 @@ typedef struct PipeDevice PipeDevice; typedef struct Pipe { struct Pipe* next; struct Pipe* next_waked; - PipeDevice* device; - uint32_t channel; - void* opaque; - const GoldfishPipeFuncs* funcs; - unsigned char wanted; - char closed; + PipeDevice* device; + uint32_t channel; + void* opaque; + const GoldfishPipeFuncs* funcs; + const PipeService* service; + char* args; + unsigned char wanted; + char closed; } Pipe; /* Forward */ static void* pipeConnector_new(Pipe* pipe); -Pipe* -pipe_new(uint32_t channel, PipeDevice* dev) +static Pipe* +pipe_new0(PipeDevice* dev) { Pipe* pipe; ANEW0(pipe); + pipe->device = dev; + return pipe; +} + +static Pipe* +pipe_new(uint32_t channel, PipeDevice* dev) +{ + Pipe* pipe = pipe_new0(dev); pipe->channel = channel; - pipe->device = dev; pipe->opaque = pipeConnector_new(pipe); return pipe; } @@ -199,6 +217,97 @@ pipe_list_remove_waked( Pipe** list, Pipe* pipe ) } } +static void +pipe_save( Pipe* pipe, QEMUFile* file ) +{ + if (pipe->service == NULL) { + /* pipe->service == NULL means we're still using a PipeConnector */ + /* Write a zero to indicate this condition */ + qemu_put_byte(file, 0); + } else { + /* Otherwise, write a '1' then the service name */ + qemu_put_byte(file, 1); + qemu_put_string(file, pipe->service->name); + } + + /* Now save other common data */ + qemu_put_be32(file, (unsigned int)pipe->channel); + qemu_put_byte(file, (int)pipe->wanted); + qemu_put_byte(file, (int)pipe->closed); + + /* Write 1 + args, if any, or simply 0 otherwise */ + if (pipe->args != NULL) { + qemu_put_byte(file, 1); + qemu_put_string(file, pipe->args); + } else { + qemu_put_byte(file, 0); + } + + if (pipe->funcs->save) { + pipe->funcs->save(pipe->opaque, file); + } +} + +static Pipe* +pipe_load( PipeDevice* dev, QEMUFile* file ) +{ + Pipe* pipe; + const PipeService* service = NULL; + int state = qemu_get_byte(file); + uint32_t channel; + + if (state != 0) { + /* Pipe is associated with a service. */ + char* name = qemu_get_string(file); + if (name == NULL) + return NULL; + + service = goldfish_pipe_find_type(name); + if (service == NULL) { + D("No QEMU pipe service named '%s'", name); + AFREE(name); + return NULL; + } + } + + channel = qemu_get_be32(file); + pipe = pipe_new(channel, dev); + pipe->wanted = qemu_get_byte(file); + pipe->closed = qemu_get_byte(file); + if (qemu_get_byte(file) != 0) { + pipe->args = qemu_get_string(file); + } + + pipe->service = service; + if (service != NULL) { + pipe->funcs = &service->funcs; + } + + if (pipe->funcs->load) { + pipe->opaque = pipe->funcs->load(pipe, service->opaque, pipe->args, file); + if (pipe->opaque == NULL) { + AFREE(pipe); + return NULL; + } + } else { + /* Force-close the pipe on load */ + pipe->closed = 1; + } + return pipe; +} + +static void +pipe_free( Pipe* pipe ) +{ + /* Call close callback */ + if (pipe->funcs->close) { + pipe->funcs->close(pipe->opaque); + } + /* Free stuff */ + AFREE(pipe->args); + AFREE(pipe); +} + /*********************************************************************** *********************************************************************** ***** @@ -293,6 +402,8 @@ pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int if (pipeArgs != NULL) { *pipeArgs++ = '\0'; + if (!*pipeArgs) + pipeArgs = NULL; } Pipe* pipe = pcon->pipe; @@ -310,7 +421,9 @@ pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int /* Do the evil switch now */ pipe->opaque = peer; + pipe->service = svc; pipe->funcs = &svc->funcs; + pipe->args = ASTRDUP(pipeArgs); AFREE(pcon); } @@ -335,6 +448,32 @@ pipeConnector_wakeOn( void* opaque, int flags ) /* nothing, really should never happen */ } +static void +pipeConnector_save( void* pipe, QEMUFile* file ) +{ + PipeConnector* pcon = pipe; + qemu_put_sbe32(file, pcon->buffpos); + qemu_put_sbuffer(file, (const int8_t*)pcon->buffer, pcon->buffpos); +} + +static void* +pipeConnector_load( void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* file ) +{ + PipeConnector* pcon; + + int len = qemu_get_sbe32(file); + if (len < 0 || len > sizeof(pcon->buffer)) { + return NULL; + } + pcon = pipeConnector_new(hwpipe); + pcon->buffpos = len; + if (qemu_get_buffer(file, (uint8_t*)pcon->buffer, pcon->buffpos) != pcon->buffpos) { + AFREE(pcon); + return NULL; + } + return pcon; +} + static const GoldfishPipeFuncs pipeConnector_funcs = { NULL, /* init */ pipeConnector_close, /* should rarely happen */ @@ -342,6 +481,8 @@ static const GoldfishPipeFuncs pipeConnector_funcs = { pipeConnector_recvBuffers, /* should not happen */ pipeConnector_poll, /* should not happen */ pipeConnector_wakeOn, /* should not happen */ + pipeConnector_save, + pipeConnector_load, }; /*********************************************************************** @@ -860,12 +1001,7 @@ pipeDevice_doCommand( PipeDevice* dev, uint32_t command ) *lookup = pipe->next; pipe->next = NULL; pipe_list_remove_waked(&dev->signaled_pipes, pipe); - /* Call close callback */ - if (pipe->funcs->close) { - pipe->funcs->close(pipe->opaque); - } - /* Free stuff */ - AFREE(pipe); + pipe_free(pipe); break; case PIPE_CMD_POLL: @@ -1018,6 +1154,69 @@ static CPUWriteMemoryFunc *pipe_dev_writefn[] = { pipe_dev_write }; +static void +goldfish_pipe_save( QEMUFile* file, void* opaque ) +{ + PipeDevice* dev = opaque; + Pipe* pipe; + + qemu_put_be32(file, dev->address); + qemu_put_be32(file, dev->size); + qemu_put_be32(file, dev->status); + qemu_put_be32(file, dev->channel); + qemu_put_be32(file, dev->wakes); + + /* Count the number of pipe connections */ + int count = 0; + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) + count++; + + qemu_put_sbe32(file, count); + + /* Now save each pipe one after the other */ + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { + pipe_save(pipe, file); + } +} + +static int +goldfish_pipe_load( QEMUFile* file, void* opaque, int version_id ) +{ + PipeDevice* dev = opaque; + Pipe* pipe; + + if (version_id != GOLDFISH_PIPE_SAVE_VERSION) + return -EINVAL; + + dev->address = qemu_get_be32(file); + dev->size = qemu_get_be32(file); + dev->status = qemu_get_be32(file); + dev->channel = qemu_get_be32(file); + dev->wakes = qemu_get_be32(file); + + /* Count the number of pipe connections */ + int count = qemu_get_sbe32(file); + + /* Load all pipe connections */ + for ( ; count > 0; count-- ) { + pipe = pipe_load(dev, file); + if (pipe == NULL) { + return -EIO; + } + pipe->next = dev->pipes; + dev->pipes = pipe; + } + + /* Now we need to wake/close all relevant pipes */ + for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { + if (pipe->wanted != 0) + goldfish_pipe_wake(pipe, pipe->wanted); + if (pipe->closed != 0) + goldfish_pipe_close(pipe); + } + return 0; +} + /* initialize the trace device */ void pipe_dev_init() { @@ -1034,6 +1233,9 @@ void pipe_dev_init() goldfish_device_add(&s->dev, pipe_dev_readfn, pipe_dev_writefn, s); + register_savevm( "goldfish_pipe", 0, GOLDFISH_PIPE_SAVE_VERSION, + goldfish_pipe_save, goldfish_pipe_load, s); + #if DEBUG_ZERO_PIPE goldfish_pipe_add_type("zero", NULL, &zeroPipe_funcs); #endif diff --git a/hw/goldfish_pipe.h b/hw/goldfish_pipe.h index 8074619..f08cef8 100644 --- a/hw/goldfish_pipe.h +++ b/hw/goldfish_pipe.h @@ -94,6 +94,27 @@ typedef struct { * then the pipe implementation shall call goldfish_pipe_wake(). */ void (*wakeOn)( void* opaque, int flags ); + + /* Called to save the pipe's state to a QEMUFile, i.e. when saving + * snapshots. This can be NULL to indicate that no state can be saved. + * In this case, when the pipe is loaded, the emulator will automatically + * force-close so the next operation the guest performs on it will return + * a PIPE_ERROR_IO error code. + */ + void (*save)( void* pipe, QEMUFile* file ); + + /* Called to load the sate of a pipe from a QEMUFile. This will always + * correspond to the state of the pipe as saved by a previous call to + * the 'save' method. Can be NULL to indicate that the pipe state cannot + * be loaded. In this case, the emulator will automatically force-close + * it. + * + * In case of success, this returns 0, and the new pipe object is returned + * in '*ppipe'. In case of errno code is returned to indicate a failure. + * 'hwpipe' and 'pipeOpaque' are the same arguments than those passed + * to 'init'. + */ + void* (*load)( void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* file); } GoldfishPipeFuncs; /* Register a new pipe handler type. 'pipeOpaque' is passed directly diff --git a/hw/hw.h b/hw/hw.h index d230448..292d42e 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -218,6 +218,11 @@ static inline void qemu_get_sbe64s(QEMUFile *f, int64_t *pv) qemu_get_be64s(f, (uint64_t *)pv); } +#ifdef CONFIG_ANDROID +void qemu_put_string(QEMUFile *f, const char* str); +char* qemu_get_string(QEMUFile *f); +#endif + #ifdef NEED_CPU_H #if TARGET_LONG_BITS == 64 #define qemu_put_betl qemu_put_be64 diff --git a/savevm.c b/savevm.c index c08e8fa..7a5dc23 100644 --- a/savevm.c +++ b/savevm.c @@ -567,6 +567,37 @@ int qemu_get_byte(QEMUFile *f) return f->buf[f->buf_index++]; } +#ifdef CONFIG_ANDROID +void qemu_put_string(QEMUFile *f, const char* str) +{ + /* We will encode NULL and the empty string in the same way */ + int slen; + if (str == NULL) { + str = ""; + } + slen = strlen(str); + qemu_put_be32(f, slen); + qemu_put_buffer(f, (const uint8_t*)str, slen); +} + +char* qemu_get_string(QEMUFile *f) +{ + int slen = qemu_get_be32(f); + char* str; + if (slen == 0) + return NULL; + + str = qemu_malloc(slen+1); + if (qemu_get_buffer(f, (uint8_t*)str, slen) != slen) { + qemu_free(str); + return NULL; + } + str[slen] = '\0'; + return str; +} +#endif + + int64_t qemu_ftell(QEMUFile *f) { return f->buf_offset - f->buf_size + f->buf_index; @@ -858,7 +889,7 @@ void unregister_savevm(const char *idstr, void *opaque) #define QEMU_VM_FILE_MAGIC 0x5145564d #define QEMU_VM_FILE_VERSION_COMPAT 0x00000002 -#define QEMU_VM_FILE_VERSION 0x00000003 +#define QEMU_VM_FILE_VERSION 0x00000004 #define QEMU_VM_EOF 0x00 #define QEMU_VM_SECTION_START 0x01 @@ -1075,8 +1106,13 @@ int qemu_loadvm_state(QEMUFile *f) v = qemu_get_be32(f); if (v == QEMU_VM_FILE_VERSION_COMPAT) return qemu_loadvm_state_v2(f); - if (v != QEMU_VM_FILE_VERSION) + if (v < QEMU_VM_FILE_VERSION) { + fprintf(stderr, "Snapshot format %d is too old for this version of the emulator, please create a new one.\n", v); + return -ENOTSUP; + } else if (v > QEMU_VM_FILE_VERSION) { + fprintf(stderr, "Snapshot format %d is more recent than the emulator, please update your Android SDK Tools.\n", v); return -ENOTSUP; + } while ((section_type = qemu_get_byte(f)) != QEMU_VM_EOF) { uint32_t instance_id, version_id, section_id; -- cgit v1.1