multiple snapshot support
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2086 c046a42c-6fe2-441c-8c8c-71466251a162
diff --git a/vl.c b/vl.c
index b31b239..6daed96 100644
--- a/vl.c
+++ b/vl.c
@@ -113,7 +113,11 @@
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 @@
/***********************************************************/
/* 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 @@
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 @@
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 @@
}
#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 @@
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 @@
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 @@
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 @@
/* 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 @@
} else
#endif
if (loadvm)
- qemu_loadvm(loadvm);
+ do_loadvm(loadvm);
{
/* XXX: simplify init */