From faea38e7863a6e29f110063388eb93840fcd475c Mon Sep 17 00:00:00 2001 From: bellard Date: Sat, 5 Aug 2006 21:31:00 +0000 Subject: [PATCH] multiple snapshot support git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2086 c046a42c-6fe2-441c-8c8c-71466251a162 --- block.c | 144 +++++++++++++++- block_int.h | 11 ++ monitor.c | 24 +-- qemu-img.c | 71 ++++---- vl.c | 550 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- vl.h | 50 +++++- 6 files changed, 711 insertions(+), 139 deletions(-) diff --git a/block.c b/block.c index 3cf8b7b..529dd4e 100644 --- a/block.c +++ b/block.c @@ -494,12 +494,10 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num, } } -#if 0 /* not necessary now */ static int bdrv_pread_em(BlockDriverState *bs, int64_t offset, - void *buf1, int count1) + uint8_t *buf, int count1) { - uint8_t *buf = buf1; uint8_t tmp_buf[SECTOR_SIZE]; int len, nb_sectors, count; int64_t sector_num; @@ -542,9 +540,8 @@ static int bdrv_pread_em(BlockDriverState *bs, int64_t offset, } static int bdrv_pwrite_em(BlockDriverState *bs, int64_t offset, - const void *buf1, int count1) + const uint8_t *buf, int count1) { - const uint8_t *buf = buf1; uint8_t tmp_buf[SECTOR_SIZE]; int len, nb_sectors, count; int64_t sector_num; @@ -589,7 +586,6 @@ static int bdrv_pwrite_em(BlockDriverState *bs, int64_t offset, } return count1; } -#endif /** * Read with byte offsets (needed only for file protocols) @@ -602,7 +598,7 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset, if (!drv) return -ENOENT; if (!drv->bdrv_pread) - return -ENOTSUP; + return bdrv_pread_em(bs, offset, buf1, count1); return drv->bdrv_pread(bs, offset, buf1, count1); } @@ -617,7 +613,7 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset, if (!drv) return -ENOENT; if (!drv->bdrv_pwrite) - return -ENOTSUP; + return bdrv_pwrite_em(bs, offset, buf1, count1); return drv->bdrv_pwrite(bs, offset, buf1, count1); } @@ -859,6 +855,137 @@ void bdrv_get_backing_filename(BlockDriverState *bs, } } +int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_write_compressed) + return -ENOTSUP; + return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors); +} + +int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_get_info) + return -ENOTSUP; + memset(bdi, 0, sizeof(*bdi)); + return drv->bdrv_get_info(bs, bdi); +} + +/**************************************************************/ +/* handling of snapshots */ + +int bdrv_snapshot_create(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_snapshot_create) + return -ENOTSUP; + return drv->bdrv_snapshot_create(bs, sn_info); +} + +int bdrv_snapshot_goto(BlockDriverState *bs, + const char *snapshot_id) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_snapshot_goto) + return -ENOTSUP; + return drv->bdrv_snapshot_goto(bs, snapshot_id); +} + +int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_snapshot_delete) + return -ENOTSUP; + return drv->bdrv_snapshot_delete(bs, snapshot_id); +} + +int bdrv_snapshot_list(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOENT; + if (!drv->bdrv_snapshot_list) + return -ENOTSUP; + return drv->bdrv_snapshot_list(bs, psn_info); +} + +#define NB_SUFFIXES 4 + +char *get_human_readable_size(char *buf, int buf_size, int64_t size) +{ + static const char suffixes[NB_SUFFIXES] = "KMGT"; + int64_t base; + int i; + + if (size <= 999) { + snprintf(buf, buf_size, "%" PRId64, size); + } else { + base = 1024; + for(i = 0; i < NB_SUFFIXES; i++) { + if (size < (10 * base)) { + snprintf(buf, buf_size, "%0.1f%c", + (double)size / base, + suffixes[i]); + break; + } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) { + snprintf(buf, buf_size, "%" PRId64 "%c", + ((size + (base >> 1)) / base), + suffixes[i]); + break; + } + base = base * 1024; + } + } + return buf; +} + +char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn) +{ + char buf1[128], date_buf[128], clock_buf[128]; + struct tm tm; + time_t ti; + int64_t secs; + + if (!sn) { + snprintf(buf, buf_size, + "%-10s%-20s%7s%20s%15s", + "ID", "TAG", "VM SIZE", "DATE", "VM CLOCK"); + } else { + ti = sn->date_sec; + localtime_r(&ti, &tm); + strftime(date_buf, sizeof(date_buf), + "%Y-%m-%d %H:%M:%S", &tm); + secs = sn->vm_clock_nsec / 1000000000; + snprintf(clock_buf, sizeof(clock_buf), + "%02d:%02d:%02d.%03d", + (int)(secs / 3600), + (int)((secs / 60) % 60), + (int)(secs % 60), + (int)((sn->vm_clock_nsec / 1000000) % 1000)); + snprintf(buf, buf_size, + "%-10s%-20s%7s%20s%15s", + sn->id_str, sn->name, + get_human_readable_size(buf1, sizeof(buf1), sn->vm_state_size), + date_buf, + clock_buf); + } + return buf; +} + /**************************************************************/ /* async I/Os */ @@ -1108,4 +1235,5 @@ void bdrv_init(void) bdrv_register(&bdrv_bochs); bdrv_register(&bdrv_vpc); bdrv_register(&bdrv_vvfat); + bdrv_register(&bdrv_qcow2); } diff --git a/block_int.h b/block_int.h index e40503e..b3bc5b3 100644 --- a/block_int.h +++ b/block_int.h @@ -57,6 +57,17 @@ struct BlockDriver { const uint8_t *buf, int count); int (*bdrv_truncate)(BlockDriverState *bs, int64_t offset); int64_t (*bdrv_getlength)(BlockDriverState *bs); + int (*bdrv_write_compressed)(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); + + int (*bdrv_snapshot_create)(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info); + int (*bdrv_snapshot_goto)(BlockDriverState *bs, + const char *snapshot_id); + int (*bdrv_snapshot_delete)(BlockDriverState *bs, const char *snapshot_id); + int (*bdrv_snapshot_list)(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info); + int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi); struct BlockDriver *next; }; diff --git a/monitor.c b/monitor.c index b48e8cc..b40eac4 100644 --- a/monitor.c +++ b/monitor.c @@ -380,18 +380,6 @@ static void do_log(const char *items) cpu_set_log(mask); } -static void do_savevm(const char *filename) -{ - if (qemu_savevm(filename) < 0) - term_printf("I/O error when saving VM to '%s'\n", filename); -} - -static void do_loadvm(const char *filename) -{ - if (qemu_loadvm(filename) < 0) - term_printf("I/O error when loading VM from '%s'\n", filename); -} - static void do_stop(void) { vm_stop(EXCP_INTERRUPT); @@ -1155,10 +1143,12 @@ static term_cmd_t term_cmds[] = { "filename", "save screen into PPM image 'filename'" }, { "log", "s", do_log, "item1[,...]", "activate logging of the specified items to '/tmp/qemu.log'" }, - { "savevm", "F", do_savevm, - "filename", "save the whole virtual machine state to 'filename'" }, - { "loadvm", "F", do_loadvm, - "filename", "restore the whole virtual machine state from 'filename'" }, + { "savevm", "s?", do_savevm, + "tag|id", "save a VM snapshot. If no tag or id are provided, a new snapshot is created" }, + { "loadvm", "s", do_loadvm, + "tag|id", "restore a VM snapshot from its tag or id" }, + { "delvm", "s", do_delvm, + "tag|id", "delete a VM snapshot from its tag or id" }, { "stop", "", do_stop, "", "stop emulation", }, { "c|cont", "", do_cont, @@ -1241,6 +1231,8 @@ static term_cmd_t info_cmds[] = { "", "show profiling information", }, { "capture", "", do_info_capture, "show capture information" }, + { "snapshots", "", do_info_snapshots, + "show the currently saved VM snapshots" }, { NULL, NULL, }, }; diff --git a/qemu-img.c b/qemu-img.c index 8b2acad..06687bc 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -159,36 +159,6 @@ void help(void) exit(1); } - -#define NB_SUFFIXES 4 - -static void get_human_readable_size(char *buf, int buf_size, int64_t size) -{ - static const char suffixes[NB_SUFFIXES] = "KMGT"; - int64_t base; - int i; - - if (size <= 999) { - snprintf(buf, buf_size, "%" PRId64, size); - } else { - base = 1024; - for(i = 0; i < NB_SUFFIXES; i++) { - if (size < (10 * base)) { - snprintf(buf, buf_size, "%0.1f%c", - (double)size / base, - suffixes[i]); - break; - } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) { - snprintf(buf, buf_size, "%" PRId64 "%c", - ((size + (base >> 1)) / base), - suffixes[i]); - break; - } - base = base * 1024; - } - } -} - #if defined(WIN32) /* XXX: put correct support for win32 */ static int read_password(char *buf, int buf_size) @@ -486,6 +456,7 @@ static int img_convert(int argc, char **argv) int64_t total_sectors, nb_sectors, sector_num; uint8_t buf[IO_BUF_SIZE]; const uint8_t *buf1; + BlockDriverInfo bdi; fmt = NULL; out_fmt = "raw"; @@ -525,9 +496,9 @@ static int img_convert(int argc, char **argv) drv = bdrv_find_format(out_fmt); if (!drv) error("Unknown file format '%s'", fmt); - if (compress && drv != &bdrv_qcow) + if (compress && drv != &bdrv_qcow && drv != &bdrv_qcow2) error("Compression not supported for this file format"); - if (encrypt && drv != &bdrv_qcow) + if (encrypt && drv != &bdrv_qcow && drv != &bdrv_qcow2) error("Encryption not supported for this file format"); if (compress && encrypt) error("Compression and encryption not supported at the same time"); @@ -544,7 +515,9 @@ static int img_convert(int argc, char **argv) out_bs = bdrv_new_open(out_filename, out_fmt); if (compress) { - cluster_size = qcow_get_cluster_size(out_bs); + if (bdrv_get_info(out_bs, &bdi) < 0) + error("could not get block driver info"); + cluster_size = bdi.cluster_size; if (cluster_size <= 0 || cluster_size > IO_BUF_SIZE) error("invalid cluster size"); cluster_sectors = cluster_size >> 9; @@ -562,12 +535,15 @@ static int img_convert(int argc, char **argv) if (n < cluster_sectors) memset(buf + n * 512, 0, cluster_size - n * 512); if (is_not_zero(buf, cluster_size)) { - if (qcow_compress_cluster(out_bs, sector_num, buf) != 0) + if (bdrv_write_compressed(out_bs, sector_num, buf, + cluster_sectors) != 0) error("error while compressing sector %" PRId64, sector_num); } sector_num += n; } + /* signal EOF to align */ + bdrv_write_compressed(out_bs, 0, NULL, 0); } else { sector_num = 0; for(;;) { @@ -630,6 +606,24 @@ static int64_t get_allocated_file_size(const char *filename) } #endif +static void dump_snapshots(BlockDriverState *bs) +{ + QEMUSnapshotInfo *sn_tab, *sn; + int nb_sns, i; + char buf[256]; + + nb_sns = bdrv_snapshot_list(bs, &sn_tab); + if (nb_sns <= 0) + return; + printf("Snapshot list:\n"); + printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL)); + for(i = 0; i < nb_sns; i++) { + sn = &sn_tab[i]; + printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn)); + } + qemu_free(sn_tab); +} + static int img_info(int argc, char **argv) { int c; @@ -640,6 +634,7 @@ static int img_info(int argc, char **argv) int64_t total_sectors, allocated_size; char backing_filename[1024]; char backing_filename2[1024]; + BlockDriverInfo bdi; fmt = NULL; for(;;) { @@ -690,13 +685,19 @@ static int img_info(int argc, char **argv) dsize_buf); if (bdrv_is_encrypted(bs)) printf("encrypted: yes\n"); + if (bdrv_get_info(bs, &bdi) >= 0) { + if (bdi.cluster_size != 0) + printf("cluster_size: %d\n", bdi.cluster_size); + } bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename)); - if (backing_filename[0] != '\0') + if (backing_filename[0] != '\0') { path_combine(backing_filename2, sizeof(backing_filename2), filename, backing_filename); printf("backing file: %s (actual path: %s)\n", backing_filename, backing_filename2); + } + dump_snapshots(bs); bdrv_delete(bs); return 0; } diff --git a/vl.c b/vl.c index b31b239..6daed96 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 */ diff --git a/vl.h b/vl.h index d653f46..f717424 100644 --- a/vl.h +++ b/vl.h @@ -396,8 +396,11 @@ void cpu_disable_ticks(void); /* VM Load/Save */ -typedef FILE QEMUFile; +typedef struct QEMUFile QEMUFile; +QEMUFile *qemu_fopen(const char *filename, const char *mode); +void qemu_fflush(QEMUFile *f); +void qemu_fclose(QEMUFile *f); void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size); void qemu_put_byte(QEMUFile *f, int v); void qemu_put_be16(QEMUFile *f, unsigned int v); @@ -467,8 +470,6 @@ int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence); typedef void SaveStateHandler(QEMUFile *f, void *opaque); typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id); -int qemu_loadvm(const char *filename); -int qemu_savevm(const char *filename); int register_savevm(const char *idstr, int instance_id, int version_id, @@ -481,6 +482,11 @@ void qemu_put_timer(QEMUFile *f, QEMUTimer *ts); void cpu_save(QEMUFile *f, void *opaque); int cpu_load(QEMUFile *f, void *opaque, int version_id); +void do_savevm(const char *name); +void do_loadvm(const char *name); +void do_delvm(const char *name); +void do_info_snapshots(void); + /* bottom halves */ typedef struct QEMUBH QEMUBH; typedef void QEMUBHFunc(void *opaque); @@ -504,6 +510,25 @@ extern BlockDriver bdrv_dmg; extern BlockDriver bdrv_bochs; extern BlockDriver bdrv_vpc; extern BlockDriver bdrv_vvfat; +extern BlockDriver bdrv_qcow2; + +typedef struct BlockDriverInfo { + /* in bytes, 0 if irrelevant */ + int cluster_size; + /* offset at which the VM state can be saved (0 if not possible) */ + int64_t vm_state_offset; +} BlockDriverInfo; + +typedef struct QEMUSnapshotInfo { + char id_str[128]; /* unique snapshot id */ + /* the following fields are informative. They are not needed for + the consistency of the snapshot */ + char name[256]; /* user choosen name */ + uint32_t vm_state_size; /* VM state info size */ + uint32_t date_sec; /* UTC date of the snapshot */ + uint32_t date_nsec; + uint64_t vm_clock_nsec; /* VM clock relative to boot */ +} QEMUSnapshotInfo; #define BDRV_O_RDONLY 0x0000 #define BDRV_O_RDWR 0x0002 @@ -594,13 +619,22 @@ int bdrv_set_key(BlockDriverState *bs, const char *key); void bdrv_iterate_format(void (*it)(void *opaque, const char *name), void *opaque); const char *bdrv_get_device_name(BlockDriverState *bs); +int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); +int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi); -int qcow_get_cluster_size(BlockDriverState *bs); -int qcow_compress_cluster(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf); void bdrv_get_backing_filename(BlockDriverState *bs, char *filename, int filename_size); - +int bdrv_snapshot_create(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info); +int bdrv_snapshot_goto(BlockDriverState *bs, + const char *snapshot_id); +int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id); +int bdrv_snapshot_list(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info); +char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn); + +char *get_human_readable_size(char *buf, int buf_size, int64_t size); int path_is_absolute(const char *path); void path_combine(char *dest, int dest_size, const char *base_path, @@ -824,7 +858,7 @@ void vnc_display_init(DisplayState *ds, int display); /* ide.c */ #define MAX_DISKS 4 -extern BlockDriverState *bs_table[MAX_DISKS]; +extern BlockDriverState *bs_table[MAX_DISKS + 1]; void isa_ide_init(int iobase, int iobase2, int irq, BlockDriverState *hd0, BlockDriverState *hd1); -- 1.7.9.5