Commit faea38e7 authored by bellard's avatar bellard

multiple snapshot support


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2086 c046a42c-6fe2-441c-8c8c-71466251a162
parent 42ca6388
......@@ -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);
}
......@@ -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;
};
......
......@@ -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, },
};
......
......@@ -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;
}
......
......@@ -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;