From faea38e7863a6e29f110063388eb93840fcd475c Mon Sep 17 00:00:00 2001 From: bellard Date: Sat, 5 Aug 2006 21:31:00 +0000 Subject: multiple snapshot support git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2086 c046a42c-6fe2-441c-8c8c-71466251a162 --- vl.c | 550 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 478 insertions(+), 72 deletions(-) (limited to 'vl.c') diff --git a/vl.c b/vl.c index b31b2398a9..6daed966b1 100644 --- a/vl.c +++ b/vl.c @@ -113,7 +113,11 @@ char phys_ram_file[1024]; void *ioport_opaque[MAX_IOPORTS]; IOPortReadFunc *ioport_read_table[3][MAX_IOPORTS]; IOPortWriteFunc *ioport_write_table[3][MAX_IOPORTS]; -BlockDriverState *bs_table[MAX_DISKS], *fd_table[MAX_FD]; +/* Note: bs_table[MAX_DISKS] is a dummy block driver if none available + to store the VM snapshots */ +BlockDriverState *bs_table[MAX_DISKS + 1], *fd_table[MAX_FD]; +/* point to the block driver where the snapshots are managed */ +BlockDriverState *bs_snapshots; int vga_ram_size; int bios_size; static DisplayState display_state; @@ -4085,14 +4089,190 @@ void qemu_del_wait_object(HANDLE handle, WaitObjectFunc *func, void *opaque) /***********************************************************/ /* savevm/loadvm support */ +#define IO_BUF_SIZE 32768 + +struct QEMUFile { + FILE *outfile; + BlockDriverState *bs; + int is_file; + int is_writable; + int64_t base_offset; + int64_t buf_offset; /* start of buffer when writing, end of buffer + when reading */ + int buf_index; + int buf_size; /* 0 when writing */ + uint8_t buf[IO_BUF_SIZE]; +}; + +QEMUFile *qemu_fopen(const char *filename, const char *mode) +{ + QEMUFile *f; + + f = qemu_mallocz(sizeof(QEMUFile)); + if (!f) + return NULL; + if (!strcmp(mode, "wb")) { + f->is_writable = 1; + } else if (!strcmp(mode, "rb")) { + f->is_writable = 0; + } else { + goto fail; + } + f->outfile = fopen(filename, mode); + if (!f->outfile) + goto fail; + f->is_file = 1; + return f; + fail: + if (f->outfile) + fclose(f->outfile); + qemu_free(f); + return NULL; +} + +QEMUFile *qemu_fopen_bdrv(BlockDriverState *bs, int64_t offset, int is_writable) +{ + QEMUFile *f; + + f = qemu_mallocz(sizeof(QEMUFile)); + if (!f) + return NULL; + f->is_file = 0; + f->bs = bs; + f->is_writable = is_writable; + f->base_offset = offset; + return f; +} + +void qemu_fflush(QEMUFile *f) +{ + if (!f->is_writable) + return; + if (f->buf_index > 0) { + if (f->is_file) { + fseek(f->outfile, f->buf_offset, SEEK_SET); + fwrite(f->buf, 1, f->buf_index, f->outfile); + } else { + bdrv_pwrite(f->bs, f->base_offset + f->buf_offset, + f->buf, f->buf_index); + } + f->buf_offset += f->buf_index; + f->buf_index = 0; + } +} + +static void qemu_fill_buffer(QEMUFile *f) +{ + int len; + + if (f->is_writable) + return; + if (f->is_file) { + fseek(f->outfile, f->buf_offset, SEEK_SET); + len = fread(f->buf, 1, IO_BUF_SIZE, f->outfile); + if (len < 0) + len = 0; + } else { + len = bdrv_pread(f->bs, f->base_offset + f->buf_offset, + f->buf, IO_BUF_SIZE); + if (len < 0) + len = 0; + } + f->buf_index = 0; + f->buf_size = len; + f->buf_offset += len; +} + +void qemu_fclose(QEMUFile *f) +{ + if (f->is_writable) + qemu_fflush(f); + if (f->is_file) { + fclose(f->outfile); + } + qemu_free(f); +} + void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) { - fwrite(buf, 1, size, f); + int l; + while (size > 0) { + l = IO_BUF_SIZE - f->buf_index; + if (l > size) + l = size; + memcpy(f->buf + f->buf_index, buf, l); + f->buf_index += l; + buf += l; + size -= l; + if (f->buf_index >= IO_BUF_SIZE) + qemu_fflush(f); + } } void qemu_put_byte(QEMUFile *f, int v) { - fputc(v, f); + f->buf[f->buf_index++] = v; + if (f->buf_index >= IO_BUF_SIZE) + qemu_fflush(f); +} + +int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size1) +{ + int size, l; + + size = size1; + while (size > 0) { + l = f->buf_size - f->buf_index; + if (l == 0) { + qemu_fill_buffer(f); + l = f->buf_size - f->buf_index; + if (l == 0) + break; + } + if (l > size) + l = size; + memcpy(buf, f->buf + f->buf_index, l); + f->buf_index += l; + buf += l; + size -= l; + } + return size1 - size; +} + +int qemu_get_byte(QEMUFile *f) +{ + 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++]; +} + +int64_t qemu_ftell(QEMUFile *f) +{ + return f->buf_offset - f->buf_size + f->buf_index; +} + +int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence) +{ + if (whence == SEEK_SET) { + /* nothing to do */ + } else if (whence == SEEK_CUR) { + pos += qemu_ftell(f); + } else { + /* SEEK_END not supported */ + return -1; + } + if (f->is_writable) { + qemu_fflush(f); + f->buf_offset = pos; + } else { + f->buf_offset = pos; + f->buf_index = 0; + f->buf_size = 0; + } + return pos; } void qemu_put_be16(QEMUFile *f, unsigned int v) @@ -4115,21 +4295,6 @@ void qemu_put_be64(QEMUFile *f, uint64_t v) qemu_put_be32(f, v); } -int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size) -{ - return fread(buf, 1, size, f); -} - -int qemu_get_byte(QEMUFile *f) -{ - int v; - v = fgetc(f); - if (v == EOF) - return 0; - else - return v; -} - unsigned int qemu_get_be16(QEMUFile *f) { unsigned int v; @@ -4156,18 +4321,6 @@ uint64_t qemu_get_be64(QEMUFile *f) return v; } -int64_t qemu_ftell(QEMUFile *f) -{ - return ftell(f); -} - -int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence) -{ - if (fseek(f, pos, whence) < 0) - return -1; - return ftell(f); -} - typedef struct SaveStateEntry { char idstr[256]; int instance_id; @@ -4209,25 +4362,18 @@ int register_savevm(const char *idstr, } #define QEMU_VM_FILE_MAGIC 0x5145564d -#define QEMU_VM_FILE_VERSION 0x00000001 +#define QEMU_VM_FILE_VERSION 0x00000002 -int qemu_savevm(const char *filename) +int qemu_savevm_state(QEMUFile *f) { SaveStateEntry *se; - QEMUFile *f; - int len, len_pos, cur_pos, saved_vm_running, ret; - - saved_vm_running = vm_running; - vm_stop(0); - - f = fopen(filename, "wb"); - if (!f) { - ret = -1; - goto the_end; - } + int len, ret; + int64_t cur_pos, len_pos, total_len_pos; qemu_put_be32(f, QEMU_VM_FILE_MAGIC); qemu_put_be32(f, QEMU_VM_FILE_VERSION); + total_len_pos = qemu_ftell(f); + qemu_put_be64(f, 0); /* total size */ for(se = first_se; se != NULL; se = se->next) { /* ID string */ @@ -4239,24 +4385,24 @@ int qemu_savevm(const char *filename) qemu_put_be32(f, se->version_id); /* record size: filled later */ - len_pos = ftell(f); + len_pos = qemu_ftell(f); qemu_put_be32(f, 0); se->save_state(f, se->opaque); /* fill record size */ - cur_pos = ftell(f); - len = ftell(f) - len_pos - 4; - fseek(f, len_pos, SEEK_SET); + cur_pos = qemu_ftell(f); + len = cur_pos - len_pos - 4; + qemu_fseek(f, len_pos, SEEK_SET); qemu_put_be32(f, len); - fseek(f, cur_pos, SEEK_SET); + qemu_fseek(f, cur_pos, SEEK_SET); } + cur_pos = qemu_ftell(f); + qemu_fseek(f, total_len_pos, SEEK_SET); + qemu_put_be64(f, cur_pos - total_len_pos - 8); + qemu_fseek(f, cur_pos, SEEK_SET); - fclose(f); ret = 0; - the_end: - if (saved_vm_running) - vm_start(); return ret; } @@ -4272,38 +4418,29 @@ static SaveStateEntry *find_se(const char *idstr, int instance_id) return NULL; } -int qemu_loadvm(const char *filename) +int qemu_loadvm_state(QEMUFile *f) { SaveStateEntry *se; - QEMUFile *f; - int len, cur_pos, ret, instance_id, record_len, version_id; - int saved_vm_running; + int len, ret, instance_id, record_len, version_id; + int64_t total_len, end_pos, cur_pos; unsigned int v; char idstr[256]; - saved_vm_running = vm_running; - vm_stop(0); - - f = fopen(filename, "rb"); - if (!f) { - ret = -1; - goto the_end; - } - v = qemu_get_be32(f); if (v != QEMU_VM_FILE_MAGIC) goto fail; v = qemu_get_be32(f); if (v != QEMU_VM_FILE_VERSION) { fail: - fclose(f); ret = -1; goto the_end; } + total_len = qemu_get_be64(f); + end_pos = total_len + qemu_ftell(f); for(;;) { - len = qemu_get_byte(f); - if (feof(f)) + if (qemu_ftell(f) >= end_pos) break; + len = qemu_get_byte(f); qemu_get_buffer(f, idstr, len); idstr[len] = '\0'; instance_id = qemu_get_be32(f); @@ -4313,7 +4450,7 @@ int qemu_loadvm(const char *filename) printf("idstr=%s instance=0x%x version=%d len=%d\n", idstr, instance_id, version_id, record_len); #endif - cur_pos = ftell(f); + cur_pos = qemu_ftell(f); se = find_se(idstr, instance_id); if (!se) { fprintf(stderr, "qemu: warning: instance 0x%x of device '%s' not present in current VM\n", @@ -4328,12 +4465,281 @@ int qemu_loadvm(const char *filename) /* always seek to exact end of record */ qemu_fseek(f, cur_pos + record_len, SEEK_SET); } - fclose(f); ret = 0; + the_end: + 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; + int i; + + if (bs_snapshots) + return bs_snapshots; + for(i = 0; i <= MAX_DISKS; i++) { + bs = bs_table[i]; + 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) +{ + QEMUSnapshotInfo *sn_tab, *sn; + int nb_sns, i, ret; + + ret = -ENOENT; + nb_sns = bdrv_snapshot_list(bs, &sn_tab); + if (nb_sns < 0) + return ret; + for(i = 0; i < nb_sns; i++) { + sn = &sn_tab[i]; + if (!strcmp(sn->id_str, name) || !strcmp(sn->name, name)) { + *sn_info = *sn; + ret = 0; + break; + } + } + qemu_free(sn_tab); + return ret; +} + +void do_savevm(const char *name) +{ + BlockDriverState *bs, *bs1; + QEMUSnapshotInfo sn1, *sn = &sn1, old_sn1, *old_sn = &old_sn1; + int must_delete, ret, i; + BlockDriverInfo bdi1, *bdi = &bdi1; + QEMUFile *f; + int saved_vm_running; + struct timeval tv; + + bs = get_bs_snapshots(); + if (!bs) { + term_printf("No block device can accept snapshots\n"); + return; + } + + 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 */ + gettimeofday(&tv, NULL); + sn->date_sec = tv.tv_sec; + sn->date_nsec = tv.tv_usec * 1000; + sn->vm_clock_nsec = qemu_get_clock(vm_clock); + + if (bdrv_get_info(bs, bdi) < 0 || bdi->vm_state_offset <= 0) { + term_printf("Device %s does not support VM state snapshots\n", + bdrv_get_device_name(bs)); + goto the_end; + } + + /* save the VM state */ + f = qemu_fopen_bdrv(bs, bdi->vm_state_offset, 1); + if (!f) { + term_printf("Could not open VM state file\n"); + goto the_end; + } + ret = qemu_savevm_state(f); + sn->vm_state_size = qemu_ftell(f); + qemu_fclose(f); + if (ret < 0) { + term_printf("Error %d while writing VM\n", ret); + goto the_end; + } + + /* create the snapshots */ + + for(i = 0; i < MAX_DISKS; i++) { + bs1 = bs_table[i]; + if (bdrv_has_snapshot(bs1)) { + if (must_delete) { + ret = bdrv_snapshot_delete(bs1, old_sn->id_str); + if (ret < 0) { + term_printf("Error while deleting snapshot on '%s'\n", + bdrv_get_device_name(bs1)); + } + } + ret = bdrv_snapshot_create(bs1, sn); + if (ret < 0) { + term_printf("Error while creating snapshot on '%s'\n", + bdrv_get_device_name(bs1)); + } + } + } + the_end: if (saved_vm_running) vm_start(); - return ret; +} + +void do_loadvm(const char *name) +{ + BlockDriverState *bs, *bs1; + BlockDriverInfo bdi1, *bdi = &bdi1; + QEMUFile *f; + int i, ret; + int saved_vm_running; + + bs = get_bs_snapshots(); + if (!bs) { + term_printf("No block device supports snapshots\n"); + return; + } + + saved_vm_running = vm_running; + vm_stop(0); + + for(i = 0; i <= MAX_DISKS; i++) { + bs1 = bs_table[i]; + if (bdrv_has_snapshot(bs1)) { + ret = bdrv_snapshot_goto(bs1, name); + if (ret < 0) { + if (bs != bs1) + term_printf("Warning: "); + switch(ret) { + case -ENOTSUP: + term_printf("Snapshots not supported on device '%s'\n", + bdrv_get_device_name(bs1)); + break; + case -ENOENT: + term_printf("Could not find snapshot '%s' on device '%s'\n", + name, bdrv_get_device_name(bs1)); + break; + default: + term_printf("Error %d while activating snapshot on '%s'\n", + ret, bdrv_get_device_name(bs1)); + break; + } + /* fatal on snapshot block device */ + if (bs == bs1) + goto the_end; + } + } + } + + if (bdrv_get_info(bs, bdi) < 0 || bdi->vm_state_offset <= 0) { + term_printf("Device %s does not support VM state snapshots\n", + bdrv_get_device_name(bs)); + return; + } + + /* restore the VM state */ + f = qemu_fopen_bdrv(bs, bdi->vm_state_offset, 0); + if (!f) { + term_printf("Could not open VM state file\n"); + goto the_end; + } + ret = qemu_loadvm_state(f); + qemu_fclose(f); + if (ret < 0) { + term_printf("Error %d while loading VM state\n", ret); + } + the_end: + if (saved_vm_running) + vm_start(); +} + +void do_delvm(const char *name) +{ + BlockDriverState *bs, *bs1; + int i, ret; + + bs = get_bs_snapshots(); + if (!bs) { + term_printf("No block device supports snapshots\n"); + return; + } + + for(i = 0; i <= MAX_DISKS; i++) { + bs1 = bs_table[i]; + if (bdrv_has_snapshot(bs1)) { + ret = bdrv_snapshot_delete(bs1, name); + if (ret < 0) { + if (ret == -ENOTSUP) + term_printf("Snapshots not supported on device '%s'\n", + bdrv_get_device_name(bs1)); + else + term_printf("Error %d while deleting snapshot on '%s'\n", + ret, bdrv_get_device_name(bs1)); + } + } + } +} + +void do_info_snapshots(void) +{ + BlockDriverState *bs, *bs1; + QEMUSnapshotInfo *sn_tab, *sn; + int nb_sns, i; + char buf[256]; + + bs = get_bs_snapshots(); + if (!bs) { + term_printf("No available block device supports snapshots\n"); + return; + } + term_printf("Snapshot devices:"); + for(i = 0; i <= MAX_DISKS; i++) { + bs1 = bs_table[i]; + if (bdrv_has_snapshot(bs1)) { + if (bs == bs1) + term_printf(" %s", bdrv_get_device_name(bs1)); + } + } + term_printf("\n"); + + nb_sns = bdrv_snapshot_list(bs, &sn_tab); + if (nb_sns < 0) { + term_printf("bdrv_snapshot_list: error %d\n", nb_sns); + return; + } + term_printf("Snapshot list (from %s):\n", bdrv_get_device_name(bs)); + term_printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL)); + for(i = 0; i < nb_sns; i++) { + sn = &sn_tab[i]; + term_printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn)); + } + qemu_free(sn_tab); } /***********************************************************/ @@ -6284,7 +6690,7 @@ int main(int argc, char **argv) } else #endif if (loadvm) - qemu_loadvm(loadvm); + do_loadvm(loadvm); { /* XXX: simplify init */ -- cgit v1.2.1